Commit 191984b31e

191984b31ef228c4077904a2831a2af593352670

parent: c39399403e

Unsigned

lost+skunk <skunky@ebloid.ru> · 2024-09-23 06:38 UTC

v1.3.2

Layout: unified · split

.gitignore +1
@@ -1,3 +1,4 @@
1**/cache 1**/cache
2**/config.json 2**/config.json
3**/skunkyart 3**/skunkyart
4**/skunkyart-*
INSTANCES.md +4 −2
@@ -1,7 +1,9 @@
1JSON variant should be used from master — https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json
2
1|Instance|Yggdrasil|I2P|Tor|NSFW|Proxifying|Modified Sources|Country| 3|Instance|Yggdrasil|I2P|Tor|NSFW|Proxifying|Modified Sources|Country|
2|:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:| 4|:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:|
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 | 5|[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | Yes | No | Russia |
4|[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden | 6|[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 | Germany | 7|[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Germany |
6|[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | Germany | 8|[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | Germany |
7|[art.bloat.cat](https://art.bloat.cat)|No|No|No| Yes | Yes | No | Germany | 9|[art.bloat.cat](https://art.bloat.cat)|No|No|No| Yes | Yes | No | Germany |\ No newline at end of file
README.md +12
@@ -7,6 +7,12 @@ Instances: [`INSTANCES.md`](/skunky/SkunkyArt/src/branch/master/INSTANCES.md)
7# EN 🇺🇸 7# EN 🇺🇸
8## Description 8## Description
9SkunkyArt 🦨 — alternative frontend for DevianArt, which works without JS. 9SkunkyArt 🦨 — alternative frontend for DevianArt, which works without JS.
10## Build (translated via DeepL)
11It is recommended to build with the 'embed' tag because it embeds the presets in the binary. If you plan to modify the templates, then do not use this tag. You can also add the `-ldflags "-w -s"` argument (GCCGO has a different name for it — `gccgoflags`) to reduce the size of the output file. Here is an example:
12
13`go build -tags embed -ldflags "-w -s"`
14
15Pre-compiled binaries can be found in the [Releases](https://git.macaw.me/skunky/skunkyart/releases) tab.
10## Setup 16## Setup
11The sample config is in the `config.example.json` file. For custom config, use `--config` option. 17The sample config is in the `config.example.json` file. For custom config, use `--config` option.
12See the [`SETUP.md`](/skunky/SkunkyArt/src/branch/master/SETUP.md) file for more info about directives. 18See the [`SETUP.md`](/skunky/SkunkyArt/src/branch/master/SETUP.md) file for more info about directives.
@@ -21,6 +27,12 @@ To do this, you must either make a PR by adding your instance to the `instances.
21# RU 🇷🇺 27# RU 🇷🇺
22## Описание 28## Описание
23SkunkyArt 🦨 — альтернативный фронтенд к DeviantArt, который полностью работает без JS (JavaScript). 29SkunkyArt 🦨 — альтернативный фронтенд к DeviantArt, который полностью работает без JS (JavaScript).
30## Сборка
31Рекомендуется производить сборку с тегом 'embed', поскольку он встраивает заготовки в бинарный файл. Если вы планируете изменять заготовки, то не используйте этот тег. Также вы можете добавить аргумент `-ldflags "-w -s"` (у GCCGO он называется по-другому — `gccgoflags`) для уменьшения размера выходного файла. Вот пример:
32
33`go build -tags embed -ldflags "-w -s"`
34
35Готовые бинари находятся во вкладке [Releases](https://git.macaw.me/skunky/skunkyart/releases).
24## Настройка 36## Настройка
25Пример конфига находится в файле `config.example.json`. Чтобы указать свой конфиг, используйте cli-аргумент `--config`. 37Пример конфига находится в файле `config.example.json`. Чтобы указать свой конфиг, используйте cli-аргумент `--config`.
26См. [`SETUP-RU.md`](/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) для информации о настройки фронтенда. 38См. [`SETUP-RU.md`](/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) для информации о настройки фронтенда.
REDIRECTS.md added +13
@@ -0,0 +1,13 @@
1# Search
2* `deviantart.com/search?q=$QUERY` => `/search?q=$QUERY&type=all`
3# Daily Deviations
4* `deviantart.com` => `/dd`
5# Deviations
6* (`$USER_GROUP.deviantart.com/art/$ID`|`deviantart.com/$USER_GROUP/art/$ID`) => `/post/$USER_GROUP/$ID`
7# Groups and users
8## Main user page
9* (`$USER_GROUP.deviantart.com`|`deviantart.com/$USER_GROUP`) => `/group_user?type=about&q=$USER_GROUP`
10## Gallery
11* (`$USER_GROUP.deviantart.com/gallery`|`deviantart.com/$USER_GROUP/gallery`) => `/group_user?type=gallery&q=$USER_GROUP`
12## Favourites
13* (`$USER_GROUP.deviantart.com/favourites`|`deviantart.com/$USER_GROUP/favourites`) => `/group_user?type=favourites&q=$USER_GROUP`
SETUP-RU.md +2 −2
@@ -12,13 +12,13 @@
12# Конфигурация 12# Конфигурация
13* `listen` — IP и порт для слушанья; заполняется по такой форме: ip:port 13* `listen` — IP и порт для слушанья; заполняется по такой форме: ip:port
14* `uri` — URI инстанса. Пример: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/ 14* `uri` — URI инстанса. Пример: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/
15* `cache` — Система кеширования; по умолчанию выключена. 15* `cache` — Система кеширования; по умолчанию выключена
16 * `enabled` — Состояние системы кеширования; требуется булёвое значение 16 * `enabled` — Состояние системы кеширования; требуется булёвое значение
17 * `path` — Полный путь до каталога, куда будет сохраняться кеш 17 * `path` — Полный путь до каталога, куда будет сохраняться кеш
18 * `lifetime` — Время жизни файла в кеше, требует целочисленное значение, дополненное суффиксом времени (см. 'Единицы времени') 18 * `lifetime` — Время жизни файла в кеше, требует целочисленное значение, дополненное суффиксом времени (см. 'Единицы времени')
19 * `max-size` — Максимальный размер файла 19 * `max-size` — Максимальный размер файла
20 * `update-interval` — Интервал для автоматической ротации кеша 20 * `update-interval` — Интервал для автоматической ротации кеша
21* `dirs-to-memory` — Массив, заполнив который скопируются все файлы из указанных каталогов 21* `static-path` — Строка, являющаяся путём до статики. SkunkyArt при запуске скопирует содержимое этого каталога в ОЗУ. Однако, если вы собрали фронтенд с тегом 'embed', то этого не произайдёт
22* `download-proxy` — Адрес прокси для загрузки файлов 22* `download-proxy` — Адрес прокси для загрузки файлов
23* `user-agent` — Строка, которая используется в качестве User-Agent'а 23* `user-agent` — Строка, которая используется в качестве User-Agent'а
24 24
SETUP.md +1 −1
@@ -18,7 +18,7 @@ Time units:
18 * `lifetime` — Cached file life time, requires numeric value, followed by multiplicative suffix (see Time Units for details) 18 * `lifetime` — Cached file life time, requires numeric value, followed by multiplicative suffix (see Time Units for details)
19 * `max-size` — Maximum file size in megabytes 19 * `max-size` — Maximum file size in megabytes
20 * `update-interval` — Automatic rotation interval 20 * `update-interval` — Automatic rotation interval
21* `dirs-to-memory` — This setting determines which directories will be copied to RAM when SkunkyArt is started. Mandatory 21* `static-path` — This setting determines path to static, which will be copied to RAM when SkunkyArt is started. Useless if you're use binary compiled with 'embed' tag.
22* `download-proxy` — Proxy address for downloading files. 22* `download-proxy` — Proxy address for downloading files.
23* `user-agent` — String, which SkunkyArt uses as UA 23* `user-agent` — String, which SkunkyArt uses as UA
24 24
app/api.go +45 −46
@@ -9,69 +9,68 @@ import (
9) 9)
10 10
11type API struct { 11type API struct {
12 main *skunkyart 12 main *skunkyart
13} 13}
14 14
15type info struct { 15type info struct {
16 Version string `json:"version"` 16 Version string `json:"version"`
17 Settings settingsParams `json:"settings"` 17 Settings settingsParams `json:"settings"`
18} 18}
19 19
20func (a API) Info() { 20func (a API) Info() {
21 json, err := json.Marshal(info{ 21 json, err := json.Marshal(info{
22 Version: a.main.Version, 22 Version: a.main.Version,
23 Settings: settingsParams{ 23 Settings: settingsParams{
24 Nsfw: CFG.Nsfw, 24 Nsfw: CFG.Nsfw,
25 Proxy: CFG.Proxy, 25 Proxy: CFG.Proxy,
26 }, 26 },
27 }) 27 })
28 try(err) 28 try(err)
29 a.main.Writer.Write(json) 29 a.main.Writer.Write(json)
30 return
31} 30}
32 31
33func (a API) Error(description string, status int) { 32func (a API) Error(description string, status int) {
34 a.main.Writer.WriteHeader(status) 33 a.main.Writer.WriteHeader(status)
35 var response strings.Builder 34 var response strings.Builder
36 response.WriteString(`{"error":"`) 35 response.WriteString(`{"error":"`)
37 response.WriteString(description) 36 response.WriteString(description)
38 response.WriteString(`"}`) 37 response.WriteString(`"}`)
39 wr(a.main.Writer, response.String()) 38 wr(a.main.Writer, response.String())
40} 39}
41 40
42func (a API) sendMedia(d *devianter.Deviation) { 41func (a API) sendMedia(d *devianter.Deviation) {
43 mediaUrl, name := devianter.UrlFromMedia(d.Media) 42 mediaUrl, name := devianter.UrlFromMedia(d.Media)
44 a.main.SetFilename(name) 43 a.main.SetFilename(name)
45 44
46 if len(mediaUrl) != 0 { 45 if len(mediaUrl) != 0 {
47 mediaUrl = mediaUrl[21:] 46 mediaUrl = mediaUrl[21:]
48 dot := strings.Index(mediaUrl, ".") 47 dot := strings.Index(mediaUrl, ".")
49 a.main.Writer.Header().Del("Content-Type") 48 a.main.Writer.Header().Del("Content-Type")
50 a.main.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:]) 49 a.main.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:])
51 } 50 }
52} 51}
53 52
54// TODO: сделать фильтры 53// TODO: сделать фильтры
55func (a API) Random() { 54func (a API) Random() {
56 for attempt := 1;; { 55 for attempt := 1; ; {
57 if attempt > 3 { 56 if attempt > 3 {
58 a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500) 57 a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500)
59 } 58 }
60 59
61 s, err, daErr := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a') 60 s, err, daErr := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a')
62 try(err) 61 try(err)
63 if daErr.RAW != nil { 62 if daErr.RAW != nil {
64 continue 63 continue
65 } 64 }
66 65
67 deviation := &s.Results[rand.Intn(len(s.Results))] 66 deviation := &s.Results[rand.Intn(len(s.Results))]
68 67
69 if deviation.NSFW && !CFG.Nsfw { 68 if deviation.NSFW && !CFG.Nsfw {
70 attempt++ 69 attempt++
71 continue 70 continue
72 } 71 }
73 72
74 a.sendMedia(deviation) 73 a.sendMedia(deviation)
75 return 74 return
76 } 75 }
77} 76}
app/cache.go +4 −4
@@ -27,10 +27,10 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) {
27 url.WriteString(".wixmp.com/") 27 url.WriteString(".wixmp.com/")
28 url.WriteString(path) 28 url.WriteString(path)
29 if t := s.Args.Get("token"); t != "" { 29 if t := s.Args.Get("token"); t != "" {
30 url.WriteString("?token=") 30 url.WriteString("?token=")
31 url.WriteString(t) 31 url.WriteString(t)
32 } 32 }
33 33
34 var response []byte 34 var response []byte
35 35
36 switch { 36 switch {
@@ -127,7 +127,7 @@ func InitCacheSystem() {
127 try(os.RemoveAll(fileName)) 127 try(os.RemoveAll(fileName))
128 } 128 }
129 } 129 }
130 130
131 if c.MaxSize != 0 && fileInfo.Size() > c.MaxSize { 131 if c.MaxSize != 0 && fileInfo.Size() > c.MaxSize {
132 try(os.RemoveAll(fileName)) 132 try(os.RemoveAll(fileName))
133 } 133 }
app/config.go +7 −2
@@ -56,13 +56,11 @@ func ExecuteConfig() {
56 if CFG.cfg != "" { 56 if CFG.cfg != "" {
57 f, err := os.ReadFile(CFG.cfg) 57 f, err := os.ReadFile(CFG.cfg)
58 tryWithExitStatus(err, 1) 58 tryWithExitStatus(err, 1)
59
60 tryWithExitStatus(json.Unmarshal(f, &CFG), 1) 59 tryWithExitStatus(json.Unmarshal(f, &CFG), 1)
61 if CFG.Cache.Enabled && !CFG.Proxy { 60 if CFG.Cache.Enabled && !CFG.Proxy {
62 exit("Incompatible settings detected: cannot use caching media content without proxy", 1) 61 exit("Incompatible settings detected: cannot use caching media content without proxy", 1)
63 } 62 }
64 63
65 static.StaticPath = CFG.StaticPath
66 if CFG.Cache.Enabled { 64 if CFG.Cache.Enabled {
67 if CFG.Cache.Lifetime != "" { 65 if CFG.Cache.Lifetime != "" {
68 var duration int64 66 var duration int64
@@ -92,6 +90,13 @@ func ExecuteConfig() {
92 CFG.Cache.MaxSize *= 1024 ^ 2 90 CFG.Cache.MaxSize *= 1024 ^ 2
93 go InitCacheSystem() 91 go InitCacheSystem()
94 } 92 }
93
94 About = instanceAbout{
95 Proxy: CFG.Proxy,
96 Nsfw: CFG.Nsfw,
97 }
98
99 static.StaticPath = CFG.StaticPath
95 devianter.UserAgent = CFG.UserAgent 100 devianter.UserAgent = CFG.UserAgent
96 } 101 }
97} 102}
app/router.go +20 −11
@@ -3,7 +3,7 @@ package app
3import ( 3import (
4 "io" 4 "io"
5 "net/http" 5 "net/http"
6 u "net/url" 6 url "net/url"
7 "skunkyart/static" 7 "skunkyart/static"
8 "strconv" 8 "strconv"
9 "strings" 9 "strings"
@@ -57,7 +57,7 @@ func Router() {
57 Path = r.URL.Path 57 Path = r.URL.Path
58 path := parsepath(Path) 58 path := parsepath(Path)
59 Host = "http://" + r.Host 59 Host = "http://" + r.Host
60 60
61 if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" { 61 if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" {
62 Host = "https://" + r.Host 62 Host = "https://" + r.Host
63 } 63 }
@@ -67,13 +67,13 @@ func Router() {
67 skunky.Args = r.URL.Query() 67 skunky.Args = r.URL.Query()
68 arg := skunky.Args.Get 68 arg := skunky.Args.Get
69 p, _ := strconv.Atoi(arg("p")) 69 p, _ := strconv.Atoi(arg("p"))
70 70
71 skunky.Endpoint = path[1] 71 skunky.Endpoint = path[1]
72 skunky.API.main = &skunky 72 skunky.API.main = &skunky
73 skunky.Writer = w 73 skunky.Writer = w
74 skunky.BasePath = CFG.URI 74 skunky.BasePath = CFG.URI
75 skunky.QueryRaw = arg("q") 75 skunky.QueryRaw = arg("q")
76 skunky.Query = u.QueryEscape(skunky.QueryRaw) 76 skunky.Query = url.QueryEscape(skunky.QueryRaw)
77 skunky.Page = p 77 skunky.Page = p
78 78
79 if t := arg("type"); len(t) > 0 { 79 if t := arg("type"); len(t) > 0 {
@@ -84,12 +84,21 @@ func Router() {
84 skunky.Atom = true 84 skunky.Atom = true
85 } 85 }
86 86
87 if CFG.Proxy {
88 w.Header().Add("Content-Security-Policy", "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'")
89 } else {
90 w.Header().Add("Content-Security-Policy", "default-src 'self'; img-src 'self' *.wixmp.com; script-src 'none'; style-src 'self' 'unsafe-inline'")
91 }
92
93 w.Header().Add("X-Frame-Options", "DENY")
94
87 switch skunky.Endpoint { 95 switch skunky.Endpoint {
88 // main 96 // main
89 case "": 97 case "":
90 skunky.ExecuteTemplate("index.htm", "html", &CFG.URI) 98 skunky.ExecuteTemplate("index.htm", "html", &CFG.URI)
91 case "about": 99 case "about":
92 skunky.About() 100 skunky.Templates.About = About
101 skunky.ExecuteTemplate("about.htm", "html", &skunky)
93 case "post": 102 case "post":
94 skunky.Deviation(path[2], path[3]) 103 skunky.Deviation(path[2], path[3])
95 case "search": 104 case "search":
@@ -120,12 +129,12 @@ func Router() {
120 case "api": 129 case "api":
121 w.Header().Add("Content-Type", "application/json") 130 w.Header().Add("Content-Type", "application/json")
122 switch path[2] { 131 switch path[2] {
123 case "instance": 132 case "instance":
124 skunky.API.Info() 133 skunky.API.Info()
125 case "random": 134 case "random":
126 skunky.API.Random() 135 skunky.API.Random()
127 default: 136 default:
128 skunky.API.Error("Not Found", 404) 137 skunky.API.Error("Not Found", 404)
129 } 138 }
130 139
131 // 404 140 // 404
app/util.go +15 −8
@@ -1,6 +1,7 @@
1package app 1package app
2 2
3import ( 3import (
4 "encoding/json"
4 "io" 5 "io"
5 "net/http" 6 "net/http"
6 "net/url" 7 "net/url"
@@ -40,18 +41,26 @@ func restore() {
40} 41}
41 42
42var instances []byte 43var instances []byte
44var About instanceAbout
43 45
44func RefreshInstances() { 46func RefreshInstances() {
45 for { 47 for {
46 func() { 48 func() {
47 defer restore() 49 defer restore()
48 instances = Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body 50 instances = Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body
51 try(json.Unmarshal(instances, &About))
49 }() 52 }()
50 time.Sleep(1 * time.Hour) 53 time.Sleep(1 * time.Hour)
51 } 54 }
52} 55}
53 56
54// some crap for frontend 57// some crap for frontend
58type instanceAbout struct {
59 Proxy bool
60 Nsfw bool
61 Instances []settings
62}
63
55type skunkyart struct { 64type skunkyart struct {
56 Writer http.ResponseWriter 65 Writer http.ResponseWriter
57 66
@@ -63,15 +72,11 @@ type skunkyart struct {
63 BasePath, Endpoint string 72 BasePath, Endpoint string
64 Query, QueryRaw string 73 Query, QueryRaw string
65 74
66 API API 75 API API
67 Version string 76 Version string
68 77
69 Templates struct { 78 Templates struct {
70 About struct { 79 About instanceAbout
71 Proxy bool
72 Nsfw bool
73 Instances []settings
74 }
75 80
76 SomeList string 81 SomeList string
77 DDStrips string 82 DDStrips string
@@ -132,7 +137,7 @@ func UrlBuilder(strs ...string) string {
132 str.WriteString(CFG.URI) 137 str.WriteString(CFG.URI)
133 for n, x := range strs { 138 for n, x := range strs {
134 str.WriteString(x) 139 str.WriteString(x)
135 if n := n+1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') { 140 if n := n + 1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') {
136 str.WriteString("/") 141 str.WriteString("/")
137 } 142 }
138 } 143 }
@@ -215,6 +220,8 @@ func ParseMedia(media devianter.Media, thumb ...int) string {
215 filename = "image.gif" 220 filename = "image.gif"
216 } 221 }
217 return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename) 222 return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename)
223 } else if !CFG.Proxy {
224 return mediaUrl
218 } 225 }
219 return "" 226 return ""
220} 227}
app/wrapper.go +10 −17
@@ -1,7 +1,6 @@
1package app 1package app
2 2
3import ( 3import (
4 "encoding/json"
5 "regexp" 4 "regexp"
6 "strconv" 5 "strconv"
7 "strings" 6 "strings"
@@ -24,8 +23,8 @@ func (s skunkyart) GRUser() {
24 s.Templates.GroupUser.GR, err, daError = g.Get() 23 s.Templates.GroupUser.GR, err, daError = g.Get()
25 try(err) 24 try(err)
26 if daError.RAW != nil { 25 if daError.RAW != nil {
27 s.Error(daError) 26 s.Error(daError)
28 return 27 return
29 } 28 }
30 29
31 group := &s.Templates.GroupUser 30 group := &s.Templates.GroupUser
@@ -68,7 +67,7 @@ func (s skunkyart) GRUser() {
68 group.About.Interests += interest.String() 67 group.About.Interests += interest.String()
69 } 68 }
70 } 69 }
71 group.About.Comments = s.ParseComments(devianter.GetComments(strconv.Itoa(group.GR.Gruser.ID),"",s.Page,4)) 70 group.About.Comments = s.ParseComments(devianter.GetComments(strconv.Itoa(group.GR.Gruser.ID), "", s.Page, 4))
72 71
73 case "cover_deviation": 72 case "cover_deviation":
74 group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation 73 group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation
@@ -225,8 +224,8 @@ func (s skunkyart) Deviation(author, postname string) {
225func (s skunkyart) DD() { 224func (s skunkyart) DD() {
226 dd, err := devianter.GetDailyDeviations(s.Page) 225 dd, err := devianter.GetDailyDeviations(s.Page)
227 if err.RAW != nil { 226 if err.RAW != nil {
228 s.Error(err) 227 s.Error(err)
229 return 228 return
230 } 229 }
231 var strips strings.Builder 230 var strips strings.Builder
232 for _, x := range dd.Strips { 231 for _, x := range dd.Strips {
@@ -312,12 +311,13 @@ func (s skunkyart) Search() {
312 return 311 return
313 } 312 }
314 try(err) 313 try(err)
315 if daError.RAW != nil {
316 s.Error(daError)
317 return
318 }
319 314
320 if s.Type != 'r' { 315 if s.Type != 'r' {
316 if daError.RAW != nil {
317 s.Error(daError)
318 return
319 }
320
321 ss.List = s.DeviationList(ss.Content.Results, false, DeviationList{ 321 ss.List = s.DeviationList(ss.Content.Results, false, DeviationList{
322 Pages: ss.Content.Pages, 322 Pages: ss.Content.Pages,
323 More: ss.Content.HasMore, 323 More: ss.Content.HasMore,
@@ -339,10 +339,3 @@ func (s skunkyart) Emojitar(name string) {
339 } 339 }
340 wr(s.Writer, ae) 340 wr(s.Writer, ae)
341} 341}
342
343func (s skunkyart) About() {
344 s.Templates.About.Nsfw = CFG.Nsfw
345 s.Templates.About.Proxy = CFG.Proxy
346 try(json.Unmarshal(instances, &s.Templates.About))
347 s.ExecuteTemplate("about.htm", "html", &s)
348}
config.example.json +2 −2
@@ -1,6 +1,6 @@
1{ 1{
2 "listen": "0.0.0.0:3003", 2 "listen": "0.0.0.0:3003",
3 "uri": "/huy/", 3 "uri": "/",
4 "cache": { 4 "cache": {
5 "enabled": true, 5 "enabled": true,
6 "path": "cache", 6 "path": "cache",
@@ -12,5 +12,5 @@
12 "download-proxy": "http://127.0.0.1:8080", 12 "download-proxy": "http://127.0.0.1:8080",
13 "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", 13 "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
14 "proxy": true, 14 "proxy": true,
15 "nsfw": false 15 "nsfw": true
16} 16}
instances.json +11 −11
@@ -8,8 +8,8 @@
8 "clearnet": "https://skunky.ebloid.ru/art" 8 "clearnet": "https://skunky.ebloid.ru/art"
9 }, 9 },
10 "settings": { 10 "settings": {
11 "nsfw": false, 11 "proxy": true,
12 "proxy": false 12 "nsfw": false
13 } 13 }
14 }, 14 },
15 { 15 {
@@ -19,8 +19,8 @@
19 "clearnet": "https://skunky.clovius.club" 19 "clearnet": "https://skunky.clovius.club"
20 }, 20 },
21 "settings": { 21 "settings": {
22 "nsfw": true, 22 "proxy": true,
23 "proxy": true 23 "nsfw": true
24 } 24 }
25 }, 25 },
26 { 26 {
@@ -30,8 +30,8 @@
30 "clearnet": "https://skunky.bloat.cat" 30 "clearnet": "https://skunky.bloat.cat"
31 }, 31 },
32 "settings": { 32 "settings": {
33 "nsfw": true, 33 "proxy": true,
34 "proxy": true 34 "nsfw": true
35 } 35 }
36 }, 36 },
37 { 37 {
@@ -41,8 +41,8 @@
41 "clearnet": "https://skunkyart.lumaeris.com" 41 "clearnet": "https://skunkyart.lumaeris.com"
42 }, 42 },
43 "settings": { 43 "settings": {
44 "nsfw": true, 44 "proxy": true,
45 "proxy": true 45 "nsfw": true
46 } 46 }
47 }, 47 },
48 { 48 {
@@ -52,9 +52,9 @@
52 "clearnet": "https://art.bloat.cat" 52 "clearnet": "https://art.bloat.cat"
53 }, 53 },
54 "settings": { 54 "settings": {
55 "nsfw": true, 55 "proxy": true,
56 "proxy": true 56 "nsfw": true
57 } 57 }
58 } 58 }
59 ] 59 ]
60} 60}\ No newline at end of file
main.go +1 −1
@@ -9,7 +9,7 @@ import (
9) 9)
10 10
11func main() { 11func main() {
12 app.Release.Version = "1.3.2-alpha" 12 app.Release.Version = "1.3.2"
13 app.Release.Description = "Two API endpoints and template embedding into binary" 13 app.Release.Description = "Two API endpoints and template embedding into binary"
14 go app.RefreshInstances() 14 go app.RefreshInstances()
15 15
static/css/skunky.css +9 −13
@@ -19,11 +19,10 @@ header h1 {
19header form { 19header form {
20 align-self: center; 20 align-self: center;
21} 21}
22header { 22header, form {
23 display: flex; 23 display: flex;
24} 24}
25form { 25form {
26 font-size: 0;
27 border: solid #164e3e 1px; 26 border: solid #164e3e 1px;
28 max-width: fit-content; 27 max-width: fit-content;
29} 28}
@@ -147,21 +146,18 @@ input:focus {
147 font-size: 80% 146 font-size: 80%
148 } 147 }
149 148
150 center form { 149 header {
151 font-size: 60% 150 margin-left: 3%;
152 }
153
154 header form {
155 font-size: 60%;
156 max-width: unset;
157 border: 0;
158 }
159 header, center {
160 text-align: center; 151 text-align: center;
161 display: block; 152 display: inline-block;
162 clear: both; 153 clear: both;
163 font-size: 200%; 154 font-size: 200%;
164 } 155 }
156
157 form {
158 font-size: 60%;
159 border: solid #164e3e 5px;
160 }
165 161
166 .content { 162 .content {
167 margin: auto; 163 margin: auto;
static/html/about.htm +1 −1
@@ -6,7 +6,7 @@
6 <p> 6 <p>
7 SkunkyArt is an alternative frontend for deviantart.com, written in Go. 7 SkunkyArt is an alternative frontend for deviantart.com, written in Go.
8 </p> 8 </p>
9 <h3><a href="https://go.kde.org/matrix/#/#skunkyart:ebloid.ru" target="_blank">Room in Matrix</a></h3> 9 <h3><a href="https://go.kde.org/matrix/#/#skunkyart:ebloid.ru" target="_blank">Room in [matrix]</a></h3>
10 <b>Instance settings:</b> 10 <b>Instance settings:</b>
11 <ul> 11 <ul>
12 <li><b>NSFW</b>: <span class="about-{{.Templates.About.Nsfw}}">{{if .Templates.About.Nsfw}}YES{{else}}NO{{end}}</span></li> 12 <li><b>NSFW</b>: <span class="about-{{.Templates.About.Nsfw}}">{{if .Templates.About.Nsfw}}YES{{else}}NO{{end}}</span></li>
static/html/head.htm +1
@@ -21,5 +21,6 @@
21 <meta name="referrer" content="no-referrer" /> 21 <meta name="referrer" content="no-referrer" />
22 <link rel="stylesheet" href="{{.BasePath}}stylesheet"> 22 <link rel="stylesheet" href="{{.BasePath}}stylesheet">
23 <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> 23 <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico">
24 <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4, user-scalable=no; user-scalable=0"/>
24</head> 25</head>
25{{end}}\ No newline at end of file 26{{end}}\ No newline at end of file
static/html/index.htm +71 −3
@@ -4,9 +4,75 @@
4 <title>SkunkyArt</title> 4 <title>SkunkyArt</title>
5 <link rel="stylesheet" href="{{.}}stylesheet"/> 5 <link rel="stylesheet" href="{{.}}stylesheet"/>
6 <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico"> 6 <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico">
7 <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4, user-scalable=no; user-scalable=0"/>
8
9 <style>
10 main {
11 display: flex;
12 max-width: fit-content;
13 position: absolute;
14 top: 50%;
15 right: 50%;
16 transform: translate(50%, -50%);
17 }
18 div {
19 transform: translate(0, 50%);
20 margin-left: 4%;
21 flex-basis: 100%;
22 height: 30%;
23 display: block;
24 max-width: fit-content;
25 }
26 div h1, form {
27 margin: 0;
28 }
29 div form {
30 font-size: 100%;
31 max-width: 100%
32 }
33 div form input {
34 width: 100%
35 }
36 img {
37 width: 30%;
38 height: 30%;
39 }
40
41 @media (orientation: portrait) {
42 main {
43 display: block;
44 width: 200%;
45 }
46 img {
47 width: 100%;
48 height: 100%;
49 }
50 form {
51 width: 200%;
52 }
53 div {
54 margin: -25%;
55 margin-top: auto;
56 width: 200%;
57 }
58 div h1 {
59 text-align: center;
60 }
61 }
62 @media (max-width: 1155px) and (orientation: landscape) {
63 img {
64 width: 50%;
65 }
66 div {
67 transform: none;
68 }
69 }
70 </style>
7 </head> 71 </head>
8 <main> 72 <main>
9 <center> 73 <img src="{{.}}favicon.ico" title="SkunkyArt logo" draggable="false">
74 <div>
75 <h1><a href="{{.}}dd">Daily Deviations</a> | <a href="{{.}}about">About</a></h1>
10 <form method="get" action="{{.}}search"> 76 <form method="get" action="{{.}}search">
11 <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> 77 <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
12 <select name="type"> 78 <select name="type">
@@ -16,7 +82,9 @@
16 </select> 82 </select>
17 <button type="submit">Search!</button> 83 <button type="submit">Search!</button>
18 </form> 84 </form>
19 <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> 85 <h1 style="margin-top: 5%; font-size: 200%; text-align: center;">
20 </center> 86 <a href="https://git.macaw.me/skunky/SkunkyArt" target="_blank" title="Source Code">SkunkyArt</a>
87 </h1>
88 </div>
21 </main> 89 </main>
22</html>\ No newline at end of file 90</html>\ No newline at end of file