krz/skunky-art
Alternative privacy frontend for DeviantArt.
clone: git clone https://gitbay.org/krz/skunky-art.git
1package app
2
3import (
4 "encoding/json"
5 "io"
6 "net/http"
7 "net/url"
8 "regexp"
9 "strconv"
10 "strings"
11 "time"
12
13 "git.macaw.me/skunky/devianter"
14 "golang.org/x/net/html"
15)
16
17var wr = io.WriteString
18var Templates = make(map[string]string)
19
20type skunkyart struct {
21 Writer http.ResponseWriter
22
23 Args url.Values
24 BasePath string
25 Type rune
26 Query, QueryRaw string
27 Page int
28 Atom bool
29
30 Templates struct {
31 About struct {
32 Proxy bool
33 Nsfw bool
34 Instances []settings
35 }
36
37 SomeList string
38 DDStrips string
39 Deviation struct {
40 Post devianter.Post
41 Related string
42 StringTime string
43 Tags string
44 Comments string
45 }
46
47 GroupUser struct {
48 GR devianter.GRuser
49 Admins string
50 Group bool
51 CreationDate string
52
53 About struct {
54 A devianter.About
55
56 DescriptionFormatted string
57 Interests, Social string
58 Comments string
59 BG string
60 BGMeta devianter.Deviation
61 }
62
63 Gallery struct {
64 Folders string
65 Pages int
66 List string
67 }
68 }
69 Search struct {
70 Content devianter.Search
71 List string
72 }
73 }
74}
75
76func (s skunkyart) GRUser() {
77 if len(s.Query) < 1 {
78 s.ReturnHTTPError(400)
79 return
80 }
81
82 var g devianter.Group
83 g.Name = s.Query
84 var err error
85 s.Templates.GroupUser.GR, err = g.GetGroup()
86 try(err)
87
88 group := &s.Templates.GroupUser
89
90 switch s.Type {
91 case 'a':
92 g := group.GR
93 s.Atom = false
94 for _, x := range g.Gruser.Page.Modules {
95 switch x.Name {
96 case "about", "group_about":
97 switch g.Owner.Group {
98 case true:
99 var about = &x.ModuleData.GroupAbout
100 group.Group = true
101 group.CreationDate = x.ModuleData.GroupAbout.FoundatedAt.UTC().String()
102 group.About.DescriptionFormatted = ParseDescription(about.Description)
103 case false:
104 group.About.A = x.ModuleData.About
105 var about = &group.About.A
106 group.CreationDate = time.Unix(time.Now().Unix()-x.ModuleData.About.RegDate, 0).UTC().String()
107
108 group.About.DescriptionFormatted = ParseDescription(about.Description)
109
110 for _, val := range x.ModuleData.About.SocialLinks {
111 var social strings.Builder
112 social.WriteString(`<a target="_blank" href="`)
113 social.WriteString(val.Value)
114 social.WriteString(`">`)
115 social.WriteString(val.Value)
116 social.WriteString("</a><br>")
117 group.About.Social += social.String()
118 }
119
120 for _, val := range x.ModuleData.About.Interests {
121 var interest strings.Builder
122 interest.WriteString(val.Label)
123 interest.WriteString(": <b>")
124 interest.WriteString(val.Value)
125 interest.WriteString("</b><br>")
126 group.About.Interests += interest.String()
127 }
128 }
129 group.About.Comments = s.ParseComments(devianter.GetComments(
130 strconv.Itoa(group.GR.Gruser.ID),
131 "",
132 s.Page,
133 4,
134 ))
135
136 case "cover_deviation":
137 group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation
138 group.About.BGMeta.Url = ConvertDeviantArtUrlToSkunkyArt(group.About.BGMeta.Url)
139 group.About.BG = ParseMedia(group.About.BGMeta.Media)
140 case "group_admins":
141 var htm strings.Builder
142 for _, z := range x.ModuleData.GroupAdmins.Results {
143 htm.WriteString(BuildUserPlate(z.User.Username))
144 }
145 group.Admins += htm.String()
146 }
147
148 }
149 case 'g':
150 folderid, _ := strconv.Atoi(s.Args.Get("folder"))
151 if s.Page == 0 {
152 s.Page++
153 }
154
155 gallery, err := g.GetGallery(s.Page, folderid)
156 try(err)
157
158 if folderid > 0 {
159 group.Gallery.List = s.DeviationList(gallery.Content.Results, true, DeviationList{
160 More: gallery.Content.HasMore,
161 })
162 } else {
163 for _, x := range gallery.Content.Gruser.Page.Modules {
164 if l := len(x.ModuleData.Folders.Results); l != 0 {
165 var folders strings.Builder
166 folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`)
167 for _, x := range x.ModuleData.Folders.Results {
168 folders.WriteString(`<div class="block folder-item">`)
169
170 if !(x.Thumb.NSFW && !CFG.Nsfw) {
171 folders.WriteString(`<a href="`)
172 folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url))
173 folders.WriteString(`"><img loading="lazy" src="`)
174 folders.WriteString(ParseMedia(x.Thumb.Media))
175 folders.WriteString(`" title="`)
176 folders.WriteString(x.Thumb.Title)
177 folders.WriteString(`"></a>`)
178 } else {
179 folders.WriteString(`<h1>[ <span class="nsfw">NSFW</span> ]</h1>`)
180 }
181 folders.WriteString("<br>")
182
183 folders.WriteString(`<a href="?folder=`)
184 folders.WriteString(strconv.Itoa(x.FolderId))
185 folders.WriteString("&q=")
186 folders.WriteString(s.Query)
187 folders.WriteString("&type=")
188 folders.WriteString(string(s.Type))
189 folders.WriteString(`">`)
190 folders.WriteString(x.Name)
191 folders.WriteString(`</a>`)
192
193 folders.WriteString("</div>")
194 }
195 folders.WriteString(`</div><h1 id="content"><a href="#content">#</a> Content</h1>`)
196 group.Gallery.Folders = folders.String()
197 }
198
199 if x.Name == "folder_deviations" {
200 group.Gallery.List = s.DeviationList(x.ModuleData.Folder.Deviations, true, DeviationList{
201 Pages: x.ModuleData.Folder.Pages,
202 More: x.ModuleData.Folder.HasMore,
203 })
204 }
205 }
206 }
207 default:
208 s.ReturnHTTPError(400)
209 }
210
211 if !s.Atom {
212 s.ExecuteTemplate("gruser.htm", &s)
213 }
214}
215
216// посты
217func (s skunkyart) Deviation(author, postname string) {
218 id_search := regexp.MustCompile("[0-9]+").FindAllString(postname, -1)
219 if len(id_search) < 1 {
220 s.ReturnHTTPError(400)
221 return
222 }
223
224 post := &s.Templates.Deviation
225
226 id := id_search[len(id_search)-1]
227 post.Post = devianter.GetDeviation(id, author)
228
229 if post.Post.Deviation.TextContent.Excerpt != "" {
230 post.Post.Description = ParseDescription(post.Post.Deviation.TextContent)
231 } else {
232 post.Post.Description = ParseDescription(post.Post.Deviation.Extended.DescriptionText)
233 }
234 // время публикации
235 post.StringTime = post.Post.Deviation.PublishedTime.UTC().String()
236 post.Post.IMG = ParseMedia(post.Post.Deviation.Media)
237 for _, x := range post.Post.Deviation.Extended.RelatedContent {
238 if len(x.Deviations) != 0 {
239 post.Related += s.DeviationList(x.Deviations, false)
240 }
241 }
242
243 // хештэги
244 for _, x := range post.Post.Deviation.Extended.Tags {
245 var tag strings.Builder
246 tag.WriteString(` <a href="`)
247 tag.WriteString(UrlBuilder("search", "?q=", x.Name, "&type=tag"))
248 tag.WriteString(`">#`)
249 tag.WriteString(x.Name)
250 tag.WriteString("</a>")
251
252 post.Tags += tag.String()
253 }
254
255 if post.Post.Comments.Total <= 50 {
256 post.Post.Comments.Cursor = ""
257 }
258
259 post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1))
260
261 s.ExecuteTemplate("deviantion.htm", &s)
262}
263
264func (s skunkyart) DD() {
265 dd := devianter.GetDailyDeviations(s.Page)
266 var strips strings.Builder
267 for _, x := range dd.Strips {
268 strips.WriteString(`<h3 class="`)
269 strips.WriteString(x.Codename)
270 strips.WriteString(`"> <a href="#`)
271 strips.WriteString(x.Codename)
272 strips.WriteString(`"># </a>`)
273 strips.WriteString(x.Title)
274 strips.WriteString(`</h3>`)
275
276 strips.WriteString(s.DeviationList(x.Deviations, false))
277 }
278 s.Templates.DDStrips = strips.String()
279 s.Templates.SomeList = s.DeviationList(dd.Deviations, true, DeviationList{
280 Pages: 0,
281 More: dd.HasMore,
282 })
283 if !s.Atom {
284 s.ExecuteTemplate("daily.htm", &s)
285 }
286}
287
288func (s skunkyart) Search() {
289 var err error
290 ss := &s.Templates.Search
291 switch s.Type {
292 case 'a', 't':
293 ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type)
294 case 'g':
295 ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type, s.Args.Get("usr"))
296 case 'r': // скраппер, поскольку девиантартовцы зажопили гостевое API для поиска групп
297 var (
298 usernames = make(map[int]string)
299 url strings.Builder
300 num int
301 )
302
303 s.Page++
304
305 url.WriteString("https://www.deviantart.com/groups/?q=")
306 url.WriteString(s.Query)
307 if s.Page > 1 {
308 url.WriteString("&offset=")
309 url.WriteString(strconv.Itoa(10 * s.Page))
310 }
311
312 dwnld := Download(url.String())
313
314 for z := html.NewTokenizer(strings.NewReader(string(dwnld.Body))); ; {
315 if n, token := z.Next(), z.Token(); n == html.StartTagToken && token.Data == "a" {
316 for _, x := range token.Attr {
317 if x.Key == "class" && x.Val == "u regular username" {
318 usernames[num] = GetValueOfTag(z)
319 num++
320 }
321 }
322 } else if n == 0 {
323 break
324 } else {
325 continue
326 }
327 }
328
329 if l := len(usernames); l != 0 {
330 ss.List += `<div class="content plates">`
331 for x := 0; x < len(usernames); x++ {
332 ss.List += BuildUserPlate(usernames[x])
333 }
334 ss.List += `</div>`
335 ss.List += s.NavBase(DeviationList{
336 More: true,
337 })
338 }
339 default:
340 s.ReturnHTTPError(400)
341 }
342 try(err)
343
344 if s.Type != 'r' {
345 ss.List = s.DeviationList(ss.Content.Results, false, DeviationList{
346 Pages: ss.Content.Pages,
347 More: ss.Content.HasMore,
348 })
349 }
350
351 s.ExecuteTemplate("search.htm", &s)
352}
353
354func (s skunkyart) Emojitar(name string) {
355 if name == "" || !(s.Type == 'a' || s.Type == 'e') {
356 s.ReturnHTTPError(400)
357 return
358 }
359
360 ae, e := devianter.AEmedia(name, s.Type)
361 if e != nil {
362 s.ReturnHTTPError(404)
363 }
364 wr(s.Writer, ae)
365}
366
367func (s skunkyart) About() {
368 s.Templates.About.Nsfw = CFG.Nsfw
369 s.Templates.About.Proxy = CFG.Proxy
370 try(json.Unmarshal([]byte(Templates["instances.json"]), &s.Templates.About))
371 s.ExecuteTemplate("about.htm", &s)
372}