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 @@
2|:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:| 2|:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:|
3|[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 | 3|[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 |
4|[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden | 4|[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 |
6|[frontendfriendly.xyz](https://skunkyart.frontendfriendly.xyz)|No|No|No| Yes | Yes | No | Finland | 6|[frontendfriendly.xyz](https://skunkyart.frontendfriendly.xyz)|No|No|No| Yes | Yes | No | Finland |
7|[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | US |\ No newline at end of file 7|[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 @@
6* ~~Исправить баг с навигацией по страницам~~ ✔️ 6* ~~Исправить баг с навигацией по страницам~~ ✔️
7* Сделать нормальное отображение ошибок 7* Сделать нормальное отображение ошибок
8* ~~Сделать единицы в конфиге более понятными~~ ✔️ 8* ~~Сделать единицы в конфиге более понятными~~ ✔️
9* Добавить просмотр понравившихся артов пользователю 9* ~~Добавить просмотр понравившихся артов пользователю~~ ✔️
10* Добавить возможность включить темплейты в бинарник [P] 10* Добавить возможность включить темплейты в бинарник [P]
11* ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~ ✔️ 11* ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~ ✔️
12* Написать Makefile и скрипт для автоматического развёртывания инстанса 12* Написать Makefile и скрипт для автоматического развёртывания инстанса
app/cache.go +18 −16
@@ -99,34 +99,36 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) {
99 99
100func InitCacheSystem() { 100func InitCacheSystem() {
101 c := &CFG.Cache 101 c := &CFG.Cache
102 os.Mkdir(c.Path, 0700)
103 for { 102 for {
104 dir, e := os.Open(c.Path) 103 dir, err := os.ReadDir(c.Path)
105 try(e) 104 if err != nil {
106 stat, e := dir.Stat() 105 if os.IsNotExist(err) {
107 try(e) 106 os.Mkdir(c.Path, 0700)
108 107 continue
109 dirnames, e := dir.Readdirnames(-1) 108 }
110 try(e) 109 println(err.Error())
111 for _, a := range dirnames { 110 }
112 a = c.Path + "/" + a 111
112 for _, file := range dir {
113 fileName := c.Path + "/" + file.Name()
114 fileInfo, err := file.Info()
115 try(err)
116
113 if c.Lifetime != "" { 117 if c.Lifetime != "" {
114 now := time.Now().UnixMilli() 118 now := time.Now().UnixMilli()
115 119
116 f, _ := os.Stat(a) 120 stat := fileInfo.Sys().(*syscall.Stat_t)
117 stat := f.Sys().(*syscall.Stat_t)
118 time := statTime(stat) 121 time := statTime(stat)
119 122
120 if time+lifetimeParsed <= now { 123 if time+lifetimeParsed <= now {
121 try(os.RemoveAll(a)) 124 try(os.RemoveAll(fileName))
122 } 125 }
123 } 126 }
124 if c.MaxSize != 0 && stat.Size() > c.MaxSize { 127 if c.MaxSize != 0 && fileInfo.Size() > c.MaxSize {
125 try(os.RemoveAll(a)) 128 try(os.RemoveAll(fileName))
126 } 129 }
127 } 130 }
128 131
129 dir.Close()
130 time.Sleep(time.Second * time.Duration(c.UpdateInterval)) 132 time.Sleep(time.Second * time.Duration(c.UpdateInterval))
131 } 133 }
132} 134}
app/util.go +59
@@ -16,6 +16,8 @@ import (
16) 16)
17 17
18/* INTERNAL */ 18/* INTERNAL */
19var wr = io.WriteString
20
19func exit(msg string, code int) { 21func exit(msg string, code int) {
20 println(msg) 22 println(msg)
21 os.Exit(code) 23 os.Exit(code)
@@ -50,6 +52,63 @@ func RefreshInstances() {
50} 52}
51 53
52// some crap for frontend 54// 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
53func (s skunkyart) ExecuteTemplate(file, dir string, data any) { 112func (s skunkyart) ExecuteTemplate(file, dir string, data any) {
54 var buf strings.Builder 113 var buf strings.Builder
55 tmp := template.New(file) 114 tmp := template.New(file)
app/wrapper.go +48 −96
@@ -2,9 +2,6 @@ package app
2 2
3import ( 3import (
4 "encoding/json" 4 "encoding/json"
5 "io"
6 "net/http"
7 "net/url"
8 "regexp" 5 "regexp"
9 "strconv" 6 "strconv"
10 "strings" 7 "strings"
@@ -14,65 +11,6 @@ import (
14 "golang.org/x/net/html" 11 "golang.org/x/net/html"
15) 12)
16 13
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
76func (s skunkyart) GRUser() { 14func (s skunkyart) GRUser() {
77 if len(s.Query) < 1 { 15 if len(s.Query) < 1 {
78 s.ReturnHTTPError(400) 16 s.ReturnHTTPError(400)
@@ -82,7 +20,7 @@ func (s skunkyart) GRUser() {
82 var g devianter.Group 20 var g devianter.Group
83 g.Name = s.Query 21 g.Name = s.Query
84 var err error 22 var err error
85 s.Templates.GroupUser.GR, err = g.GetGroup() 23 s.Templates.GroupUser.GR, err = g.Get()
86 try(err) 24 try(err)
87 25
88 group := &s.Templates.GroupUser 26 group := &s.Templates.GroupUser
@@ -146,51 +84,65 @@ func (s skunkyart) GRUser() {
146 } 84 }
147 85
148 } 86 }
149 case 'g': 87 case 'g', 'f':
88 var all bool
89 var content devianter.Group
90
150 folderid, _ := strconv.Atoi(s.Args.Get("folder")) 91 folderid, _ := strconv.Atoi(s.Args.Get("folder"))
92
93 if a := s.Args.Get("all"); a == "true" {
94 all = true
95 }
96
151 if s.Page == 0 { 97 if s.Page == 0 {
152 s.Page++ 98 s.Page++
153 } 99 }
154 100
155 gallery, err := g.GetGallery(s.Page, folderid) 101 if s.Type == 'f' {
156 try(err) 102 content = g.Favourites(s.Page, all, folderid)
103 } else {
104 content, err = g.Gallery(s.Page, folderid)
105 try(err)
106 }
157 107
158 if folderid > 0 { 108 if folderid > 0 || (s.Type == 'f' && all) {
159 group.Gallery.List = s.DeviationList(gallery.Content.Results, true, DeviationList{ 109 group.Gallery.List = s.DeviationList(content.Content.Results, true, DeviationList{
160 More: gallery.Content.HasMore, 110 More: content.Content.HasMore,
161 }) 111 })
162 } else { 112 } else {
163 for _, x := range gallery.Content.Gruser.Page.Modules { 113 for _, x := range content.Content.Gruser.Page.Modules {
164 if l := len(x.ModuleData.Folders.Results); l != 0 { 114 if l := len(x.ModuleData.Folders.Results); l != 0 {
165 var folders strings.Builder 115 var folders strings.Builder
166 folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`) 116 folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`)
167 for _, x := range x.ModuleData.Folders.Results { 117 for _, x := range x.ModuleData.Folders.Results {
168 folders.WriteString(`<div class="block folder-item">`) 118 if x.FolderId != -1 && x.Size != 0 {
169 119 folders.WriteString(`<div class="block folder-item">`)
170 if !(x.Thumb.NSFW && !CFG.Nsfw) { 120
171 folders.WriteString(`<a href="`) 121 if !(x.Thumb.NSFW && !CFG.Nsfw) {
172 folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url)) 122 folders.WriteString(`<a href="`)
173 folders.WriteString(`"><img loading="lazy" src="`) 123 folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url))
174 folders.WriteString(ParseMedia(x.Thumb.Media, x.Thumb.Title)) 124 folders.WriteString(`"><img loading="lazy" src="`)
175 folders.WriteString(`" title="`) 125 folders.WriteString(ParseMedia(x.Thumb.Media, x.Thumb.Title))
176 folders.WriteString(x.Thumb.Title) 126 folders.WriteString(`" title="`)
177 folders.WriteString(`"></a>`) 127 folders.WriteString(x.Thumb.Title)
178 } else { 128 folders.WriteString(`"></a>`)
179 folders.WriteString(`<h1>[ <span class="nsfw">NSFW</span> ]</h1>`) 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>")
180 } 145 }
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>")
194 } 146 }
195 folders.WriteString(`</div><h1 id="content"><a href="#content">#</a> Content</h1>`) 147 folders.WriteString(`</div><h1 id="content"><a href="#content">#</a> Content</h1>`)
196 group.Gallery.Folders = folders.String() 148 group.Gallery.Folders = folders.String()
@@ -296,7 +248,7 @@ func (s skunkyart) Search() {
296 switch s.Type { 248 switch s.Type {
297 case 'a', 't': 249 case 'a', 't':
298 ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type) 250 ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type)
299 case 'g': 251 case 'g', 'f':
300 ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type, s.Args.Get("usr")) 252 ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type, s.Args.Get("usr"))
301 case 'r': // скраппер, поскольку девиантартовцы зажопили гостевое API для поиска групп 253 case 'r': // скраппер, поскольку девиантартовцы зажопили гостевое API для поиска групп
302 var ( 254 var (
go.mod +1
@@ -2,6 +2,7 @@ module skunkyart
2 2
3go 1.22.3 3go 1.22.3
4 4
5replace git.macaw.me/skunky/devianter v0.2.5 => /home/skunk/projects/devianter
5require ( 6require (
6 git.macaw.me/skunky/devianter v0.2.5 7 git.macaw.me/skunky/devianter v0.2.5
7 golang.org/x/net v0.27.0 8 golang.org/x/net v0.27.0
instances.json +1 −1
@@ -25,7 +25,7 @@
25 }, 25 },
26 { 26 {
27 "title": "bloat.cat", 27 "title": "bloat.cat",
28 "country": "Romania", 28 "country": "Germany",
29 "urls": { 29 "urls": {
30 "clearnet": "https://skunky.bloat.cat" 30 "clearnet": "https://skunky.bloat.cat"
31 }, 31 },
static/html/gruser.htm +14 −3
@@ -3,14 +3,25 @@
3 {{template "head" . }} 3 {{template "head" . }}
4 <main> 4 <main>
5 <header> 5 <header>
6 <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a> 6 <h1>
7 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a> 7 <a href="{{.BasePath}}">HOME</a>
8 | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a></h1> 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>
9 <form method="get" action="{{.BasePath}}search"> 19 <form method="get" action="{{.BasePath}}search">
10 <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> 20 <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
11 <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}"> 21 <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}">
12 <select name="type"> 22 <select name="type">
13 <option value="gallery">Gallery</option> 23 <option value="gallery">Gallery</option>
24 <option value="folders">Folders</option>
14 <option value="all">All</option> 25 <option value="all">All</option>
15 <option value="tag">Tag</option> 26 <option value="tag">Tag</option>
16 <option value="r">Groups</option> 27 <option value="r">Groups</option>
static/html/head.htm +6 −5
@@ -5,12 +5,13 @@
5 "{{.QueryRaw}}" 5 "{{.QueryRaw}}"
6 {{else if eq .Endpoint "post"}} 6 {{else if eq .Endpoint "post"}}
7 {{.Templates.Deviation.Post.Deviation.Author.Username}} — {{.Templates.Deviation.Post.Deviation.Title}} 7 {{.Templates.Deviation.Post.Deviation.Author.Username}} — {{.Templates.Deviation.Post.Deviation.Title}}
8 {{else if eq .Type 'a'}} 8 {{else if eq .Endpoint "group_user"}}
9 {{if .Templates.GroupUser.GR.Owner.Username}} 9 {{if eq .Type 'g'}}
10 {{.Templates.GroupUser.GR.Owner.Username}} 10 gallery of
11 {{else}} 11 {{else if eq .Type 'f'}}
12 gallery of {{.Templates.GroupUser.GR.Owner.Username}} 12 favourites of
13 {{end}} 13 {{end}}
14 {{.Templates.GroupUser.GR.Owner.Username}}
14 {{else}} 15 {{else}}
15 {{.Endpoint}} 16 {{.Endpoint}}
16 {{end}} 17 {{end}}