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