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