krz/skunky-art

Alternative privacy frontend for DeviantArt.

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

v1.3.2: app/parsers.go · raw

  1package app
  2
  3import (
  4	"encoding/json"
  5	"strconv"
  6	"strings"
  7
  8	"git.macaw.me/skunky/devianter"
  9	"golang.org/x/net/html"
 10)
 11
 12func (s skunkyart) ParseComments(c devianter.Comments, daError devianter.Error) string {
 13	if daError.RAW != nil {
 14		return "Failed to fetch comments :("
 15	}
 16
 17	var cmmts strings.Builder
 18	replied := make(map[int]string)
 19
 20	cmmts.WriteString("<details><summary>Comments: <b>")
 21	cmmts.WriteString(strconv.Itoa(c.Total))
 22	cmmts.WriteString("</b></summary>")
 23	for _, x := range c.Thread {
 24		replied[x.ID] = x.User.Username
 25		cmmts.WriteString(`<div class="msg`)
 26		if x.Parent > 0 {
 27			cmmts.WriteString(` reply`)
 28		}
 29		cmmts.WriteString(`"><p id="`)
 30		cmmts.WriteString(strconv.Itoa(x.ID))
 31		cmmts.WriteString(`"><img src="`)
 32		cmmts.WriteString(UrlBuilder("media", "emojitar", x.User.Username, "?type=a"))
 33		cmmts.WriteString(`" width="30px" height="30px"><a href="`)
 34		cmmts.WriteString(UrlBuilder("group_user", "?q=", x.User.Username, "&type=a"))
 35		cmmts.WriteString(`"><b`)
 36		cmmts.WriteString(` class="`)
 37		if x.User.Banned {
 38			cmmts.WriteString(`banned`)
 39		}
 40		if x.Author {
 41			cmmts.WriteString(`author`)
 42		}
 43		cmmts.WriteString(`">`)
 44		cmmts.WriteString(x.User.Username)
 45		cmmts.WriteString("</b></a> ")
 46
 47		if x.Parent > 0 {
 48			cmmts.WriteString(` In reply to <a href="`)
 49			cmmts.WriteString(Path)
 50			cmmts.WriteString("#")
 51			cmmts.WriteString(strconv.Itoa(x.Parent))
 52			cmmts.WriteString(`">`)
 53			if replied[x.Parent] == "" {
 54				cmmts.WriteString("???")
 55			} else {
 56				cmmts.WriteString(replied[x.Parent])
 57			}
 58			cmmts.WriteString("</a>")
 59		}
 60		cmmts.WriteString(" [")
 61		cmmts.WriteString(x.Posted.UTC().String())
 62		cmmts.WriteString("]<p>")
 63
 64		cmmts.WriteString(ParseDescription(x.TextContent))
 65		cmmts.WriteString("<p>👍: ")
 66		cmmts.WriteString(strconv.Itoa(x.Likes))
 67		cmmts.WriteString(" ⏩: ")
 68		cmmts.WriteString(strconv.Itoa(x.Replies))
 69		cmmts.WriteString("</p></div>\n")
 70	}
 71	cmmts.WriteString(s.NavBase(DeviationList{
 72		Pages: 0,
 73		More:  c.HasMore,
 74	}))
 75	cmmts.WriteString("</details>")
 76	return cmmts.String()
 77}
 78
 79func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, content ...DeviationList) string {
 80	if s.Atom && s.Page > 1 {
 81		s.ReturnHTTPError(400)
 82		return ""
 83	}
 84
 85	var list, listContent strings.Builder
 86
 87	for i, l := 0, len(devs); i < l; i++ {
 88		data := &devs[i]
 89		if preview, fullview := ParseMedia(data.Media, 320), ParseMedia(data.Media); !(data.NSFW && !CFG.Nsfw) {
 90			if allowAtom && s.Atom {
 91				s.Writer.Header().Add("Content-type", "application/atom+xml")
 92				id := strconv.Itoa(data.ID)
 93				listContent.WriteString(`<entry><author><name>`)
 94				listContent.WriteString(data.Author.Username)
 95				listContent.WriteString(`</name></author><title>`)
 96				listContent.WriteString(data.Title)
 97				listContent.WriteString(`</title><link rel="alternate" type="text/html" href="`)
 98				listContent.WriteString(UrlBuilder("post", data.Author.Username, "atom-"+id))
 99				listContent.WriteString(`"/><id>`)
100				listContent.WriteString(id)
101				listContent.WriteString(`</id><published>`)
102				listContent.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700"))
103				listContent.WriteString(`</published>`)
104				listContent.WriteString(`<media:group><media:title>`)
105				listContent.WriteString(data.Title)
106				listContent.WriteString(`</media:title><media:thumbinal url="`)
107				listContent.WriteString(preview)
108				listContent.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`)
109				listContent.WriteString(ConvertDeviantArtUrlToSkunkyArt(data.Url))
110				listContent.WriteString(`"><img src="`)
111				listContent.WriteString(fullview)
112				listContent.WriteString(`"/></a><p>`)
113				listContent.WriteString(ParseDescription(data.TextContent))
114				listContent.WriteString(`</p></div></content></entry>`)
115			} else {
116				listContent.WriteString(`<div class="block">`)
117				if fullview != "" && preview != "" {
118					listContent.WriteString(`<a title="open/download" href="`)
119					listContent.WriteString(fullview)
120					listContent.WriteString(`"><img loading="lazy" src="`)
121					listContent.WriteString(preview)
122					listContent.WriteString(`" width="15%"></a>`)
123				} else {
124					listContent.WriteString(`<h1>[ TEXT ]</h1>`)
125				}
126				listContent.WriteString(`<br><a href="`)
127				listContent.WriteString(ConvertDeviantArtUrlToSkunkyArt(data.Url))
128				listContent.WriteString(`">`)
129				listContent.WriteString(data.Author.Username)
130				listContent.WriteString(" - ")
131				listContent.WriteString(data.Title)
132
133				if data.NSFW {
134					listContent.WriteString(` [<span class="nsfw">NSFW</span>]`)
135				}
136				if data.AI {
137					listContent.WriteString(" [🤖]")
138				}
139				if data.DD {
140					listContent.WriteString(` [<span class="dd">DD</span>]`)
141				}
142
143				listContent.WriteString("</a></div>")
144			}
145		}
146	}
147
148	if allowAtom && s.Atom {
149		list.WriteString(`<?xml version="1.0" encoding="UTF-8"?><feed xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom">`)
150
151		list.WriteString(`<title>`)
152		if s.Type == 0 {
153			list.WriteString("Daily Deviations")
154		} else if s.Type == 'g' && len(devs) != 0 {
155			list.WriteString(devs[0].Author.Username)
156		} else {
157			list.WriteString("SkunkyArt")
158		}
159		list.WriteString(`</title>`)
160
161		list.WriteString(`<link rel="alternate" href="`)
162		list.WriteString(Host)
163		list.WriteString(`"/>`)
164
165		list.WriteString(listContent.String())
166
167		list.WriteString("</feed>")
168		wr(s.Writer, list.String())
169	} else {
170		list.WriteString(`<div class="content">`)
171
172		list.WriteString(listContent.String())
173
174		list.WriteString("</div>")
175		if content != nil {
176			list.WriteString(s.NavBase(content[0]))
177		}
178	}
179
180	return list.String()
181}
182
183/* DESCRIPTION/COMMENT PARSER */
184type text struct {
185	TXT     string
186	TXT_RAW string
187	From    int
188	To      int
189}
190
191// переписать весь этот пиздец нахуй
192func ParseDescription(dscr devianter.Text) string {
193	var parsedDescription strings.Builder
194	TagBuilder := func(content string, tags ...string) string {
195		l := len(tags)
196		for x := 0; x < l; x++ {
197			var htm strings.Builder
198			htm.WriteString("<")
199			htm.WriteString(tags[x])
200			htm.WriteString(">")
201
202			htm.WriteString(content)
203
204			htm.WriteString("</")
205			htm.WriteString(tags[x])
206			htm.WriteString(">")
207			content = htm.String()
208		}
209		return content
210	}
211	DeleteTrackingFromUrl := func(url string) string {
212		if len(url) > 42 && url[:42] == "https://www.deviantart.com/users/outgoing?" {
213			url = url[42:]
214		}
215		return url
216	}
217
218	if description, dl := dscr.Html.Markup, len(dscr.Html.Markup); dl != 0 &&
219		description[0] == '{' &&
220		description[dl-1] == '}' {
221		var descr struct {
222			Blocks []struct {
223				Text, Type        string
224				InlineStyleRanges []struct {
225					Offset, Length int
226					Style          string
227				}
228				EntityRanges []struct {
229					Offset, Length int
230					Key            int
231				}
232				Data struct {
233					TextAlignment string
234				}
235			}
236			EntityMap map[string]struct {
237				Type string
238				Data struct {
239					Url    string
240					Config struct {
241						Aligment string
242						Width    int
243					}
244					Data devianter.Deviation
245				}
246			}
247		}
248		e := json.Unmarshal([]byte(description), &descr)
249		try(e)
250
251		entities := make(map[int]devianter.Deviation)
252		urls := make(map[int]string)
253		for n, x := range descr.EntityMap {
254			num, _ := strconv.Atoi(n)
255			if x.Data.Url != "" {
256				urls[num] = DeleteTrackingFromUrl(x.Data.Url)
257			}
258			entities[num] = x.Data.Data
259		}
260
261		for _, x := range descr.Blocks {
262			Styles := make([]text, len(x.InlineStyleRanges))
263
264			if len(x.InlineStyleRanges) != 0 {
265				var tags = make(map[int][]string)
266				for n, rngs := range x.InlineStyleRanges {
267					Styles := &Styles[n]
268					switch rngs.Style {
269					case "BOLD":
270						rngs.Style = "b"
271					case "UNDERLINE":
272						rngs.Style = "u"
273					case "ITALIC":
274						rngs.Style = "i"
275					}
276					Styles.From = rngs.Offset
277					Styles.To = rngs.Offset + rngs.Length
278					FT := Styles.From * Styles.To
279					tags[FT] = append(tags[FT], rngs.Style)
280				}
281				for n := 0; n < len(Styles); n++ {
282					Styles := &Styles[n]
283					Styles.TXT_RAW = x.Text[Styles.From:Styles.To]
284					Styles.TXT = TagBuilder(Styles.TXT_RAW, tags[Styles.From*Styles.To]...)
285				}
286			}
287
288			switch x.Type {
289			case "atomic":
290				if len(x.EntityRanges) != 0 {
291					d := entities[x.EntityRanges[0].Key]
292					parsedDescription.WriteString(`<a href="`)
293					parsedDescription.WriteString(ConvertDeviantArtUrlToSkunkyArt(d.Url))
294					parsedDescription.WriteString(`"><img width="50%" src="`)
295					parsedDescription.WriteString(ParseMedia(d.Media))
296					parsedDescription.WriteString(`" title="`)
297					parsedDescription.WriteString(d.Author.Username)
298					parsedDescription.WriteString(" - ")
299					parsedDescription.WriteString(d.Title)
300					parsedDescription.WriteString(`"></a>`)
301				}
302			case "unstyled":
303				if l := len(Styles); l != 0 {
304					for n, r := range Styles {
305						var tag string
306						if x.Type == "header-two" {
307							tag = "h2"
308						}
309
310						parsedDescription.WriteString(x.Text[:r.From])
311						if len(urls) != 0 && len(x.EntityRanges) != 0 {
312							ra := &x.EntityRanges[0]
313
314							parsedDescription.WriteString(`<a target="_blank" href="`)
315							parsedDescription.WriteString(urls[ra.Key])
316							parsedDescription.WriteString(`">`)
317							parsedDescription.WriteString(r.TXT)
318							parsedDescription.WriteString(`</a>`)
319						} else if l > n+1 {
320							parsedDescription.WriteString(r.TXT)
321						}
322						parsedDescription.WriteString(TagBuilder(tag, x.Text[r.To:]))
323					}
324				} else {
325					parsedDescription.WriteString(x.Text)
326				}
327			}
328			parsedDescription.WriteString("<br>")
329		}
330	} else if dl != 0 {
331		for tt := html.NewTokenizer(strings.NewReader(dscr.Html.Markup)); ; {
332			switch tt.Next() {
333			case html.ErrorToken:
334				return parsedDescription.String()
335			case html.StartTagToken, html.EndTagToken, html.SelfClosingTagToken:
336				token := tt.Token()
337				switch token.Data {
338				case "a":
339					for _, a := range token.Attr {
340						if a.Key == "href" {
341							url := DeleteTrackingFromUrl(a.Val)
342							parsedDescription.WriteString(`<a target="_blank" href="`)
343							parsedDescription.WriteString(url)
344							parsedDescription.WriteString(`">`)
345							parsedDescription.WriteString(GetValueOfTag(tt))
346							parsedDescription.WriteString("</a> ")
347						}
348					}
349				case "img":
350					var uri, title string
351					for b, a := range token.Attr {
352						switch a.Key {
353						case "src":
354							if len(a.Val) > 9 && a.Val[8:9] == "e" {
355								uri = UrlBuilder("media", "emojitar", a.Val[37:len(a.Val)-4], "?type=e")
356							}
357						case "title":
358							title = a.Val
359						}
360						if title != "" {
361							for x := -1; x < b; x++ {
362								parsedDescription.WriteString(`<img src="`)
363								parsedDescription.WriteString(uri)
364								parsedDescription.WriteString(`" title="`)
365								parsedDescription.WriteString(title)
366								parsedDescription.WriteString(`">`)
367							}
368						}
369					}
370				case "br", "li", "ul", "p", "b":
371					parsedDescription.WriteString(token.String())
372				case "div":
373					parsedDescription.WriteString("<p> ")
374				}
375			case html.TextToken:
376				parsedDescription.Write(tt.Text())
377			}
378		}
379	}
380
381	return parsedDescription.String()
382}