From c5514c3875b9b782688047d547dfd6663a5b77bb Mon Sep 17 00:00:00 2001
From: lost+skunk
+Единицы времени:
+* `i` — минуты
+* `h` — часы
+* `w` — недели
+* `m` — месяца
+* `y` — года
+
+# Конфигурация
+* `listen` — IP и порт для слушанья; заполняется по такой форме: ip:port
+* `uri` — URI инстанса. Пример: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/
+* `cache` — Система кеширования; по умолчанию выключена.
+ * `enabled` — Состояние системы кеширования; требуется булёвое значение
+ * `path` — Полный путь до каталога, куда будет сохраняться кеш
+ * `lifetime` — Время жизни файла в кеше, требует целочисленное значение, дополненное суффиксом времени (см. 'Единицы времени')
+ * `max-size` — Максимальный размер файла
+ * `update-interval` — Интервал для автоматической ротации кеша
+* `dirs-to-memory` — Массив, заполнив который скопируются все файлы из указанных каталогов
+* `download-proxy` — Адрес прокси для загрузки файлов
+* `user-agent` — Строка, которая используется в качестве User-Agent'а
+
+# Настройка обратного прокси
+Если вы собираетесь хостить инстанс в Интернете, то вам следует настроить заголовок прокси [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto). В противном случае, все ссылки на вашем инстансе будут вида "http". Ниже есть информация о том, как настроить обратное проксирование:
+
+Nginx:
+```apache
+server {
+ listen 443 ssl;
+ server_name skunky.example.com;
+
+ # Если используется поддомен, то вместо ((BASE_URL)), укажите '/'.
+ location ((BASE_URL)) {
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Host $host;
+ proxy_http_version 1.1;
+ proxy_pass http://((IP)):((PORT));
+ }
+}
+```
\ No newline at end of file
diff --git a/SETUP.md b/SETUP.md
new file mode 100644
index 0000000..ab30165
--- /dev/null
+++ b/SETUP.md
@@ -0,0 +1,42 @@
+[Версия на русском языке 🇷🇺](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP-RU.md)
+
+# Units
+Maximum file size in megabytes, requires numeric value.
+Time units:
+* `i` — minutes
+* `h` — hours
+* `w` — weeks
+* `m` — months
+* `y` — years
+
+# Config
+* `listen` — IP and port to listen on in the following form: ip:port
+* `uri` — Instance URI. Example: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/
+* `cache` — Caching system; default is off.
+ * `enabled` — Caching system state, requires boolean value
+ * `path` — Path to cache directory, requires absolute filesystem path
+ * `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
+* `download-proxy` — Proxy address for downloading files.
+* `user-agent` — String, which SkunkyArt uses as UA
+
+# Setting up reverse proxy
+Pretty 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.
+
+Nginx example configuration:
+```apache
+server {
+ listen 443 ssl;
+ server_name skunky.example.com;
+
+ # In case of subdomain, use / instend of ((BASE_URL))
+ location ((BASE_URL)) {
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Host $host;
+ proxy_http_version 1.1;
+ proxy_pass http://((IP)):((PORT));
+ }
+}
+```
\ No newline at end of file
diff --git a/TODO.md b/TODO.md
index cdb4c95..ee50111 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,14 +1,17 @@
# v1.3.x
-* Написать Makefile
* Почистить говнокод
* **Доделать парсинг описания**
* Избавиться от хардкода под Linux
* ~~Реализовать стрипы в ежедневных артах~~
+* Сделать нормальное отображение ошибок
* ~~Исправить баг с навигацией по страницам~~
* ~~Сделать единицы в конфиге более понятными~~
* Добавить возможность включить темплейты в бинарник
* ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~
+* Написать Makefile и скрипт для автоматического развёртывания инстанса
* **Реализовать отображение контента, отличного от картинок (видео, аудио, etc)**
+* Исправить баг с эмоджи, когда некоторые кастомные эмоции могут не отображаться
+* Добавить флаг сборки, который позволит собрать бинарник со встроенными темплейтами
* Улучшить систему кеширования: добавить рейтинг для удаления и копирование изображений в ОЗУ
# v1.4
* Реализовать API
diff --git a/app/cli.go b/app/cli.go
new file mode 100644
index 0000000..4d4f849
--- /dev/null
+++ b/app/cli.go
@@ -0,0 +1,166 @@
+package app
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "os"
+ "time"
+)
+
+func ExecuteCommandLineArguments() {
+ 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/v1.3.1`
+
+ a := os.Args[1:]
+ for n, x := range a {
+ switch x {
+ case "-c", "--config":
+ if len(a) >= 2 {
+ CFG.cfg = a[n+1]
+ } else {
+ exit("Not enought arguments", 1)
+ }
+ case "-h", "--help":
+ exit(helpmsg, 0)
+ case "-a", "--add-instance":
+ addInstance()
+ }
+ }
+}
+
+type settingsUrls struct {
+ I2P string `json:"i2p,omitempty"`
+ Ygg string `json:"ygg,omitempty"`
+ Tor string `json:"tor,omitempty"`
+ Clearnet string `json:"clearnet,omitempty"`
+}
+
+type settingsParams struct {
+ Nsfw bool `json:"nsfw"`
+ Proxy bool `json:"proxy"`
+}
+
+type settings struct {
+ Title string `json:"title"`
+ Country string `json:"country"`
+ ModifiedSrc string `json:"modified-src,omitempty"`
+ Urls settingsUrls `json:"urls"`
+ Settings settingsParams `json:"settings"`
+}
+
+func addInstance() {
+ prompt := func(txt string, necessary bool) string {
+ input := bufio.NewScanner(os.Stdin)
+ for {
+ print(txt)
+ print(": ")
+ input.Scan()
+
+ if i := input.Text(); necessary && i == "" {
+ println("Please specify the", txt)
+ } else {
+ return i
+ }
+ }
+ }
+
+ var settingsVar struct {
+ Instances []settings `json:"instances"`
+ }
+ instancesJson, err := os.OpenFile("instances.test.json", os.O_CREATE|os.O_WRONLY, 0644)
+ try(err)
+ defer instancesJson.Close()
+
+ instances, err := os.OpenFile("INSTANCES.md", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ try(err)
+ defer instances.Close()
+
+ for {
+ if Templates["instances.json"] == "" {
+ print("\rDownloading instance list...")
+ } else {
+ println("\r\033[2KDownloaded!")
+ try(json.Unmarshal([]byte(Templates["instances.json"]), &settingsVar))
+
+ settingsVar.Instances = append(settingsVar.Instances, settings{
+ Title: prompt("Title", true),
+ Country: prompt("Country", true),
+ ModifiedSrc: prompt("Link to modified sources", false),
+ Settings: settingsParams{
+ Nsfw: CFG.Nsfw,
+ Proxy: CFG.Proxy,
+ },
+ Urls: settingsUrls{
+ Clearnet: prompt("Clearnet link", false),
+ Ygg: prompt("Yggdrasil link", false),
+ Tor: prompt("Onion link", false),
+ I2P: prompt("I2P link", false),
+ },
+ })
+
+ j, err := json.MarshalIndent(&settingsVar, "", " ")
+ try(err)
+
+ instancesJson.Write(j)
+
+ settingsVar := &settingsVar.Instances[len(settingsVar.Instances)-1]
+ var mdstr bytes.Buffer
+
+ mdstr.WriteString("\n|")
+ 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]
+ 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++ {
+ if settings[i] {
+ mdstr.WriteString("Yes|")
+ } else {
+ mdstr.WriteString("No|")
+ }
+ }
+
+ if settingsVar.ModifiedSrc != "" {
+ mdstr.WriteString("[Yes](")
+ mdstr.WriteString(settingsVar.ModifiedSrc)
+ mdstr.WriteString(")|")
+ } else {
+ mdstr.WriteString("No|")
+ }
+
+ mdstr.WriteString(settingsVar.Country)
+ mdstr.WriteString("|")
+
+ instances.Write(mdstr.Bytes())
+ break
+ }
+ time.Sleep(500 * time.Millisecond)
+ }
+ 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)
+}
diff --git a/app/config.go b/app/config.go
index 3fb8103..a6ac756 100644
--- a/app/config.go
+++ b/app/config.go
@@ -21,7 +21,7 @@ type cache_config struct {
type config struct {
cfg string
Listen string
- BasePath string `json:"base-path"`
+ URI string `json:"uri"`
Cache cache_config
Proxy, Nsfw bool
UserAgent string `json:"user-agent"`
@@ -30,15 +30,15 @@ type config struct {
}
var CFG = config{
- cfg: "config.json",
- Listen: "127.0.0.1:3003",
- BasePath: "/",
+ cfg: "config.json",
+ Listen: "127.0.0.1:3003",
+ URI: "/",
Cache: cache_config{
- Enabled: true,
+ Enabled: false,
Path: "cache",
UpdateInterval: 1,
},
- Dirs: []string{"html", "css"},
+ 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,
@@ -47,42 +47,6 @@ var CFG = config{
var lifetimeParsed int64
func ExecuteConfig() {
- go func() {
- for {
- func() {
- 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)
- }
- }()
-
- const helpmsg = `SkunkyArt v1.3.1 [CSS improvements for mobile and strips on Daily Deviations]
-Usage:
- - [-c|--config] - path to config
- - [-h|--help] - returns this message
-Example:
- ./skunkyart -c config.json
-Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3.1`
-
- a := os.Args
- for n, x := range a {
- switch x {
- case "-c", "--config":
- if len(a) >= 3 {
- CFG.cfg = a[n+1]
- } else {
- exit("Not enought arguments", 1)
- }
- case "-h", "--help":
- exit(helpmsg, 0)
- }
- }
-
if CFG.cfg != "" {
f, err := os.ReadFile(CFG.cfg)
tryWithExitStatus(err, 1)
diff --git a/app/router.go b/app/router.go
index 33c3189..f87aa50 100644
--- a/app/router.go
+++ b/app/router.go
@@ -11,7 +11,7 @@ var Host string
func Router() {
parsepath := func(path string) map[int]string {
- if l := len(CFG.BasePath); len(path) > l {
+ if l := len(CFG.URI); len(path) > l {
path = path[l-1:]
} else {
path = "/"
@@ -54,7 +54,7 @@ func Router() {
var skunky skunkyart
skunky.Writer = w
skunky.Args = r.URL.Query()
- skunky.BasePath = CFG.BasePath
+ skunky.BasePath = CFG.URI
arg := skunky.Args.Get
skunky.QueryRaw = arg("q")
@@ -75,7 +75,7 @@ func Router() {
default:
skunky.ReturnHTTPError(404)
case "":
- skunky.ExecuteTemplate("index.htm", &CFG.BasePath)
+ skunky.ExecuteTemplate("index.htm", &CFG.URI)
case "post":
skunky.Deviation(path[2], path[3])
case "search":
diff --git a/app/util.go b/app/util.go
index 11f81a1..8c26760 100644
--- a/app/util.go
+++ b/app/util.go
@@ -32,6 +32,20 @@ func tryWithExitStatus(err error, code int) {
}
}
+func RefreshInstances() {
+ for {
+ func() {
+ 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
func (s skunkyart) ExecuteTemplate(file string, data any) {
var buf strings.Builder
@@ -46,7 +60,7 @@ func UrlBuilder(strs ...string) string {
var str strings.Builder
l := len(strs)
str.WriteString(Host)
- str.WriteString(CFG.BasePath)
+ str.WriteString(CFG.URI)
for n, x := range strs {
str.WriteString(x)
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 {
func GetValueOfTag(t *html.Tokenizer) string {
for tt := t.Next(); ; {
- switch tt {
- default:
- return ""
- case html.TextToken:
+ if tt == html.TextToken {
return string(t.Text())
+ } else {
+ return ""
}
}
}
diff --git a/app/wrapper.go b/app/wrapper.go
index 51d76a9..250e88b 100644
--- a/app/wrapper.go
+++ b/app/wrapper.go
@@ -31,20 +31,7 @@ type skunkyart struct {
About struct {
Proxy bool
Nsfw bool
- Instances []struct {
- Title string
- Country string
- Urls []struct {
- I2P string `json:"i2p"`
- Ygg string
- Tor string
- Clearnet string
- }
- Settings struct {
- Nsfw bool
- Proxy bool
- }
- }
+ Instances []settings
}
SomeList string
@@ -229,48 +216,49 @@ func (s skunkyart) GRUser() {
// посты
func (s skunkyart) Deviation(author, postname string) {
id_search := regexp.MustCompile("[0-9]+").FindAllString(postname, -1)
- if len(id_search) >= 1 {
- post := &s.Templates.Deviation
-
- id := id_search[len(id_search)-1]
- 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)
- }
- }
-
- // хештэги
- for _, x := range post.Post.Deviation.Extended.Tags {
- var tag strings.Builder
- tag.WriteString(` #`)
- tag.WriteString(x.Name)
- tag.WriteString("")
-
- post.Tags += tag.String()
- }
-
- if post.Post.Comments.Total <= 50 {
- post.Post.Comments.Cursor = ""
- }
-
- post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1))
-
- s.ExecuteTemplate("deviantion.htm", &s)
- } else {
+ if len(id_search) < 1 {
s.ReturnHTTPError(400)
+ return
}
+
+ post := &s.Templates.Deviation
+
+ id := id_search[len(id_search)-1]
+ 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)
+ }
+ }
+
+ // хештэги
+ for _, x := range post.Post.Deviation.Extended.Tags {
+ var tag strings.Builder
+ tag.WriteString(` #`)
+ tag.WriteString(x.Name)
+ tag.WriteString("")
+
+ post.Tags += tag.String()
+ }
+
+ if post.Post.Comments.Total <= 50 {
+ post.Post.Comments.Cursor = ""
+ }
+
+ post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1))
+
+ s.ExecuteTemplate("deviantion.htm", &s)
}
func (s skunkyart) DD() {
@@ -298,7 +286,6 @@ func (s skunkyart) DD() {
}
func (s skunkyart) Search() {
- s.Atom = false
var err error
ss := &s.Templates.Search
switch s.Type {
@@ -365,15 +352,16 @@ func (s skunkyart) Search() {
}
func (s skunkyart) Emojitar(name string) {
- if name != "" && (s.Type == 'a' || s.Type == 'e') {
- ae, e := devianter.AEmedia(name, s.Type)
- if e != nil {
- s.ReturnHTTPError(404)
- }
- wr(s.Writer, ae)
- } else {
+ if name == "" || !(s.Type == 'a' || s.Type == 'e') {
s.ReturnHTTPError(400)
+ return
}
+
+ ae, e := devianter.AEmedia(name, s.Type)
+ if e != nil {
+ s.ReturnHTTPError(404)
+ }
+ wr(s.Writer, ae)
}
func (s skunkyart) About() {
diff --git a/config.example.json b/config.example.json
index b4d1ce2..0934fe7 100644
--- a/config.example.json
+++ b/config.example.json
@@ -1,17 +1,17 @@
{
- "listen": "0.0.0.0:3003",
- "base-path": "/",
+ "listen": "0:3003",
+ "uri": "/",
"cache": {
"enabled": true,
- "path": "/home/skunk/projects/skunkyart/cache",
- "lifetime": "1w",
+ "path": "cache",
+ "lifetime": null,
"max-size": 1024,
"update-interval": 5
},
"dirs-to-memory": [
- "/home/skunk/projects/skunkyart/html",
- "/home/skunk/projects/skunkyart/css",
- "/home/skunk/projects/skunkyart/misc"
+ "html",
+ "css",
+ "misc"
],
"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",
diff --git a/go.mod b/go.mod
index 7e5e771..8016fa3 100644
--- a/go.mod
+++ b/go.mod
@@ -2,9 +2,7 @@ module skunkyart
go 1.22.3
-replace git.macaw.me/skunky/devianter v0.2.0 => /home/skunk/projects/devianter
-
require (
- git.macaw.me/skunky/devianter v0.2.0
+ git.macaw.me/skunky/devianter v0.2.5
golang.org/x/net v0.27.0
)
diff --git a/go.sum b/go.sum
index 59de12e..8e79784 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,4 @@
-git.macaw.me/skunky/devianter v0.2.0 h1:2vnMPb1Dax37CbAOfmHcSoK8+1goFkWHbtbh31Ytsww=
-git.macaw.me/skunky/devianter v0.2.0/go.mod h1:ZLn527xBlnpXrUB1B8z/MhyeiWVK4nPWjyfnhWOE8Is=
+git.macaw.me/skunky/devianter v0.2.5 h1:aAc6CG/ghvG130Ob7gGUdK4IV3MSeCD5t3QIJjto1M0=
+git.macaw.me/skunky/devianter v0.2.5/go.mod h1:ZLn527xBlnpXrUB1B8z/MhyeiWVK4nPWjyfnhWOE8Is=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
diff --git a/html/about.htm b/html/about.htm
index 31f58c8..edb576d 100644
--- a/html/about.htm
+++ b/html/about.htm
@@ -3,6 +3,7 @@
Copyright lost+skunk, X11. SkunkyArt v1.3.1