Commit 7f1ab1b73d

7f1ab1b73d1e7a0d8a730354bbc3136c5584fea3

parent: 8b839f9406

Unsigned

lost+skunk <skunky@ebloid.ru> · 2024-07-05 21:46 UTC

Реализация конфига

Layout: unified · split

app/config.go +50 −19
@@ -1,26 +1,31 @@
11package app
22
33import (
4 "encoding/json"
5 "errors"
46 "os"
57)
68
79type cache_config struct {
8 Enabled bool
9 Path string
10 Max_size, Lifetime int64
10 Enabled bool
11 Path string
12 MaxSize int64 `json:"max-size"`
13 Lifetime int64
1114}
1215
1316type config struct {
14 cfg string
15 Listen, Base_uri string
16 Cache cache_config
17 Proxy, Nsfw bool
17 cfg string
18 Listen string
19 BasePath string `json:"base-path"`
20 Cache cache_config
21 Proxy, Nsfw bool
22 WixmpProxy string `json:"wixmp-proxy"`
1823}
1924
2025var CFG = config{
2126 cfg: "config.json",
2227 Listen: "127.0.0.1:3003",
23 Base_uri: "/",
28 BasePath: "/",
2429 Cache: cache_config{
2530 Enabled: true,
2631 Path: "cache",
@@ -29,20 +34,46 @@ var CFG = config{
2934 Nsfw: true,
3035}
3136
32func execcfg() {
37func ExecuteConfig() {
38 try := func(err error, exitcode int) {
39 if err != nil {
40 println(err.Error())
41 os.Exit(exitcode)
42 }
43 }
44
3345 a := os.Args
34 for num, val := range a {
35 switch val {
36 case "-conf":
37 CFG.cfg = a[num]
38 case "-help":
39 println(`SkunkyArt v 1.3 [refactoring]
46 if l := len(a); l > 1 {
47 switch a[1] {
48 case "-c", "--config":
49 if l >= 3 {
50 CFG.cfg = a[2]
51 } else {
52 try(errors.New("Not enought arguments"), 1)
53 }
54 case "-h", "--help":
55 try(errors.New(`SkunkyArt v1.3 [refactoring]
4056Usage:
41 - -conf - path to config
42 - -help this message
57 - [-c|--config] - path to config
58 - [-h|--help] - returns this message
4359Example:
44 ./skunkyart -conf config.json
45Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`)
60 ./skunkyart -c config.json
61Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`), 0)
62 default:
63 try(errors.New("Unreconginzed argument: "+a[1]), 1)
64 }
65 if CFG.cfg != "" {
66 f, err := os.ReadFile(CFG.cfg)
67 try(err, 1)
68
69 try(json.Unmarshal(f, &CFG), 1)
70 if CFG.Cache.Enabled && !CFG.Proxy {
71 try(errors.New("Incompatible settings detected: cannot use caching media content without proxy"), 1)
72 }
73
74 if CFG.Cache.MaxSize != 0 || CFG.Cache.Lifetime != 0 {
75 go InitCacheSystem()
76 }
4677 }
4778 }
4879}
app/router.go +21 −9
@@ -9,11 +9,14 @@ import (
99 "strings"
1010)
1111
12const addr string = "0.0.0.0:3003"
13
14// роутер
1512func Router() {
1613 parsepath := func(path string) map[int]string {
14 if l := len(CFG.BasePath); len(path) > l {
15 path = path[l-1:]
16 } else {
17 path = "/"
18 }
19
1720 parsedpath := make(map[int]string)
1821 for x := 0; true; x++ {
1922 slash := strings.Index(path, "/") + 1
@@ -52,16 +55,20 @@ func Router() {
5255 var skunky skunkyart
5356 skunky.Args = r.URL.Query()
5457 skunky.Writer = w
58 skunky.BasePath = CFG.BasePath
5559
5660 arg := skunky.Args.Get
57 skunky.Query = u.QueryEscape(arg("q"))
61 skunky.QueryRaw = arg("q")
62 skunky.Query = u.QueryEscape(skunky.QueryRaw)
63
5864 if t := arg("type"); len(t) > 0 {
5965 skunky.Type = rune(t[0])
6066 }
6167 p, _ := strconv.Atoi(arg("p"))
6268 skunky.Page = p
69
6370 if arg("atom") == "true" {
64 skunky.atom = true
71 skunky.Atom = true
6572 }
6673
6774 // пути
@@ -69,7 +76,7 @@ func Router() {
6976 default:
7077 skunky.ReturnHTTPError(404)
7178 case "":
72 open_n_send("html/index.htm")
79 skunky.ExecuteTemplate("html/index.htm", &CFG.BasePath)
7380 case "post":
7481 skunky.Deviation(path[2], path[3])
7582 case "search":
@@ -80,9 +87,14 @@ func Router() {
8087 skunky.GRUser()
8188
8289 case "media":
83 skunky.Emojitar(path[2])
90 switch path[2] {
91 case "file":
92 skunky.DownloadAndSendMedia(path[3], next(path, 4))
93 case "emojitar":
94 skunky.Emojitar(path[3])
95 }
8496 case "about":
85 open_n_send("html/about.htm")
97 skunky.About()
8698 case "gui":
8799 w.Header().Add("content-type", "text/css")
88100 open_n_send(next(path, 2))
@@ -90,7 +102,7 @@ func Router() {
90102 }
91103
92104 http.HandleFunc("/", handle)
93 http.ListenAndServe(addr, nil)
105 http.ListenAndServe(CFG.Listen, nil)
94106}
95107
96108func err(e error) {
app/util.go +186 −71
@@ -1,11 +1,17 @@
11package app
22
33import (
4 "encoding/base64"
45 "encoding/json"
6 "io"
57 "net/http"
8 u "net/url"
9 "os"
610 "strconv"
711 "strings"
12 "syscall"
813 "text/template"
14 "time"
915
1016 "git.macaw.me/skunky/devianter"
1117)
@@ -19,11 +25,15 @@ func (s skunkyart) ExecuteTemplate(file string, data any) {
1925 wr(s.Writer, buf.String())
2026}
2127
22func (s skunkyart) UrlBuilder(strs ...string) string {
28func UrlBuilder(strs ...string) string {
2329 var str strings.Builder
24 for _, x := range strs {
30 l := len(strs)
31 str.WriteString(CFG.BasePath)
32 for n, x := range strs {
2533 str.WriteString(x)
26 str.WriteString("/")
34 if n+1 < l && !(strs[n+1][0] == '?' || strs[n+1][0] == '&') && !(x[0] == '?' || x[0] == '&') {
35 str.WriteString("/")
36 }
2737 }
2838 return str.String()
2939}
@@ -31,10 +41,10 @@ func (s skunkyart) UrlBuilder(strs ...string) string {
3141func (s skunkyart) ReturnHTTPError(status int) {
3242 s.Writer.WriteHeader(status)
3343
34 // пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения
3544 var msg strings.Builder
36 msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`)
37 msg.WriteString("<h1>")
45 msg.WriteString(`<html><link rel="stylesheet" href="`)
46 msg.WriteString(UrlBuilder("gui", "css", "skunky.css"))
47 msg.WriteString(`" /><h1>`)
3848 msg.WriteString(strconv.Itoa(status))
3949 msg.WriteString(" - ")
4050 msg.WriteString(http.StatusText(status))
@@ -43,6 +53,15 @@ func (s skunkyart) ReturnHTTPError(status int) {
4353 wr(s.Writer, msg.String())
4454}
4555
56func (s skunkyart) ConvertDeviantArtUrlToSkunkyArt(url string) (output string) {
57 if len(url) > 32 && url[27:32] != "stash" {
58 url = url[27:]
59 toart := strings.Index(url, "/art/")
60 output = UrlBuilder("post", url[:toart], url[toart+5:])
61 }
62 return
63}
64
4665type text struct {
4766 TXT string
4867 from int
@@ -200,80 +219,78 @@ func (s skunkyart) NavBase(c dlist) string {
200219
201220func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string {
202221 var list strings.Builder
203 if !s.atom {
204 list.WriteString(`<div class="content">`)
205 } else {
206 list.WriteString(`<?xml version="1.0" encoding="UTF-8"?><feed xmlns:media="http://search.yahoo.com/mrss" xmlns="http://www.w3.org/2005/Atom">`)
222 if s.Atom && s.Page > 1 {
223 s.ReturnHTTPError(400)
224 return ""
225 } else if s.Atom {
226 list.WriteString(`<?xml version="1.0" encoding="UTF-8"?><feed xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom">`)
207227 list.WriteString(`<title>SkunkyArt</title>`)
208228 // list.WriteString(`<link rel="alternate" href="HOMEPAGE_URL"/><link href="FEED_URL" rel="self"/>`)
229 } else {
230 list.WriteString(`<div class="content">`)
209231 }
210232 for _, data := range devs {
211 url := devianter.UrlFromMedia(data.Media)
212 if s.atom {
213 id := strconv.Itoa(data.ID)
214 list.WriteString(`<entry><author><name>`)
215 list.WriteString(data.Author.Username)
216 list.WriteString(`</name></author><title>`)
217 list.WriteString(data.Title)
218 list.WriteString(`</title><link rel="alternate" type="text/html" href="`)
219 list.WriteString(CFG.Base_uri)
220 list.WriteString("/post/")
221 list.WriteString(data.Author.Username)
222 list.WriteString("/atom-")
223 list.WriteString(id)
224 list.WriteString(`"/><id>`)
225 list.WriteString(id)
226 list.WriteString(`</id><published>`)
227 list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700"))
228 list.WriteString(`</published>`)
229 list.WriteString(`<media:group><media:title>`)
230 list.WriteString(data.Title)
231 list.WriteString(`</media:title><media:thumbinal url="`)
232 list.WriteString(url)
233 list.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`)
234 list.WriteString(data.Url)
235 list.WriteString(`"><img src="`)
236 list.WriteString(url)
237 list.WriteString(`"/></a><p>`)
238 list.WriteString(ParseDescription(data.TextContent))
239 list.WriteString(`</p></div></content></entry>`)
240 } else {
241 list.WriteString(`<div class="block">`)
242 if url != "" {
243 list.WriteString(`<a title="open/download" href="`)
233 if !(data.NSFW && !CFG.Nsfw) {
234 url := s.ParseMedia(data.Media)
235 if s.Atom {
236 id := strconv.Itoa(data.ID)
237 list.WriteString(`<entry><author><name>`)
238 list.WriteString(data.Author.Username)
239 list.WriteString(`</name></author><title>`)
240 list.WriteString(data.Title)
241 list.WriteString(`</title><link rel="alternate" type="text/html" href="`)
242 list.WriteString(UrlBuilder("post", data.Author.Username, "atom-"+id))
243 list.WriteString(`"/><id>`)
244 list.WriteString(id)
245 list.WriteString(`</id><published>`)
246 list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700"))
247 list.WriteString(`</published>`)
248 list.WriteString(`<media:group><media:title>`)
249 list.WriteString(data.Title)
250 list.WriteString(`</media:title><media:thumbinal url="`)
244251 list.WriteString(url)
252 list.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`)
253 list.WriteString(data.Url)
245254 list.WriteString(`"><img src="`)
246255 list.WriteString(url)
247 list.WriteString(`" width="15%"></a>`)
256 list.WriteString(`"/></a><p>`)
257 list.WriteString(ParseDescription(data.TextContent))
258 list.WriteString(`</p></div></content></entry>`)
248259 } else {
249 list.WriteString(`<h1>[ TEXT ]</h1>`)
250 }
251 list.WriteString(`<br><a href="`)
252 list.WriteString("/post/")
253 list.WriteString(data.Author.Username)
254 list.WriteString("/")
255 list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:])
256 list.WriteString(`">`)
257 list.WriteString(data.Author.Username)
258 list.WriteString(" - ")
259 list.WriteString(data.Title)
260 list.WriteString(`<div class="block">`)
261 if url != "" {
262 list.WriteString(`<a title="open/download" href="`)
263 list.WriteString(url)
264 list.WriteString(`"><img src="`)
265 list.WriteString(url)
266 list.WriteString(`" width="15%"></a>`)
267 } else {
268 list.WriteString(`<h1>[ TEXT ]</h1>`)
269 }
270 list.WriteString(`<br><a href="`)
271 list.WriteString(s.ConvertDeviantArtUrlToSkunkyArt(data.Url))
272 list.WriteString(`">`)
273 list.WriteString(data.Author.Username)
274 list.WriteString(" - ")
275 list.WriteString(data.Title)
276
277 // шильдики нсфв, аи и ежедневного поста
278 if data.NSFW {
279 list.WriteString(` [<span class="nsfw">NSFW</span>]`)
280 }
281 if data.AI {
282 list.WriteString(" [🤖]")
283 }
284 if data.DD {
285 list.WriteString(` [<span class="dd">DD</span>]`)
286 }
260287
261 // шильдики нсфв, аи и ежедневного поста
262 if data.NSFW {
263 list.WriteString(` [<span class="nsfw">NSFW</span>]`)
264 }
265 if data.AI {
266 list.WriteString(" [🤖]")
267 }
268 if data.DD {
269 list.WriteString(` [<span class="dd">DD</span>]`)
288 list.WriteString("</a></div>")
270289 }
271
272 list.WriteString("</a></div>")
273290 }
274291 }
275292
276 if s.atom {
293 if s.Atom {
277294 list.WriteString("</feed>")
278295 s.Writer.Write([]byte(list.String()))
279296 return ""
@@ -301,10 +318,10 @@ func (s skunkyart) ParseComments(c devianter.Comments) string {
301318 }
302319 cmmts.WriteString(`"><p id="`)
303320 cmmts.WriteString(strconv.Itoa(x.ID))
304 cmmts.WriteString(`"><img src="/media/`)
305 cmmts.WriteString(x.User.Username)
306 cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/group_user?type=about&q=`)
307 cmmts.WriteString(x.User.Username)
321 cmmts.WriteString(`"><img src="`)
322 cmmts.WriteString(UrlBuilder("media", "emojitar", x.User.Username, "?type=a"))
323 cmmts.WriteString(`" width="30px" height="30px"><a href="`)
324 cmmts.WriteString(UrlBuilder("group_user", "?q=", x.User.Username, "&type=a"))
308325 cmmts.WriteString(`"><b`)
309326 cmmts.WriteString(` class="`)
310327 if x.User.Banned {
@@ -346,3 +363,101 @@ func (s skunkyart) ParseComments(c devianter.Comments) string {
346363 cmmts.WriteString("</details>")
347364 return cmmts.String()
348365}
366
367func (s skunkyart) ParseMedia(media devianter.Media) string {
368 url := devianter.UrlFromMedia(media)
369 if len(url) != 0 {
370 url = url[21:]
371 dot := strings.Index(url, ".")
372
373 return UrlBuilder("media", "file", url[:dot], "/", url[dot+10:])
374 }
375 return ""
376}
377
378func (s skunkyart) DownloadAndSendMedia(subdomain, path string) {
379 var url strings.Builder
380 url.WriteString("https://images-wixmp-")
381 url.WriteString(subdomain)
382 url.WriteString(".wixmp.com/")
383 url.WriteString(path)
384 url.WriteString("?token=")
385 url.WriteString(s.Args.Get("token"))
386
387 download := func() (body []byte, status int, headers http.Header) {
388 cli := &http.Client{}
389 if CFG.WixmpProxy != "" {
390 u, e := u.Parse(CFG.WixmpProxy)
391 err(e)
392 cli.Transport = &http.Transport{Proxy: http.ProxyURL(u)}
393 }
394
395 req, e := http.NewRequest("GET", url.String(), nil)
396 err(e)
397 req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0.0")
398
399 resp, e := cli.Do(req)
400 err(e)
401 defer resp.Body.Close()
402
403 b, e := io.ReadAll(resp.Body)
404 err(e)
405 return b, resp.StatusCode, resp.Header
406 }
407
408 if CFG.Cache.Enabled {
409 os.Mkdir(CFG.Cache.Path, 0700)
410 fname := CFG.Cache.Path + "/" + base64.StdEncoding.EncodeToString([]byte(subdomain+path))
411 file, e := os.Open(fname)
412
413 if e != nil {
414 b, status, headers := download()
415 if status == 200 && headers["Content-Type"][0][:5] == "image" {
416 err(os.WriteFile(fname, b, 0700))
417 s.Writer.Write(b)
418 }
419 } else {
420 file, e := io.ReadAll(file)
421 err(e)
422 s.Writer.Write(file)
423 }
424 } else if CFG.Proxy {
425 b, _, _ := download()
426 s.Writer.Write(b)
427 } else {
428 s.Writer.Write([]byte("Sorry, butt proxy on this instance disabled."))
429 }
430}
431
432func InitCacheSystem() {
433 c := &CFG.Cache
434 for {
435 dir, e := os.Open(c.Path)
436 err(e)
437 stat, e := dir.Stat()
438 err(e)
439
440 dirnames, e := dir.Readdirnames(-1)
441 err(e)
442 for _, a := range dirnames {
443 a = c.Path + "/" + a
444 if c.Lifetime != 0 {
445 now := time.Now().UnixMilli()
446
447 f, _ := os.Stat(a)
448 stat := f.Sys().(*syscall.Stat_t)
449 time := time.Unix(stat.Ctim.Unix()).UnixMilli()
450
451 if time+c.Lifetime <= now {
452 os.RemoveAll(a)
453 }
454 }
455 if c.MaxSize != 0 && stat.Size() > c.MaxSize {
456 os.RemoveAll(a)
457 }
458 }
459
460 dir.Close()
461 time.Sleep(time.Second)
462 }
463}
app/wraper.go +58 −33
@@ -15,13 +15,27 @@ import (
1515var wr = io.WriteString
1616
1717type skunkyart struct {
18 Writer http.ResponseWriter
19 Args url.Values
20 Type rune
21 Query string
22 Page int
23 atom bool
24 Templates struct {
18 Writer http.ResponseWriter
19 Args url.Values
20 BasePath string
21 Type rune
22 Query, QueryRaw string
23 Page int
24 Atom bool
25 Templates struct {
26 About struct {
27 Proxy bool
28 Nsfw bool
29 }
30
31 SomeList string
32 Deviation struct {
33 Post devianter.Post
34 StringTime string
35 Tags string
36 Comments string
37 }
38
2539 GroupUser struct {
2640 GR devianter.GRuser
2741 Admins string
@@ -51,6 +65,19 @@ type skunkyart struct {
5165 }
5266}
5367
68// var Templates struct {
69// Index string
70// About string
71//
72// GRuser string
73// Deviation string
74// List string
75// Search string
76// }
77
78// //go:embed ../html/*
79// var Templates embed.FS
80
5481func (s skunkyart) GRUser() {
5582 if len(s.Query) < 1 {
5683 s.ReturnHTTPError(400)
@@ -65,7 +92,7 @@ func (s skunkyart) GRUser() {
6592 switch s.Type {
6693 case 'a':
6794 g := group.GR
68 s.atom = false
95 s.Atom = false
6996 for _, x := range g.Gruser.Page.Modules {
7097 switch x.Name {
7198 case "about", "group_about":
@@ -110,17 +137,15 @@ func (s skunkyart) GRUser() {
110137
111138 case "cover_deviation":
112139 group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation
113 group.About.BG = devianter.UrlFromMedia(group.About.BGMeta.Media)
140 group.About.BGMeta.Url = s.ConvertDeviantArtUrlToSkunkyArt(group.About.BGMeta.Url)
141 group.About.BG = s.ParseMedia(group.About.BGMeta.Media)
114142 case "group_admins":
115143 var htm strings.Builder
116144 for _, z := range x.ModuleData.GroupAdmins.Results {
117145 htm.WriteString(`<p><img src="`)
118 htm.WriteString("/media/")
119 htm.WriteString(z.User.Username)
120 htm.WriteString("?type=a")
146 htm.WriteString(UrlBuilder("media", "emojitar", z.User.Username, "?type=a"))
121147 htm.WriteString(`"><a href="`)
122 htm.WriteString("/group_user?type=about&q=")
123 htm.WriteString(z.User.Username)
148 htm.WriteString(UrlBuilder("group_user", "?type=about&q=", z.User.Username))
124149 htm.WriteString(`">`)
125150 htm.WriteString(z.User.Username)
126151 htm.WriteString(`</a></p>`)
@@ -175,24 +200,18 @@ func (s skunkyart) GRUser() {
175200 s.ReturnHTTPError(400)
176201 }
177202
178 if !s.atom {
203 if !s.Atom {
179204 s.ExecuteTemplate("html/gruser.htm", &s)
180205 }
181206}
182207
183208// посты
184209func (s skunkyart) Deviation(author, postname string) {
185 // поиск ID
186 re := regexp.MustCompile("[0-9]+").FindAllString(postname, -1)
187 if len(re) >= 1 {
188 var post struct {
189 Post devianter.Post
190 StringTime string
191 Tags string
192 Comments string
193 }
210 id_search := regexp.MustCompile("[0-9]+").FindAllString(postname, -1)
211 if len(id_search) >= 1 {
212 post := &s.Templates.Deviation
194213
195 id := re[len(re)-1]
214 id := id_search[len(id_search)-1]
196215 post.Post = devianter.DeviationFunc(id, author)
197216
198217 post.Post.Description = ParseDescription(post.Post.Deviation.TextContent)
@@ -202,9 +221,9 @@ func (s skunkyart) Deviation(author, postname string) {
202221 // хештэги
203222 for _, x := range post.Post.Deviation.Extended.Tags {
204223 var tag strings.Builder
205 tag.WriteString(` <a href="/search?q=`)
206 tag.WriteString(x.Name)
207 tag.WriteString(`&type=tag">#`)
224 tag.WriteString(` <a href="`)
225 tag.WriteString(UrlBuilder("search", "?q=", x.Name, "&type=tag"))
226 tag.WriteString(`">#`)
208227 tag.WriteString(x.Name)
209228 tag.WriteString("</a>")
210229
@@ -213,7 +232,7 @@ func (s skunkyart) Deviation(author, postname string) {
213232
214233 post.Comments = s.ParseComments(devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1))
215234
216 s.ExecuteTemplate("html/deviantion.htm", &post)
235 s.ExecuteTemplate("html/deviantion.htm", &s)
217236 } else {
218237 s.ReturnHTTPError(400)
219238 }
@@ -221,17 +240,17 @@ func (s skunkyart) Deviation(author, postname string) {
221240
222241func (s skunkyart) DD() {
223242 dd := devianter.DailyDeviationsFunc(s.Page)
224 ddparsed := s.DeviationList(dd.Deviations, dlist{
243 s.Templates.SomeList = s.DeviationList(dd.Deviations, dlist{
225244 Pages: 0,
226245 More: dd.HasMore,
227246 })
228 if !s.atom {
229 s.ExecuteTemplate("html/list.htm", &ddparsed)
247 if !s.Atom {
248 s.ExecuteTemplate("html/list.htm", &s)
230249 }
231250}
232251
233252func (s skunkyart) Search() {
234 s.atom = false
253 s.Atom = false
235254 var e error
236255 ss := &s.Templates.Search
237256 switch s.Type {
@@ -263,3 +282,9 @@ func (s skunkyart) Emojitar(name string) {
263282 s.ReturnHTTPError(400)
264283 }
265284}
285
286func (s skunkyart) About() {
287 s.Templates.About.Nsfw = CFG.Nsfw
288 s.Templates.About.Proxy = CFG.Proxy
289 s.ExecuteTemplate("html/about.htm", &s)
290}
atom.xml deleted −43
@@ -1,43 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<feed xmlns:yt="http://www.youtube.com/xml/schemas/2015" xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US">
3 <link rel="self" href="https://inv.clovius.club/feed/channel/UCuxpxCCevIlF-k-K5YU8XPA"/>
4 <id>yt:channel:UCuxpxCCevIlF-k-K5YU8XPA</id>
5 <yt:channelId>UCuxpxCCevIlF-k-K5YU8XPA</yt:channelId>
6 <icon>https://yt3.googleusercontent.com/ytc/AIdro_n7bKr9iusclFjvYw3U8UR74iTCoMcdGTKI3h55SOG8mSsY=s900-c-k-c0x00ffffff-no-rj</icon>
7 <title>Test</title>
8 <link rel="alternate" href="https://skunky.ebloid.ru"/>
9 <author>
10 <name>lost+skunk</name>
11 <uri>https://skunky.ebloid.ru</uri>
12 </author>
13 <image>
14 <url>https://ebloid.ru/_matrix/media/v3/download/ebloid.ru/enfAtfdjzCQMRNBgLXmiURfJ?allow_redirect=true</url>
15 <title>lost+skunk</title>
16 <link rel="self" href="https://skunky.ebloid.ru"/>
17 </image>
18 <entry>
19 <title>Test</title>
20 <link rel="alternate" href="https://2ip.ua"/>
21 <author>
22 <name>lost+skunk</name>
23 <uri>https://skunky.ebloid.ru</uri>
24 </author>
25 <content type="xhtml">
26 <div xmlns="http://www.w3.org/1999/xhtml">
27 <a href="https://2ip.ua">
28 <img src="https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/526e735c-e3da-4542-a339-72ccdf827a00/dh3mfuq-5bd6472c-68f4-428d-bb90-8d461fc21410.jpg/v1/fill/w_1920,h_1080/image.gif?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9MTA4MCIsInBhdGgiOiJcL2ZcLzUyNmU3MzVjLWUzZGEtNDU0Mi1hMzM5LTcyY2NkZjgyN2EwMFwvZGgzbWZ1cS01YmQ2NDcyYy02OGY0LTQyOGQtYmI5MC04ZDQ2MWZjMjE0MTAuanBnIiwid2lkdGgiOiI8PTE5MjAifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.S_1DGdd29F5l6Y46Q_bumtGlEEUf_WawZsmF8kBygBs"/>
29 </a>
30 <p>test</p>
31 </div>
32 </content>
33 <published>2024-07-04T00:37:55+00:00</published>
34 <media:group>
35 <media:title>GM Just Announced Their Shutting Down Production in America and Firing Their Workers</media:title>
36 <media:thumbnail url="https://ebloid.ru/_matrix/media/v3/download/ebloid.ru/enfAtfdjzCQMRNBgLXmiURfJ?allow_redirect=true" width="320" height="180"/>
37 <media:description>test</media:description>
38 </media:group>
39 <media:community>
40 <media:statistics views="91247"/>
41 </media:community>
42 </entry>
43</feed>
config.example.json +4 −3
@@ -1,12 +1,13 @@
11{
22 "listen": "0.0.0.0:3003",
3 "base_uri": null,
3 "base-path": "/skunky/",
44 "cache": {
55 "enabled": true,
66 "path": "cache",
77 "lifetime": null,
8 "max_size": 10000
8 "max-size": 100000
99 },
10 "proxy": false,
10 "proxy": true,
11 "wixmp-proxy": "http://127.0.0.1:8080",
1112 "nsfw": false
1213}
\ No newline at end of file
css/skunky.css +4 −1
@@ -28,9 +28,12 @@ form input, button, select {
2828 border: 0px;
2929 border-radius: 1px;
3030}
31.nsfw, .true {
31.nsfw, .about-false {
3232 color: red;
3333}
34.about-true {
35 color: green;
36}
3437.author {
3538 color: seagreen;
3639}
html/about.htm +9 −3
@@ -2,12 +2,12 @@
22<html>
33 <head>
44 <title>SkunkyArt</title>
5 <link rel="stylesheet" href="gui/css/skunky.css">
5 <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css">
66 </head>
77 <main>
88 <header>
9 <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1>
10 <form method="get" action="/search">
9 <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1>
10 <form method="get" action="{{.BasePath}}search">
1111 <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
1212 <select name="type">
1313 <option value="all">All</option>
@@ -19,5 +19,11 @@
1919 <p>
2020 SkunkyArt is an alternative frontend for deviantart.com, written in Go.
2121 </p>
22 <h2>Instance settings:</h2>
23 <ul>
24 <li><b>NSFW</b>: <span class="about-{{.Templates.About.Nsfw}}">{{if .Templates.About.Nsfw}}YES{{else}}NO{{end}}</span></li>
25 <li><b>Proxyfing</b>: <span class="about-{{.Templates.About.Proxy}}">{{if .Templates.About.Proxy}}YES{{else}}NO{{end}}</span></li>
26 </ul>
27 <p>Copyright <a href="https://go.kde.org/matrix/#/@softpigeones:ebloid.ru" target="_blank">lost+skunk</a>, X11. <a href="https://git.macaw.me/skunky/skunkyart/src/tag/v1.3" target="_blank">SkunkyArt v1.3</a></p>
2228 </main>
2329</html>
\ No newline at end of file
html/deviantion.htm +19 −19
@@ -1,13 +1,13 @@
11<!DOCTYPE html>
22<html>
33 <head>
4 <title>SkunkyArt | {{.Post.Deviation.Author.Username}} - {{.Post.Deviation.Title}}</title>
5 <link rel="stylesheet" href="/gui/css/skunky.css">
4 <title>SkunkyArt | {{.Templates.Deviation.Post.Deviation.Author.Username}} - {{.Templates.Deviation.Post.Deviation.Title}}</title>
5 <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css">
66 </head>
77 <main>
88 <header>
9 <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1>
10 <form method="get" action="/search">
9 <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1>
10 <form method="get" action="{{.BasePath}}search">
1111 <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
1212 <select name="type">
1313 <option value="all">All</option>
@@ -17,29 +17,29 @@
1717 </form>
1818 </header>
1919 <figure>
20 <img src="/media/{{.Post.Deviation.Author.Username}}?type=a" width="30px">
21 <span><strong><a href="/group_user?type=about&q={{.Post.Deviation.Author.Username}}">{{.Post.Deviation.Author.Username}}</a></strong> — {{if (.Post.Deviation.DD)}}
22 <span class="dd" title="Daily Deviation!"><b>{{.Post.Deviation.Title}}</b></span>
23 {{else}}{{.Post.Deviation.Title}}{{end}}
24 {{if (ne .Post.Deviation.License "none")}}<mark title="License">{{.Post.Deviation.License}}</mark>{{end}} {{if (.Post.Deviation.AI)}}[🤖]{{end}}
25 {{if (.Post.Deviation.NSFW)}}[<span class="nsfw">NSFW</span>]{{end}}
20 <img src="{{.BasePath}}media/emojitar/{{.Templates.Deviation.Post.Deviation.Author.Username}}?type=a" width="30px">
21 <span><strong><a href="{{.BasePath}}group_user?type=about&q={{.Templates.Deviation.Post.Deviation.Author.Username}}">{{.Templates.Deviation.Post.Deviation.Author.Username}}</a></strong> — {{if (.Templates.Deviation.Post.Deviation.DD)}}
22 <span class="dd" title="Daily Deviation!"><b>{{.Templates.Deviation.Post.Deviation.Title}}</b></span>
23 {{else}}{{.Templates.Deviation.Post.Deviation.Title}}{{end}}
24 {{if (ne .Templates.Deviation.Post.Deviation.License "none")}}<mark title="License">{{.Templates.Deviation.Post.Deviation.License}}</mark>{{end}} {{if (.Templates.Deviation.Post.Deviation.AI)}}[🤖]{{end}}
25 {{if (.Templates.Deviation.Post.Deviation.NSFW)}}[<span class="nsfw">NSFW</span>]{{end}}
2626 </span>
2727 <br>
28 {{if (ne .Post.IMG "")}}
29 <a href="{{.Post.IMG}}" title="open/download image"><img src="{{.Post.IMG}}" width="50%"></a>
28 {{if (ne .Templates.Deviation.Post.IMG "")}}
29 <a href="{{.Templates.Deviation.Post.IMG}}" title="open/download image"><img src="{{.Templates.Deviation.Post.IMG}}" width="50%"></a>
3030 <br>
3131 {{end}}
32 {{if (ne .Tags "")}}
33 {{.Tags}}<br>
32 {{if (ne .Templates.Deviation.Tags "")}}
33 {{.Templates.Deviation.Tags}}<br>
3434 {{end}}
35 <span>Published: <strong>{{.StringTime}}</strong>; Views: <strong>{{.Post.Deviation.Stats.Views}}</strong>; Favourites: <strong>{{.Post.Deviation.Stats.Favourites}}</strong>; Downloads: <strong>{{.Post.Deviation.Stats.Downloads}}</strong></span>
36 {{if (ne .Post.Description "")}}
35 <span>Published: <strong>{{.Templates.Deviation.StringTime}}</strong>; Views: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Views}}</strong>; Favourites: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Favourites}}</strong>; Downloads: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Downloads}}</strong></span>
36 {{if (ne .Templates.Deviation.Post.Description "")}}
3737 <figcaption>
38 {{.Post.Description}}
38 {{.Templates.Deviation.Post.Description}}
3939 </figcaption>
4040 {{end}}
41 {{if (ne .Comments "")}}
42 {{.Comments}}
41 {{if (ne .Templates.Deviation.Comments "")}}
42 {{.Templates.Deviation.Comments}}
4343 {{end}}
4444 </figure>
4545 </main>
html/gruser.htm +4 −4
@@ -8,14 +8,14 @@
88 gallery of {{.Templates.GroupUser.GR.Owner.Username}}
99 {{end}}
1010 </title>
11 <link rel="stylesheet" href="/gui/css/skunky.css">
11 <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css">
1212 </head>
1313 <main>
1414 <header>
15 <h1><a href="/">HOME</a> | <a href="/dd">DD</a>
15 <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a>
1616 | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a>
1717 | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a></h1>
18 <form method="get" action="/search">
18 <form method="get" action="{{.BasePath}}search">
1919 <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
2020 <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}">
2121 <select name="type">
@@ -32,7 +32,7 @@
3232 {{.Templates.GroupUser.About.BGMeta.Author.Username}} - {{end}}{{.Templates.GroupUser.About.BGMeta.Title}}" src="{{.Templates.GroupUser.About.BG}}"></a>
3333 {{end}}
3434
35 <img src="/media/{{.Templates.GroupUser.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.Templates.GroupUser.GR.Owner.Username}}</b>
35 <img src="{{.BasePath}}media/emojitar/{{.Templates.GroupUser.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.Templates.GroupUser.GR.Owner.Username}}</b>
3636 {{if (eq .Templates.GroupUser.About.A.Gender "male")}}
3737 ♂️
3838 {{end}}
html/index.htm +3 −3
@@ -2,11 +2,11 @@
22<html>
33 <head>
44 <title>SkunkyArt</title>
5 <link rel="stylesheet" href="gui/css/skunky.css"
5 <link rel="stylesheet" href="{{.}}gui/css/skunky.css"/>
66 </head>
77 <main>
88 <center>
9 <form method="get" action="search">
9 <form method="get" action="{{.}}search">
1010 <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
1111 <select name="type">
1212 <option value="all">All</option>
@@ -14,7 +14,7 @@
1414 </select>
1515 <button type="submit">Search!</button>
1616 </form>
17 <h1><a href="dd">Daily Deviations</a> | <a href="about">About</a> | <a href="https://git.macaw.me/skunky/SkunkyArt" target="_blank">Source Code</a></h1>
17 <h1><a href="{{.}}dd">Daily Deviations</a> | <a href="{{.}}about">About</a> | <a href="https://git.macaw.me/skunky/SkunkyArt" target="_blank">Source Code</a></h1>
1818 </center>
1919 </main>
2020</html>
\ No newline at end of file
html/list.htm +4 −4
@@ -2,12 +2,12 @@
22<html>
33 <head>
44 <title>SkunkyArt</title>
5 <link rel="stylesheet" href="gui/css/skunky.css">
5 <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css">
66 </head>
77 <main>
88 <header>
9 <h1><a href="/">HOME</a> | <a href="/dd">DD</a> | <a href="?atom=true">RSS</a></h1>
10 <form method="get" action="/search">
9 <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a> | <a href="?atom=true">RSS</a></h1>
10 <form method="get" action="{{.BasePath}}search">
1111 <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
1212 <select name="type">
1313 <option value="all">All</option>
@@ -16,6 +16,6 @@
1616 <button type="submit">Search!</button>
1717 </form>
1818 </header>
19 {{.}}
19 {{.Templates.SomeList}}
2020 </main>
2121</html>
\ No newline at end of file
html/search.htm +3 −3
@@ -1,12 +1,12 @@
11<!DOCTYPE html>
22<html>
33 <head>
4 <title>SkunkyArt | Search "{{.Query}}"</title>
5 <link rel="stylesheet" href="/gui/css/skunky.css">
4 <title>SkunkyArt | Search "{{.QueryRaw}}"</title>
5 <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css">
66 </head>
77 <main>
88 <header>
9 <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1>
9 <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1>
1010 <form method="get" action="search">
1111 <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
1212 <select name="type">
main.go +1
@@ -7,6 +7,7 @@ import (
77)
88
99func main() {
10 app.ExecuteConfig()
1011 err := devianter.UpdateCSRF()
1112 if err != nil {
1213 println(err.Error())