parsers.go 11 KB

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