Commit c5514c3875

c5514c3875b9b782688047d547dfd6663a5b77bb

parent: 2dfeaae772

Unsigned

lost+skunk <skunky@ebloid.ru> · 2024-08-01 19:48 UTC

v1.3

Layout: unified · split

INSTANCES.md added +7
@@ -0,0 +1,7 @@
1|Instance|Yggdrasil|I2P|Tor|NSFW|Proxifying|Modified Sources|Country|
2|:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:|
3|[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | No | No | Russia |
4|[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden |
5|[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Romania |
6|[frontendfriendly.xyz](https://skunkyart.frontendfriendly.xyz)|No|No|No| Yes | Yes | No | Finland |
7|[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | US |
\ No newline at end of file
README.md +20 −70
@@ -1,83 +1,33 @@
1<img src="https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/misc/logo.png" alt="SkunkyArt" title="SkunkyArt Logo" width="20%"/>
1<img src="https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/misc/logo.png" alt="SkunkyArt" title="SkunkyArt Logo" width="20%" loading="lazy"/>
22
33[![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)
4# Instances
5|Instance|Yggdrasil|I2P|Tor|NSFW|Proxifying|Country|
6|:------:|:-------:|:-:|:-:|:--:|:--------:|:-----:|
7|[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | No | Russia |
8|[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | Sweden |
9|[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | Romania |
10|[frontendfriendly.xyz](https://skunkyart.frontendfriendly.xyz)|No|No|No| Yes | Yes | Finland |
11|[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | US |
4
5Instances: [`INSTANCES.md`](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/INSTANCES.md)
126
137# EN 🇺🇸
148## Description
15SkunkyArt 🦨 -- alternative frontend to DeviantArt, which will work without problems even on quite old hardware, due to the lack of JavaScript.
16## Config
17The sample config is in the `config.example.json` file. To specify your own path to the config, use the CLI argument `-c` or `--config`.
18* `listen` -- the address and port on which SkunkyArt will listen
19* `base-path` -- the path to the instance. Example: `"base-path":"/art/"` -> https://skunky.ebloid.ru/art/
20* `cache` -- caching system; default is off.
21 * `path` -- the path to the cache
22 * `lifetime` -- the lifetime of the file in the cache. Units: i, h, d, w, m, y. I -- minute, all other units I think are self-explanatory.
23 * `max-size` -- maximum file size in megabytes.
24* `dirs-to-memory` -- this setting determines which directories will be copied to RAM when SkunkyArt is started. Mandatory
25* `download-proxy` -- proxy address for downloading files.
26## Examples of reverse proxies
27Nginx:
28```apache
29server {
30 listen 443 ssl;
31 server_name skunky.example.com;
32
33 location ((BASE URL)) { # if you have a separate subdomain for the frontend, insert '/' instead of '((BASE URL)))'.
34 proxy_set_header X-Forwarded-Proto $scheme;
35 proxy_set_header Host $host;
36 proxy_http_version 1.1;
37 proxy_pass http://((IP)):((PORT));
38 }
39}
40```
41Pretty much business as usual, except for the [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) header setting.
42## How do I add my instance to the list?
43To do this, you must either make a PR by adding your instance to the `instances.json` and `README.md` files, or create an Issue, or report it to the room in Matrix. I don't think it needs any description. However, be warned, this list has a couple rules:
9SkunkyArt 🦨 — alternative frontend for DevianArt, which works without JS.
10## Setup
11The sample config is in the `config.example.json` file. For custom config, use `--config` option.
12See the [`SETUP.md`](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP.md) file for more info about directives.
13## Adding instance to the list
14To 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:
44151. the Instance must not use Cloudflare.
45162. If your instance has modified source code, you need to publish it to any free platform. For example, Github and Gitlab are not.
4617## Acknowledgements
47* [Лис⚛](https://go.kde.org/matrix/#/@fox:matrix.org) -- helped me understand Go and gave me a lot of useful advice on this language.
18* [Лис⚛](https://go.kde.org/matrix/#/@fox:matrix.org) — helped me understand Go and gave me a lot of useful advice on this language.
19* [meoww](https://codeberg.org/meoww) — translated some sentences into English and wrote a service for openrc
4820
4921# RU 🇷🇺
5022## Описание
51SkunkyArt 🦨 -- альтернативный фронтенд к DeviantArt, который будет работать без проблем даже на довольно старом оборудовании, за счёт отсутствия JavaScript.
52## Конфиг
53Пример конфига находится в файле `config.example.json`. Чтобы указать свой путь до конфига, используйте CLI-аргумент `-c` или `--config`.
54* `listen` -- адрес и порт, на котором будет слушать SkunkyArt
55* `base-path` -- путь к инстансу. Пример: `"base-path": "/art/"` -> https://skunky.ebloid.ru/art/
56* `cache` -- система кеширования; по умолчанию - выкл.
57 * `path` -- путь до кеша
58 * `lifetime` -- время жизни файла в кеше. Единицы измерения: i, h, d, w, m, y. I -- минута, всё остальные единицы измерения, я считаю понятными и без объяснения.
59 * `max-size` -- максимальный размер файла в мегабайтах
60* `dirs-to-memory` -- данная настройка определяет какие каталоги будут скопированы в ОЗУ при запуске SkunkyArt. Обязательна
61* `download-proxy` -- адрес прокси для загрузки файлов
62## Примеры reverse-прокси
63Nginx:
64```apache
65server {
66 listen 443 ssl;
67 server_name skunky.example.com;
68
69 location ((BASE URL)) { # если у вас отдельный поддомен для фронтенда, вместо '((BASE URL))' вставляйте '/'
70 proxy_set_header X-Forwarded-Proto $scheme;
71 proxy_set_header Host $host;
72 proxy_http_version 1.1;
73 proxy_pass http://((IP)):((PORT));
74 }
75}
76```
77В целом, всё как обычно, за исключением настройки заголовка [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto).
78## Как добавить свой инстанс в список?
79Чтобы это сделать, вы должны либо сделать PR, добавив в файлы `instances.json` и `README.md` свой инстанс, либо создать Issue, или сообщить о нём в комнате в Matrix. Думаю, он не нуждается в описании. Однако учтите, у этого списка есть пара правил:
801. Инстанс не должен использовать Cloudflare.
23SkunkyArt 🦨 — альтернативный фронтенд к DeviantArt, который полностью работает без JS (JavaScript).
24## Настройка
25Пример конфига находится в файле `config.example.json`. Чтобы указать свой конфиг, используйте cli-аргумент `--config`.
26См. [`SETUP-RU.md`](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) для информации о настройки фронтенда.
27## Добавление инстанса в список
28Чтобы это сделать, вы должны либо сделать PR, добавив в файлы `instances.json` и `INSTANCES.md` свой инстанс (можете воспользоваться cli-аргументом `--add-instance`, который автоматически это сделает), либо создать Issue, или сообщить о нём в комнате в Matrix. Учтите, что ваш инстанс должен соблюсти следущие правила:
291. Инстанс не должен использовать Cloudflare итп.
81302. Если ваш инстанс имеет модифицированный исходный код, то вам нужно опубликовать его на любую свободную площадку. Например, Github и Gitlab таковыми не являются.
8231## Благодарности
83* [Лис⚛](https://go.kde.org/matrix/#/@fox:matrix.org) -- помог разобраться в Go и много чего полезного посоветовал по этому языку.
\ No newline at end of file
32* [Лис⚛](https://go.kde.org/matrix/#/@fox:matrix.org) — помог разобраться в Go и много чего полезного посоветовал по этому языку.
33* [meoww](https://codeberg.org/meoww) — перевела некоторые предложения на английский язык и написала сервис для openrc
\ No newline at end of file
SETUP-RU.md added +42
@@ -0,0 +1,42 @@
1[English version 🇬🇧](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP.md)
2
3# Единицы измерения
4Размер файла в кеше измеряется в мегабайтах.<br>
5Единицы времени:
6* `i` — минуты
7* `h` — часы
8* `w` — недели
9* `m` — месяца
10* `y` — года
11
12# Конфигурация
13* `listen` — IP и порт для слушанья; заполняется по такой форме: ip:port
14* `uri` — URI инстанса. Пример: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/
15* `cache` — Система кеширования; по умолчанию выключена.
16 * `enabled` — Состояние системы кеширования; требуется булёвое значение
17 * `path` — Полный путь до каталога, куда будет сохраняться кеш
18 * `lifetime` — Время жизни файла в кеше, требует целочисленное значение, дополненное суффиксом времени (см. 'Единицы времени')
19 * `max-size` — Максимальный размер файла
20 * `update-interval` — Интервал для автоматической ротации кеша
21* `dirs-to-memory` — Массив, заполнив который скопируются все файлы из указанных каталогов
22* `download-proxy` — Адрес прокси для загрузки файлов
23* `user-agent` — Строка, которая используется в качестве User-Agent'а
24
25# Настройка обратного прокси
26Если вы собираетесь хостить инстанс в Интернете, то вам следует настроить заголовок прокси [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto). В противном случае, все ссылки на вашем инстансе будут вида "http". Ниже есть информация о том, как настроить обратное проксирование:
27
28Nginx:
29```apache
30server {
31 listen 443 ssl;
32 server_name skunky.example.com;
33
34 # Если используется поддомен, то вместо ((BASE_URL)), укажите '/'.
35 location ((BASE_URL)) {
36 proxy_set_header X-Forwarded-Proto $scheme;
37 proxy_set_header Host $host;
38 proxy_http_version 1.1;
39 proxy_pass http://((IP)):((PORT));
40 }
41}
42```
\ No newline at end of file
SETUP.md added +42
@@ -0,0 +1,42 @@
1[Версия на русском языке 🇷🇺](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP-RU.md)
2
3# Units
4Maximum file size in megabytes, requires numeric value.<br>
5Time units:
6* `i` — minutes
7* `h` — hours
8* `w` — weeks
9* `m` — months
10* `y` — years
11
12# Config
13* `listen` — IP and port to listen on in the following form: ip:port
14* `uri` — Instance URI. Example: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/
15* `cache` — Caching system; default is off.
16 * `enabled` — Caching system state, requires boolean value
17 * `path` — Path to cache directory, requires absolute filesystem path
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
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
22* `download-proxy` — Proxy address for downloading files.
23* `user-agent` — String, which SkunkyArt uses as UA
24
25# Setting up reverse proxy
26Pretty much business as usual, except for the [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) header setting.
27
28Nginx example configuration:
29```apache
30server {
31 listen 443 ssl;
32 server_name skunky.example.com;
33
34 # In case of subdomain, use / instend of ((BASE_URL))
35 location ((BASE_URL)) {
36 proxy_set_header X-Forwarded-Proto $scheme;
37 proxy_set_header Host $host;
38 proxy_http_version 1.1;
39 proxy_pass http://((IP)):((PORT));
40 }
41}
42```
\ No newline at end of file
TODO.md +4 −1
@@ -1,14 +1,17 @@
11# v1.3.x
2* Написать Makefile
32* Почистить говнокод
43* **Доделать парсинг описания**
54* Избавиться от хардкода под Linux
65* ~~Реализовать стрипы в ежедневных артах~~
6* Сделать нормальное отображение ошибок
77* ~~Исправить баг с навигацией по страницам~~
88* ~~Сделать единицы в конфиге более понятными~~
99* Добавить возможность включить темплейты в бинарник
1010* ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~
11* Написать Makefile и скрипт для автоматического развёртывания инстанса
1112* **Реализовать отображение контента, отличного от картинок (видео, аудио, etc)**
13* Исправить баг с эмоджи, когда некоторые кастомные эмоции могут не отображаться
14* Добавить флаг сборки, который позволит собрать бинарник со встроенными темплейтами
1215* Улучшить систему кеширования: добавить рейтинг для удаления и копирование изображений в ОЗУ
1316# v1.4
1417* Реализовать API
app/cli.go added +166
@@ -0,0 +1,166 @@
1package app
2
3import (
4 "bufio"
5 "bytes"
6 "encoding/json"
7 "os"
8 "time"
9)
10
11func ExecuteCommandLineArguments() {
12 const helpmsg = `SkunkyArt v1.3.1 [CSS improvements for mobile and the strips on Daily Deviations]
13Usage:
14 - [-c|--config] | path to config
15 - [-a|--add-instance] | generates 'instances.json' and 'INSTANCES.md' files with ur instance
16 - [-h|--help] | returns this message
17Example:
18 ./skunkyart -c config.json
19Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3.1`
20
21 a := os.Args[1:]
22 for n, x := range a {
23 switch x {
24 case "-c", "--config":
25 if len(a) >= 2 {
26 CFG.cfg = a[n+1]
27 } else {
28 exit("Not enought arguments", 1)
29 }
30 case "-h", "--help":
31 exit(helpmsg, 0)
32 case "-a", "--add-instance":
33 addInstance()
34 }
35 }
36}
37
38type settingsUrls struct {
39 I2P string `json:"i2p,omitempty"`
40 Ygg string `json:"ygg,omitempty"`
41 Tor string `json:"tor,omitempty"`
42 Clearnet string `json:"clearnet,omitempty"`
43}
44
45type settingsParams struct {
46 Nsfw bool `json:"nsfw"`
47 Proxy bool `json:"proxy"`
48}
49
50type settings struct {
51 Title string `json:"title"`
52 Country string `json:"country"`
53 ModifiedSrc string `json:"modified-src,omitempty"`
54 Urls settingsUrls `json:"urls"`
55 Settings settingsParams `json:"settings"`
56}
57
58func addInstance() {
59 prompt := func(txt string, necessary bool) string {
60 input := bufio.NewScanner(os.Stdin)
61 for {
62 print(txt)
63 print(": ")
64 input.Scan()
65
66 if i := input.Text(); necessary && i == "" {
67 println("Please specify the", txt)
68 } else {
69 return i
70 }
71 }
72 }
73
74 var settingsVar struct {
75 Instances []settings `json:"instances"`
76 }
77 instancesJson, err := os.OpenFile("instances.test.json", os.O_CREATE|os.O_WRONLY, 0644)
78 try(err)
79 defer instancesJson.Close()
80
81 instances, err := os.OpenFile("INSTANCES.md", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
82 try(err)
83 defer instances.Close()
84
85 for {
86 if Templates["instances.json"] == "" {
87 print("\rDownloading instance list...")
88 } else {
89 println("\r\033[2KDownloaded!")
90 try(json.Unmarshal([]byte(Templates["instances.json"]), &settingsVar))
91
92 settingsVar.Instances = append(settingsVar.Instances, settings{
93 Title: prompt("Title", true),
94 Country: prompt("Country", true),
95 ModifiedSrc: prompt("Link to modified sources", false),
96 Settings: settingsParams{
97 Nsfw: CFG.Nsfw,
98 Proxy: CFG.Proxy,
99 },
100 Urls: settingsUrls{
101 Clearnet: prompt("Clearnet link", false),
102 Ygg: prompt("Yggdrasil link", false),
103 Tor: prompt("Onion link", false),
104 I2P: prompt("I2P link", false),
105 },
106 })
107
108 j, err := json.MarshalIndent(&settingsVar, "", " ")
109 try(err)
110
111 instancesJson.Write(j)
112
113 settingsVar := &settingsVar.Instances[len(settingsVar.Instances)-1]
114 var mdstr bytes.Buffer
115
116 mdstr.WriteString("\n|")
117 if settingsVar.Urls.Clearnet != "" {
118 mdstr.WriteString("[")
119 mdstr.WriteString(settingsVar.Title)
120 mdstr.WriteString("](")
121 mdstr.WriteString(settingsVar.Urls.Clearnet)
122 mdstr.WriteString(")")
123 } else {
124 mdstr.WriteString(settingsVar.Title)
125 }
126 mdstr.WriteString("|")
127
128 urls := []string{settingsVar.Urls.Ygg, settingsVar.Urls.I2P, settingsVar.Urls.Tor}
129 for i, l := 0, len(urls); i < l; i++ {
130 url := urls[i]
131 if url != "" {
132 mdstr.WriteString("[Yes](")
133 mdstr.WriteString(url)
134 mdstr.WriteString(")|")
135 } else {
136 mdstr.WriteString("No|")
137 }
138 }
139
140 settings := []bool{settingsVar.Settings.Nsfw, settingsVar.Settings.Proxy}
141 for i, l := 0, len(settings); i < l; i++ {
142 if settings[i] {
143 mdstr.WriteString("Yes|")
144 } else {
145 mdstr.WriteString("No|")
146 }
147 }
148
149 if settingsVar.ModifiedSrc != "" {
150 mdstr.WriteString("[Yes](")
151 mdstr.WriteString(settingsVar.ModifiedSrc)
152 mdstr.WriteString(")|")
153 } else {
154 mdstr.WriteString("No|")
155 }
156
157 mdstr.WriteString(settingsVar.Country)
158 mdstr.WriteString("|")
159
160 instances.Write(mdstr.Bytes())
161 break
162 }
163 time.Sleep(500 * time.Millisecond)
164 }
165 exit("Done! Now add the files 'instances.json' and 'INSTANCES.md' to the 'master' branch in the repository https://git.macaw.me/skunky/SkunkyArt", 0)
166}
app/config.go +6 −42
@@ -21,7 +21,7 @@ type cache_config struct {
2121type config struct {
2222 cfg string
2323 Listen string
24 BasePath string `json:"base-path"`
24 URI string `json:"uri"`
2525 Cache cache_config
2626 Proxy, Nsfw bool
2727 UserAgent string `json:"user-agent"`
@@ -30,15 +30,15 @@ type config struct {
3030}
3131
3232var CFG = config{
33 cfg: "config.json",
34 Listen: "127.0.0.1:3003",
35 BasePath: "/",
33 cfg: "config.json",
34 Listen: "127.0.0.1:3003",
35 URI: "/",
3636 Cache: cache_config{
37 Enabled: true,
37 Enabled: false,
3838 Path: "cache",
3939 UpdateInterval: 1,
4040 },
41 Dirs: []string{"html", "css"},
41 Dirs: []string{"html", "css", "misc"},
4242 UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
4343 Proxy: true,
4444 Nsfw: true,
@@ -47,42 +47,6 @@ var CFG = config{
4747var lifetimeParsed int64
4848
4949func ExecuteConfig() {
50 go func() {
51 for {
52 func() {
53 defer func() {
54 if r := recover(); r != nil {
55 recover()
56 }
57 }()
58 Templates["instances.json"] = string(Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body)
59 }()
60 time.Sleep(1 * time.Hour)
61 }
62 }()
63
64 const helpmsg = `SkunkyArt v1.3.1 [CSS improvements for mobile and strips on Daily Deviations]
65Usage:
66 - [-c|--config] - path to config
67 - [-h|--help] - returns this message
68Example:
69 ./skunkyart -c config.json
70Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3.1`
71
72 a := os.Args
73 for n, x := range a {
74 switch x {
75 case "-c", "--config":
76 if len(a) >= 3 {
77 CFG.cfg = a[n+1]
78 } else {
79 exit("Not enought arguments", 1)
80 }
81 case "-h", "--help":
82 exit(helpmsg, 0)
83 }
84 }
85
8650 if CFG.cfg != "" {
8751 f, err := os.ReadFile(CFG.cfg)
8852 tryWithExitStatus(err, 1)
app/router.go +3 −3
@@ -11,7 +11,7 @@ var Host string
1111
1212func Router() {
1313 parsepath := func(path string) map[int]string {
14 if l := len(CFG.BasePath); len(path) > l {
14 if l := len(CFG.URI); len(path) > l {
1515 path = path[l-1:]
1616 } else {
1717 path = "/"
@@ -54,7 +54,7 @@ func Router() {
5454 var skunky skunkyart
5555 skunky.Writer = w
5656 skunky.Args = r.URL.Query()
57 skunky.BasePath = CFG.BasePath
57 skunky.BasePath = CFG.URI
5858
5959 arg := skunky.Args.Get
6060 skunky.QueryRaw = arg("q")
@@ -75,7 +75,7 @@ func Router() {
7575 default:
7676 skunky.ReturnHTTPError(404)
7777 case "":
78 skunky.ExecuteTemplate("index.htm", &CFG.BasePath)
78 skunky.ExecuteTemplate("index.htm", &CFG.URI)
7979 case "post":
8080 skunky.Deviation(path[2], path[3])
8181 case "search":
app/util.go +18 −5
@@ -32,6 +32,20 @@ func tryWithExitStatus(err error, code int) {
3232 }
3333}
3434
35func RefreshInstances() {
36 for {
37 func() {
38 defer func() {
39 if r := recover(); r != nil {
40 recover()
41 }
42 }()
43 Templates["instances.json"] = string(Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body)
44 }()
45 time.Sleep(1 * time.Hour)
46 }
47}
48
3549// some crap for frontend
3650func (s skunkyart) ExecuteTemplate(file string, data any) {
3751 var buf strings.Builder
@@ -46,7 +60,7 @@ func UrlBuilder(strs ...string) string {
4660 var str strings.Builder
4761 l := len(strs)
4862 str.WriteString(Host)
49 str.WriteString(CFG.BasePath)
63 str.WriteString(CFG.URI)
5064 for n, x := range strs {
5165 str.WriteString(x)
5266 if n+1 < l && !(strs[n+1][0] == '?' || strs[n+1][0] == '&') && !(x[0] == '?' || x[0] == '&') {
@@ -220,11 +234,10 @@ func BuildUserPlate(name string) string {
220234
221235func GetValueOfTag(t *html.Tokenizer) string {
222236 for tt := t.Next(); ; {
223 switch tt {
224 default:
225 return ""
226 case html.TextToken:
237 if tt == html.TextToken {
227238 return string(t.Text())
239 } else {
240 return ""
228241 }
229242 }
230243}
app/wrapper.go +44 −56
@@ -31,20 +31,7 @@ type skunkyart struct {
3131 About struct {
3232 Proxy bool
3333 Nsfw bool
34 Instances []struct {
35 Title string
36 Country string
37 Urls []struct {
38 I2P string `json:"i2p"`
39 Ygg string
40 Tor string
41 Clearnet string
42 }
43 Settings struct {
44 Nsfw bool
45 Proxy bool
46 }
47 }
34 Instances []settings
4835 }
4936
5037 SomeList string
@@ -229,48 +216,49 @@ func (s skunkyart) GRUser() {
229216// посты
230217func (s skunkyart) Deviation(author, postname string) {
231218 id_search := regexp.MustCompile("[0-9]+").FindAllString(postname, -1)
232 if len(id_search) >= 1 {
233 post := &s.Templates.Deviation
219 if len(id_search) < 1 {
220 s.ReturnHTTPError(400)
221 return
222 }
234223
235 id := id_search[len(id_search)-1]
236 post.Post = devianter.GetDeviation(id, author)
224 post := &s.Templates.Deviation
237225
238 if post.Post.Deviation.TextContent.Excerpt != "" {
239 post.Post.Description = ParseDescription(post.Post.Deviation.TextContent)
240 } else {
241 post.Post.Description = ParseDescription(post.Post.Deviation.Extended.DescriptionText)
242 }
243 // время публикации
244 post.StringTime = post.Post.Deviation.PublishedTime.UTC().String()
245 post.Post.IMG = ParseMedia(post.Post.Deviation.Media)
246 for _, x := range post.Post.Deviation.Extended.RelatedContent {
247 if len(x.Deviations) != 0 {
248 post.Related += s.DeviationList(x.Deviations, false)
249 }
250 }
226 id := id_search[len(id_search)-1]
227 post.Post = devianter.GetDeviation(id, author)
251228
252 // хештэги
253 for _, x := range post.Post.Deviation.Extended.Tags {
254 var tag strings.Builder
255 tag.WriteString(` <a href="`)
256 tag.WriteString(UrlBuilder("search", "?q=", x.Name, "&type=tag"))
257 tag.WriteString(`">#`)
258 tag.WriteString(x.Name)
259 tag.WriteString("</a>")
260
261 post.Tags += tag.String()
229 if post.Post.Deviation.TextContent.Excerpt != "" {
230 post.Post.Description = ParseDescription(post.Post.Deviation.TextContent)
231 } else {
232 post.Post.Description = ParseDescription(post.Post.Deviation.Extended.DescriptionText)
233 }
234 // время публикации
235 post.StringTime = post.Post.Deviation.PublishedTime.UTC().String()
236 post.Post.IMG = ParseMedia(post.Post.Deviation.Media)
237 for _, x := range post.Post.Deviation.Extended.RelatedContent {
238 if len(x.Deviations) != 0 {
239 post.Related += s.DeviationList(x.Deviations, false)
262240 }
241 }
263242
264 if post.Post.Comments.Total <= 50 {
265 post.Post.Comments.Cursor = ""
266 }
243 // хештэги
244 for _, x := range post.Post.Deviation.Extended.Tags {
245 var tag strings.Builder
246 tag.WriteString(` <a href="`)
247 tag.WriteString(UrlBuilder("search", "?q=", x.Name, "&type=tag"))
248 tag.WriteString(`">#`)
249 tag.WriteString(x.Name)
250 tag.WriteString("</a>")
267251
268 post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1))
252 post.Tags += tag.String()
253 }
269254
270 s.ExecuteTemplate("deviantion.htm", &s)
271 } else {
272 s.ReturnHTTPError(400)
255 if post.Post.Comments.Total <= 50 {
256 post.Post.Comments.Cursor = ""
273257 }
258
259 post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1))
260
261 s.ExecuteTemplate("deviantion.htm", &s)
274262}
275263
276264func (s skunkyart) DD() {
@@ -298,7 +286,6 @@ func (s skunkyart) DD() {
298286}
299287
300288func (s skunkyart) Search() {
301 s.Atom = false
302289 var err error
303290 ss := &s.Templates.Search
304291 switch s.Type {
@@ -365,15 +352,16 @@ func (s skunkyart) Search() {
365352}
366353
367354func (s skunkyart) Emojitar(name string) {
368 if name != "" && (s.Type == 'a' || s.Type == 'e') {
369 ae, e := devianter.AEmedia(name, s.Type)
370 if e != nil {
371 s.ReturnHTTPError(404)
372 }
373 wr(s.Writer, ae)
374 } else {
355 if name == "" || !(s.Type == 'a' || s.Type == 'e') {
375356 s.ReturnHTTPError(400)
357 return
358 }
359
360 ae, e := devianter.AEmedia(name, s.Type)
361 if e != nil {
362 s.ReturnHTTPError(404)
376363 }
364 wr(s.Writer, ae)
377365}
378366
379367func (s skunkyart) About() {
config.example.json +7 −7
@@ -1,17 +1,17 @@
11{
2 "listen": "0.0.0.0:3003",
3 "base-path": "/",
2 "listen": "0:3003",
3 "uri": "/",
44 "cache": {
55 "enabled": true,
6 "path": "/home/skunk/projects/skunkyart/cache",
7 "lifetime": "1w",
6 "path": "cache",
7 "lifetime": null,
88 "max-size": 1024,
99 "update-interval": 5
1010 },
1111 "dirs-to-memory": [
12 "/home/skunk/projects/skunkyart/html",
13 "/home/skunk/projects/skunkyart/css",
14 "/home/skunk/projects/skunkyart/misc"
12 "html",
13 "css",
14 "misc"
1515 ],
1616 "download-proxy": "http://127.0.0.1:8080",
1717 "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",
go.mod +1 −3
@@ -2,9 +2,7 @@ module skunkyart
22
33go 1.22.3
44
5replace git.macaw.me/skunky/devianter v0.2.0 => /home/skunk/projects/devianter
6
75require (
8 git.macaw.me/skunky/devianter v0.2.0
6 git.macaw.me/skunky/devianter v0.2.5
97 golang.org/x/net v0.27.0
108)
go.sum +2 −2
@@ -1,4 +1,4 @@
1git.macaw.me/skunky/devianter v0.2.0 h1:2vnMPb1Dax37CbAOfmHcSoK8+1goFkWHbtbh31Ytsww=
2git.macaw.me/skunky/devianter v0.2.0/go.mod h1:ZLn527xBlnpXrUB1B8z/MhyeiWVK4nPWjyfnhWOE8Is=
1git.macaw.me/skunky/devianter v0.2.5 h1:aAc6CG/ghvG130Ob7gGUdK4IV3MSeCD5t3QIJjto1M0=
2git.macaw.me/skunky/devianter v0.2.5/go.mod h1:ZLn527xBlnpXrUB1B8z/MhyeiWVK4nPWjyfnhWOE8Is=
33golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
44golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
html/about.htm +32 −31
@@ -3,6 +3,7 @@
33 <head>
44 <title>SkunkyArt</title>
55 <link rel="stylesheet" href="{{.BasePath}}stylesheet">
6 <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico">
67 </head>
78 <main>
89 <header>
@@ -21,43 +22,43 @@
2122 SkunkyArt is an alternative frontend for deviantart.com, written in Go.
2223 </p>
2324 <h3><a href="https://go.kde.org/matrix/#/#skunkyart:ebloid.ru" target="_blank">Room in Matrix</a></h3>
24 <h2>Instance settings:</h2>
25 <b>Instance settings:</b>
2526 <ul>
2627 <li><b>NSFW</b>: <span class="about-{{.Templates.About.Nsfw}}">{{if .Templates.About.Nsfw}}YES{{else}}NO{{end}}</span></li>
2728 <li><b>Proxyfing</b>: <span class="about-{{.Templates.About.Proxy}}">{{if .Templates.About.Proxy}}YES{{else}}NO{{end}}</span></li>
2829 </ul>
29 <h2>Instances:</h2>
30 <ul>
31 {{range .Templates.About.Instances}}
32 <li><u><b>{{.Title}}</b></u>:
33 <ul>
34 <li><b>Country</b>: {{.Country}}</li>
35 <li><b>URLs</b>: </li>
36 <ul>
37 {{range .Urls}}
38 {{if ne .I2P ""}}
39 <li><b>I2P</b>: <a href="{{.I2P}}">Yes</a></li>
40 {{end}}
41 {{if ne .Ygg ""}}
42 <li><b>Ygg</b>: <a href="{{.Ygg}}">Yes</a></li>
43 {{end}}
44 {{if ne .Tor ""}}
45 <li><b>Tor</b>: <a href="{{.Tor}}">Yes</a></li>
46 {{end}}
47 {{if ne .Clearnet ""}}
48 <li><b>Clearnet</b>: <a href="{{.Clearnet}}">{{.Clearnet}}</a></li>
49 {{end}}
50 {{end}}
51 </ul>
52 <li><b>Settings</b>: </li>
30 <details>
31 <summary><b>Instances:</b></summary>
32 <ul>
33 {{range .Templates.About.Instances}}
34 <li><u><b>{{.Title}}</b></u>:
5335 <ul>
54 <li><b>NSFW</b>: <span class="about-{{.Settings.Nsfw}}">{{if .Settings.Nsfw}}YES{{else}}NO{{end}}</span></li>
55 <li><b>Proxyfing</b>: <span class="about-{{.Settings.Proxy}}">{{if .Settings.Proxy}}YES{{else}}NO{{end}}</span></li>
36 <li><b>Country</b>: {{.Country}}</li>
37 <li><b>URLs</b>: </li>
38 <ul>
39 {{if ne .Urls.I2P ""}}
40 <li><b>I2P</b>: <a href="{{.Urls.I2P}}">Yes</a></li>
41 {{end}}
42 {{if ne .Urls.Ygg ""}}
43 <li><b>Ygg</b>: <a href="{{.Urls.Ygg}}">Yes</a></li>
44 {{end}}
45 {{if ne .Urls.Tor ""}}
46 <li><b>Tor</b>: <a href="{{.Urls.Tor}}">Yes</a></li>
47 {{end}}
48 {{if ne .Urls.Clearnet ""}}
49 <li><b>Clearnet</b>: <a href="{{.Urls.Clearnet}}">{{.Urls.Clearnet}}</a></li>
50 {{end}}
51 </ul>
52 <li><b>Settings</b>: </li>
53 <ul>
54 <li><b>NSFW</b>: <span class="about-{{.Settings.Nsfw}}">{{if .Settings.Nsfw}}YES{{else}}NO{{end}}</span></li>
55 <li><b>Proxyfing</b>: <span class="about-{{.Settings.Proxy}}">{{if .Settings.Proxy}}YES{{else}}NO{{end}}</span></li>
56 </ul>
5657 </ul>
57 </ul>
58 </li>
59 {{end}}
60 </ul>
58 </li>
59 {{end}}
60 </ul>
61 </details>
6162 <p>Copyright <a href="https://go.kde.org/matrix/#/@softpigeones:ebloid.ru" target="_blank">lost+skunk</a>, X11. <a href="https://git.macaw.me/skunky/skunkyart/src/tag/v1.3.1" target="_blank">SkunkyArt v1.3.1</a></p>
6263 </main>
6364</html>
\ No newline at end of file
html/daily.htm +1
@@ -3,6 +3,7 @@
33 <head>
44 <title>SkunkyArt | Daily Deviations</title>
55 <link rel="stylesheet" href="{{.BasePath}}stylesheet">
6 <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico">
67 </head>
78 <main>
89 <header>
html/deviantion.htm +1
@@ -4,6 +4,7 @@
44 <title>SkunkyArt | {{.Templates.Deviation.Post.Deviation.Author.Username}} - {{.Templates.Deviation.Post.Deviation.Title}}</title>
55 <link rel="stylesheet" href="{{.BasePath}}stylesheet">
66 <meta name="referrer" content="no-referrer" />
7 <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico">
78 </head>
89 <main>
910 <header>
html/gruser.htm +1
@@ -9,6 +9,7 @@
99 {{end}}
1010 </title>
1111 <link rel="stylesheet" href="{{.BasePath}}stylesheet">
12 <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico">
1213 </head>
1314 <main>
1415 <header>
html/index.htm +1
@@ -3,6 +3,7 @@
33 <head>
44 <title>SkunkyArt</title>
55 <link rel="stylesheet" href="{{.}}stylesheet"/>
6 <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico">
67 </head>
78 <main>
89 <center>
html/search.htm +1
@@ -3,6 +3,7 @@
33 <head>
44 <title>SkunkyArt | Search "{{.QueryRaw}}"</title>
55 <link rel="stylesheet" href="{{.BasePath}}stylesheet">
6 <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico">
67 </head>
78 <main>
89 <header>
instances.json +10 −10
@@ -3,10 +3,10 @@
33 {
44 "title": "skunky.ebloid.ru",
55 "country": "Russia",
6 "urls": [{
6 "urls": {
77 "ygg": "http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art",
88 "clearnet": "https://skunky.ebloid.ru/art"
9 }],
9 },
1010 "settings": {
1111 "nsfw": false,
1212 "proxy": false
@@ -15,9 +15,9 @@
1515 {
1616 "title": "clovius.club",
1717 "country": "Sweden",
18 "urls": [{
18 "urls": {
1919 "clearnet": "https://skunky.clovius.club"
20 }],
20 },
2121 "settings": {
2222 "nsfw": true,
2323 "proxy": true
@@ -26,9 +26,9 @@
2626 {
2727 "title": "bloat.cat",
2828 "country": "Romania",
29 "urls": [{
29 "urls": {
3030 "clearnet": "https://skunky.bloat.cat"
31 }],
31 },
3232 "settings": {
3333 "nsfw": true,
3434 "proxy": true
@@ -37,9 +37,9 @@
3737 {
3838 "title": "frontendfriendly.xyz",
3939 "country": "Finland",
40 "urls": [{
40 "urls": {
4141 "clearnet": "https://skunkyart.frontendfriendly.xyz"
42 }],
42 },
4343 "settings": {
4444 "nsfw": true,
4545 "proxy": true
@@ -48,9 +48,9 @@
4848 {
4949 "title": "lumaeris.com",
5050 "country": "US",
51 "urls": [{
51 "urls": {
5252 "clearnet": "https://skunkyart.lumaeris.com"
53 }],
53 },
5454 "settings": {
5555 "nsfw": true,
5656 "proxy": true
main.go +14 −4
@@ -2,17 +2,27 @@ package main
22
33import (
44 "skunkyart/app"
5 "time"
56
67 "git.macaw.me/skunky/devianter"
78)
89
910func main() {
11 go app.RefreshInstances()
12
13 app.ExecuteCommandLineArguments()
1014 app.ExecuteConfig()
1115 app.CopyTemplatesToMemory()
12 err := devianter.UpdateCSRF()
13 if err != nil {
14 println(err.Error())
15 }
16
17 go func() {
18 for {
19 err := devianter.UpdateCSRF()
20 if err != nil {
21 println(err.Error())
22 }
23 time.Sleep(12 * time.Hour)
24 }
25 }()
1626
1727 app.Router()
1828}
services/skunkyart.example.openrc +15 −8
@@ -1,13 +1,20 @@
11#!/sbin/openrc-run
2
3directory=<path_to_dir_with_skunkyart>
4# command_args="-c $directory/config.json" # if SA wasn't start, try to uncomment this line
5command=$directory/skunkyart
6
7name="SkunkyArt"
8description="Privacy frontend for deviantart.com"
92supervisor=supervise-daemon
3user=skunkyart:skunkyart
4name=SkunkyArt
5directory=/opt/skunkyart
6command=$directory/bin/skunkyart
7description="Privacy-oriented frontend for DeviantArt"
8error_logger=logger
9output_logger=logger
10no_new_privs=true
11umask=0077
12
13# if you use old version openrc, uncomment function lower
14# start_pre() {
15# cd $directory
16# }
1017
1118depend() {
12 need net
19 need net localmount bootmisc
1320}
\ No newline at end of file
services/skunkyart.example.service +1 −1
@@ -1,7 +1,7 @@
11# Note: i didn't use systemd, so it can be not works :)
22
33[Unit]
4Description=Privacy frontend for deviantart.com
4Description=Privacy-oriented frontend for DeviantArt
55
66[Service]
77Directory=<path-to-dir-with-skunkyart>