krz/skunky-art

Alternative privacy frontend for DeviantArt.

clone: git clone https://gitbay.org/krz/skunky-art.git

v1.3.2: app/util.go · raw

  1package app
  2
  3import (
  4	"encoding/json"
  5	"io"
  6	"net/http"
  7	"net/url"
  8	"os"
  9	"skunkyart/static"
 10	"strconv"
 11	"strings"
 12	"text/template"
 13	"time"
 14
 15	"git.macaw.me/skunky/devianter"
 16	"golang.org/x/net/html"
 17)
 18
 19/* INTERNAL */
 20var wr = io.WriteString
 21
 22func exit(msg string, code int) {
 23	println(msg)
 24	os.Exit(code)
 25}
 26func try(e error) {
 27	if e != nil {
 28		println(e.Error())
 29	}
 30}
 31func tryWithExitStatus(err error, code int) {
 32	if err != nil {
 33		exit(err.Error(), code)
 34	}
 35}
 36
 37func restore() {
 38	if r := recover(); r != nil {
 39		recover()
 40	}
 41}
 42
 43var instances []byte
 44var About instanceAbout
 45
 46func RefreshInstances() {
 47	for {
 48		func() {
 49			defer restore()
 50			instances = Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body
 51			try(json.Unmarshal(instances, &About))
 52		}()
 53		time.Sleep(1 * time.Hour)
 54	}
 55}
 56
 57// some crap for frontend
 58type instanceAbout struct {
 59	Proxy     bool
 60	Nsfw      bool
 61	Instances []settings
 62}
 63
 64type skunkyart struct {
 65	Writer http.ResponseWriter
 66
 67	Args url.Values
 68	Page int
 69	Type rune
 70	Atom bool
 71
 72	BasePath, Endpoint string
 73	Query, QueryRaw    string
 74
 75	API     API
 76	Version string
 77
 78	Templates struct {
 79		About instanceAbout
 80
 81		SomeList  string
 82		DDStrips  string
 83		Deviation struct {
 84			Post       devianter.Post
 85			Related    string
 86			StringTime string
 87			Tags       string
 88			Comments   string
 89		}
 90
 91		GroupUser struct {
 92			GR           devianter.GRuser
 93			Admins       string
 94			Group        bool
 95			CreationDate string
 96
 97			About struct {
 98				A devianter.About
 99
100				DescriptionFormatted string
101				Interests, Social    string
102				Comments             string
103				BG                   string
104				BGMeta               devianter.Deviation
105			}
106
107			Gallery struct {
108				Folders string
109				Pages   int
110				List    string
111			}
112		}
113		Search struct {
114			Content devianter.Search
115			List    string
116		}
117	}
118}
119
120func (s skunkyart) ExecuteTemplate(file, dir string, data any) {
121	var buf strings.Builder
122	tmp := template.New(file)
123	tmp, err := tmp.ParseFS(static.Templates, dir+"/*")
124	if err != nil {
125		s.Writer.WriteHeader(500)
126		wr(s.Writer, err.Error())
127		return
128	}
129	try(tmp.Execute(&buf, &data))
130	wr(s.Writer, buf.String())
131}
132
133func UrlBuilder(strs ...string) string {
134	var str strings.Builder
135	l := len(strs)
136	str.WriteString(Host)
137	str.WriteString(CFG.URI)
138	for n, x := range strs {
139		str.WriteString(x)
140		if n := n + 1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') {
141			str.WriteString("/")
142		}
143	}
144	return str.String()
145}
146
147func (s skunkyart) Error(dAerr devianter.Error) {
148	s.Writer.WriteHeader(502)
149
150	var msg strings.Builder
151	msg.WriteString(`<html><link rel="stylesheet" href="`)
152	msg.WriteString(UrlBuilder("stylesheet"))
153	msg.WriteString(`" /><h3>DeviantArt error — '`)
154	msg.WriteString(dAerr.Error)
155	msg.WriteString("'</h3></html>")
156
157	wr(s.Writer, msg.String())
158}
159
160func (s skunkyart) ReturnHTTPError(status int) {
161	s.Writer.WriteHeader(status)
162
163	var msg strings.Builder
164	msg.WriteString(`<html><link rel="stylesheet" href="`)
165	msg.WriteString(UrlBuilder("stylesheet"))
166	msg.WriteString(`" /><h1>`)
167	msg.WriteString(strconv.Itoa(status))
168	msg.WriteString(" - ")
169	msg.WriteString(http.StatusText(status))
170	msg.WriteString("</h1></html>")
171
172	wr(s.Writer, msg.String())
173}
174
175func (s skunkyart) SetFilename(name string) {
176	var filename strings.Builder
177	filename.WriteString(`filename="`)
178	filename.WriteString(name)
179	filename.WriteString(`"`)
180	s.Writer.Header().Add("Content-Disposition", filename.String())
181}
182
183type Downloaded struct {
184	Headers http.Header
185	Status  int
186	Body    []byte
187}
188
189func Download(urlString string) (d Downloaded) {
190	cli := &http.Client{}
191	if CFG.DownloadProxy != "" {
192		u, e := url.Parse(CFG.DownloadProxy)
193		try(e)
194		cli.Transport = &http.Transport{Proxy: http.ProxyURL(u)}
195	}
196
197	req, e := http.NewRequest("GET", urlString, nil)
198	try(e)
199	req.Header.Set("User-Agent", CFG.UserAgent)
200
201	resp, e := cli.Do(req)
202	try(e)
203	defer resp.Body.Close()
204	b, e := io.ReadAll(resp.Body)
205	try(e)
206
207	d.Body = b
208	d.Status = resp.StatusCode
209	d.Headers = resp.Header
210	return
211}
212
213/* PARSING HELPERS */
214func ParseMedia(media devianter.Media, thumb ...int) string {
215	mediaUrl, filename := devianter.UrlFromMedia(media, thumb...)
216	if len(mediaUrl) != 0 && CFG.Proxy {
217		mediaUrl = mediaUrl[21:]
218		dot := strings.Index(mediaUrl, ".")
219		if filename == "" {
220			filename = "image.gif"
221		}
222		return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename)
223	} else if !CFG.Proxy {
224		return mediaUrl
225	}
226	return ""
227}
228
229func ConvertDeviantArtUrlToSkunkyArt(url string) (output string) {
230	if len(url) > 32 && url[27:32] != "stash" {
231		url = url[27:]
232		firstshash := strings.Index(url, "/")
233		lastshash := firstshash + strings.Index(url[firstshash+1:], "/")
234		if lastshash != -1 {
235			output = UrlBuilder("post", url[:firstshash], url[lastshash+2:])
236		}
237	}
238	return
239}
240
241func BuildUserPlate(name string) string {
242	var htm strings.Builder
243	htm.WriteString(`<div class="user-plate"><img src="`)
244	htm.WriteString(UrlBuilder("media", "emojitar", name, "?type=a"))
245	htm.WriteString(`"><a href="`)
246	htm.WriteString(UrlBuilder("group_user", "?type=about&q=", name))
247	htm.WriteString(`">`)
248	htm.WriteString(name)
249	htm.WriteString(`</a></div>`)
250	return htm.String()
251}
252
253func GetValueOfTag(t *html.Tokenizer) string {
254	for tt := t.Next(); ; {
255		if tt == html.TextToken {
256			return string(t.Text())
257		} else {
258			return ""
259		}
260	}
261}
262
263// навигация по страницам
264type DeviationList struct {
265	Pages int
266	More  bool
267}
268
269// FIXME: на некоротрых артах первая страница может вызывать полное отсутствие панели навигации.
270func (s skunkyart) NavBase(c DeviationList) string {
271	var list strings.Builder
272
273	list.WriteString("<br>")
274	prevrev := func(msg string, page int, onpage bool) {
275		if !onpage {
276			list.WriteString(`<a href="`)
277			list.WriteString(Path)
278			list.WriteString(`?p=`)
279			list.WriteString(strconv.Itoa(page))
280			if s.Type != 0 {
281				list.WriteString("&type=")
282				list.WriteRune(s.Type)
283			}
284			if s.Query != "" {
285				list.WriteString("&q=")
286				list.WriteString(s.Query)
287			}
288			if f := s.Args.Get("folder"); f != "" {
289				list.WriteString("&folder=")
290				list.WriteString(f)
291			}
292			list.WriteString(`">`)
293			list.WriteString(msg)
294			list.WriteString("</a> ")
295		} else {
296			list.WriteString(strconv.Itoa(page))
297			list.WriteString(" ")
298		}
299	}
300
301	p := s.Page
302
303	if p > 1 {
304		prevrev("<= Prev |", p-1, false)
305	} else {
306		p = 1
307	}
308
309	for i, x := p-6, 0; (i <= c.Pages && i <= p+6) && x < 12; i++ {
310		if i > 0 {
311			var onPage bool
312			if i == p {
313				onPage = true
314			}
315
316			prevrev(strconv.Itoa(i), i, onPage)
317			x++
318		}
319	}
320
321	if c.More {
322		prevrev("| Next =>", p+1, false)
323	}
324
325	return list.String()
326}