krz/skunky-art

Alternative privacy frontend for DeviantArt.

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

v1.3: app/wrapper.go · raw

  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}