krz/skunky-art

Alternative privacy frontend for DeviantArt.

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

v1.4.0: app/wrapper.go · raw

  1package app
  2
  3import (
  4	"regexp"
  5	"strconv"
  6	"strings"
  7	"time"
  8
  9	"github.com/krazywarez/devianter"
 10	"golang.org/x/net/html"
 11)
 12
 13// GRUser renders a group or user page: the about tab, the gallery, or favourites,
 14// selected by the request's type argument.
 15func (s skunkyart) GRUser() {
 16	if len(s.Query) < 1 {
 17		s.ReturnHTTPError(400)
 18		return
 19	}
 20
 21	var g devianter.Group
 22	var daError devianter.Error
 23	g.Name = s.Query
 24	var err error
 25	s.Templates.GroupUser.GR, daError, err = g.Get()
 26	try(err)
 27	if daError.RAW != nil {
 28		s.Error(daError)
 29		return
 30	}
 31
 32	group := &s.Templates.GroupUser
 33
 34	switch s.Type {
 35	case 'a':
 36		g := group.GR
 37		s.Atom = false
 38		for _, x := range g.Gruser.Page.Modules {
 39			switch x.Name {
 40			case "about", "group_about":
 41				if g.Owner.Group {
 42					var about = &x.ModuleData.GroupAbout
 43					group.Group = true
 44					group.CreationDate = x.ModuleData.GroupAbout.FoundatedAt.UTC().String()
 45					group.About.DescriptionFormatted = ParseDescription(s.Host, about.Description)
 46				} else if false {
 47					group.About.A = x.ModuleData.About
 48					var about = &group.About.A
 49					group.CreationDate = time.Unix(time.Now().Unix()-x.ModuleData.About.RegDate, 0).UTC().String()
 50					group.About.DescriptionFormatted = ParseDescription(s.Host, about.Description)
 51
 52					for _, val := range x.ModuleData.About.SocialLinks {
 53						var social strings.Builder
 54						social.WriteString(`<a target="_blank" href="`)
 55						social.WriteString(val.Value)
 56						social.WriteString(`">`)
 57						social.WriteString(val.Value)
 58						social.WriteString("</a><br>")
 59						group.About.Social += social.String()
 60					}
 61
 62					for _, val := range x.ModuleData.About.Interests {
 63						var interest strings.Builder
 64						interest.WriteString(val.Label)
 65						interest.WriteString(": <b>")
 66						interest.WriteString(val.Value)
 67						interest.WriteString("</b><br>")
 68						group.About.Interests += interest.String()
 69					}
 70				}
 71				group.About.Comments = s.ParseComments(devianter.GetComments(strconv.Itoa(group.GR.Gruser.ID), "", s.Page, 4))
 72
 73			case "cover_deviation":
 74				group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation
 75				group.About.BGMeta.Url = ConvertDeviantArtURLToSkunkyArt(s.Host, group.About.BGMeta.Url)
 76				group.About.BG = ParseMedia(s.Host, group.About.BGMeta.Media)
 77			case "group_admins":
 78				var htm strings.Builder
 79				for _, z := range x.ModuleData.GroupAdmins.Results {
 80					htm.WriteString(BuildUserPlate(s.Host, z.User.Username))
 81				}
 82				group.Admins += htm.String()
 83			}
 84
 85		}
 86	case 'g', 'f':
 87		var all bool
 88		var content devianter.Group
 89
 90		folderid, _ := strconv.Atoi(s.Args.Get("folder"))
 91
 92		if s.Args.Get("all") == "true" {
 93			all = true
 94		}
 95
 96		if s.Page == 0 {
 97			s.Page++
 98		}
 99
100		if s.Type == 'f' {
101			content, daError = g.Favourites(s.Page, all, folderid)
102		} else {
103			content, daError, err = g.Gallery(s.Page, folderid)
104			try(err)
105		}
106
107		if daError.RAW != nil {
108			s.Error(daError)
109			return
110		}
111
112		if folderid > 0 || (s.Type == 'f' && all) {
113			group.Gallery.List = s.DeviationList(content.Content.Results, true, DeviationList{
114				More: content.Content.HasMore,
115			})
116		} else {
117			for _, x := range content.Content.Gruser.Page.Modules {
118				if len(x.ModuleData.Folders.Results) != 0 {
119					var folders strings.Builder
120					folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`)
121					for _, x := range x.ModuleData.Folders.Results {
122						if x.FolderId != -1 && x.Size != 0 {
123							folders.WriteString(`<div class="block folder-item">`)
124
125							if !x.Thumb.NSFW || CFG.Nsfw {
126								folders.WriteString(`<a href="`)
127								folders.WriteString(ConvertDeviantArtURLToSkunkyArt(s.Host, x.Thumb.Url))
128								folders.WriteString(`"><img loading="lazy" src="`)
129								folders.WriteString(ParseMedia(s.Host, x.Thumb.Media))
130								folders.WriteString(`" title="`)
131								folders.WriteString(x.Thumb.Title)
132								folders.WriteString(`"></a>`)
133							} else {
134								folders.WriteString(`<h1>[ <span class="nsfw">NSFW</span> ]</h1>`)
135							}
136							folders.WriteString("<br>")
137
138							folders.WriteString(`<a href="group_user?folder=`)
139							folders.WriteString(strconv.Itoa(x.FolderId))
140							folders.WriteString("&q=")
141							folders.WriteString(s.Query)
142							folders.WriteString("&type=")
143							folders.WriteRune(s.Type)
144							folders.WriteString(`">`)
145							folders.WriteString(x.Name)
146							folders.WriteString(`</a>`)
147
148							folders.WriteString("</div>")
149						}
150					}
151					folders.WriteString(`</div><h1 id="content"><a href="#content">#</a> Content</h1>`)
152					group.Gallery.Folders = folders.String()
153				}
154
155				if x.Name == "folder_deviations" {
156					group.Gallery.List = s.DeviationList(x.ModuleData.Folder.Deviations, true, DeviationList{
157						Pages: x.ModuleData.Folder.Pages,
158						More:  x.ModuleData.Folder.HasMore,
159					})
160				}
161			}
162		}
163	default:
164		s.ReturnHTTPError(400)
165	}
166
167	if !s.Atom {
168		s.ExecuteTemplate("gruser.htm", "html", &s)
169	}
170}
171
172// Deviation renders a single artwork page, with its description, tags, comments
173// and related work. It responds 403 for NSFW posts on instances that disallow them.
174func (s skunkyart) Deviation(author, postname string) {
175	idSearch := regexp.MustCompile("[0-9]+").FindAllString(postname, -1)
176	if len(idSearch) < 1 {
177		s.ReturnHTTPError(400)
178		return
179	}
180
181	var err devianter.Error
182	post := &s.Templates.Deviation
183
184	id := idSearch[len(idSearch)-1]
185	post.Post, err = devianter.GetDeviation(id, author)
186	if err.RAW != nil {
187		s.Error(err)
188		return
189	}
190
191	if post.Post.Deviation.NSFW && !CFG.Nsfw {
192		s.Writer.WriteHeader(403)
193		wr(s.Writer, `<html><link rel="stylesheet" href="`+
194			URLBuilder(s.Host, "stylesheet")+
195			`" /><h1>NSFW content are disabled on this instance.</h1></html>`)
196		return
197	}
198
199	if post.Post.Comments.Total <= 50 {
200		post.Post.Comments.Cursor = ""
201	}
202
203	if post.Post.Deviation.TextContent.Excerpt != "" {
204		post.Post.Description = ParseDescription(s.Host, post.Post.Deviation.TextContent)
205	} else {
206		post.Post.Description = ParseDescription(s.Host, post.Post.Deviation.Extended.DescriptionText)
207	}
208
209	for _, x := range post.Post.Deviation.Extended.RelatedContent {
210		if len(x.Deviations) != 0 {
211			post.Related += s.DeviationList(x.Deviations, false)
212		}
213	}
214
215	// hashtags
216	for _, x := range post.Post.Deviation.Extended.Tags {
217		var tag strings.Builder
218		tag.WriteString(` <a href="`)
219		tag.WriteString(URLBuilder(s.Host, "search", "?q=", x.Name, "&type=tag"))
220		tag.WriteString(`">#`)
221		tag.WriteString(x.Name)
222		tag.WriteString("</a>")
223
224		post.Tags += tag.String()
225	}
226
227	post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1))
228	post.StringTime = post.Post.Deviation.PublishedTime.UTC().String()
229	post.Post.IMG = ParseMedia(s.Host, post.Post.Deviation.Media)
230
231	s.ExecuteTemplate("deviantion.htm", "html", &s)
232}
233
234// DD renders the Daily Deviations page, including each themed strip.
235func (s skunkyart) DD() {
236	dd, err := devianter.GetDailyDeviations(s.Page)
237	if err.RAW != nil {
238		s.Error(err)
239		return
240	}
241	var strips strings.Builder
242	for _, x := range dd.Strips {
243		strips.WriteString(`<h3 class="`)
244		strips.WriteString(x.Codename)
245		strips.WriteString(`"> <a href="#`)
246		strips.WriteString(x.Codename)
247		strips.WriteString(`"># </a>`)
248		strips.WriteString(x.Title)
249		strips.WriteString(`</h3>`)
250
251		strips.WriteString(s.DeviationList(x.Deviations, false))
252	}
253	s.Templates.DDStrips = strips.String()
254	s.Templates.SomeList = s.DeviationList(dd.Deviations, true, DeviationList{
255		Pages: 0,
256		More:  dd.HasMore,
257	})
258	if !s.Atom {
259		s.ExecuteTemplate("daily.htm", "html", &s)
260	}
261}
262
263// Search renders search results for the request's query. Group search is scraped
264// rather than fetched from the API, which DeviantArt does not expose to guests.
265func (s skunkyart) Search() {
266	if s.Query == "" {
267		s.ReturnHTTPError(400)
268		return
269	}
270
271	var err error
272	var daError devianter.Error
273	ss := &s.Templates.Search
274	switch s.Type {
275	case 'a', 't':
276		ss.Content, daError, err = devianter.PerformSearch(s.Query, s.Page, s.Type)
277	case 'g', 'f':
278		ss.Content, daError, err = devianter.PerformSearch(s.Query, s.Page, s.Type, s.Args.Get("usr"))
279	case 'r': // scraper, since DeviantArt withholds the guest API for group search
280		var (
281			usernames = make(map[int]string)
282			url       strings.Builder
283			num       int
284		)
285
286		s.Page++
287
288		url.WriteString("https://www.deviantart.com/groups/?q=")
289		url.WriteString(s.Query)
290		if s.Page > 1 {
291			url.WriteString("&offset=")
292			url.WriteString(strconv.Itoa(10 * s.Page))
293		}
294
295		dwnld := Download(url.String())
296
297		for z := html.NewTokenizer(strings.NewReader(string(dwnld.Body))); ; {
298			if n, token := z.Next(), z.Token(); n == html.StartTagToken && token.Data == "a" {
299				for _, x := range token.Attr {
300					if x.Key == "class" && x.Val == "u regular username" {
301						usernames[num] = GetValueOfTag(z)
302						num++
303					}
304				}
305			} else if n == 0 {
306				break
307			} else {
308				continue
309			}
310		}
311
312		if len(usernames) != 0 {
313			ss.List += `<div class="content plates">`
314			for x := range len(usernames) {
315				ss.List += BuildUserPlate(s.Host, usernames[x])
316			}
317			ss.List += `</div>`
318			ss.List += s.NavBase(DeviationList{
319				More: true,
320			})
321		}
322	default:
323		s.ReturnHTTPError(400)
324		return
325	}
326	try(err)
327
328	if s.Type != 'r' {
329		if daError.RAW != nil {
330			s.Error(daError)
331			return
332		}
333
334		ss.List = s.DeviationList(ss.Content.Results, false, DeviationList{
335			Pages: ss.Content.Pages,
336			More:  ss.Content.HasMore,
337		})
338	}
339
340	s.ExecuteTemplate("search.htm", "html", &s)
341}
342
343// Emojitar proxies a user's avatar or emoji image, selected by the request's
344// type argument.
345func (s skunkyart) Emojitar(name string) {
346	if name == "" || (s.Type != 'a' && s.Type != 'e') {
347		s.ReturnHTTPError(400)
348		return
349	}
350
351	ae, e := devianter.AEmedia(name, s.Type)
352	if e != nil {
353		s.ReturnHTTPError(404)
354	}
355	wr(s.Writer, ae)
356}