Commit db53a8bd90
Unsigned
Layout: unified · split
TODO.md +4 −1
| @@ -1,11 +1,13 @@ | |||
| 1 | # v1.3.x | 1 | # v1.3.x |
| 2 | * Почистить говнокод | 2 | * Почистить говнокод |
| 3 | * Добавить фильтры поиска | ||
| 3 | * ~~Сделать порт под FreeBSD~~ ✔️ | 4 | * ~~Сделать порт под FreeBSD~~ ✔️ |
| 4 | * **Доделать парсинг описания** | 5 | * **Доделать парсинг описания** |
| 5 | * ~~Реализовать стрипы в ежедневных артах~~ ✔️ | 6 | * ~~Реализовать стрипы в ежедневных артах~~ ✔️ |
| 6 | * ~~Исправить баг с навигацией по страницам~~ ✔️ | 7 | * ~~Исправить баг с навигацией по страницам~~ ✔️ |
| 7 | * Сделать нормальное отображение ошибок | 8 | * Сделать нормальное отображение ошибок |
| 8 | * ~~Сделать единицы в конфиге более понятными~~ ✔️ | 9 | * ~~Сделать единицы в конфиге более понятными~~ ✔️ |
| 10 | * Добавить чекер инстанса на работоспособность | ||
| 9 | * ~~Добавить просмотр понравившихся артов пользователю~~ ✔️ | 11 | * ~~Добавить просмотр понравившихся артов пользователю~~ ✔️ |
| 10 | * Добавить возможность включить темплейты в бинарник [P] | 12 | * Добавить возможность включить темплейты в бинарник [P] |
| 11 | * ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~ ✔️ | 13 | * ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~ ✔️ |
| @@ -13,9 +15,10 @@ | |||
| 13 | * **Реализовать отображение контента, отличного от картинок (видео, аудио, etc)** | 15 | * **Реализовать отображение контента, отличного от картинок (видео, аудио, etc)** |
| 14 | * Исправить баг с эмоджи, когда некоторые кастомные эмоции могут не отображаться | 16 | * Исправить баг с эмоджи, когда некоторые кастомные эмоции могут не отображаться |
| 15 | * ~~Добавить аргумент &filename, который будет выдавать файл с нормально выглядещем именем~~ ✔️ | 17 | * ~~Добавить аргумент &filename, который будет выдавать файл с нормально выглядещем именем~~ ✔️ |
| 16 | * ~~Улучшить систему кеширования: добавить рейтинг для удаления и копирование изображений в ОЗУ~~ BUG: почему-то всё переодически встаёт раком | 18 | * ~~Улучшить систему кеширования: добавить рейтинг для удаления и копирование изображений в ОЗУ~~ ✔️ |
| 17 | # v1.4 | 19 | # v1.4 |
| 18 | * Реализовать API | 20 | * Реализовать API |
| 19 | * Реализовать темы | 21 | * Реализовать темы |
| 20 | * Перейти на арены в кеше | 22 | * Перейти на арены в кеше |
| 21 | * Реализовать многоязычный интерфейс | 23 | * Реализовать многоязычный интерфейс |
| 24 | |||
app/api.go +50 −11
| @@ -1,6 +1,7 @@ | |||
| 1 | package app | 1 | package app |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/json" | ||
| 4 | "math/rand" | 5 | "math/rand" |
| 5 | "strings" | 6 | "strings" |
| 6 | 7 | ||
| @@ -8,27 +9,65 @@ import ( | |||
| 8 | ) | 9 | ) |
| 9 | 10 | ||
| 10 | type API struct { | 11 | type API struct { |
| 11 | skunkyartLink *skunkyart | 12 | main *skunkyart |
| 13 | } | ||
| 14 | |||
| 15 | type info struct { | ||
| 16 | Version string `json:"version"` | ||
| 17 | Settings settingsParams `json:"settings"` | ||
| 18 | } | ||
| 19 | |||
| 20 | func (a API) Info() { | ||
| 21 | json, err := json.Marshal(info{ | ||
| 22 | Version: a.main.Version, | ||
| 23 | Settings: settingsParams{ | ||
| 24 | Nsfw: CFG.Nsfw, | ||
| 25 | Proxy: CFG.Proxy, | ||
| 26 | }, | ||
| 27 | }) | ||
| 28 | try(err) | ||
| 29 | a.main.Writer.Write(json) | ||
| 30 | return | ||
| 31 | } | ||
| 32 | |||
| 33 | func (a API) Error(description string, status int) { | ||
| 34 | a.main.Writer.WriteHeader(status) | ||
| 35 | var response strings.Builder | ||
| 36 | response.WriteString(`{"error":"`) | ||
| 37 | response.WriteString(description) | ||
| 38 | response.WriteString(`"}`) | ||
| 39 | wr(a.main.Writer, response.String()) | ||
| 12 | } | 40 | } |
| 13 | 41 | ||
| 14 | func (a API) sendMedia(d *devianter.Deviation) { | 42 | func (a API) sendMedia(d *devianter.Deviation) { |
| 15 | mediaUrl, name := devianter.UrlFromMedia(d.Media) | 43 | mediaUrl, name := devianter.UrlFromMedia(d.Media) |
| 16 | 44 | a.main.SetFilename(name) | |
| 17 | var filename strings.Builder | ||
| 18 | filename.WriteString(`filename="`) | ||
| 19 | filename.WriteString(name) | ||
| 20 | filename.WriteString(`"`) | ||
| 21 | a.skunkyartLink.Writer.Header().Add("Content-Disposition", filename.String()) | ||
| 22 | 45 | ||
| 23 | if len(mediaUrl) != 0 { | 46 | if len(mediaUrl) != 0 { |
| 24 | mediaUrl = mediaUrl[21:] | 47 | mediaUrl = mediaUrl[21:] |
| 25 | dot := strings.Index(mediaUrl, ".") | 48 | dot := strings.Index(mediaUrl, ".") |
| 26 | a.skunkyartLink.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:]) | 49 | a.main.Writer.Header().Del("Content-Type") |
| 50 | a.main.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:]) | ||
| 27 | } | 51 | } |
| 28 | } | 52 | } |
| 29 | 53 | ||
| 54 | // TODO: сделать фильтры | ||
| 30 | func (a API) Random() { | 55 | func (a API) Random() { |
| 31 | s, err := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a') | 56 | for attempt := 1;; { |
| 32 | try(err) | 57 | if attempt > 3 { |
| 33 | a.sendMedia(&s.Results[rand.Intn(len(s.Results))]) | 58 | a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500) |
| 59 | } | ||
| 60 | |||
| 61 | s, err := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a') | ||
| 62 | try(err) | ||
| 63 | deviation := &s.Results[rand.Intn(len(s.Results))] | ||
| 64 | |||
| 65 | if deviation.NSFW && !CFG.Nsfw { | ||
| 66 | attempt++ | ||
| 67 | continue | ||
| 68 | } | ||
| 69 | |||
| 70 | a.sendMedia(deviation) | ||
| 71 | return | ||
| 72 | } | ||
| 34 | } | 73 | } |
app/cli.go +8 −3
| @@ -4,19 +4,20 @@ import ( | |||
| 4 | "bufio" | 4 | "bufio" |
| 5 | "bytes" | 5 | "bytes" |
| 6 | "encoding/json" | 6 | "encoding/json" |
| 7 | "html/template" | ||
| 7 | "os" | 8 | "os" |
| 8 | "time" | 9 | "time" |
| 9 | ) | 10 | ) |
| 10 | 11 | ||
| 11 | func ExecuteCommandLineArguments() { | 12 | func ExecuteCommandLineArguments() { |
| 12 | const helpmsg = `SkunkyArt v1.3.1 [CSS improvements for mobile and the strips on Daily Deviations] | 13 | var helpmsg = `SkunkyArt v{{.Version}} [{{.Description}}] |
| 13 | Usage: | 14 | Usage: |
| 14 | - [-c|--config] | path to config | 15 | - [-c|--config] | path to config |
| 15 | - [-a|--add-instance] | generates 'instances.json' and 'INSTANCES.md' files with ur instance | 16 | - [-a|--add-instance] | generates 'instances.json' and 'INSTANCES.md' files with ur instance |
| 16 | - [-h|--help] | returns this message | 17 | - [-h|--help] | returns this message |
| 17 | Example: | 18 | Example: |
| 18 | ./skunkyart -c config.json | 19 | ./skunkyart -c config.json |
| 19 | Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3.1` | 20 | Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v{{.Version}}` |
| 20 | 21 | ||
| 21 | a := os.Args[1:] | 22 | a := os.Args[1:] |
| 22 | for n, x := range a { | 23 | for n, x := range a { |
| @@ -28,7 +29,11 @@ Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3.1` | |||
| 28 | exit("Not enought arguments", 1) | 29 | exit("Not enought arguments", 1) |
| 29 | } | 30 | } |
| 30 | case "-h", "--help": | 31 | case "-h", "--help": |
| 31 | exit(helpmsg, 0) | 32 | var buf bytes.Buffer |
| 33 | t := template.New("help") | ||
| 34 | t.Parse(helpmsg) | ||
| 35 | t.Execute(&buf, &Release) | ||
| 36 | exit(buf.String(), 0) | ||
| 32 | case "-a", "--add-instance": | 37 | case "-a", "--add-instance": |
| 33 | addInstance() | 38 | addInstance() |
| 34 | } | 39 | } |
app/config.go +5
| @@ -11,6 +11,11 @@ import ( | |||
| 11 | "git.macaw.me/skunky/devianter" | 11 | "git.macaw.me/skunky/devianter" |
| 12 | ) | 12 | ) |
| 13 | 13 | ||
| 14 | var Release struct { | ||
| 15 | Version string | ||
| 16 | Description string | ||
| 17 | } | ||
| 18 | |||
| 14 | type cache_config struct { | 19 | type cache_config struct { |
| 15 | Enabled bool | 20 | Enabled bool |
| 16 | Path string | 21 | Path string |
app/router.go +29 −27
| @@ -13,10 +13,9 @@ var Host, Path string | |||
| 13 | 13 | ||
| 14 | func Router() { | 14 | func Router() { |
| 15 | parsepath := func(path string) map[int]string { | 15 | parsepath := func(path string) map[int]string { |
| 16 | path = "/" | ||
| 16 | if l := len(CFG.URI); len(path) > l { | 17 | if l := len(CFG.URI); len(path) > l { |
| 17 | path = path[l-1:] | 18 | path = path[l-1:] |
| 18 | } else { | ||
| 19 | path = "/" | ||
| 20 | } | 19 | } |
| 21 | 20 | ||
| 22 | parsedpath := make(map[int]string) | 21 | parsedpath := make(map[int]string) |
| @@ -54,44 +53,42 @@ func Router() { | |||
| 54 | 53 | ||
| 55 | // функция, что управляет всем | 54 | // функция, что управляет всем |
| 56 | handle := func(w http.ResponseWriter, r *http.Request) { | 55 | handle := func(w http.ResponseWriter, r *http.Request) { |
| 56 | Path = r.URL.Path | ||
| 57 | path := parsepath(Path) | ||
| 58 | Host = "http://" + r.Host | ||
| 59 | |||
| 57 | if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" { | 60 | if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" { |
| 58 | Host = h[0] + "://" + r.Host | 61 | Host = "https://" + r.Host |
| 59 | } else { | ||
| 60 | Host = "http://" + r.Host | ||
| 61 | } | 62 | } |
| 62 | 63 | ||
| 63 | Path = r.URL.Path | 64 | var skunky = skunkyart{Version: Release.Version} |
| 64 | path := parsepath(Path) | 65 | |
| 65 | // структура с функциями | 66 | arg := skunky.Args.Get |
| 66 | var skunky skunkyart | 67 | p, _ := strconv.Atoi(arg("p")) |
| 68 | |||
| 69 | skunky.Endpoint = path[1] | ||
| 70 | skunky.API.main = &skunky | ||
| 67 | skunky.Writer = w | 71 | skunky.Writer = w |
| 68 | skunky.Args = r.URL.Query() | 72 | skunky.Args = r.URL.Query() |
| 69 | skunky.BasePath = CFG.URI | 73 | skunky.BasePath = CFG.URI |
| 70 | |||
| 71 | arg := skunky.Args.Get | ||
| 72 | skunky.QueryRaw = arg("q") | 74 | skunky.QueryRaw = arg("q") |
| 73 | skunky.Query = u.QueryEscape(skunky.QueryRaw) | 75 | skunky.Query = u.QueryEscape(skunky.QueryRaw) |
| 76 | skunky.Page = p | ||
| 74 | 77 | ||
| 75 | if t := arg("type"); len(t) > 0 { | 78 | if t := arg("type"); len(t) > 0 { |
| 76 | skunky.Type = rune(t[0]) | 79 | skunky.Type = rune(t[0]) |
| 77 | } | 80 | } |
| 78 | p, _ := strconv.Atoi(arg("p")) | ||
| 79 | skunky.Page = p | ||
| 80 | 81 | ||
| 81 | if arg("atom") == "true" { | 82 | if arg("atom") == "true" { |
| 82 | skunky.Atom = true | 83 | skunky.Atom = true |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 85 | skunky.Endpoint = path[1] | ||
| 86 | skunky.API.skunkyartLink = &skunky | ||
| 87 | |||
| 88 | // пути | ||
| 89 | switch skunky.Endpoint { | 86 | switch skunky.Endpoint { |
| 90 | default: | 87 | // main |
| 91 | skunky.ReturnHTTPError(404) | ||
| 92 | |||
| 93 | case "": | 88 | case "": |
| 94 | skunky.ExecuteTemplate("index.htm", "html", &CFG.URI) | 89 | skunky.ExecuteTemplate("index.htm", "html", &CFG.URI) |
| 90 | case "about": | ||
| 91 | skunky.About() | ||
| 95 | case "post": | 92 | case "post": |
| 96 | skunky.Deviation(path[2], path[3]) | 93 | skunky.Deviation(path[2], path[3]) |
| 97 | case "search": | 94 | case "search": |
| @@ -101,33 +98,38 @@ func Router() { | |||
| 101 | case "group_user": | 98 | case "group_user": |
| 102 | skunky.GRUser() | 99 | skunky.GRUser() |
| 103 | 100 | ||
| 101 | // media | ||
| 104 | case "media": | 102 | case "media": |
| 105 | switch path[2] { | 103 | switch path[2] { |
| 106 | case "file": | 104 | case "file": |
| 107 | if a := arg("filename"); a != "" { | 105 | if a := arg("filename"); a != "" { |
| 108 | var filename strings.Builder | 106 | skunky.SetFilename(a) |
| 109 | filename.WriteString(`filename="`) | ||
| 110 | filename.WriteString(a) | ||
| 111 | filename.WriteString(`"`) | ||
| 112 | w.Header().Add("Content-Disposition", filename.String()) | ||
| 113 | } | 107 | } |
| 114 | skunky.DownloadAndSendMedia(path[3], next(path, 4)) | 108 | skunky.DownloadAndSendMedia(path[3], next(path, 4)) |
| 115 | case "emojitar": | 109 | case "emojitar": |
| 116 | skunky.Emojitar(path[3]) | 110 | skunky.Emojitar(path[3]) |
| 117 | } | 111 | } |
| 118 | case "about": | ||
| 119 | skunky.About() | ||
| 120 | case "stylesheet": | 112 | case "stylesheet": |
| 121 | w.Header().Add("content-type", "text/css") | 113 | w.Header().Add("content-type", "text/css") |
| 122 | w.Write(open("css/skunky.css")) | 114 | w.Write(open("css/skunky.css")) |
| 123 | case "favicon.ico": | 115 | case "favicon.ico": |
| 124 | w.Write(open("images/logo.png")) | 116 | w.Write(open("images/logo.png")) |
| 125 | 117 | ||
| 118 | // API | ||
| 126 | case "api": | 119 | case "api": |
| 120 | w.Header().Add("Content-Type", "application/json") | ||
| 127 | switch path[2] { | 121 | switch path[2] { |
| 122 | case "instance": | ||
| 123 | skunky.API.Info() | ||
| 128 | case "random": | 124 | case "random": |
| 129 | skunky.API.Random() | 125 | skunky.API.Random() |
| 126 | default: | ||
| 127 | skunky.API.Error("Not Found", 404) | ||
| 130 | } | 128 | } |
| 129 | |||
| 130 | // 404 | ||
| 131 | default: | ||
| 132 | skunky.ReturnHTTPError(404) | ||
| 131 | } | 133 | } |
| 132 | } | 134 | } |
| 133 | 135 | ||
app/util.go +13 −1
| @@ -64,6 +64,7 @@ type skunkyart struct { | |||
| 64 | Query, QueryRaw string | 64 | Query, QueryRaw string |
| 65 | 65 | ||
| 66 | API API | 66 | API API |
| 67 | Version string | ||
| 67 | 68 | ||
| 68 | Templates struct { | 69 | Templates struct { |
| 69 | About struct { | 70 | About struct { |
| @@ -131,7 +132,7 @@ func UrlBuilder(strs ...string) string { | |||
| 131 | str.WriteString(CFG.URI) | 132 | str.WriteString(CFG.URI) |
| 132 | for n, x := range strs { | 133 | for n, x := range strs { |
| 133 | str.WriteString(x) | 134 | str.WriteString(x) |
| 134 | if n+1 < l && !(strs[n+1][0] == '?' || strs[n+1][0] == '&') && !(x[0] == '?' || x[0] == '&') { | 135 | if n := n+1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') { |
| 135 | str.WriteString("/") | 136 | str.WriteString("/") |
| 136 | } | 137 | } |
| 137 | } | 138 | } |
| @@ -153,6 +154,14 @@ func (s skunkyart) ReturnHTTPError(status int) { | |||
| 153 | wr(s.Writer, msg.String()) | 154 | wr(s.Writer, msg.String()) |
| 154 | } | 155 | } |
| 155 | 156 | ||
| 157 | func (s skunkyart) SetFilename(name string) { | ||
| 158 | var filename strings.Builder | ||
| 159 | filename.WriteString(`filename="`) | ||
| 160 | filename.WriteString(name) | ||
| 161 | filename.WriteString(`"`) | ||
| 162 | s.Writer.Header().Add("Content-Disposition", filename.String()) | ||
| 163 | } | ||
| 164 | |||
| 156 | type Downloaded struct { | 165 | type Downloaded struct { |
| 157 | Headers http.Header | 166 | Headers http.Header |
| 158 | Status int | 167 | Status int |
| @@ -189,6 +198,9 @@ func ParseMedia(media devianter.Media, thumb ...int) string { | |||
| 189 | if len(mediaUrl) != 0 && CFG.Proxy { | 198 | if len(mediaUrl) != 0 && CFG.Proxy { |
| 190 | mediaUrl = mediaUrl[21:] | 199 | mediaUrl = mediaUrl[21:] |
| 191 | dot := strings.Index(mediaUrl, ".") | 200 | dot := strings.Index(mediaUrl, ".") |
| 201 | if filename == "" { | ||
| 202 | filename = "image.gif" | ||
| 203 | } | ||
| 192 | return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename) | 204 | return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename) |
| 193 | } | 205 | } |
| 194 | return mediaUrl | 206 | return mediaUrl |
app/wrapper.go −1
| @@ -42,7 +42,6 @@ func (s skunkyart) GRUser() { | |||
| 42 | group.About.A = x.ModuleData.About | 42 | group.About.A = x.ModuleData.About |
| 43 | var about = &group.About.A | 43 | var about = &group.About.A |
| 44 | group.CreationDate = time.Unix(time.Now().Unix()-x.ModuleData.About.RegDate, 0).UTC().String() | 44 | group.CreationDate = time.Unix(time.Now().Unix()-x.ModuleData.About.RegDate, 0).UTC().String() |
| 45 | |||
| 46 | group.About.DescriptionFormatted = ParseDescription(about.Description) | 45 | group.About.DescriptionFormatted = ParseDescription(about.Description) |
| 47 | 46 | ||
| 48 | for _, val := range x.ModuleData.About.SocialLinks { | 47 | for _, val := range x.ModuleData.About.SocialLinks { |
go.mod +1 −1
| @@ -2,7 +2,7 @@ module skunkyart | |||
| 2 | 2 | ||
| 3 | go 1.18 | 3 | go 1.18 |
| 4 | 4 | ||
| 5 | // replace git.macaw.me/skunky/devianter v0.2.5 => /home/skunk/projects/devianter | 5 | replace git.macaw.me/skunky/devianter v0.2.5 => /home/skunk/projects/devianter |
| 6 | require ( | 6 | require ( |
| 7 | git.macaw.me/skunky/devianter v0.2.5 | 7 | git.macaw.me/skunky/devianter v0.2.5 |
| 8 | golang.org/x/net v0.27.0 | 8 | golang.org/x/net v0.27.0 |
main.go +2
| @@ -9,6 +9,8 @@ import ( | |||
| 9 | ) | 9 | ) |
| 10 | 10 | ||
| 11 | func main() { | 11 | func main() { |
| 12 | app.Release.Version = "1.3.2-alpha" | ||
| 13 | app.Release.Description = "Two API endpoints and template embedding into binary" | ||
| 12 | go app.RefreshInstances() | 14 | go app.RefreshInstances() |
| 13 | 15 | ||
| 14 | app.ExecuteCommandLineArguments() | 16 | app.ExecuteCommandLineArguments() |
static/html/about.htm +2 −2
| @@ -44,6 +44,6 @@ | |||
| 44 | {{end}} | 44 | {{end}} |
| 45 | </ul> | 45 | </ul> |
| 46 | </details> | 46 | </details> |
| 47 | <p>Copyright <a href="https://go.kde.org/matrix/#/@softpigeones:ebloid.ru" target="_blank">lost+skunk</a>, X11. <a href="https://git.macaw.me/skunky/skunkyart/src/tag/v1.3.1" target="_blank">SkunkyArt v1.3.1</a></p> | 47 | <p>Copyright <a href="https://go.kde.org/matrix/#/@softpigeones:ebloid.ru" target="_blank">lost+skunk</a>, X11. <a href="https://git.macaw.me/skunky/skunkyart/src/tag/v{{.Version}}" target="_blank">SkunkyArt v{{.Version}}</a></p> |
| 48 | </main> | 48 | </main> |
| 49 | </html>\ No newline at end of file | 49 | </html> |