From 1537da9b16dfda1ecb2730b5d1de3b9005397618 Mon Sep 17 00:00:00 2001 From: lost+skunk Date: Tue, 13 Aug 2024 15:59:52 +0300 Subject: темплейты в бинарнике и улучшенная система кеша MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- SETUP-RU.md | 2 +- SETUP.md | 2 +- TODO.md | 18 ++-- app/cache.go | 132 ++++++++++++++++++++++++++++ app/cli.go | 63 +++++++------- app/config.go | 16 ++-- app/parsers.go | 8 +- app/router.go | 33 +++++-- app/stat-freebsd.go | 13 +++ app/stat.go | 13 +++ app/util.go | 136 +++++++---------------------- app/wrapper.go | 40 +++++---- config.example.json | 6 +- css/skunky.css | 195 ------------------------------------------ html/about.htm | 64 -------------- html/daily.htm | 28 ------ html/deviantion.htm | 60 ------------- html/gruser.htm | 91 -------------------- html/index.htm | 22 ----- html/search.htm | 30 ------- main.go | 3 +- misc/logo.png | Bin 408075 -> 0 bytes static/css/skunky.css | 204 ++++++++++++++++++++++++++++++++++++++++++++ static/html/about.htm | 49 +++++++++++ static/html/daily.htm | 13 +++ static/html/deviantion.htm | 44 ++++++++++ static/html/gruser.htm | 81 ++++++++++++++++++ static/html/head.htm | 24 ++++++ static/html/header.htm | 14 +++ static/html/index.htm | 22 +++++ static/html/search.htm | 16 ++++ static/images/logo.png | Bin 0 -> 408075 bytes static/templates-noembed.go | 148 ++++++++++++++++++++++++++++++++ static/templates.go | 16 ++++ 35 files changed, 933 insertions(+), 681 deletions(-) create mode 100644 app/cache.go create mode 100644 app/stat-freebsd.go create mode 100644 app/stat.go delete mode 100644 css/skunky.css delete mode 100644 html/about.htm delete mode 100644 html/daily.htm delete mode 100644 html/deviantion.htm delete mode 100644 html/gruser.htm delete mode 100644 html/index.htm delete mode 100644 html/search.htm delete mode 100644 misc/logo.png create mode 100644 static/css/skunky.css create mode 100644 static/html/about.htm create mode 100644 static/html/daily.htm create mode 100644 static/html/deviantion.htm create mode 100644 static/html/gruser.htm create mode 100644 static/html/head.htm create mode 100644 static/html/header.htm create mode 100644 static/html/index.htm create mode 100644 static/html/search.htm create mode 100644 static/images/logo.png create mode 100644 static/templates-noembed.go create mode 100644 static/templates.go diff --git a/README.md b/README.md index 6eaa831..735cc17 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -SkunkyArt +SkunkyArt [![Matrix room](https://img.shields.io/badge/matrix-000000?style=for-the-badge&logo=Matrix&logoColor=white)](https://go.kde.org/matrix/#/#skunkyart:ebloid.ru) -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) # Единицы измерения Размер файла в кеше измеряется в мегабайтах.
diff --git a/SETUP.md b/SETUP.md index ab30165..169e7a0 100644 --- a/SETUP.md +++ b/SETUP.md @@ -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.
diff --git a/TODO.md b/TODO.md index ee50111..84678b0 100644 --- a/TODO.md +++ b/TODO.md @@ -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)) + } +} diff --git a/app/cli.go b/app/cli.go index 2247223..261e103 100644 --- a/app/cli.go +++ b/app/cli.go @@ -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(" ") if x.Parent > 0 { - cmmts.WriteString(` In reply to `) 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(``) @@ -284,7 +286,7 @@ func ParseDescription(dscr devianter.Text) string { parsedDescription.WriteString(` 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("
") prevrev := func(msg string, page int, onpage bool) { if !onpage { - list.WriteString(`
`) @@ -180,7 +180,7 @@ func (s skunkyart) GRUser() { } folders.WriteString("
") - folders.WriteString(` - - - SkunkyArt - - - -
-
-

HOME | DD

-
- - - -
-
-

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

-

Room in Matrix

- Instance settings: - -
- Instances: -
    - {{range .Templates.About.Instances}} -
  • {{.Title}}: -
      -
    • Country: {{.Country}}
    • -
    • URLs:
    • -
        - {{if ne .Urls.I2P ""}} -
      • I2P: Yes
      • - {{end}} - {{if ne .Urls.Ygg ""}} -
      • Ygg: Yes
      • - {{end}} - {{if ne .Urls.Tor ""}} -
      • Tor: Yes
      • - {{end}} - {{if ne .Urls.Clearnet ""}} -
      • Clearnet: {{.Urls.Clearnet}}
      • - {{end}} -
      -
    • Settings:
    • -
        -
      • NSFW: {{if .Settings.Nsfw}}YES{{else}}NO{{end}}
      • -
      • Proxyfing: {{if .Settings.Proxy}}YES{{else}}NO{{end}}
      • -
      -
    -
  • - {{end}} -
-
-

Copyright lost+skunk, X11. SkunkyArt v1.3.1

-
- \ No newline at end of file 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 @@ - - - - SkunkyArt | Daily Deviations - - - -
-
-

HOME | DD | RSS

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

# Strips

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

# Content

- {{.Templates.SomeList}} -
- \ No newline at end of file diff --git a/html/deviantion.htm b/html/deviantion.htm deleted file mode 100644 index 50710b6..0000000 --- a/html/deviantion.htm +++ /dev/null @@ -1,60 +0,0 @@ - - - - SkunkyArt | {{.Templates.Deviation.Post.Deviation.Author.Username}} - {{.Templates.Deviation.Post.Deviation.Title}} - - - - -
-
-

HOME | DD

-
- - - -
-
-
- - {{.Templates.Deviation.Post.Deviation.Author.Username}} — {{if (.Templates.Deviation.Post.Deviation.DD)}} - {{.Templates.Deviation.Post.Deviation.Title}} - {{else}}{{.Templates.Deviation.Post.Deviation.Title}}{{end}} - {{if (ne .Templates.Deviation.Post.Deviation.License "none")}}{{.Templates.Deviation.Post.Deviation.License}}{{end}} {{if (.Templates.Deviation.Post.Deviation.AI)}}[🤖]{{end}} - {{if (.Templates.Deviation.Post.Deviation.NSFW)}}[NSFW]{{end}} - -
- {{if (ne .Templates.Deviation.Post.IMG "")}} - -
- {{end}} - {{if (ne .Templates.Deviation.Tags "")}} - {{.Templates.Deviation.Tags}}
- {{end}} - Published: {{.Templates.Deviation.StringTime}}; Views: {{.Templates.Deviation.Post.Deviation.Stats.Views}}; Favourites: {{.Templates.Deviation.Post.Deviation.Stats.Favourites}}; Downloads: {{.Templates.Deviation.Post.Deviation.Stats.Downloads}} -
Redirect to original -
- {{if (ne .Templates.Deviation.Post.Description "")}} -
-
- Description - {{.Templates.Deviation.Post.Description}} -
-
- {{end}} - {{if ne .Templates.Deviation.Related ""}} -
- Related content - {{.Templates.Deviation.Related}} -
- {{end}} - {{if (ne .Templates.Deviation.Comments "")}} - {{.Templates.Deviation.Comments}} - {{end}} -
-
- \ No newline at end of file diff --git a/html/gruser.htm b/html/gruser.htm deleted file mode 100644 index a219e36..0000000 --- a/html/gruser.htm +++ /dev/null @@ -1,91 +0,0 @@ - - - - SkunkyArt | - {{if eq .Type 'a'}} - {{.Templates.GroupUser.GR.Owner.Username}} - {{else}} - gallery of {{.Templates.GroupUser.GR.Owner.Username}} - {{end}} - - - - -
-
-

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

-
- - - - -

| {{.Templates.GroupUser.GR.Owner.Username}}

-
- {{if eq .Type 'a'}} - {{if ne .Templates.GroupUser.About.BG ""}} - - {{end}} - - {{.Templates.GroupUser.GR.Owner.Username}} - {{if (eq .Templates.GroupUser.About.A.Gender "male")}} - ♂️ - {{end}} - {{if (eq .Templates.GroupUser.About.A.Gender "female")}} - ♀️ - {{end}} - [{{.Templates.GroupUser.GR.Gruser.ID}}] - [{{.Templates.GroupUser.CreationDate}}] - {{if ne .Templates.GroupUser.GR.Extra.Tag ""}} - "{{.Templates.GroupUser.GR.Extra.Tag}}"{{end}} {{if ne .Templates.GroupUser.About.A.Country ""}} - ({{.Templates.GroupUser.About.A.Country}}) - {{end}} - - {{if .Templates.GroupUser.Group}} -

# Statistics

-

Watchers: {{.Templates.GroupUser.GR.Extra.Stats.Watchers}}; Pageviews: {{.Templates.GroupUser.GR.Extra.Stats.Pageviews}} - {{else}} -

# Statistics

-

Favourites: {{.Templates.GroupUser.GR.Extra.Stats.Favourites}}; Deviations: {{.Templates.GroupUser.GR.Extra.Stats.Deviations}}; Watchers: {{.Templates.GroupUser.GR.Extra.Stats.Watchers}} -

Watching: {{.Templates.GroupUser.GR.Extra.Stats.Watching}}; Pageviews: {{.Templates.GroupUser.GR.Extra.Stats.Pageviews}}; Comments Made: {{.Templates.GroupUser.GR.Extra.Stats.CommentsMade}}; Friends: {{.Templates.GroupUser.GR.Extra.Stats.Friends}}

- {{end}} - - {{if ne .Templates.GroupUser.Admins ""}} -

# Group admins

-
- {{.Templates.GroupUser.Admins}} -
- {{end}} - - {{if ne .Templates.GroupUser.About.Interests ""}} -

# Interests

- {{.Templates.GroupUser.About.Interests}} - {{end}} - - {{if ne .Templates.GroupUser.About.Social ""}} -

# Social Links

- {{.Templates.GroupUser.About.Social}} - {{end}} - - {{if ne .Templates.GroupUser.About.DescriptionFormatted ""}} -

# About me

- {{.Templates.GroupUser.About.DescriptionFormatted}} - {{end}} - {{if ne .Templates.GroupUser.About.Comments ""}} -
-

# Comments

- {{.Templates.GroupUser.About.Comments}} - {{end}} - {{else}} - {{.Templates.GroupUser.Gallery.Folders}} - {{.Templates.GroupUser.Gallery.List}} - {{end}} -
- diff --git a/html/index.htm b/html/index.htm deleted file mode 100644 index 08feba5..0000000 --- a/html/index.htm +++ /dev/null @@ -1,22 +0,0 @@ - - - - SkunkyArt - - - -
-
-
- - - -
-

Daily Deviations | About | Source Code

-
-
- \ 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 @@ - - - - SkunkyArt | Search "{{.QueryRaw}}" - - - -
-
-

HOME | DD

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

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

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

No results :(

- {{end}} -
- \ No newline at end of file diff --git a/main.go b/main.go index 0224816..190d79b 100644 --- a/main.go +++ b/main.go @@ -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/misc/logo.png b/misc/logo.png deleted file mode 100644 index e895de7..0000000 Binary files a/misc/logo.png and /dev/null differ diff --git a/static/css/skunky.css b/static/css/skunky.css new file mode 100644 index 0000000..bdc894c --- /dev/null +++ b/static/css/skunky.css @@ -0,0 +1,204 @@ +/* TAGS */ +html { + font-family: Ubuntu; + background-color:black; + color: rgb(234, 216, 216); +} +a { + text-decoration: none; + color: cadetblue; +} +a:hover { + color: #d0ff00; + transition: 400ms; +} +header h1 { + padding-right: 0.2%; + padding-left: 0.2%; +} +header form { + align-self: center; +} +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: 0; +} +input:focus { + outline: none; +} + +/* BLOCKS */ +.content { + align-items: center; + border-radius: 3px; + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.block { + max-width: 20%; + height: 0%; + padding: 4px; + border-radius: 2px; + border: 3px solid #091f19; + word-break: break-all; + background-color: #091f19; + margin-left: 5px; + margin-top: 5px; + text-align: center; +} +.block:hover { + border: 3px solid #4d27d6; + transition: 400ms; +} +.block img, .plates .user-plate img { + width: 100%; +} +.block p { + word-break: break-all; +} + +/* MESSAGES */ +.msg { + background-color: #091f19; + color: whitesmoke; + width: fit-content; + max-width: 90%; + padding: 4px; + border-radius: 2px; + margin-top: 6px; + text-wrap: pretty; + transition: 350ms; +} +.msg:hover { + background-color: #134134; +} +.reply { + border-radius: 0px 2px 2px 0px; + border-left: #258268 solid; + margin-left: 40px; +} + +/* FOLDER & PLATES */ +.folders, .plates { + display: flex; + flex-wrap: wrap; +} +.folder-item { + background-color: #060820; + border: 3px solid #060820; + width: 10% +} +.admins .user-plate { + width: 5%; + background-color: #011522; +} +.plates .user-plate { + margin-left: 1%; + margin-bottom: 1%; + background-color: #091f19; + padding: 3px; + word-break: break-all; + text-align: center; + max-width: 15%; +} + +/* USER/GROUP BACKGROUNDS */ +.ubg { + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.ubg img { + width: 20%; +} + +/* COLORS */ +.nsfw, .about-false { + color: red; +} +.about-true { + color: green; +} +.author { + color: seagreen; +} +.dd { + color: rgb(160, 0, 147); +} + +/* SCREEN OPTIMISATIONS */ +@media (orientation: portrait) { + * { + font-size: 120% + } + + ul { + font-size: 80% + } + + center form { + font-size: 60% + } + + header form { + font-size: 60%; + max-width: unset; + border: 0; + } + header, center { + text-align: center; + display: block; + clear: both; + font-size: 200%; + } + + .content { + margin: auto; + display: inherit; + scale: 100%; + } + .block { + margin-top: 10%; + max-width: 200%; + } + .folder-item { + width: 25% + } + .folders { + display: flexbox; + justify-content: center + } + figure img { + width: 10% + } + figure a img { + width: 100% + } + .msg { + font-size: 60%; + max-width: 80% + } +} + +@media (max-width: 1462px) and (orientation: landscape) { + .block { + max-width: 30%; + } +} + +@media (min-width: 788px) and (max-width: 884px) { + .block { + max-width: 35%; + } +} \ No newline at end of file diff --git a/static/html/about.htm b/static/html/about.htm new file mode 100644 index 0000000..d2278d6 --- /dev/null +++ b/static/html/about.htm @@ -0,0 +1,49 @@ + + + {{template "head" .}} +
+ {{template "header" .}} +

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

+

Room in Matrix

+ Instance settings: + +
+ Instances: +
    + {{range .Templates.About.Instances}} +
  • {{.Title}}: +
      +
    • Country: {{.Country}}
    • +
    • URLs:
    • +
        + {{if ne .Urls.I2P ""}} +
      • I2P: Yes
      • + {{end}} + {{if ne .Urls.Ygg ""}} +
      • Ygg: Yes
      • + {{end}} + {{if ne .Urls.Tor ""}} +
      • Tor: Yes
      • + {{end}} + {{if ne .Urls.Clearnet ""}} +
      • Clearnet: {{.Urls.Clearnet}}
      • + {{end}} +
      +
    • Settings:
    • +
        +
      • NSFW: {{if .Settings.Nsfw}}YES{{else}}NO{{end}}
      • +
      • Proxyfing: {{if .Settings.Proxy}}YES{{else}}NO{{end}}
      • +
      +
    +
  • + {{end}} +
+
+

Copyright lost+skunk, X11. SkunkyArt v1.3.1

+
+ \ No newline at end of file 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 @@ + + + {{template "head" . }} +
+ {{template "header" . }} + {{if ne .Templates.DDStrips ""}} +

# Strips

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

# Content

+ {{.Templates.SomeList}} +
+ \ No newline at end of file diff --git a/static/html/deviantion.htm b/static/html/deviantion.htm new file mode 100644 index 0000000..f357dd7 --- /dev/null +++ b/static/html/deviantion.htm @@ -0,0 +1,44 @@ + + + {{template "head" . }} +
+ {{template "header" . }} +
+ + {{.Templates.Deviation.Post.Deviation.Author.Username}} — {{if (.Templates.Deviation.Post.Deviation.DD)}} + {{.Templates.Deviation.Post.Deviation.Title}} + {{else}}{{.Templates.Deviation.Post.Deviation.Title}}{{end}} + {{if (ne .Templates.Deviation.Post.Deviation.License "none")}}{{.Templates.Deviation.Post.Deviation.License}}{{end}} {{if (.Templates.Deviation.Post.Deviation.AI)}}[🤖]{{end}} + {{if (.Templates.Deviation.Post.Deviation.NSFW)}}[NSFW]{{end}} + +
+ {{if (ne .Templates.Deviation.Post.IMG "")}} + +
+ {{end}} + {{if (ne .Templates.Deviation.Tags "")}} + {{.Templates.Deviation.Tags}}
+ {{end}} + Published: {{.Templates.Deviation.StringTime}}; Views: {{.Templates.Deviation.Post.Deviation.Stats.Views}}; Favourites: {{.Templates.Deviation.Post.Deviation.Stats.Favourites}}; Downloads: {{.Templates.Deviation.Post.Deviation.Stats.Downloads}} +
Redirect to original +
+ {{if (ne .Templates.Deviation.Post.Description "")}} +
+
+ Description + {{.Templates.Deviation.Post.Description}} +
+
+ {{end}} + {{if ne .Templates.Deviation.Related ""}} +
+ Related content + {{.Templates.Deviation.Related}} +
+ {{end}} + {{if (ne .Templates.Deviation.Comments "")}} + {{.Templates.Deviation.Comments}} + {{end}} +
+
+ \ No newline at end of file diff --git a/static/html/gruser.htm b/static/html/gruser.htm new file mode 100644 index 0000000..c8227ef --- /dev/null +++ b/static/html/gruser.htm @@ -0,0 +1,81 @@ + + + {{template "head" . }} +
+
+

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

+
+ + + + +

| {{.Templates.GroupUser.GR.Owner.Username}}

+
+ {{if eq .Type 'a'}} + {{if ne .Templates.GroupUser.About.BG ""}} + + {{end}} + + {{.Templates.GroupUser.GR.Owner.Username}} + {{if (eq .Templates.GroupUser.About.A.Gender "male")}} + ♂️ + {{end}} + {{if (eq .Templates.GroupUser.About.A.Gender "female")}} + ♀️ + {{end}} + [{{.Templates.GroupUser.GR.Gruser.ID}}] + [{{.Templates.GroupUser.CreationDate}}] + {{if ne .Templates.GroupUser.GR.Extra.Tag ""}} + "{{.Templates.GroupUser.GR.Extra.Tag}}"{{end}} {{if ne .Templates.GroupUser.About.A.Country ""}} + ({{.Templates.GroupUser.About.A.Country}}) + {{end}} + + {{if .Templates.GroupUser.Group}} +

# Statistics

+

Watchers: {{.Templates.GroupUser.GR.Extra.Stats.Watchers}}; Pageviews: {{.Templates.GroupUser.GR.Extra.Stats.Pageviews}} + {{else}} +

# Statistics

+

Favourites: {{.Templates.GroupUser.GR.Extra.Stats.Favourites}}; Deviations: {{.Templates.GroupUser.GR.Extra.Stats.Deviations}}; Watchers: {{.Templates.GroupUser.GR.Extra.Stats.Watchers}} +

Watching: {{.Templates.GroupUser.GR.Extra.Stats.Watching}}; Pageviews: {{.Templates.GroupUser.GR.Extra.Stats.Pageviews}}; Comments Made: {{.Templates.GroupUser.GR.Extra.Stats.CommentsMade}}; Friends: {{.Templates.GroupUser.GR.Extra.Stats.Friends}}

+ {{end}} + + {{if ne .Templates.GroupUser.Admins ""}} +

# Group admins

+
+ {{.Templates.GroupUser.Admins}} +
+ {{end}} + + {{if ne .Templates.GroupUser.About.Interests ""}} +

# Interests

+ {{.Templates.GroupUser.About.Interests}} + {{end}} + + {{if ne .Templates.GroupUser.About.Social ""}} +

# Social Links

+ {{.Templates.GroupUser.About.Social}} + {{end}} + + {{if ne .Templates.GroupUser.About.DescriptionFormatted ""}} +

# About me

+ {{.Templates.GroupUser.About.DescriptionFormatted}} + {{end}} + {{if ne .Templates.GroupUser.About.Comments ""}} +
+

# Comments

+ {{.Templates.GroupUser.About.Comments}} + {{end}} + {{else}} + {{.Templates.GroupUser.Gallery.Folders}} + {{.Templates.GroupUser.Gallery.List}} + {{end}} +
+ 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"}} + + 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}} + + + + + + + +{{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"}} +
+

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

+
+ + + +
+
+{{end}} \ No newline at end of file diff --git a/static/html/index.htm b/static/html/index.htm new file mode 100644 index 0000000..78f1d8a --- /dev/null +++ b/static/html/index.htm @@ -0,0 +1,22 @@ + + + + SkunkyArt + + + +
+
+
+ + + +
+

Daily Deviations | About | Source Code

+
+
+ \ 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 @@ + + + {{template "head" . }} +
+ {{template "header" . }} + + {{if ne .Templates.Search.List ""}} + {{if ne .Templates.Search.Content.Total 0}} +

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

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

No results :(

+ {{end}} +
+ \ No newline at end of file diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100644 index 0000000..e895de7 Binary files /dev/null and b/static/images/logo.png differ 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 +} -- cgit v1.2.3