diff --git a/.dockerignore b/.dockerignore deleted file mode 100755 index 6833fbe..0000000 --- a/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -cache -compose.yaml -*.json -LICENSE -*.md -services diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 63ca398..4686488 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ **/cache -**/compose.yaml **/config.json **/skunkyart -**/skunkyart-* diff --git a/Dockerfile b/Dockerfile deleted file mode 100755 index fdc1919..0000000 --- a/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -ARG GO_VERSION=1.18 - -FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /build -COPY . . -RUN CGO_ENABLED=0 GOARCH=${TARGETARCH} GOOS=${TARGETOS} go build -ldflags "-s -w -extldflags '-static'" && \ - echo "skunkyart:x:10000:10000:SkunkyArt user:/:/sbin/nologin" > /etc/minimal-passwd && \ - echo "skunkyart:x:10000:" > /etc/minimal-group - -FROM scratch - -COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -COPY --from=build /build/static /static -COPY --from=build /build/skunkyart /skunkyart -COPY --from=build /etc/minimal-passwd /etc/passwd -COPY --from=build /etc/minimal-group /etc/group - -USER skunkyart - -ENTRYPOINT ["/skunkyart"] diff --git a/INSTANCES.md b/INSTANCES.md old mode 100755 new mode 100644 index 38cd5bb..1637971 --- a/INSTANCES.md +++ b/INSTANCES.md @@ -1,11 +1,7 @@ -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| |:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:| -|[lost-skunk.cc](https://lost-skunk.cc/skunkyart)|[Yes](http://[201:f137:d1ac:920e:cd42:bfd1:1e83:da1d]/skunkyart)|No|No| No | Yes | No | Finland | -|[orehus.club](https://sa.orehus.club)|No|No|No| Yes | No | No | Germany | -|[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 | -|[dc09.ru](https://sa.dc09.ru)|No|No|No| No | Yes | No | Russia | -|[opnxng.com](https://da.opnxng.com)|No|No|No| Yes | Yes | No | Singapore | +|[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 | +|[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 | Romania | +|[frontendfriendly.xyz](https://skunkyart.frontendfriendly.xyz)|No|No|No| Yes | Yes | No | Finland | +|[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | US | \ No newline at end of file diff --git a/LICENSE b/LICENSE old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 index e5f404d..6eaa831 --- a/README.md +++ b/README.md @@ -1,51 +1,33 @@ -> [!NOTE] -> Currently, due to school, I cannot actively develop this project :( -> However, this does not mean that development has stopped. Just wait for the summer. For questions, write either to the Matrix room or to me in DM. +SkunkyArt -SkunkyArt +[![Matrix room](https://img.shields.io/badge/matrix-000000?style=for-the-badge&logo=Matrix&logoColor=white)](https://go.kde.org/matrix/#/#skunkyart:ebloid.ru) -[![Matrix room](https://img.shields.io/badge/matrix-000000?style=for-the-badge&logo=Matrix&logoColor=white)](https://go.kde.org/matrix/#/#skunkyart:gnulinux.club) - -Instances: [`INSTANCES.md`](/skunky/SkunkyArt/src/branch/master/INSTANCES.md) +Instances: [`INSTANCES.md`](https://git.macaw.me/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. +See the [`SETUP.md`](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP.md) file for more info about directives. ## Adding instance to the list To do this, you must either make a PR by adding your instance to the `instances.json` and `INSTANCES.md` files (you can use `--add-instance` cli-argument to automatically add the instance to these files), or create an Issue, or report it to the room in Matrix. Keep in mind that your instance must comply with the following rules: 1. the Instance must not use Cloudflare. 2. If your instance has modified source code, you need to publish it to any free platform. For example, Github and Gitlab are not. ## Acknowledgements -* [vlnst](https://git.bloat.cat/vlnst) — wrote a Docker file. * [Лис⚛](https://go.kde.org/matrix/#/@fox:matrix.org) — helped me understand Go and gave me a lot of useful advice on this language. * [meoww](https://codeberg.org/meoww) — translated some sentences into English and wrote a service for openrc # 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) для информации о настройки фронтенда. +См. [`SETUP-RU.md`](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) для информации о настройки фронтенда. ## Добавление инстанса в список Чтобы это сделать, вы должны либо сделать PR, добавив в файлы `instances.json` и `INSTANCES.md` свой инстанс (можете воспользоваться cli-аргументом `--add-instance`, который автоматически это сделает), либо создать Issue, или сообщить о нём в комнате в Matrix. Учтите, что ваш инстанс должен соблюсти следущие правила: 1. Инстанс не должен использовать Cloudflare итп. 2. Если ваш инстанс имеет модифицированный исходный код, то вам нужно опубликовать его на любую свободную площадку. Например, Github и Gitlab таковыми не являются. ## Благодарности -* [vlnst](https://git.bloat.cat/vlnst) — написал Docker-файл. * [Лис⚛](https://go.kde.org/matrix/#/@fox:matrix.org) — помог разобраться в Go и много чего полезного посоветовал по этому языку. * [meoww](https://codeberg.org/meoww) — перевела некоторые предложения на английский язык и написала сервис для openrc \ No newline at end of file diff --git a/REDIRECTS.md b/REDIRECTS.md deleted file mode 100755 index 2469977..0000000 --- a/REDIRECTS.md +++ /dev/null @@ -1,13 +0,0 @@ -# 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 old mode 100755 new mode 100644 index 7d7d04c..6ff3c23 --- a/SETUP-RU.md +++ b/SETUP-RU.md @@ -1,4 +1,4 @@ -[English version 🇬🇧](/skunky/SkunkyArt/src/branch/master/SETUP.md) +[English version 🇬🇧](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP.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` — Интервал для автоматической ротации кеша -* `static-path` — Строка, являющаяся путём до статики. SkunkyArt при запуске скопирует содержимое этого каталога в ОЗУ. Однако, если вы собрали фронтенд с тегом 'embed', то этого не произайдёт +* `dirs-to-memory` — Массив, заполнив который скопируются все файлы из указанных каталогов * `download-proxy` — Адрес прокси для загрузки файлов * `user-agent` — Строка, которая используется в качестве User-Agent'а diff --git a/SETUP.md b/SETUP.md old mode 100755 new mode 100644 index cc89118..ab30165 --- a/SETUP.md +++ b/SETUP.md @@ -1,4 +1,4 @@ -[Версия на русском языке 🇷🇺](/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) +[Версия на русском языке 🇷🇺](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) # Units Maximum file size in megabytes, requires numeric value.
@@ -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 -* `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. +* `dirs-to-memory` — This setting determines which directories will be copied to RAM when SkunkyArt is started. Mandatory * `download-proxy` — Proxy address for downloading files. * `user-agent` — String, which SkunkyArt uses as UA diff --git a/TODO.md b/TODO.md old mode 100755 new mode 100644 index 01bc524..ee50111 --- a/TODO.md +++ b/TODO.md @@ -1,23 +1,19 @@ # v1.3.x * Почистить говнокод -* Добавить фильтры поиска -* ~~Сделать порт под FreeBSD~~ ✔️ * **Доделать парсинг описания** -* ~~Реализовать стрипы в ежедневных артах~~ ✔️ -* ~~Исправить баг с навигацией по страницам~~ ✔️ -* ~~Сделать нормальное отображение ошибок~~ ✔️ -* ~~Сделать единицы в конфиге более понятными~~ ✔️ -* Добавить чекер инстанса на работоспособность -* ~~Добавить просмотр понравившихся артов пользователю~~ ✔️ -* Добавить возможность включить темплейты в бинарник [P] -* ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~ ✔️ +* Избавиться от хардкода под Linux +* ~~Реализовать стрипы в ежедневных артах~~ +* Сделать нормальное отображение ошибок +* ~~Исправить баг с навигацией по страницам~~ +* ~~Сделать единицы в конфиге более понятными~~ +* Добавить возможность включить темплейты в бинарник +* ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~ * Написать Makefile и скрипт для автоматического развёртывания инстанса +* **Реализовать отображение контента, отличного от картинок (видео, аудио, etc)** * Исправить баг с эмоджи, когда некоторые кастомные эмоции могут не отображаться -* ~~Добавить аргумент &filename, который будет выдавать файл с нормально выглядещем именем~~ ✔️ -* ~~Улучшить систему кеширования: добавить рейтинг для удаления и копирование изображений в ОЗУ~~ ✔️ +* Добавить флаг сборки, который позволит собрать бинарник со встроенными темплейтами +* Улучшить систему кеширования: добавить рейтинг для удаления и копирование изображений в ОЗУ # v1.4 * Реализовать API * Реализовать темы -* Перейти на арены в кеше -* Реализовать многоязычный интерфейс - +* Реализовать многоязычный интерфейс \ No newline at end of file diff --git a/app/api.go b/app/api.go deleted file mode 100755 index d2a5655..0000000 --- a/app/api.go +++ /dev/null @@ -1,82 +0,0 @@ -package app - -import ( - "encoding/json" - "math/rand" - "strings" - - "git.macaw.me/skunky/devianter" -) - -type API struct { - main *skunkyart -} - -type info struct { - 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) -} - -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()) -} - -func (a API) sendMedia(d *devianter.Deviation) { - mediaUrl, name := devianter.UrlFromMedia(d.Media) - a.main.SetFilename(name) - if len(mediaUrl) != 0 { - return - } - - if CFG.Proxy { - mediaUrl = mediaUrl[21:] - dot := strings.Index(mediaUrl, ".") - a.main.Writer.Header().Del("Content-Type") - a.main.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:]) - } else { - a.main.Writer.Header().Add("Location", mediaUrl) - a.main.Writer.WriteHeader(302) - } -} - -// 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 - } -} diff --git a/app/cache.go b/app/cache.go deleted file mode 100755 index e03db67..0000000 --- a/app/cache.go +++ /dev/null @@ -1,152 +0,0 @@ -// TODO: реализовать кеширование JSON и почистить код -package app - -import ( - "crypto/sha1" - "encoding/hex" - "io" - "os" - "strings" - "sync" - "syscall" - "time" -) - -type file struct { - Score int - Content []byte -} - -var tempFS = make(map[[20]byte]*file) -var mx = &sync.RWMutex{} - -func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { - var url strings.Builder - url.WriteString("https://images-wixmp-") - url.WriteString(subdomain) - url.WriteString(".wixmp.com/") - url.WriteString(path) - if t := s.Args.Get("token"); t != "" { - url.WriteString("?token=") - url.WriteString(t) - } - - var response []byte - - switch { - case CFG.Cache.Enabled: - fileName := sha1.Sum([]byte(subdomain + path)) - filePath := CFG.Cache.Path + "/" + hex.EncodeToString(fileName[:]) - - c := func() { - file, err := os.Open(filePath) - if err != nil { - if dwnld := Download(url.String()); dwnld.Status == 200 && dwnld.Headers["Content-Type"][0][:5] == "image" { - response = dwnld.Body - try(os.WriteFile(filePath, response, 0700)) - } else { - s.ReturnHTTPError(dwnld.Status) - return - } - } else { - file, e := io.ReadAll(file) - try(e) - response = file - } - } - - if CFG.Cache.MemCache { - mx.Lock() - if tempFS[fileName] == nil { - tempFS[fileName] = &file{} - } - mx.Unlock() - - if tempFS[fileName].Content != nil { - response = tempFS[fileName].Content - tempFS[fileName].Score += 2 - break - } else { - c() - go func() { - defer restore() - - mx.RLock() - tempFS[fileName].Content = response - mx.RUnlock() - - for { - time.Sleep(1 * time.Minute) - - mx.Lock() - if tempFS[fileName].Score <= 0 { - delete(tempFS, fileName) - mx.Unlock() - return - } - tempFS[fileName].Score-- - mx.Unlock() - } - }() - } - } else { - c() - } - case CFG.Proxy: - dwnld := Download(url.String()) - if dwnld.Status != 200 { - s.ReturnHTTPError(dwnld.Status) - return - } - response = dwnld.Body - default: - s.Writer.WriteHeader(403) - response = []byte("Sorry, butt proxy on this instance are disabled.") - } - - s.Writer.Write(response) -} - -func InitCacheSystem() { - c := &CFG.Cache - for { - dir, err := os.ReadDir(c.Path) - if err != nil { - if os.IsNotExist(err) { - os.Mkdir(c.Path, 0700) - continue - } - println(err.Error()) - } - - var total int64 - for _, file := range dir { - fileName := c.Path + "/" + file.Name() - fileInfo, err := file.Info() - try(err) - - if c.Lifetime != "" { - now := time.Now().UnixMilli() - - stat := fileInfo.Sys().(*syscall.Stat_t) - time := statTime(stat) - - if time+lifetimeParsed <= now { - try(os.RemoveAll(fileName)) - } - } - - total += fileInfo.Size() - // if c.MaxSize != 0 && fileInfo.Size() > c.MaxSize { - // try(os.RemoveAll(fileName)) - // } - } - - if c.MaxSize != 0 && total > c.MaxSize { - try(os.RemoveAll(c.Path)) - os.Mkdir(c.Path, 0700) - } - - time.Sleep(time.Second * time.Duration(c.UpdateInterval)) - } -} diff --git a/app/cli.go b/app/cli.go old mode 100755 new mode 100644 index 0480bdb..2247223 --- a/app/cli.go +++ b/app/cli.go @@ -4,20 +4,19 @@ import ( "bufio" "bytes" "encoding/json" - "html/template" "os" "time" ) func ExecuteCommandLineArguments() { - var helpmsg = `SkunkyArt v{{.Version}} [{{.Description}}] + const helpmsg = `SkunkyArt v1.3.1 [CSS improvements for mobile and the strips on Daily Deviations] Usage: - [-c|--config] | path to config - [-a|--add-instance] | generates 'instances.json' and 'INSTANCES.md' files with ur instance - [-h|--help] | returns this message Example: ./skunkyart -c config.json -Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v{{.Version}}` +Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3.1` a := os.Args[1:] for n, x := range a { @@ -29,11 +28,7 @@ Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v{{.Ver exit("Not enought arguments", 1) } case "-h", "--help": - var buf bytes.Buffer - t := template.New("help") - t.Parse(helpmsg) - t.Execute(&buf, &Release) - exit(buf.String(), 0) + exit(helpmsg, 0) case "-a", "--add-instance": addInstance() } @@ -83,16 +78,16 @@ func addInstance() { try(err) defer instancesJson.Close() - instancesFile, err := os.OpenFile("INSTANCES.md", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + instances, err := os.OpenFile("INSTANCES.md", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) try(err) - defer instancesFile.Close() + defer instances.Close() for { - if string(instances) == "" { + if Templates["instances.json"] == "" { print("\rDownloading instance list...") } else { println("\r\033[2KDownloaded!") - try(json.Unmarshal(instances, &settingsVar)) + try(json.Unmarshal([]byte(Templates["instances.json"]), &settingsVar)) settingsVar.Instances = append(settingsVar.Instances, settings{ Title: prompt("Title", true), @@ -118,46 +113,51 @@ func addInstance() { settingsVar := &settingsVar.Instances[len(settingsVar.Instances)-1] var mdstr bytes.Buffer - mdbuilder := func(yes bool, link string, title string) { - switch { - case yes && (title != "" && link != ""): - mdstr.WriteString("[") - mdstr.WriteString(title) - mdstr.WriteString("](") - mdstr.WriteString(link) - mdstr.WriteString(")") - case yes && link != "": - mdstr.WriteString("[Yes](") - mdstr.WriteString(link) - mdstr.WriteString(")") - case yes: - mdstr.WriteString("Yes") - default: - mdstr.WriteString("No") - } - mdstr.WriteString("|") - } - mdstr.WriteString("\n|") - mdbuilder(settingsVar.Urls.Clearnet != "", settingsVar.Urls.Clearnet, settingsVar.Title) + if settingsVar.Urls.Clearnet != "" { + mdstr.WriteString("[") + mdstr.WriteString(settingsVar.Title) + mdstr.WriteString("](") + mdstr.WriteString(settingsVar.Urls.Clearnet) + mdstr.WriteString(")") + } else { + mdstr.WriteString(settingsVar.Title) + } + mdstr.WriteString("|") urls := []string{settingsVar.Urls.Ygg, settingsVar.Urls.I2P, settingsVar.Urls.Tor} for i, l := 0, len(urls); i < l; i++ { url := urls[i] - mdbuilder(url != "", url, "") + if url != "" { + mdstr.WriteString("[Yes](") + mdstr.WriteString(url) + mdstr.WriteString(")|") + } else { + mdstr.WriteString("No|") + } } settings := []bool{settingsVar.Settings.Nsfw, settingsVar.Settings.Proxy} for i, l := 0, len(settings); i < l; i++ { - mdbuilder(settings[i], "", "") + if settings[i] { + mdstr.WriteString("Yes|") + } else { + mdstr.WriteString("No|") + } } - mdbuilder(settingsVar.ModifiedSrc != "", settingsVar.ModifiedSrc, "") + if settingsVar.ModifiedSrc != "" { + mdstr.WriteString("[Yes](") + mdstr.WriteString(settingsVar.ModifiedSrc) + mdstr.WriteString(")|") + } else { + mdstr.WriteString("No|") + } mdstr.WriteString(settingsVar.Country) mdstr.WriteString("|") - instancesFile.Write(mdstr.Bytes()) + instances.Write(mdstr.Bytes()) break } time.Sleep(500 * time.Millisecond) diff --git a/app/config.go b/app/config.go old mode 100755 new mode 100644 index 813453c..a6ac756 --- a/app/config.go +++ b/app/config.go @@ -4,21 +4,14 @@ import ( "encoding/json" "os" "regexp" - "skunkyart/static" "strconv" "time" "git.macaw.me/skunky/devianter" ) -var Release struct { - Version string - Description string -} - type cache_config struct { Enabled bool - MemCache bool `json:"memcache"` Path string MaxSize int64 `json:"max-size"` Lifetime string @@ -31,9 +24,9 @@ type config struct { URI string `json:"uri"` Cache cache_config Proxy, Nsfw bool - UserAgent string `json:"user-agent"` - DownloadProxy string `json:"download-proxy"` - StaticPath string `json:"static-path"` + UserAgent string `json:"user-agent"` + DownloadProxy string `json:"download-proxy"` + Dirs []string `json:"dirs-to-memory"` } var CFG = config{ @@ -45,10 +38,10 @@ var CFG = config{ Path: "cache", UpdateInterval: 1, }, - StaticPath: "static", - UserAgent: "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: true, + Dirs: []string{"html", "css", "misc"}, + UserAgent: "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: true, } var lifetimeParsed int64 @@ -57,6 +50,7 @@ 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) @@ -88,16 +82,9 @@ func ExecuteConfig() { lifetimeParsed = duration * int64(num) } - CFG.Cache.MaxSize *= 1024 ^ 2 + 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/parsers.go b/app/parsers.go old mode 100755 new mode 100644 index 192a88d..b39e172 --- a/app/parsers.go +++ b/app/parsers.go @@ -9,11 +9,7 @@ import ( "golang.org/x/net/html" ) -func (s skunkyart) ParseComments(c devianter.Comments, daError devianter.Error) string { - if daError.RAW != nil { - return "Failed to fetch comments :(" - } - +func (s skunkyart) ParseComments(c devianter.Comments) string { var cmmts strings.Builder replied := make(map[int]string) @@ -45,9 +41,7 @@ func (s skunkyart) ParseComments(c devianter.Comments, daError devianter.Error) cmmts.WriteString(" ") if x.Parent > 0 { - cmmts.WriteString(` In reply to `) if replied[x.Parent] == "" { @@ -88,7 +82,6 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con data := &devs[i] if preview, fullview := ParseMedia(data.Media, 320), ParseMedia(data.Media); !(data.NSFW && !CFG.Nsfw) { if allowAtom && s.Atom { - s.Writer.Header().Add("Content-type", "application/atom+xml") id := strconv.Itoa(data.ID) listContent.WriteString(``) listContent.WriteString(data.Author.Username) @@ -188,7 +181,6 @@ type text struct { To int } -// переписать весь этот пиздец нахуй func ParseDescription(dscr devianter.Text) string { var parsedDescription strings.Builder TagBuilder := func(content string, tags ...string) string { diff --git a/app/router.go b/app/router.go old mode 100755 new mode 100644 index ac65c3d..f87aa50 --- a/app/router.go +++ b/app/router.go @@ -1,10 +1,8 @@ package app import ( - "io" "net/http" - url "net/url" - "skunkyart/static" + u "net/url" "strconv" "strings" ) @@ -43,61 +41,41 @@ func Router() { return } - open := func(name string) []byte { - file, err := static.Templates.Open(name) - try(err) - fileReaded, err := io.ReadAll(file) - try(err) - - return fileReaded - } - // функция, что управляет всем handle := func(w http.ResponseWriter, r *http.Request) { - path := parsepath(r.URL.Path) - Host = "http://" + r.Host if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" { - Host = "https://" + r.Host + Host = h[0] + "://" + r.Host + } else { + Host = "http://" + r.Host } - var skunky = skunkyart{Version: Release.Version} - skunky._pth = r.URL.Path - - skunky.Args = r.URL.Query() - arg := skunky.Args.Get - p, _ := strconv.Atoi(arg("p")) - - skunky.Endpoint = path[1] - skunky.API.main = &skunky + path := parsepath(r.URL.Path) + // структура с функциями + var skunky skunkyart skunky.Writer = w + skunky.Args = r.URL.Query() skunky.BasePath = CFG.URI + + arg := skunky.Args.Get skunky.QueryRaw = arg("q") - skunky.Query = url.QueryEscape(skunky.QueryRaw) - skunky.Page = p + skunky.Query = u.QueryEscape(skunky.QueryRaw) if t := arg("type"); len(t) > 0 { skunky.Type = rune(t[0]) } + p, _ := strconv.Atoi(arg("p")) + skunky.Page = p if arg("atom") == "true" { 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 + // пути + switch path[1] { + default: + skunky.ReturnHTTPError(404) case "": - skunky.ExecuteTemplate("index.htm", "html", &CFG.URI) - case "about": - skunky.Templates.About = About - skunky.ExecuteTemplate("about.htm", "html", &skunky) + skunky.ExecuteTemplate("index.htm", &CFG.URI) case "post": skunky.Deviation(path[2], path[3]) case "search": @@ -107,38 +85,20 @@ func Router() { case "group_user": skunky.GRUser() - // media case "media": switch path[2] { case "file": - if a := arg("filename"); a != "" { - skunky.SetFilename(a) - } skunky.DownloadAndSendMedia(path[3], next(path, 4)) case "emojitar": skunky.Emojitar(path[3]) } + case "about": + skunky.About() case "stylesheet": w.Header().Add("content-type", "text/css") - w.Write(open("css/skunky.css")) + wr(w, Templates["skunky.css"]) case "favicon.ico": - w.Write(open("images/logo.png")) - - // API - 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) - } - - // 404 - default: - skunky.ReturnHTTPError(404) + wr(w, Templates["logo.png"]) } } diff --git a/app/stat-freebsd.go b/app/stat-freebsd.go deleted file mode 100755 index 445eeae..0000000 --- a/app/stat-freebsd.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build freebsd -// +build freebsd - -package app - -import ( - "syscall" - "time" -) - -func statTime(stat *syscall.Stat_t) int64 { - return time.Unix(stat.Ctimespec.Unix()).UnixMilli() -} diff --git a/app/stat.go b/app/stat.go deleted file mode 100755 index a5bebcc..0000000 --- a/app/stat.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !freebsd -// +build !freebsd - -package app - -import ( - "syscall" - "time" -) - -func statTime(stat *syscall.Stat_t) int64 { - return time.Unix(stat.Ctim.Unix()).UnixMilli() -} diff --git a/app/util.go b/app/util.go old mode 100755 new mode 100644 index 6ece2e8..8c26760 --- a/app/util.go +++ b/app/util.go @@ -1,14 +1,14 @@ package app import ( - "encoding/json" + "encoding/base64" "io" "net/http" - "net/url" + u "net/url" "os" - "skunkyart/static" "strconv" "strings" + "syscall" "text/template" "time" @@ -17,8 +17,6 @@ import ( ) /* INTERNAL */ -var wr = io.WriteString - func exit(msg string, code int) { println(msg) os.Exit(code) @@ -34,99 +32,26 @@ func tryWithExitStatus(err error, code int) { } } -func restore() { - if r := recover(); r != nil { - recover() - } -} - -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)) + defer func() { + if r := recover(); r != nil { + recover() + } + }() + Templates["instances.json"] = string(Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body) }() time.Sleep(1 * time.Hour) } } // some crap for frontend -type instanceAbout struct { - Proxy bool - Nsfw bool - Instances []settings -} - -type skunkyart struct { - Writer http.ResponseWriter - _pth string - - Args url.Values - Page int - Type rune - Atom bool - - BasePath, Endpoint string - Query, QueryRaw string - - API API - Version string - - Templates struct { - About instanceAbout - - SomeList string - DDStrips string - Deviation struct { - Post devianter.Post - Related string - StringTime string - Tags string - Comments string - } - - GroupUser struct { - GR devianter.GRuser - Admins string - Group bool - CreationDate string - - About struct { - A devianter.About - - DescriptionFormatted string - Interests, Social string - Comments string - BG string - BGMeta devianter.Deviation - } - - Gallery struct { - Folders string - Pages int - List string - } - } - Search struct { - Content devianter.Search - List string - } - } -} - -func (s skunkyart) ExecuteTemplate(file, dir string, data any) { +func (s skunkyart) ExecuteTemplate(file string, data any) { var buf strings.Builder tmp := template.New(file) - tmp, err := tmp.ParseFS(static.Templates, dir+"/*") - if err != nil { - s.Writer.WriteHeader(500) - wr(s.Writer, err.Error()) - return - } + tmp, e := tmp.Parse(Templates[file]) + try(e) try(tmp.Execute(&buf, &data)) wr(s.Writer, buf.String()) } @@ -138,26 +63,13 @@ 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+1 < l && !(strs[n+1][0] == '?' || strs[n+1][0] == '&') && !(x[0] == '?' || x[0] == '&') { str.WriteString("/") } } return str.String() } -func (s skunkyart) Error(dAerr devianter.Error) { - s.Writer.WriteHeader(502) - - var msg strings.Builder - msg.WriteString(`

DeviantArt error — '`) - msg.WriteString(dAerr.Error) - msg.WriteString("'

") - - wr(s.Writer, msg.String()) -} - func (s skunkyart) ReturnHTTPError(status int) { s.Writer.WriteHeader(status) @@ -173,29 +85,21 @@ func (s skunkyart) ReturnHTTPError(status int) { wr(s.Writer, msg.String()) } -func (s skunkyart) SetFilename(name string) { - var filename strings.Builder - filename.WriteString(`filename="`) - filename.WriteString(name) - filename.WriteString(`"`) - s.Writer.Header().Add("Content-Disposition", filename.String()) -} - type Downloaded struct { Headers http.Header Status int Body []byte } -func Download(urlString string) (d Downloaded) { +func Download(url string) (d Downloaded) { cli := &http.Client{} if CFG.DownloadProxy != "" { - u, e := url.Parse(CFG.DownloadProxy) + u, e := u.Parse(CFG.DownloadProxy) try(e) cli.Transport = &http.Transport{Proxy: http.ProxyURL(u)} } - req, e := http.NewRequest("GET", urlString, nil) + req, e := http.NewRequest("GET", url, nil) try(e) req.Header.Set("User-Agent", CFG.UserAgent) @@ -211,20 +115,97 @@ func Download(urlString string) (d Downloaded) { return } +// caching +func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { + var url strings.Builder + url.WriteString("https://images-wixmp-") + url.WriteString(subdomain) + url.WriteString(".wixmp.com/") + url.WriteString(path) + url.WriteString("?token=") + url.WriteString(s.Args.Get("token")) + + if CFG.Cache.Enabled { + fname := CFG.Cache.Path + "/" + base64.StdEncoding.EncodeToString([]byte(subdomain+path)) + file, e := os.Open(fname) + + if e != nil { + dwnld := Download(url.String()) + if dwnld.Status == 200 && dwnld.Headers["Content-Type"][0][:5] == "image" { + try(os.WriteFile(fname, dwnld.Body, 0700)) + s.Writer.Write(dwnld.Body) + } + } else { + file, e := io.ReadAll(file) + try(e) + s.Writer.Write(file) + } + } else if CFG.Proxy { + dwnld := Download(url.String()) + s.Writer.Write(dwnld.Body) + } else { + s.Writer.WriteHeader(403) + s.Writer.Write([]byte("Sorry, butt proxy on this instance are disabled.")) + } +} + +func InitCacheSystem() { + c := &CFG.Cache + os.Mkdir(CFG.Cache.Path, 0700) + for { + dir, e := os.Open(c.Path) + try(e) + stat, e := dir.Stat() + try(e) + + dirnames, e := dir.Readdirnames(-1) + try(e) + for _, a := range dirnames { + a = c.Path + "/" + a + if c.Lifetime != "" { + now := time.Now().UnixMilli() + + f, _ := os.Stat(a) + stat := f.Sys().(*syscall.Stat_t) + time := time.Unix(stat.Ctim.Unix()).UnixMilli() + + if time+lifetimeParsed <= now { + try(os.RemoveAll(a)) + } + } + if c.MaxSize != 0 && stat.Size() > c.MaxSize { + try(os.RemoveAll(a)) + } + } + + dir.Close() + time.Sleep(time.Second * time.Duration(CFG.Cache.UpdateInterval)) + } +} + +func CopyTemplatesToMemory() { + for _, dirname := range CFG.Dirs { + dir, e := os.ReadDir(dirname) + tryWithExitStatus(e, 1) + + for _, x := range dir { + file, e := os.ReadFile(dirname + "/" + x.Name()) + tryWithExitStatus(e, 1) + Templates[x.Name()] = string(file) + } + } +} + /* PARSING HELPERS */ func ParseMedia(media devianter.Media, thumb ...int) string { - mediaUrl, filename := devianter.UrlFromMedia(media, thumb...) - if len(mediaUrl) != 0 && CFG.Proxy { - mediaUrl = mediaUrl[21:] - dot := strings.Index(mediaUrl, ".") - if filename == "" { - filename = "image.gif" - } - return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename) - } else if !CFG.Proxy { - return mediaUrl + url := devianter.UrlFromMedia(media, thumb...) + if len(url) != 0 && CFG.Proxy { + url = url[21:] + dot := strings.Index(url, ".") + + return UrlBuilder("media", "file", url[:dot], url[dot+11:]) } - return "" + return url } func ConvertDeviantArtUrlToSkunkyArt(url string) (output string) { @@ -274,9 +255,7 @@ func (s skunkyart) NavBase(c DeviationList) string { list.WriteString("
") prevrev := func(msg string, page int, onpage bool) { if !onpage { - list.WriteString(`
0 || (s.Type == 'f' && all) { - group.Gallery.List = s.DeviationList(content.Content.Results, true, DeviationList{ - More: content.Content.HasMore, + if folderid > 0 { + group.Gallery.List = s.DeviationList(gallery.Content.Results, true, DeviationList{ + More: gallery.Content.HasMore, }) } else { - for _, x := range content.Content.Gruser.Page.Modules { + for _, x := range gallery.Content.Gruser.Page.Modules { if l := len(x.ModuleData.Folders.Results); l != 0 { var folders strings.Builder folders.WriteString(`

# Folders


`) for _, x := range x.ModuleData.Folders.Results { - if x.FolderId != -1 && x.Size != 0 { - folders.WriteString(`
`) + folders.WriteString(`
`) - if !(x.Thumb.NSFW && !CFG.Nsfw) { - folders.WriteString(``) - } else { - folders.WriteString(`

[ NSFW ]

`) - } - folders.WriteString("
") - - folders.WriteString(``) - folders.WriteString(x.Name) - folders.WriteString(``) - - folders.WriteString("
") + if !(x.Thumb.NSFW && !CFG.Nsfw) { + folders.WriteString(``) + } else { + folders.WriteString(`

[ NSFW ]

`) } + folders.WriteString("
") + + folders.WriteString(``) + folders.WriteString(x.Name) + folders.WriteString(``) + + folders.WriteString("
") } folders.WriteString(`

# Content

`) group.Gallery.Folders = folders.String() @@ -163,7 +209,7 @@ func (s skunkyart) GRUser() { } if !s.Atom { - s.ExecuteTemplate("gruser.htm", "html", &s) + s.ExecuteTemplate("gruser.htm", &s) } } @@ -175,34 +221,19 @@ func (s skunkyart) Deviation(author, postname string) { return } - var err devianter.Error post := &s.Templates.Deviation id := id_search[len(id_search)-1] - post.Post, err = devianter.GetDeviation(id, author) - if err.RAW != nil { - s.Error(err) - return - } - - if post.Post.Deviation.NSFW && !CFG.Nsfw { - s.Writer.WriteHeader(403) - wr(s.Writer, `

NSFW content are disabled on this instance.

`) - return - } - - if post.Post.Comments.Total <= 50 { - post.Post.Comments.Cursor = "" - } + post.Post = devianter.GetDeviation(id, author) if post.Post.Deviation.TextContent.Excerpt != "" { post.Post.Description = ParseDescription(post.Post.Deviation.TextContent) } else { post.Post.Description = ParseDescription(post.Post.Deviation.Extended.DescriptionText) } - + // время публикации + post.StringTime = post.Post.Deviation.PublishedTime.UTC().String() + post.Post.IMG = ParseMedia(post.Post.Deviation.Media) for _, x := range post.Post.Deviation.Extended.RelatedContent { if len(x.Deviations) != 0 { post.Related += s.DeviationList(x.Deviations, false) @@ -221,19 +252,17 @@ func (s skunkyart) Deviation(author, postname string) { post.Tags += tag.String() } - post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1)) - post.StringTime = post.Post.Deviation.PublishedTime.UTC().String() - post.Post.IMG = ParseMedia(post.Post.Deviation.Media) + if post.Post.Comments.Total <= 50 { + post.Post.Comments.Cursor = "" + } - s.ExecuteTemplate("deviantion.htm", "html", &s) + post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1)) + + s.ExecuteTemplate("deviantion.htm", &s) } func (s skunkyart) DD() { - dd, err := devianter.GetDailyDeviations(s.Page) - if err.RAW != nil { - s.Error(err) - return - } + dd := devianter.GetDailyDeviations(s.Page) var strips strings.Builder for _, x := range dd.Strips { strips.WriteString(`

- {{template "head" .}} + + SkunkyArt + + +
- {{template "header" .}} +
+

HOME | DD

+
+ + + +
+

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

-

Room in [matrix]

+

Room in Matrix

Instance settings: -

Copyright lost+skunk, X11. SkunkyArt v{{.Version}}

+

Copyright lost+skunk, X11. SkunkyArt v1.3.1

- + \ No newline at end of file diff --git a/html/daily.htm b/html/daily.htm new file mode 100644 index 0000000..c464268 --- /dev/null +++ b/html/daily.htm @@ -0,0 +1,28 @@ + + + + SkunkyArt | Daily Deviations + + + +
+
+

HOME | DD | RSS

+
+ + + +
+
+ {{if ne .Templates.DDStrips ""}} +

# Strips

+ {{.Templates.DDStrips}} + {{end}} +

# Content

+ {{.Templates.SomeList}} +
+ \ No newline at end of file diff --git a/static/html/deviantion.htm b/html/deviantion.htm old mode 100755 new mode 100644 similarity index 72% rename from static/html/deviantion.htm rename to html/deviantion.htm index f357dd7..50710b6 --- a/static/html/deviantion.htm +++ b/html/deviantion.htm @@ -1,8 +1,24 @@ - {{template "head" . }} + + SkunkyArt | {{.Templates.Deviation.Post.Deviation.Author.Username}} - {{.Templates.Deviation.Post.Deviation.Title}} + + + +
- {{template "header" . }} +
+

HOME | DD

+
+ + + +
+
{{.Templates.Deviation.Post.Deviation.Author.Username}} — {{if (.Templates.Deviation.Post.Deviation.DD)}} diff --git a/static/html/gruser.htm b/html/gruser.htm old mode 100755 new mode 100644 similarity index 77% rename from static/html/gruser.htm rename to html/gruser.htm index 4e8e3d1..a219e36 --- a/static/html/gruser.htm +++ b/html/gruser.htm @@ -1,27 +1,26 @@ - {{template "head" . }} + + SkunkyArt | + {{if eq .Type 'a'}} + {{.Templates.GroupUser.GR.Owner.Username}} + {{else}} + gallery of {{.Templates.GroupUser.GR.Owner.Username}} + {{end}} + + + +
-

- HOME - | DD - {{if ne .Type 'f'}} - | Gallery{{else}}about">About{{end}} - | Favourites - {{else}} - | About - | Gallery - | Favourites - {{end}} - | RSS -

+

HOME | DD + | Gallery{{else}}about">About{{end}} + | RSS

+ + +
+

Daily Deviations | About | Source Code

+ +
+ \ No newline at end of file diff --git a/html/search.htm b/html/search.htm new file mode 100644 index 0000000..df1cf85 --- /dev/null +++ b/html/search.htm @@ -0,0 +1,30 @@ + + + + SkunkyArt | Search "{{.QueryRaw}}" + + + +
+
+

HOME | DD

+
+ + + +
+
+ {{if ne .Templates.Search.List ""}} + {{if ne .Templates.Search.Content.Total 0}} +

Results by request '{{.QueryRaw}}': {{.Templates.Search.Content.Total}}

+ {{end}} + {{.Templates.Search.List}} + {{else}} +

No results :(

+ {{end}} +
+ \ No newline at end of file diff --git a/instances.json b/instances.json old mode 100755 new mode 100644 index ceec4f6..cd95b78 --- a/instances.json +++ b/instances.json @@ -1,82 +1,60 @@ { "instances": [ { - "title": "lost-skunk.cc", - "country": "Finland", + "title": "skunky.ebloid.ru", + "country": "Russia", "urls": { - "ygg": "http://[201:f137:d1ac:920e:cd42:bfd1:1e83:da1d]/skunkyart", - "clearnet": "https://lost-skunk.cc/skunkyart" + "ygg": "http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art", + "clearnet": "https://skunky.ebloid.ru/art" }, "settings": { - "proxy": true, - "nsfw": false + "nsfw": false, + "proxy": false } }, { - "title": "orehus.club", - "country": "Germany", + "title": "clovius.club", + "country": "Sweden", "urls": { - "clearnet": "https://sa.orehus.club" + "clearnet": "https://skunky.clovius.club" }, "settings": { - "proxy": false, - "nsfw": true + "nsfw": true, + "proxy": true } }, { "title": "bloat.cat", - "country": "Germany", + "country": "Romania", "urls": { "clearnet": "https://skunky.bloat.cat" }, "settings": { - "proxy": true, - "nsfw": true + "nsfw": true, + "proxy": true + } + }, + { + "title": "frontendfriendly.xyz", + "country": "Finland", + "urls": { + "clearnet": "https://skunkyart.frontendfriendly.xyz" + }, + "settings": { + "nsfw": true, + "proxy": true } }, { "title": "lumaeris.com", - "country": "Germany", + "country": "US", "urls": { "clearnet": "https://skunkyart.lumaeris.com" }, "settings": { - "proxy": true, - "nsfw": true - } - }, - { - "title": "art.bloat.cat", - "country": "Germany", - "urls": { - "clearnet": "https://art.bloat.cat" - }, - "settings": { - "proxy": true, - "nsfw": true - } - }, - { - "title": "dc09.ru", - "country": "Russia", - "urls": { - "clearnet": "https://sa.dc09.ru" - }, - "settings": { - "proxy": true, - "nsfw": false - } - }, - { - "title": "opnxng.com", - "country": "Singapore", - "urls": { - "clearnet": "https://da.opnxng.com" - }, - "settings": { - "proxy": true, - "nsfw": true + "nsfw": true, + "proxy": true } } ] -} +} \ No newline at end of file diff --git a/main.go b/main.go old mode 100755 new mode 100644 index d7009ae..0224816 --- a/main.go +++ b/main.go @@ -2,20 +2,17 @@ package main import ( "skunkyart/app" - "skunkyart/static" "time" "git.macaw.me/skunky/devianter" ) func main() { - app.Release.Version = "1.3.2" - app.Release.Description = "Two API endpoints and template embedding into binary" go app.RefreshInstances() app.ExecuteCommandLineArguments() app.ExecuteConfig() - static.CopyTemplatesToMemory() + app.CopyTemplatesToMemory() go func() { for { diff --git a/static/images/logo.png b/misc/logo.png old mode 100755 new mode 100644 similarity index 100% rename from static/images/logo.png rename to misc/logo.png diff --git a/services/skunkyart.example.service b/services/skunkyart.example.service old mode 100755 new mode 100644 diff --git a/static/html/daily.htm b/static/html/daily.htm deleted file mode 100755 index b3b6044..0000000 --- a/static/html/daily.htm +++ /dev/null @@ -1,13 +0,0 @@ - - - {{template "head" . }} -
- {{template "header" . }} - {{if ne .Templates.DDStrips ""}} -

# Strips

- {{.Templates.DDStrips}} - {{end}} -

# Content

- {{.Templates.SomeList}} -
- \ No newline at end of file diff --git a/static/html/head.htm b/static/html/head.htm deleted file mode 100755 index aa36c73..0000000 --- a/static/html/head.htm +++ /dev/null @@ -1,26 +0,0 @@ -{{define "head"}} - - SkunkyArt | - {{if eq .Endpoint "search"}} - "{{.QueryRaw}}" - {{else if eq .Endpoint "post"}} - {{.Templates.Deviation.Post.Deviation.Author.Username}} — {{.Templates.Deviation.Post.Deviation.Title}} - {{else if eq .Endpoint "group_user"}} - {{if eq .Type 'g'}} - gallery of - {{else if eq .Type 'f'}} - favourites of - {{end}} - {{.Templates.GroupUser.GR.Owner.Username}} - {{else}} - {{.Endpoint}} - {{end}} - - - - - - - - -{{end}} \ No newline at end of file diff --git a/static/html/header.htm b/static/html/header.htm deleted file mode 100755 index f8537a3..0000000 --- a/static/html/header.htm +++ /dev/null @@ -1,14 +0,0 @@ -{{define "header"}} -
-

HOME | DD {{if eq .Endpoint "dd"}}| RSS{{end}}

-
- - - -
-
-{{end}} \ No newline at end of file diff --git a/static/html/index.htm b/static/html/index.htm deleted file mode 100755 index 219bb97..0000000 --- a/static/html/index.htm +++ /dev/null @@ -1,90 +0,0 @@ - - - - SkunkyArt - - - - - - -
- -
-

Daily Deviations | About

-
- - - -
-

- SkunkyArt -

-
-
- \ No newline at end of file diff --git a/static/html/search.htm b/static/html/search.htm deleted file mode 100755 index 74bbd38..0000000 --- a/static/html/search.htm +++ /dev/null @@ -1,16 +0,0 @@ - - - {{template "head" . }} -
- {{template "header" . }} - - {{if ne .Templates.Search.List ""}} - {{if ne .Templates.Search.Content.Total 0}} -

Results by request '{{.QueryRaw}}': {{.Templates.Search.Content.Total}}

- {{end}} - {{.Templates.Search.List}} - {{else}} -

No results :(

- {{end}} -
- \ No newline at end of file diff --git a/static/templates-noembed.go b/static/templates-noembed.go deleted file mode 100755 index 7a3c541..0000000 --- a/static/templates-noembed.go +++ /dev/null @@ -1,148 +0,0 @@ -//go:build !embed -// +build !embed - -package static - -import ( - "bytes" - "io/fs" - "os" - "strings" - "time" -) - -var Templates FS - -type file struct { - path string - name string - content []byte -} - -var templateNames = []string{} -var templates = make(map[string][]file) -var StaticPath string - -func CopyTemplatesToMemory() { - baseDir, err := os.ReadDir(StaticPath) - try(err) - - for _, c := range baseDir { - if c.IsDir() { - templateNames = append(templateNames, c.Name()) - - var filePath strings.Builder - filePath.WriteString(StaticPath) - filePath.WriteString("/") - filePath.WriteString(c.Name()) - - dir, err := os.ReadDir(filePath.String()) - try(err) - - filePath.WriteString("/") - for _, cd := range dir { - f, err := os.ReadFile(filePath.String() + cd.Name()) - try(err) - templates[c.Name()] = append(templates[c.Name()], file{ - content: f, - name: cd.Name(), - path: c.Name() + "/" + cd.Name(), - }) - } - } - } -} - -type FS struct{} - -func (FS) Open(name string) (fs.File, error) { - for i, l := 0, len(templateNames); i < l; i++ { - for _, x := range templates[templateNames[i]] { - if x.content != nil && name == x.path { - return &File{ - name: x.path, - content: bytes.NewBuffer(x.content), - }, nil - } - } - } - return nil, &fs.PathError{} -} - -func (FS) Glob(pattern string) ([]string, error) { - trimmed := strings.Split(pattern, "/") - var matches = []string{} - for x, s := range templates { - for i, l := 0, len(s); i < l && trimmed[0] == x; i++ { - s := s[i] - matches = append(matches, s.path) - } - } - if len(matches) != 0 { - return matches, nil - } - return nil, &fs.PathError{} -} - -func try(err error) { - if err != nil { - println(err.Error()) - os.Exit(1) - } -} - -/* сделано на основе https://github.com/psanford/memfs; требуется для корректной работы templates.ParseFS */ -type fileInfo struct { - name string -} - -func (fi fileInfo) Name() string { - return fi.name -} - -func (fi fileInfo) Size() int64 { - return 4096 -} - -func (fileInfo) Mode() fs.FileMode { - return 0 -} - -func (fileInfo) ModTime() time.Time { - return time.Time{} -} - -func (fileInfo) IsDir() bool { - return false -} - -func (fileInfo) Sys() interface{} { - return nil -} - -type File struct { - name string - content *bytes.Buffer - closed bool -} - -func (f *File) Stat() (fs.FileInfo, error) { - return fileInfo{ - name: f.name, - }, nil -} - -func (f *File) Read(b []byte) (int, error) { - if f.closed { - return 0, fs.ErrClosed - } - return f.content.Read(b) -} - -func (f *File) Close() error { - if f.closed { - return fs.ErrClosed - } - f.closed = true - return nil -} diff --git a/static/templates.go b/static/templates.go deleted file mode 100755 index 28a057a..0000000 --- a/static/templates.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build embed -// +build embed - -package static - -import "embed" - -//go:embed * -var Templates embed.FS -var Enabled bool = true - -var StaticPath string - -func CopyTemplatesToMemory() { - _ = StaticPath -}