From 191984b31ef228c4077904a2831a2af593352670 Mon Sep 17 00:00:00 2001 From: lost+skunk Date: Mon, 23 Sep 2024 09:38:32 +0300 Subject: [PATCH] v1.3.2 --- .gitignore | 1 + INSTANCES.md | 6 ++- README.md | 12 ++++++ REDIRECTS.md | 13 +++++++ SETUP-RU.md | 4 +- SETUP.md | 2 +- app/api.go | 91 +++++++++++++++++++++---------------------- app/cache.go | 8 ++-- app/config.go | 9 ++++- app/router.go | 31 +++++++++------ app/util.go | 23 +++++++---- app/wrapper.go | 27 +++++-------- config.example.json | 4 +- instances.json | 22 +++++------ main.go | 2 +- static/css/skunky.css | 22 +++++------ static/html/about.htm | 2 +- static/html/head.htm | 1 + static/html/index.htm | 74 +++++++++++++++++++++++++++++++++-- 19 files changed, 230 insertions(+), 124 deletions(-) create mode 100644 REDIRECTS.md diff --git a/.gitignore b/.gitignore index 4686488..b52ba21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ **/cache **/config.json **/skunkyart +**/skunkyart-* diff --git a/INSTANCES.md b/INSTANCES.md index 70b68dc..32b0e50 100644 --- a/INSTANCES.md +++ b/INSTANCES.md @@ -1,7 +1,9 @@ +JSON variant should be used from master — https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json + |Instance|Yggdrasil|I2P|Tor|NSFW|Proxifying|Modified Sources|Country| |:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:| -|[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 | +|[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 | |[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden | |[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Germany | |[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | Germany | -|[art.bloat.cat](https://art.bloat.cat)|No|No|No| Yes | Yes | No | Germany | +|[art.bloat.cat](https://art.bloat.cat)|No|No|No| Yes | Yes | No | Germany | \ No newline at end of file diff --git a/README.md b/README.md index cfc302f..b61b0bd 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,12 @@ Instances: [`INSTANCES.md`](/skunky/SkunkyArt/src/branch/master/INSTANCES.md) # EN 🇺🇸 ## Description SkunkyArt 🦨 — alternative frontend for DevianArt, which works without JS. +## Build (translated via DeepL) +It 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: + +`go build -tags embed -ldflags "-w -s"` + +Pre-compiled binaries can be found in the [Releases](https://git.macaw.me/skunky/skunkyart/releases) tab. ## Setup The sample config is in the `config.example.json` file. For custom config, use `--config` option. See 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. # RU 🇷🇺 ## Описание SkunkyArt 🦨 — альтернативный фронтенд к DeviantArt, который полностью работает без JS (JavaScript). +## Сборка +Рекомендуется производить сборку с тегом 'embed', поскольку он встраивает заготовки в бинарный файл. Если вы планируете изменять заготовки, то не используйте этот тег. Также вы можете добавить аргумент `-ldflags "-w -s"` (у GCCGO он называется по-другому — `gccgoflags`) для уменьшения размера выходного файла. Вот пример: + +`go build -tags embed -ldflags "-w -s"` + +Готовые бинари находятся во вкладке [Releases](https://git.macaw.me/skunky/skunkyart/releases). ## Настройка Пример конфига находится в файле `config.example.json`. Чтобы указать свой конфиг, используйте cli-аргумент `--config`. См. [`SETUP-RU.md`](/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) для информации о настройки фронтенда. diff --git a/REDIRECTS.md b/REDIRECTS.md new file mode 100644 index 0000000..2469977 --- /dev/null +++ b/REDIRECTS.md @@ -0,0 +1,13 @@ +# Search +* `deviantart.com/search?q=$QUERY` => `/search?q=$QUERY&type=all` +# Daily Deviations +* `deviantart.com` => `/dd` +# Deviations +* (`$USER_GROUP.deviantart.com/art/$ID`|`deviantart.com/$USER_GROUP/art/$ID`) => `/post/$USER_GROUP/$ID` +# Groups and users +## Main user page +* (`$USER_GROUP.deviantart.com`|`deviantart.com/$USER_GROUP`) => `/group_user?type=about&q=$USER_GROUP` +## Gallery +* (`$USER_GROUP.deviantart.com/gallery`|`deviantart.com/$USER_GROUP/gallery`) => `/group_user?type=gallery&q=$USER_GROUP` +## Favourites +* (`$USER_GROUP.deviantart.com/favourites`|`deviantart.com/$USER_GROUP/favourites`) => `/group_user?type=favourites&q=$USER_GROUP` diff --git a/SETUP-RU.md b/SETUP-RU.md index 994f193..7d7d04c 100644 --- a/SETUP-RU.md +++ b/SETUP-RU.md @@ -12,13 +12,13 @@ # Конфигурация * `listen` — IP и порт для слушанья; заполняется по такой форме: ip:port * `uri` — URI инстанса. Пример: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/ -* `cache` — Система кеширования; по умолчанию выключена. +* `cache` — Система кеширования; по умолчанию выключена * `enabled` — Состояние системы кеширования; требуется булёвое значение * `path` — Полный путь до каталога, куда будет сохраняться кеш * `lifetime` — Время жизни файла в кеше, требует целочисленное значение, дополненное суффиксом времени (см. 'Единицы времени') * `max-size` — Максимальный размер файла * `update-interval` — Интервал для автоматической ротации кеша -* `dirs-to-memory` — Массив, заполнив который скопируются все файлы из указанных каталогов +* `static-path` — Строка, являющаяся путём до статики. SkunkyArt при запуске скопирует содержимое этого каталога в ОЗУ. Однако, если вы собрали фронтенд с тегом 'embed', то этого не произайдёт * `download-proxy` — Адрес прокси для загрузки файлов * `user-agent` — Строка, которая используется в качестве User-Agent'а diff --git a/SETUP.md b/SETUP.md index 169e7a0..cc89118 100644 --- a/SETUP.md +++ b/SETUP.md @@ -18,7 +18,7 @@ Time units: * `lifetime` — Cached file life time, requires numeric value, followed by multiplicative suffix (see Time Units for details) * `max-size` — Maximum file size in megabytes * `update-interval` — Automatic rotation interval -* `dirs-to-memory` — This setting determines which directories will be copied to RAM when SkunkyArt is started. Mandatory +* `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. * `download-proxy` — Proxy address for downloading files. * `user-agent` — String, which SkunkyArt uses as UA diff --git a/app/api.go b/app/api.go index fdf8270..048bc2a 100644 --- a/app/api.go +++ b/app/api.go @@ -9,69 +9,68 @@ import ( ) type API struct { - main *skunkyart + main *skunkyart } type info struct { - Version string `json:"version"` - Settings settingsParams `json:"settings"` + Version string `json:"version"` + Settings settingsParams `json:"settings"` } func (a API) Info() { - json, err := json.Marshal(info{ - Version: a.main.Version, - Settings: settingsParams{ - Nsfw: CFG.Nsfw, - Proxy: CFG.Proxy, - }, - }) - try(err) - a.main.Writer.Write(json) - return + json, err := json.Marshal(info{ + Version: a.main.Version, + Settings: settingsParams{ + Nsfw: CFG.Nsfw, + Proxy: CFG.Proxy, + }, + }) + try(err) + a.main.Writer.Write(json) } func (a API) Error(description string, status int) { - a.main.Writer.WriteHeader(status) - var response strings.Builder - response.WriteString(`{"error":"`) - response.WriteString(description) - response.WriteString(`"}`) - wr(a.main.Writer, response.String()) + a.main.Writer.WriteHeader(status) + var response strings.Builder + response.WriteString(`{"error":"`) + response.WriteString(description) + response.WriteString(`"}`) + wr(a.main.Writer, response.String()) } func (a API) sendMedia(d *devianter.Deviation) { - mediaUrl, name := devianter.UrlFromMedia(d.Media) + mediaUrl, name := devianter.UrlFromMedia(d.Media) a.main.SetFilename(name) - - if len(mediaUrl) != 0 { - mediaUrl = mediaUrl[21:] + + if len(mediaUrl) != 0 { + mediaUrl = mediaUrl[21:] dot := strings.Index(mediaUrl, ".") a.main.Writer.Header().Del("Content-Type") - a.main.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:]) - } + a.main.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:]) + } } // TODO: сделать фильтры func (a API) Random() { - for attempt := 1;; { - if attempt > 3 { - a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500) - } - - s, err, daErr := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a') - try(err) - if daErr.RAW != nil { - continue - } - - deviation := &s.Results[rand.Intn(len(s.Results))] - - if deviation.NSFW && !CFG.Nsfw { - attempt++ - continue - } - - a.sendMedia(deviation) - return - } + for attempt := 1; ; { + if attempt > 3 { + a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500) + } + + s, err, daErr := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a') + try(err) + if daErr.RAW != nil { + continue + } + + deviation := &s.Results[rand.Intn(len(s.Results))] + + if deviation.NSFW && !CFG.Nsfw { + attempt++ + continue + } + + a.sendMedia(deviation) + return + } } diff --git a/app/cache.go b/app/cache.go index 975fa0a..f9225be 100644 --- a/app/cache.go +++ b/app/cache.go @@ -27,10 +27,10 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { url.WriteString(".wixmp.com/") url.WriteString(path) if t := s.Args.Get("token"); t != "" { - url.WriteString("?token=") - url.WriteString(t) + url.WriteString("?token=") + url.WriteString(t) } - + var response []byte switch { @@ -127,7 +127,7 @@ func InitCacheSystem() { try(os.RemoveAll(fileName)) } } - + if c.MaxSize != 0 && fileInfo.Size() > c.MaxSize { try(os.RemoveAll(fileName)) } diff --git a/app/config.go b/app/config.go index 745d2f4..2ba39a9 100644 --- a/app/config.go +++ b/app/config.go @@ -56,13 +56,11 @@ func ExecuteConfig() { if CFG.cfg != "" { f, err := os.ReadFile(CFG.cfg) tryWithExitStatus(err, 1) - tryWithExitStatus(json.Unmarshal(f, &CFG), 1) if CFG.Cache.Enabled && !CFG.Proxy { exit("Incompatible settings detected: cannot use caching media content without proxy", 1) } - static.StaticPath = CFG.StaticPath if CFG.Cache.Enabled { if CFG.Cache.Lifetime != "" { var duration int64 @@ -92,6 +90,13 @@ func ExecuteConfig() { CFG.Cache.MaxSize *= 1024 ^ 2 go InitCacheSystem() } + + About = instanceAbout{ + Proxy: CFG.Proxy, + Nsfw: CFG.Nsfw, + } + + static.StaticPath = CFG.StaticPath devianter.UserAgent = CFG.UserAgent } } diff --git a/app/router.go b/app/router.go index 32488cb..eddf0a9 100644 --- a/app/router.go +++ b/app/router.go @@ -3,7 +3,7 @@ package app import ( "io" "net/http" - u "net/url" + url "net/url" "skunkyart/static" "strconv" "strings" @@ -57,7 +57,7 @@ func Router() { Path = r.URL.Path path := parsepath(Path) Host = "http://" + r.Host - + if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" { Host = "https://" + r.Host } @@ -67,13 +67,13 @@ func Router() { skunky.Args = r.URL.Query() arg := skunky.Args.Get p, _ := strconv.Atoi(arg("p")) - + skunky.Endpoint = path[1] skunky.API.main = &skunky skunky.Writer = w skunky.BasePath = CFG.URI skunky.QueryRaw = arg("q") - skunky.Query = u.QueryEscape(skunky.QueryRaw) + skunky.Query = url.QueryEscape(skunky.QueryRaw) skunky.Page = p if t := arg("type"); len(t) > 0 { @@ -84,12 +84,21 @@ func Router() { skunky.Atom = true } + if CFG.Proxy { + w.Header().Add("Content-Security-Policy", "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'") + } else { + w.Header().Add("Content-Security-Policy", "default-src 'self'; img-src 'self' *.wixmp.com; script-src 'none'; style-src 'self' 'unsafe-inline'") + } + + w.Header().Add("X-Frame-Options", "DENY") + switch skunky.Endpoint { // main case "": skunky.ExecuteTemplate("index.htm", "html", &CFG.URI) case "about": - skunky.About() + skunky.Templates.About = About + skunky.ExecuteTemplate("about.htm", "html", &skunky) case "post": skunky.Deviation(path[2], path[3]) case "search": @@ -120,12 +129,12 @@ func Router() { case "api": w.Header().Add("Content-Type", "application/json") switch path[2] { - case "instance": - skunky.API.Info() - case "random": - skunky.API.Random() - default: - skunky.API.Error("Not Found", 404) + case "instance": + skunky.API.Info() + case "random": + skunky.API.Random() + default: + skunky.API.Error("Not Found", 404) } // 404 diff --git a/app/util.go b/app/util.go index c698090..9c8d400 100644 --- a/app/util.go +++ b/app/util.go @@ -1,6 +1,7 @@ package app import ( + "encoding/json" "io" "net/http" "net/url" @@ -40,18 +41,26 @@ func restore() { } var instances []byte +var About instanceAbout func RefreshInstances() { for { func() { defer restore() instances = Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body + try(json.Unmarshal(instances, &About)) }() time.Sleep(1 * time.Hour) } } // some crap for frontend +type instanceAbout struct { + Proxy bool + Nsfw bool + Instances []settings +} + type skunkyart struct { Writer http.ResponseWriter @@ -63,15 +72,11 @@ type skunkyart struct { BasePath, Endpoint string Query, QueryRaw string - API API - Version string + API API + Version string Templates struct { - About struct { - Proxy bool - Nsfw bool - Instances []settings - } + About instanceAbout SomeList string DDStrips string @@ -132,7 +137,7 @@ func UrlBuilder(strs ...string) string { str.WriteString(CFG.URI) for n, x := range strs { str.WriteString(x) - if n := n+1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') { + if n := n + 1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') { str.WriteString("/") } } @@ -215,6 +220,8 @@ func ParseMedia(media devianter.Media, thumb ...int) string { filename = "image.gif" } return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename) + } else if !CFG.Proxy { + return mediaUrl } return "" } diff --git a/app/wrapper.go b/app/wrapper.go index a2e0fa4..5cac175 100644 --- a/app/wrapper.go +++ b/app/wrapper.go @@ -1,7 +1,6 @@ package app import ( - "encoding/json" "regexp" "strconv" "strings" @@ -24,8 +23,8 @@ func (s skunkyart) GRUser() { s.Templates.GroupUser.GR, err, daError = g.Get() try(err) if daError.RAW != nil { - s.Error(daError) - return + s.Error(daError) + return } group := &s.Templates.GroupUser @@ -68,7 +67,7 @@ func (s skunkyart) GRUser() { group.About.Interests += interest.String() } } - group.About.Comments = s.ParseComments(devianter.GetComments(strconv.Itoa(group.GR.Gruser.ID),"",s.Page,4)) + group.About.Comments = s.ParseComments(devianter.GetComments(strconv.Itoa(group.GR.Gruser.ID), "", s.Page, 4)) case "cover_deviation": group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation @@ -225,8 +224,8 @@ func (s skunkyart) Deviation(author, postname string) { func (s skunkyart) DD() { dd, err := devianter.GetDailyDeviations(s.Page) if err.RAW != nil { - s.Error(err) - return + s.Error(err) + return } var strips strings.Builder for _, x := range dd.Strips { @@ -312,12 +311,13 @@ func (s skunkyart) Search() { return } try(err) - if daError.RAW != nil { - s.Error(daError) - return - } if s.Type != 'r' { + if daError.RAW != nil { + s.Error(daError) + return + } + ss.List = s.DeviationList(ss.Content.Results, false, DeviationList{ Pages: ss.Content.Pages, More: ss.Content.HasMore, @@ -339,10 +339,3 @@ func (s skunkyart) Emojitar(name string) { } wr(s.Writer, ae) } - -func (s skunkyart) About() { - s.Templates.About.Nsfw = CFG.Nsfw - s.Templates.About.Proxy = CFG.Proxy - try(json.Unmarshal(instances, &s.Templates.About)) - s.ExecuteTemplate("about.htm", "html", &s) -} diff --git a/config.example.json b/config.example.json index f4909a7..b38e47e 100644 --- a/config.example.json +++ b/config.example.json @@ -1,6 +1,6 @@ { "listen": "0.0.0.0:3003", - "uri": "/huy/", + "uri": "/", "cache": { "enabled": true, "path": "cache", @@ -12,5 +12,5 @@ "download-proxy": "http://127.0.0.1:8080", "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", "proxy": true, - "nsfw": false + "nsfw": true } diff --git a/instances.json b/instances.json index 4b43a06..a3a2f4a 100644 --- a/instances.json +++ b/instances.json @@ -8,8 +8,8 @@ "clearnet": "https://skunky.ebloid.ru/art" }, "settings": { - "nsfw": false, - "proxy": false + "proxy": true, + "nsfw": false } }, { @@ -19,8 +19,8 @@ "clearnet": "https://skunky.clovius.club" }, "settings": { - "nsfw": true, - "proxy": true + "proxy": true, + "nsfw": true } }, { @@ -30,8 +30,8 @@ "clearnet": "https://skunky.bloat.cat" }, "settings": { - "nsfw": true, - "proxy": true + "proxy": true, + "nsfw": true } }, { @@ -41,8 +41,8 @@ "clearnet": "https://skunkyart.lumaeris.com" }, "settings": { - "nsfw": true, - "proxy": true + "proxy": true, + "nsfw": true } }, { @@ -52,9 +52,9 @@ "clearnet": "https://art.bloat.cat" }, "settings": { - "nsfw": true, - "proxy": true + "proxy": true, + "nsfw": true } } ] -} +} \ No newline at end of file diff --git a/main.go b/main.go index 3dc4374..d7009ae 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,7 @@ import ( ) func main() { - app.Release.Version = "1.3.2-alpha" + app.Release.Version = "1.3.2" app.Release.Description = "Two API endpoints and template embedding into binary" go app.RefreshInstances() diff --git a/static/css/skunky.css b/static/css/skunky.css index bdc894c..a7258c8 100644 --- a/static/css/skunky.css +++ b/static/css/skunky.css @@ -19,11 +19,10 @@ header h1 { header form { align-self: center; } -header { +header, form { display: flex; } form { - font-size: 0; border: solid #164e3e 1px; max-width: fit-content; } @@ -147,21 +146,18 @@ input:focus { font-size: 80% } - center form { - font-size: 60% - } - - header form { - font-size: 60%; - max-width: unset; - border: 0; - } - header, center { + header { + margin-left: 3%; text-align: center; - display: block; + display: inline-block; clear: both; font-size: 200%; } + + form { + font-size: 60%; + border: solid #164e3e 5px; + } .content { margin: auto; diff --git a/static/html/about.htm b/static/html/about.htm index 94bbf5c..eac4e47 100644 --- a/static/html/about.htm +++ b/static/html/about.htm @@ -6,7 +6,7 @@

SkunkyArt is an alternative frontend for deviantart.com, written in Go.

-

Room in Matrix

+

Room in [matrix]

Instance settings: