Commit 2f8c35ba32

2f8c35ba322e3c8c6e0bff8f7818d4eedf002928

parent: 513543cc7a

Unsigned

lost+skunk <skunky@ebloid.ru> · 2024-08-14 16:18 UTC

user favourites

Layout: unified · split

INSTANCES.md +1 −1
@@ -2,6 +2,6 @@
22|:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:|
33|[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | No | No | Russia |
44|[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden |
5|[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Romania |
5|[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Germany |
66|[frontendfriendly.xyz](https://skunkyart.frontendfriendly.xyz)|No|No|No| Yes | Yes | No | Finland |
77|[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | US |
\ No newline at end of file
TODO.md +1 −1
@@ -6,7 +6,7 @@
66* ~~Исправить баг с навигацией по страницам~~ ✔️
77* Сделать нормальное отображение ошибок
88* ~~Сделать единицы в конфиге более понятными~~ ✔️
9* Добавить просмотр понравившихся артов пользователю
9* ~~Добавить просмотр понравившихся артов пользователю~~ ✔️
1010* Добавить возможность включить темплейты в бинарник [P]
1111* ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~ ✔️
1212* Написать Makefile и скрипт для автоматического развёртывания инстанса
app/cache.go +18 −16
@@ -99,34 +99,36 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) {
9999
100100func InitCacheSystem() {
101101 c := &CFG.Cache
102 os.Mkdir(c.Path, 0700)
103102 for {
104 dir, e := os.Open(c.Path)
105 try(e)
106 stat, e := dir.Stat()
107 try(e)
108
109 dirnames, e := dir.Readdirnames(-1)
110 try(e)
111 for _, a := range dirnames {
112 a = c.Path + "/" + a
103 dir, err := os.ReadDir(c.Path)
104 if err != nil {
105 if os.IsNotExist(err) {
106 os.Mkdir(c.Path, 0700)
107 continue
108 }
109 println(err.Error())
110 }
111
112 for _, file := range dir {
113 fileName := c.Path + "/" + file.Name()
114 fileInfo, err := file.Info()
115 try(err)
116
113117 if c.Lifetime != "" {
114118 now := time.Now().UnixMilli()
115119
116 f, _ := os.Stat(a)
117 stat := f.Sys().(*syscall.Stat_t)
120 stat := fileInfo.Sys().(*syscall.Stat_t)
118121 time := statTime(stat)
119122
120123 if time+lifetimeParsed <= now {
121 try(os.RemoveAll(a))
124 try(os.RemoveAll(fileName))
122125 }
123126 }
124 if c.MaxSize != 0 && stat.Size() > c.MaxSize {
125 try(os.RemoveAll(a))
127 if c.MaxSize != 0 && fileInfo.Size() > c.MaxSize {
128 try(os.RemoveAll(fileName))
126129 }
127130 }
128131
129 dir.Close()
130132 time.Sleep(time.Second * time.Duration(c.UpdateInterval))
131133 }
132134}
app/util.go +59
@@ -16,6 +16,8 @@ import (
1616)
1717
1818/* INTERNAL */
19var wr = io.WriteString
20
1921func exit(msg string, code int) {
2022 println(msg)
2123 os.Exit(code)
@@ -50,6 +52,63 @@ func RefreshInstances() {
5052}
5153
5254// some crap for frontend
55type skunkyart struct {
56 Writer http.ResponseWriter
57
58 Args url.Values
59 Page int
60 Type rune
61 Atom bool
62
63 BasePath, Endpoint string
64 Query, QueryRaw string
65
66 Templates struct {
67 About struct {
68 Proxy bool
69 Nsfw bool
70 Instances []settings
71 }
72
73 SomeList string
74 DDStrips string
75 Deviation struct {
76 Post devianter.Post
77 Related string
78 StringTime string
79 Tags string
80 Comments string
81 }
82
83 GroupUser struct {
84 GR devianter.GRuser
85 Admins string
86 Group bool
87 CreationDate string
88
89 About struct {
90 A devianter.About
91
92 DescriptionFormatted string
93 Interests, Social string
94 Comments string
95 BG string
96 BGMeta devianter.Deviation
97 }
98
99 Gallery struct {
100 Folders string
101 Pages int
102 List string
103 }
104 }
105 Search struct {
106 Content devianter.Search
107 List string
108 }
109 }
110}
111
53112func (s skunkyart) ExecuteTemplate(file, dir string, data any) {
54113 var buf strings.Builder
55114 tmp := template.New(file)
app/wrapper.go +48 −96
@@ -2,9 +2,6 @@ package app
22
33import (
44 "encoding/json"
5 "io"
6 "net/http"
7 "net/url"
85 "regexp"
96 "strconv"
107 "strings"
@@ -14,65 +11,6 @@ import (
1411 "golang.org/x/net/html"
1512)
1613
17var wr = io.WriteString
18
19type skunkyart struct {
20 Writer http.ResponseWriter
21
22 Args url.Values
23 Page int
24 Type rune
25 Atom bool
26
27 BasePath, Endpoint string
28 Query, QueryRaw string
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
7614func (s skunkyart) GRUser() {
7715 if len(s.Query) < 1 {
7816 s.ReturnHTTPError(400)
@@ -82,7 +20,7 @@ func (s skunkyart) GRUser() {
8220 var g devianter.Group
8321 g.Name = s.Query
8422 var err error
85 s.Templates.GroupUser.GR, err = g.GetGroup()
23 s.Templates.GroupUser.GR, err = g.Get()
8624 try(err)
8725
8826 group := &s.Templates.GroupUser
@@ -146,51 +84,65 @@ func (s skunkyart) GRUser() {
14684 }
14785
14886 }
149 case 'g':
87 case 'g', 'f':
88 var all bool
89 var content devianter.Group
90
15091 folderid, _ := strconv.Atoi(s.Args.Get("folder"))
92
93 if a := s.Args.Get("all"); a == "true" {
94 all = true
95 }
96
15197 if s.Page == 0 {
15298 s.Page++
15399 }
154100
155 gallery, err := g.GetGallery(s.Page, folderid)
156 try(err)
101 if s.Type == 'f' {
102 content = g.Favourites(s.Page, all, folderid)
103 } else {
104 content, err = g.Gallery(s.Page, folderid)
105 try(err)
106 }
157107
158 if folderid > 0 {
159 group.Gallery.List = s.DeviationList(gallery.Content.Results, true, DeviationList{
160 More: gallery.Content.HasMore,
108 if folderid > 0 || (s.Type == 'f' && all) {
109 group.Gallery.List = s.DeviationList(content.Content.Results, true, DeviationList{
110 More: content.Content.HasMore,
161111 })
162112 } else {
163 for _, x := range gallery.Content.Gruser.Page.Modules {
113 for _, x := range content.Content.Gruser.Page.Modules {
164114 if l := len(x.ModuleData.Folders.Results); l != 0 {
165115 var folders strings.Builder
166116 folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`)
167117 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, x.Thumb.Title))
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>`)
118 if x.FolderId != -1 && x.Size != 0 {
119 folders.WriteString(`<div class="block folder-item">`)
120
121 if !(x.Thumb.NSFW && !CFG.Nsfw) {
122 folders.WriteString(`<a href="`)
123 folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url))
124 folders.WriteString(`"><img loading="lazy" src="`)
125 folders.WriteString(ParseMedia(x.Thumb.Media, x.Thumb.Title))
126 folders.WriteString(`" title="`)
127 folders.WriteString(x.Thumb.Title)
128 folders.WriteString(`"></a>`)
129 } else {
130 folders.WriteString(`<h1>[ <span class="nsfw">NSFW</span> ]</h1>`)
131 }
132 folders.WriteString("<br>")
133
134 folders.WriteString(`<a href="group_user?folder=`)
135 folders.WriteString(strconv.Itoa(x.FolderId))
136 folders.WriteString("&q=")
137 folders.WriteString(s.Query)
138 folders.WriteString("&type=")
139 folders.WriteString(string(s.Type))
140 folders.WriteString(`">`)
141 folders.WriteString(x.Name)
142 folders.WriteString(`</a>`)
143
144 folders.WriteString("</div>")
180145 }
181 folders.WriteString("<br>")
182
183 folders.WriteString(`<a href="group_user?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>")
194146 }
195147 folders.WriteString(`</div><h1 id="content"><a href="#content">#</a> Content</h1>`)
196148 group.Gallery.Folders = folders.String()
@@ -296,7 +248,7 @@ func (s skunkyart) Search() {
296248 switch s.Type {
297249 case 'a', 't':
298250 ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type)
299 case 'g':
251 case 'g', 'f':
300252 ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type, s.Args.Get("usr"))
301253 case 'r': // скраппер, поскольку девиантартовцы зажопили гостевое API для поиска групп
302254 var (
go.mod +1
@@ -2,6 +2,7 @@ module skunkyart
22
33go 1.22.3
44
5replace git.macaw.me/skunky/devianter v0.2.5 => /home/skunk/projects/devianter
56require (
67 git.macaw.me/skunky/devianter v0.2.5
78 golang.org/x/net v0.27.0
instances.json +1 −1
@@ -25,7 +25,7 @@
2525 },
2626 {
2727 "title": "bloat.cat",
28 "country": "Romania",
28 "country": "Germany",
2929 "urls": {
3030 "clearnet": "https://skunky.bloat.cat"
3131 },
static/html/gruser.htm +14 −3
@@ -3,14 +3,25 @@
33 {{template "head" . }}
44 <main>
55 <header>
6 <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a>
7 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a>
8 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a></h1>
6 <h1>
7 <a href="{{.BasePath}}">HOME</a>
8 | <a href="{{.BasePath}}dd">DD</a>
9 {{if ne .Type 'f'}}
10 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a>
11 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=favourites">Favourites</a>
12 {{else}}
13 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=about">About</a>
14 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery">Gallery</a>
15 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=favourites">Favourites</a>
16 {{end}}
17 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a>
18 </h1>
919 <form method="get" action="{{.BasePath}}search">
1020 <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
1121 <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}">
1222 <select name="type">
1323 <option value="gallery">Gallery</option>
24 <option value="folders">Folders</option>
1425 <option value="all">All</option>
1526 <option value="tag">Tag</option>
1627 <option value="r">Groups</option>
static/html/head.htm +6 −5
@@ -5,12 +5,13 @@
55 "{{.QueryRaw}}"
66 {{else if eq .Endpoint "post"}}
77 {{.Templates.Deviation.Post.Deviation.Author.Username}} — {{.Templates.Deviation.Post.Deviation.Title}}
8 {{else if eq .Type 'a'}}
9 {{if .Templates.GroupUser.GR.Owner.Username}}
10 {{.Templates.GroupUser.GR.Owner.Username}}
11 {{else}}
12 gallery of {{.Templates.GroupUser.GR.Owner.Username}}
8 {{else if eq .Endpoint "group_user"}}
9 {{if eq .Type 'g'}}
10 gallery of
11 {{else if eq .Type 'f'}}
12 favourites of
1313 {{end}}
14 {{.Templates.GroupUser.GR.Owner.Username}}
1415 {{else}}
1516 {{.Endpoint}}
1617 {{end}}