diff options
| -rw-r--r-- | README.md | 8 | ||||
| -rw-r--r-- | SETUP-RU.md | 2 | ||||
| -rw-r--r-- | SETUP.md | 2 | ||||
| -rw-r--r-- | TODO.md | 18 | ||||
| -rw-r--r-- | app/cache.go | 132 | ||||
| -rw-r--r-- | app/cli.go | 63 | ||||
| -rw-r--r-- | app/config.go | 16 | ||||
| -rw-r--r-- | app/parsers.go | 8 | ||||
| -rw-r--r-- | app/router.go | 33 | ||||
| -rw-r--r-- | app/stat-freebsd.go | 13 | ||||
| -rw-r--r-- | app/stat.go | 13 | ||||
| -rw-r--r-- | app/util.go | 136 | ||||
| -rw-r--r-- | app/wrapper.go | 40 | ||||
| -rw-r--r-- | config.example.json | 6 | ||||
| -rw-r--r-- | html/daily.htm | 28 | ||||
| -rw-r--r-- | html/search.htm | 30 | ||||
| -rw-r--r-- | main.go | 3 | ||||
| -rw-r--r-- | static/css/skunky.css (renamed from css/skunky.css) | 13 | ||||
| -rw-r--r-- | static/html/about.htm (renamed from html/about.htm) | 19 | ||||
| -rw-r--r-- | static/html/daily.htm | 13 | ||||
| -rw-r--r-- | static/html/deviantion.htm (renamed from html/deviantion.htm) | 20 | ||||
| -rw-r--r-- | static/html/gruser.htm (renamed from html/gruser.htm) | 16 | ||||
| -rw-r--r-- | static/html/head.htm | 24 | ||||
| -rw-r--r-- | static/html/header.htm | 14 | ||||
| -rw-r--r-- | static/html/index.htm (renamed from html/index.htm) | 8 | ||||
| -rw-r--r-- | static/html/search.htm | 16 | ||||
| -rw-r--r-- | static/images/logo.png (renamed from misc/logo.png) | bin | 408075 -> 408075 bytes | |||
| -rw-r--r-- | static/templates-noembed.go | 148 | ||||
| -rw-r--r-- | static/templates.go | 16 |
29 files changed, 555 insertions, 303 deletions
@@ -1,15 +1,15 @@ -<img src="https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/misc/logo.png" alt="SkunkyArt" title="SkunkyArt Logo" width="20%" loading="lazy"/> +<img src="/skunky/SkunkyArt/raw/branch/master/misc/logo.png" alt="SkunkyArt" title="SkunkyArt Logo" width="20%" loading="lazy"/> [](https://go.kde.org/matrix/#/#skunkyart:ebloid.ru) -Instances: [`INSTANCES.md`](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/INSTANCES.md) +Instances: [`INSTANCES.md`](/skunky/SkunkyArt/src/branch/master/INSTANCES.md) # EN πΊπΈ ## Description SkunkyArt 𦨠β alternative frontend for DevianArt, which works without JS. ## Setup The sample config is in the `config.example.json` file. For custom config, use `--config` option. -See the [`SETUP.md`](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP.md) file for more info about directives. +See the [`SETUP.md`](/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. @@ -23,7 +23,7 @@ To do this, you must either make a PR by adding your instance to the `instances. SkunkyArt 𦨠β Π°Π»ΡΡΠ΅ΡΠ½Π°ΡΠΈΠ²Π½ΡΠΉ ΡΡΠΎΠ½ΡΠ΅Π½Π΄ ΠΊ DeviantArt, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ»Π½ΠΎΡΡΡΡ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ Π±Π΅Π· JS (JavaScript). ## ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΠΡΠΈΠΌΠ΅Ρ ΠΊΠΎΠ½ΡΠΈΠ³Π° Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π² ΡΠ°ΠΉΠ»Π΅ `config.example.json`. Π§ΡΠΎΠ±Ρ ΡΠΊΠ°Π·Π°ΡΡ ΡΠ²ΠΎΠΉ ΠΊΠΎΠ½ΡΠΈΠ³, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ cli-Π°ΡΠ³ΡΠΌΠ΅Π½Ρ `--config`. -Π‘ΠΌ. [`SETUP-RU.md`](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) Π΄Π»Ρ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΎ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ ΡΡΠΎΠ½ΡΠ΅Π½Π΄Π°. +Π‘ΠΌ. [`SETUP-RU.md`](/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) Π΄Π»Ρ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΎ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ ΡΡΠΎΠ½ΡΠ΅Π½Π΄Π°. ## ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΈΠ½ΡΡΠ°Π½ΡΠ° Π² ΡΠΏΠΈΡΠΎΠΊ Π§ΡΠΎΠ±Ρ ΡΡΠΎ ΡΠ΄Π΅Π»Π°ΡΡ, Π²Ρ Π΄ΠΎΠ»ΠΆΠ½Ρ Π»ΠΈΠ±ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ PR, Π΄ΠΎΠ±Π°Π²ΠΈΠ² Π² ΡΠ°ΠΉΠ»Ρ `instances.json` ΠΈ `INSTANCES.md` ΡΠ²ΠΎΠΉ ΠΈΠ½ΡΡΠ°Π½Ρ (ΠΌΠΎΠΆΠ΅ΡΠ΅ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ cli-Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠΎΠΌ `--add-instance`, ΠΊΠΎΡΠΎΡΡΠΉ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΡΡΠΎ ΡΠ΄Π΅Π»Π°Π΅Ρ), Π»ΠΈΠ±ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ Issue, ΠΈΠ»ΠΈ ΡΠΎΠΎΠ±ΡΠΈΡΡ ΠΎ Π½ΡΠΌ Π² ΠΊΠΎΠΌΠ½Π°ΡΠ΅ Π² Matrix. Π£ΡΡΠΈΡΠ΅, ΡΡΠΎ Π²Π°Ρ ΠΈΠ½ΡΡΠ°Π½Ρ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ±Π»ΡΡΡΠΈ ΡΠ»Π΅Π΄ΡΡΠΈΠ΅ ΠΏΡΠ°Π²ΠΈΠ»Π°: 1. ΠΠ½ΡΡΠ°Π½Ρ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Cloudflare ΠΈΡΠΏ. diff --git a/SETUP-RU.md b/SETUP-RU.md index 6ff3c23..994f193 100644 --- a/SETUP-RU.md +++ b/SETUP-RU.md @@ -1,4 +1,4 @@ -[English version π¬π§](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP.md) +[English version π¬π§](/skunky/SkunkyArt/src/branch/master/SETUP.md) # ΠΠ΄ΠΈΠ½ΠΈΡΡ ΠΈΠ·ΠΌΠ΅ΡΠ΅Π½ΠΈΡ Π Π°Π·ΠΌΠ΅Ρ ΡΠ°ΠΉΠ»Π° Π² ΠΊΠ΅ΡΠ΅ ΠΈΠ·ΠΌΠ΅ΡΡΠ΅ΡΡΡ Π² ΠΌΠ΅Π³Π°Π±Π°ΠΉΡΠ°Ρ
.<br> @@ -1,4 +1,4 @@ -[ΠΠ΅ΡΡΠΈΡ Π½Π° ΡΡΡΡΠΊΠΎΠΌ ΡΠ·ΡΠΊΠ΅ π·πΊ](https://git.macaw.me/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) +[ΠΠ΅ΡΡΠΈΡ Π½Π° ΡΡΡΡΠΊΠΎΠΌ ΡΠ·ΡΠΊΠ΅ π·πΊ](/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) # Units Maximum file size in megabytes, requires numeric value.<br> @@ -1,19 +1,21 @@ # v1.3.x * ΠΠΎΡΠΈΡΡΠΈΡΡ Π³ΠΎΠ²Π½ΠΎΠΊΠΎΠ΄ +* ~~Π‘Π΄Π΅Π»Π°ΡΡ ΠΏΠΎΡΡ ΠΏΠΎΠ΄ FreeBSD~~ βοΈ * **ΠΠΎΠ΄Π΅Π»Π°ΡΡ ΠΏΠ°ΡΡΠΈΠ½Π³ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ** -* ΠΠ·Π±Π°Π²ΠΈΡΡΡΡ ΠΎΡ Ρ
Π°ΡΠ΄ΠΊΠΎΠ΄Π° ΠΏΠΎΠ΄ Linux -* ~~Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΡΡΡΠΈΠΏΡ Π² Π΅ΠΆΠ΅Π΄Π½Π΅Π²Π½ΡΡ
Π°ΡΡΠ°Ρ
~~ +* ~~Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΡΡΡΠΈΠΏΡ Π² Π΅ΠΆΠ΅Π΄Π½Π΅Π²Π½ΡΡ
Π°ΡΡΠ°Ρ
~~ βοΈ +* ~~ΠΡΠΏΡΠ°Π²ΠΈΡΡ Π±Π°Π³ Ρ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΠ΅ΠΉ ΠΏΠΎ ΡΡΡΠ°Π½ΠΈΡΠ°ΠΌ~~ βοΈ * Π‘Π΄Π΅Π»Π°ΡΡ Π½ΠΎΡΠΌΠ°Π»ΡΠ½ΠΎΠ΅ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΡΠΈΠ±ΠΎΠΊ -* ~~ΠΡΠΏΡΠ°Π²ΠΈΡΡ Π±Π°Π³ Ρ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΠ΅ΠΉ ΠΏΠΎ ΡΡΡΠ°Π½ΠΈΡΠ°ΠΌ~~ -* ~~Π‘Π΄Π΅Π»Π°ΡΡ Π΅Π΄ΠΈΠ½ΠΈΡΡ Π² ΠΊΠΎΠ½ΡΠΈΠ³Π΅ Π±ΠΎΠ»Π΅Π΅ ΠΏΠΎΠ½ΡΡΠ½ΡΠΌΠΈ~~ -* ΠΠΎΠ±Π°Π²ΠΈΡΡ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ Π²ΠΊΠ»ΡΡΠΈΡΡ ΡΠ΅ΠΌΠΏΠ»Π΅ΠΉΡΡ Π² Π±ΠΈΠ½Π°ΡΠ½ΠΈΠΊ -* ~~Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΌΠΈΠ½ΠΈΠ°ΡΡΡΡ ΠΈ ΠΎΠΏΡΠΈΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ CSS ΠΏΠΎΠ΄ ΠΌΠ°Π»Π΅Π½ΡΠΊΠΈΠ΅ ΡΠΊΡΠ°Π½Ρ~~ +* ~~Π‘Π΄Π΅Π»Π°ΡΡ Π΅Π΄ΠΈΠ½ΠΈΡΡ Π² ΠΊΠΎΠ½ΡΠΈΠ³Π΅ Π±ΠΎΠ»Π΅Π΅ ΠΏΠΎΠ½ΡΡΠ½ΡΠΌΠΈ~~ βοΈ +* ΠΠΎΠ±Π°Π²ΠΈΡΡ ΠΏΡΠΎΡΠΌΠΎΡΡ ΠΏΠΎΠ½ΡΠ°Π²ΠΈΠ²ΡΠΈΡ
ΡΡ Π°ΡΡΠΎΠ² ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ +* ΠΠΎΠ±Π°Π²ΠΈΡΡ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ Π²ΠΊΠ»ΡΡΠΈΡΡ ΡΠ΅ΠΌΠΏΠ»Π΅ΠΉΡΡ Π² Π±ΠΈΠ½Π°ΡΠ½ΠΈΠΊ [P] +* ~~Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΌΠΈΠ½ΠΈΠ°ΡΡΡΡ ΠΈ ΠΎΠΏΡΠΈΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ CSS ΠΏΠΎΠ΄ ΠΌΠ°Π»Π΅Π½ΡΠΊΠΈΠ΅ ΡΠΊΡΠ°Π½Ρ~~ βοΈ * ΠΠ°ΠΏΠΈΡΠ°ΡΡ Makefile ΠΈ ΡΠΊΡΠΈΠΏΡ Π΄Π»Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ ΡΠ°Π·Π²ΡΡΡΡΠ²Π°Π½ΠΈΡ ΠΈΠ½ΡΡΠ°Π½ΡΠ° * **Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΠ΅ ΠΊΠΎΠ½ΡΠ΅Π½ΡΠ°, ΠΎΡΠ»ΠΈΡΠ½ΠΎΠ³ΠΎ ΠΎΡ ΠΊΠ°ΡΡΠΈΠ½ΠΎΠΊ (Π²ΠΈΠ΄Π΅ΠΎ, Π°ΡΠ΄ΠΈΠΎ, etc)** * ΠΡΠΏΡΠ°Π²ΠΈΡΡ Π±Π°Π³ Ρ ΡΠΌΠΎΠ΄ΠΆΠΈ, ΠΊΠΎΠ³Π΄Π° Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΠΊΠ°ΡΡΠΎΠΌΠ½ΡΠ΅ ΡΠΌΠΎΡΠΈΠΈ ΠΌΠΎΠ³ΡΡ Π½Π΅ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ°ΡΡΡΡ -* ΠΠΎΠ±Π°Π²ΠΈΡΡ ΡΠ»Π°Π³ ΡΠ±ΠΎΡΠΊΠΈ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ ΡΠΎΠ±ΡΠ°ΡΡ Π±ΠΈΠ½Π°ΡΠ½ΠΈΠΊ ΡΠΎ Π²ΡΡΡΠΎΠ΅Π½Π½ΡΠΌΠΈ ΡΠ΅ΠΌΠΏΠ»Π΅ΠΉΡΠ°ΠΌΠΈ -* Π£Π»ΡΡΡΠΈΡΡ ΡΠΈΡΡΠ΅ΠΌΡ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ: Π΄ΠΎΠ±Π°Π²ΠΈΡΡ ΡΠ΅ΠΉΡΠΈΠ½Π³ Π΄Π»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ ΠΈ ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΠΉ Π² ΠΠΠ£ +* ~~ΠΠΎΠ±Π°Π²ΠΈΡΡ Π°ΡΠ³ΡΠΌΠ΅Π½Ρ &filename, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ Π²ΡΠ΄Π°Π²Π°ΡΡ ΡΠ°ΠΉΠ» Ρ Π½ΠΎΡΠΌΠ°Π»ΡΠ½ΠΎ Π²ΡΠ³Π»ΡΠ΄Π΅ΡΠ΅ΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ~~ βοΈ +* ~~Π£Π»ΡΡΡΠΈΡΡ ΡΠΈΡΡΠ΅ΠΌΡ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ: Π΄ΠΎΠ±Π°Π²ΠΈΡΡ ΡΠ΅ΠΉΡΠΈΠ½Π³ Π΄Π»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ ΠΈ ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΠΉ Π² ΠΠΠ£~~ # v1.4 * Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ API * Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΡΠ΅ΠΌΡ +* ΠΠ΅ΡΠ΅ΠΉΡΠΈ Π½Π° Π°ΡΠ΅Π½Ρ Π² ΠΊΠ΅ΡΠ΅ * Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΌΠ½ΠΎΠ³ΠΎΡΠ·ΡΡΠ½ΡΠΉ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ
\ No newline at end of file diff --git a/app/cache.go b/app/cache.go new file mode 100644 index 0000000..dedba83 --- /dev/null +++ b/app/cache.go @@ -0,0 +1,132 @@ +// 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) + url.WriteString("?token=") + url.WriteString(s.Args.Get("token")) + + var response []byte + + switch { + case CFG.Cache.Enabled: + fileName := sha1.Sum([]byte(subdomain + path)) + filePath := CFG.Cache.Path + "/" + hex.EncodeToString(fileName[:]) + + mx.Lock() + if tempFS[fileName] == nil { + tempFS[fileName] = &file{} + } + f := *tempFS[fileName] + mx.Unlock() + + if f.Content != nil { + f.Score += 2 + } else { + file, err := os.Open(filePath) + if err != nil { + if dwnld := Download(url.String()); dwnld.Status == 200 && dwnld.Headers["Content-Type"][0][:5] == "image" { + f.Content = dwnld.Body + try(os.WriteFile(filePath, f.Content, 0700)) + } else { + s.ReturnHTTPError(dwnld.Status) + return + } + } else { + file, e := io.ReadAll(file) + try(e) + f.Content = file + } + + go func() { + defer restore() + for { + time.Sleep(1 * time.Minute) + + mx.Lock() + if tempFS[fileName].Score <= 0 { + delete(tempFS, fileName) + mx.Unlock() + return + } + tempFS[fileName].Score-- + mx.Unlock() + } + }() + } + + mx.Lock() + tempFS[fileName] = &f + mx.Unlock() + response = f.Content + 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 + os.Mkdir(c.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 := statTime(stat) + + 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(c.UpdateInterval)) + } +} @@ -78,16 +78,16 @@ func addInstance() { try(err) defer instancesJson.Close() - instances, err := os.OpenFile("INSTANCES.md", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + instancesFile, err := os.OpenFile("INSTANCES.md", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) try(err) - defer instances.Close() + defer instancesFile.Close() for { - if Templates["instances.json"] == "" { + if string(instances) == "" { print("\rDownloading instance list...") } else { println("\r\033[2KDownloaded!") - try(json.Unmarshal([]byte(Templates["instances.json"]), &settingsVar)) + try(json.Unmarshal(instances, &settingsVar)) settingsVar.Instances = append(settingsVar.Instances, settings{ Title: prompt("Title", true), @@ -113,51 +113,46 @@ func addInstance() { 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) + 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("|") + + mdstr.WriteString("\n|") + mdbuilder(settingsVar.Urls.Clearnet != "", settingsVar.Urls.Clearnet, settingsVar.Title) 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|") - } + mdbuilder(url != "", url, "") } 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|") - } + mdbuilder(settings[i], "", "") } - if settingsVar.ModifiedSrc != "" { - mdstr.WriteString("[Yes](") - mdstr.WriteString(settingsVar.ModifiedSrc) - mdstr.WriteString(")|") - } else { - mdstr.WriteString("No|") - } + mdbuilder(settingsVar.ModifiedSrc != "", settingsVar.ModifiedSrc, "") mdstr.WriteString(settingsVar.Country) mdstr.WriteString("|") - instances.Write(mdstr.Bytes()) + instancesFile.Write(mdstr.Bytes()) break } time.Sleep(500 * time.Millisecond) diff --git a/app/config.go b/app/config.go index a6ac756..7d159e7 100644 --- a/app/config.go +++ b/app/config.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "regexp" + "skunkyart/static" "strconv" "time" @@ -24,9 +25,9 @@ type config struct { URI string `json:"uri"` Cache cache_config Proxy, Nsfw bool - UserAgent string `json:"user-agent"` - DownloadProxy string `json:"download-proxy"` - Dirs []string `json:"dirs-to-memory"` + UserAgent string `json:"user-agent"` + DownloadProxy string `json:"download-proxy"` + StaticPath string `json:"static-path"` } var CFG = config{ @@ -38,10 +39,10 @@ var CFG = config{ Path: "cache", UpdateInterval: 1, }, - 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, + 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, } var lifetimeParsed int64 @@ -56,6 +57,7 @@ func ExecuteConfig() { exit("Incompatible settings detected: cannot use caching media content without proxy", 1) } + static.StaticPath = CFG.StaticPath if CFG.Cache.Enabled { if CFG.Cache.Lifetime != "" { var duration int64 diff --git a/app/parsers.go b/app/parsers.go index b39e172..f93c18d 100644 --- a/app/parsers.go +++ b/app/parsers.go @@ -41,7 +41,9 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { cmmts.WriteString("</b></a> ") if x.Parent > 0 { - cmmts.WriteString(` In reply to <a href="#`) + cmmts.WriteString(` In reply to <a href="`) + cmmts.WriteString(Path) + cmmts.WriteString("#") cmmts.WriteString(strconv.Itoa(x.Parent)) cmmts.WriteString(`">`) if replied[x.Parent] == "" { @@ -80,7 +82,7 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con for i, l := 0, len(devs); i < l; i++ { data := &devs[i] - if preview, fullview := ParseMedia(data.Media, 320), ParseMedia(data.Media); !(data.NSFW && !CFG.Nsfw) { + if preview, fullview := ParseMedia(data.Media, data.Title, 320), ParseMedia(data.Media, data.Title); !(data.NSFW && !CFG.Nsfw) { if allowAtom && s.Atom { id := strconv.Itoa(data.ID) listContent.WriteString(`<entry><author><name>`) @@ -284,7 +286,7 @@ func ParseDescription(dscr devianter.Text) string { parsedDescription.WriteString(`<a href="`) parsedDescription.WriteString(ConvertDeviantArtUrlToSkunkyArt(d.Url)) parsedDescription.WriteString(`"><img width="50%" src="`) - parsedDescription.WriteString(ParseMedia(d.Media)) + parsedDescription.WriteString(ParseMedia(d.Media, d.Title)) parsedDescription.WriteString(`" title="`) parsedDescription.WriteString(d.Author.Username) parsedDescription.WriteString(" - ") diff --git a/app/router.go b/app/router.go index f87aa50..35550f8 100644 --- a/app/router.go +++ b/app/router.go @@ -1,13 +1,15 @@ package app import ( + "io" "net/http" u "net/url" + "skunkyart/static" "strconv" "strings" ) -var Host string +var Host, Path string func Router() { parsepath := func(path string) map[int]string { @@ -41,6 +43,15 @@ 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) { if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" { @@ -49,7 +60,8 @@ func Router() { Host = "http://" + r.Host } - path := parsepath(r.URL.Path) + Path = r.URL.Path + path := parsepath(Path) // ΡΡΡΡΠΊΡΡΡΠ° Ρ ΡΡΠ½ΠΊΡΠΈΡΠΌΠΈ var skunky skunkyart skunky.Writer = w @@ -70,12 +82,14 @@ func Router() { skunky.Atom = true } + skunky.Endpoint = path[1] + // ΠΏΡΡΠΈ - switch path[1] { + switch skunky.Endpoint { default: skunky.ReturnHTTPError(404) case "": - skunky.ExecuteTemplate("index.htm", &CFG.URI) + w.Write(open("html/index.htm")) case "post": skunky.Deviation(path[2], path[3]) case "search": @@ -88,6 +102,13 @@ func Router() { case "media": switch path[2] { case "file": + if a := arg("filename"); a != "" { + var filename strings.Builder + filename.WriteString(`filename="`) + filename.WriteString(a) + filename.WriteString(`"`) + w.Header().Add("Content-Disposition", filename.String()) + } skunky.DownloadAndSendMedia(path[3], next(path, 4)) case "emojitar": skunky.Emojitar(path[3]) @@ -96,9 +117,9 @@ func Router() { skunky.About() case "stylesheet": w.Header().Add("content-type", "text/css") - wr(w, Templates["skunky.css"]) + w.Write(open("css/skunky.css")) case "favicon.ico": - wr(w, Templates["logo.png"]) + w.Write(open("images/logo.png")) } } diff --git a/app/stat-freebsd.go b/app/stat-freebsd.go new file mode 100644 index 0000000..445eeae --- /dev/null +++ b/app/stat-freebsd.go @@ -0,0 +1,13 @@ +//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 new file mode 100644 index 0000000..a5bebcc --- /dev/null +++ b/app/stat.go @@ -0,0 +1,13 @@ +//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 index 8c26760..a5b978d 100644 --- a/app/util.go +++ b/app/util.go @@ -1,14 +1,13 @@ package app import ( - "encoding/base64" "io" "net/http" - u "net/url" + "net/url" "os" + "skunkyart/static" "strconv" "strings" - "syscall" "text/template" "time" @@ -32,26 +31,34 @@ func tryWithExitStatus(err error, code int) { } } +func restore() { + if r := recover(); r != nil { + recover() + } +} + +var instances []byte + 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) + defer restore() + instances = 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) { +func (s skunkyart) ExecuteTemplate(file, dir string, data any) { var buf strings.Builder tmp := template.New(file) - tmp, e := tmp.Parse(Templates[file]) - try(e) + tmp, err := tmp.ParseFS(static.Templates, dir+"/*") + if err != nil { + s.Writer.WriteHeader(500) + wr(s.Writer, err.Error()) + return + } try(tmp.Execute(&buf, &data)) wr(s.Writer, buf.String()) } @@ -91,15 +98,15 @@ type Downloaded struct { Body []byte } -func Download(url string) (d Downloaded) { +func Download(urlString string) (d Downloaded) { cli := &http.Client{} if CFG.DownloadProxy != "" { - u, e := u.Parse(CFG.DownloadProxy) + u, e := url.Parse(CFG.DownloadProxy) try(e) cli.Transport = &http.Transport{Proxy: http.ProxyURL(u)} } - req, e := http.NewRequest("GET", url, nil) + req, e := http.NewRequest("GET", urlString, nil) try(e) req.Header.Set("User-Agent", CFG.UserAgent) @@ -115,97 +122,16 @@ func Download(url 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 { - url := devianter.UrlFromMedia(media, thumb...) - if len(url) != 0 && CFG.Proxy { - url = url[21:] - dot := strings.Index(url, ".") +func ParseMedia(media devianter.Media, filename string, thumb ...int) string { + mediaUrl := devianter.UrlFromMedia(media, thumb...) + if len(mediaUrl) != 0 && CFG.Proxy { + mediaUrl = mediaUrl[21:] + dot := strings.Index(mediaUrl, ".") - return UrlBuilder("media", "file", url[:dot], url[dot+11:]) + return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", url.QueryEscape(filename)) } - return url + return mediaUrl } func ConvertDeviantArtUrlToSkunkyArt(url string) (output string) { @@ -255,7 +181,9 @@ func (s skunkyart) NavBase(c DeviationList) string { list.WriteString("<br>") prevrev := func(msg string, page int, onpage bool) { if !onpage { - list.WriteString(`<a href="?p=`) + list.WriteString(`<a href="`) + list.WriteString(Path) + list.WriteString(`?p=`) list.WriteString(strconv.Itoa(page)) if s.Type != 0 { list.WriteString("&type=") diff --git a/app/wrapper.go b/app/wrapper.go index 250e88b..5e8b6c1 100644 --- a/app/wrapper.go +++ b/app/wrapper.go @@ -15,17 +15,17 @@ import ( ) var wr = io.WriteString -var Templates = make(map[string]string) type skunkyart struct { Writer http.ResponseWriter - Args url.Values - BasePath string - Type rune - Query, QueryRaw string - Page int - Atom bool + Args url.Values + Page int + Type rune + Atom bool + + BasePath, Endpoint string + Query, QueryRaw string Templates struct { About struct { @@ -136,7 +136,7 @@ func (s skunkyart) GRUser() { case "cover_deviation": group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation group.About.BGMeta.Url = ConvertDeviantArtUrlToSkunkyArt(group.About.BGMeta.Url) - group.About.BG = ParseMedia(group.About.BGMeta.Media) + group.About.BG = ParseMedia(group.About.BGMeta.Media, group.About.BGMeta.Title) case "group_admins": var htm strings.Builder for _, z := range x.ModuleData.GroupAdmins.Results { @@ -171,7 +171,7 @@ func (s skunkyart) GRUser() { folders.WriteString(`<a href="`) folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url)) folders.WriteString(`"><img loading="lazy" src="`) - folders.WriteString(ParseMedia(x.Thumb.Media)) + folders.WriteString(ParseMedia(x.Thumb.Media, x.Thumb.Title)) folders.WriteString(`" title="`) folders.WriteString(x.Thumb.Title) folders.WriteString(`"></a>`) @@ -180,7 +180,7 @@ func (s skunkyart) GRUser() { } folders.WriteString("<br>") - folders.WriteString(`<a href="?folder=`) + folders.WriteString(`<a href="group_user?folder=`) folders.WriteString(strconv.Itoa(x.FolderId)) folders.WriteString("&q=") folders.WriteString(s.Query) @@ -209,7 +209,7 @@ func (s skunkyart) GRUser() { } if !s.Atom { - s.ExecuteTemplate("gruser.htm", &s) + s.ExecuteTemplate("gruser.htm", "html", &s) } } @@ -233,7 +233,7 @@ func (s skunkyart) Deviation(author, postname string) { } // Π²ΡΠ΅ΠΌΡ ΠΏΡΠ±Π»ΠΈΠΊΠ°ΡΠΈΠΈ post.StringTime = post.Post.Deviation.PublishedTime.UTC().String() - post.Post.IMG = ParseMedia(post.Post.Deviation.Media) + post.Post.IMG = ParseMedia(post.Post.Deviation.Media, post.Post.Deviation.Title) for _, x := range post.Post.Deviation.Extended.RelatedContent { if len(x.Deviations) != 0 { post.Related += s.DeviationList(x.Deviations, false) @@ -258,7 +258,7 @@ func (s skunkyart) Deviation(author, postname string) { post.Comments = s.ParseComments(devianter.GetComments(id, post.Post.Comments.Cursor, s.Page, 1)) - s.ExecuteTemplate("deviantion.htm", &s) + s.ExecuteTemplate("deviantion.htm", "html", &s) } func (s skunkyart) DD() { @@ -281,11 +281,16 @@ func (s skunkyart) DD() { More: dd.HasMore, }) if !s.Atom { - s.ExecuteTemplate("daily.htm", &s) + s.ExecuteTemplate("daily.htm", "html", &s) } } func (s skunkyart) Search() { + if s.Query == "" { + s.ReturnHTTPError(400) + return + } + var err error ss := &s.Templates.Search switch s.Type { @@ -338,6 +343,7 @@ func (s skunkyart) Search() { } default: s.ReturnHTTPError(400) + return } try(err) @@ -348,7 +354,7 @@ func (s skunkyart) Search() { }) } - s.ExecuteTemplate("search.htm", &s) + s.ExecuteTemplate("search.htm", "html", &s) } func (s skunkyart) Emojitar(name string) { @@ -367,6 +373,6 @@ func (s skunkyart) Emojitar(name string) { func (s skunkyart) About() { s.Templates.About.Nsfw = CFG.Nsfw s.Templates.About.Proxy = CFG.Proxy - try(json.Unmarshal([]byte(Templates["instances.json"]), &s.Templates.About)) - s.ExecuteTemplate("about.htm", &s) + try(json.Unmarshal(instances, &s.Templates.About)) + s.ExecuteTemplate("about.htm", "html", &s) } diff --git a/config.example.json b/config.example.json index 0934fe7..73eb32d 100644 --- a/config.example.json +++ b/config.example.json @@ -8,11 +8,7 @@ "max-size": 1024, "update-interval": 5 }, - "dirs-to-memory": [ - "html", - "css", - "misc" - ], + "static-path": "static", "download-proxy": "http://127.0.0.1:8080", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", "proxy": true, diff --git a/html/daily.htm b/html/daily.htm deleted file mode 100644 index c464268..0000000 --- a/html/daily.htm +++ /dev/null @@ -1,28 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>SkunkyArt | Daily Deviations</title> - <link rel="stylesheet" href="{{.BasePath}}stylesheet"> - <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> - </head> - <main> - <header> - <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a> | <a href="?atom=true">RSS</a></h1> - <form method="get" action="{{.BasePath}}search"> - <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> - <select name="type"> - <option value="all">All</option> - <option value="tag">Tag</option> - <option value="r">Groups</option> - </select> - <button type="submit">Search!</button> - </form> - </header> - {{if ne .Templates.DDStrips ""}} - <h2 id="strips"><a href="#strips">#</a> Strips</h2> - {{.Templates.DDStrips}} - {{end}} - <h2 id="content"><a href="#content">#</a> Content</h2> - {{.Templates.SomeList}} - </main> -</html>
\ No newline at end of file diff --git a/html/search.htm b/html/search.htm deleted file mode 100644 index df1cf85..0000000 --- a/html/search.htm +++ /dev/null @@ -1,30 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>SkunkyArt | Search "{{.QueryRaw}}"</title> - <link rel="stylesheet" href="{{.BasePath}}stylesheet"> - <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> - </head> - <main> - <header> - <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1> - <form method="get" action="search"> - <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> - <select name="type"> - <option value="all">All</option> - <option value="tag">Tag</option> - <option value="r">Groups</option> - </select> - <button type="submit">Search!</button> - </form> - </header> - {{if ne .Templates.Search.List ""}} - {{if ne .Templates.Search.Content.Total 0}} - <h1>Results by request '{{.QueryRaw}}': {{.Templates.Search.Content.Total}}</h1> - {{end}} - {{.Templates.Search.List}} - {{else}} - <p>No results :(</p> - {{end}} - </main> -</html>
\ No newline at end of file @@ -2,6 +2,7 @@ package main import ( "skunkyart/app" + "skunkyart/static" "time" "git.macaw.me/skunky/devianter" @@ -12,7 +13,7 @@ func main() { app.ExecuteCommandLineArguments() app.ExecuteConfig() - app.CopyTemplatesToMemory() + static.CopyTemplatesToMemory() go func() { for { diff --git a/css/skunky.css b/static/css/skunky.css index 2126968..bdc894c 100644 --- a/css/skunky.css +++ b/static/css/skunky.css @@ -22,12 +22,19 @@ header form { header { display: flex; } +form { + font-size: 0; + border: solid #164e3e 1px; + max-width: fit-content; +} form input, button, select { background-color: #134134; padding: 5px; color: whitesmoke; - border: 0px; - border-radius: 1px; + border: 0; +} +input:focus { + outline: none; } /* BLOCKS */ @@ -146,6 +153,8 @@ form input, button, select { header form { font-size: 60%; + max-width: unset; + border: 0; } header, center { text-align: center; diff --git a/html/about.htm b/static/html/about.htm index edb576d..d2278d6 100644 --- a/html/about.htm +++ b/static/html/about.htm @@ -1,23 +1,8 @@ <!DOCTYPE html> <html> - <head> - <title>SkunkyArt</title> - <link rel="stylesheet" href="{{.BasePath}}stylesheet"> - <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> - </head> + {{template "head" .}} <main> - <header> - <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1> - <form method="get" action="{{.BasePath}}search"> - <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> - <select name="type"> - <option value="all">All</option> - <option value="tag">Tag</option> - <option value="r">Groups</option> - </select> - <button type="submit">Search!</button> - </form> - </header> + {{template "header" .}} <p> SkunkyArt is an alternative frontend for deviantart.com, written in Go. </p> diff --git a/static/html/daily.htm b/static/html/daily.htm new file mode 100644 index 0000000..b3b6044 --- /dev/null +++ b/static/html/daily.htm @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> + {{template "head" . }} + <main> + {{template "header" . }} + {{if ne .Templates.DDStrips ""}} + <h2 id="strips"><a href="#strips">#</a> Strips</h2> + {{.Templates.DDStrips}} + {{end}} + <h2 id="content"><a href="#content">#</a> Content</h2> + {{.Templates.SomeList}} + </main> +</html>
\ No newline at end of file diff --git a/html/deviantion.htm b/static/html/deviantion.htm index 50710b6..f357dd7 100644 --- a/html/deviantion.htm +++ b/static/html/deviantion.htm @@ -1,24 +1,8 @@ <!DOCTYPE html> <html> - <head> - <title>SkunkyArt | {{.Templates.Deviation.Post.Deviation.Author.Username}} - {{.Templates.Deviation.Post.Deviation.Title}}</title> - <link rel="stylesheet" href="{{.BasePath}}stylesheet"> - <meta name="referrer" content="no-referrer" /> - <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> - </head> + {{template "head" . }} <main> - <header> - <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1> - <form method="get" action="{{.BasePath}}search"> - <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> - <select name="type"> - <option value="all">All</option> - <option value="tag">Tag</option> - <option value="r">Groups</option> - </select> - <button type="submit">Search!</button> - </form> - </header> + {{template "header" . }} <figure> <img src="{{.BasePath}}media/emojitar/{{.Templates.Deviation.Post.Deviation.Author.Username}}?type=a" width="30px"> <span><strong><a href="{{.BasePath}}group_user?type=about&q={{.Templates.Deviation.Post.Deviation.Author.Username}}">{{.Templates.Deviation.Post.Deviation.Author.Username}}</a></strong> β {{if (.Templates.Deviation.Post.Deviation.DD)}} diff --git a/html/gruser.htm b/static/html/gruser.htm index a219e36..c8227ef 100644 --- a/html/gruser.htm +++ b/static/html/gruser.htm @@ -1,21 +1,11 @@ <!DOCTYPE html> <html> - <head> - <title>SkunkyArt | - {{if eq .Type 'a'}} - {{.Templates.GroupUser.GR.Owner.Username}} - {{else}} - gallery of {{.Templates.GroupUser.GR.Owner.Username}} - {{end}} - </title> - <link rel="stylesheet" href="{{.BasePath}}stylesheet"> - <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> - </head> + {{template "head" . }} <main> <header> <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a> - | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a> - | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a></h1> + | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a> + | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a></h1> <form method="get" action="{{.BasePath}}search"> <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}"> diff --git a/static/html/head.htm b/static/html/head.htm new file mode 100644 index 0000000..f7f6b91 --- /dev/null +++ b/static/html/head.htm @@ -0,0 +1,24 @@ +{{define "head"}} +<head> + <title>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 .Type 'a'}} + {{if .Templates.GroupUser.GR.Owner.Username}} + {{.Templates.GroupUser.GR.Owner.Username}} + {{else}} + gallery of {{.Templates.GroupUser.GR.Owner.Username}} + {{end}} + {{else}} + {{.Endpoint}} + {{end}} + </title> + + <base href="{{.BasePath}}"> + <meta name="referrer" content="no-referrer" /> + <link rel="stylesheet" href="{{.BasePath}}stylesheet"> + <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> +</head> +{{end}}
\ No newline at end of file diff --git a/static/html/header.htm b/static/html/header.htm new file mode 100644 index 0000000..f8537a3 --- /dev/null +++ b/static/html/header.htm @@ -0,0 +1,14 @@ +{{define "header"}} +<header> + <h1><a href="">HOME</a> | <a href="dd">DD</a> {{if eq .Endpoint "dd"}}| <a href="{{.Endpoint}}?atom=true">RSS</a>{{end}}</h1> + <form method="get" action="search"> + <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false" value="{{.QueryRaw}}"> + <select name="type"> + <option value="all">All</option> + <option value="tag">Tag</option> + <option value="r">Groups</option> + </select> + <button type="submit">Search!</button> + </form> +</header> +{{end}}
\ No newline at end of file diff --git a/html/index.htm b/static/html/index.htm index 08feba5..78f1d8a 100644 --- a/html/index.htm +++ b/static/html/index.htm @@ -2,12 +2,12 @@ <html> <head> <title>SkunkyArt</title> - <link rel="stylesheet" href="{{.}}stylesheet"/> - <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico"> + <link rel="stylesheet" href="stylesheet"/> + <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <main> <center> - <form method="get" action="{{.}}search"> + <form method="get" action="search"> <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> <select name="type"> <option value="all">All</option> @@ -16,7 +16,7 @@ </select> <button type="submit">Search!</button> </form> - <h1><a href="{{.}}dd">Daily Deviations</a> | <a href="{{.}}about">About</a> | <a href="https://git.macaw.me/skunky/SkunkyArt" target="_blank">Source Code</a></h1> + <h1><a href="dd">Daily Deviations</a> | <a href="about">About</a> | <a href="https://git.macaw.me/skunky/SkunkyArt" target="_blank">Source Code</a></h1> </center> </main> </html>
\ No newline at end of file diff --git a/static/html/search.htm b/static/html/search.htm new file mode 100644 index 0000000..74bbd38 --- /dev/null +++ b/static/html/search.htm @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + {{template "head" . }} + <main> + {{template "header" . }} + + {{if ne .Templates.Search.List ""}} + {{if ne .Templates.Search.Content.Total 0}} + <h1>Results by request '{{.QueryRaw}}': {{.Templates.Search.Content.Total}}</h1> + {{end}} + {{.Templates.Search.List}} + {{else}} + <p>No results :(</p> + {{end}} + </main> +</html>
\ No newline at end of file diff --git a/misc/logo.png b/static/images/logo.png Binary files differindex e895de7..e895de7 100644 --- a/misc/logo.png +++ b/static/images/logo.png diff --git a/static/templates-noembed.go b/static/templates-noembed.go new file mode 100644 index 0000000..7a3c541 --- /dev/null +++ b/static/templates-noembed.go @@ -0,0 +1,148 @@ +//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 new file mode 100644 index 0000000..28a057a --- /dev/null +++ b/static/templates.go @@ -0,0 +1,16 @@ +//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 +} |
