Commit 1464584264
Unsigned
Layout: unified · split
INSTANCES.md +2 −2
| @@ -3,5 +3,5 @@ | |||
| 3 | |[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | No | No | Russia | | 3 | |[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | No | No | Russia | |
| 4 | |[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden | | 4 | |[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden | |
| 5 | |[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Germany | | 5 | |[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Germany | |
| 6 | |[frontendfriendly.xyz](https://skunkyart.frontendfriendly.xyz)|No|No|No| Yes | Yes | No | Finland | | 6 | |[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | Germany | |
| 7 | |[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | US |\ No newline at end of file | 7 | |[art.bloat.cat](https://art.bloat.cat)|No|No|No| Yes | Yes | No | Germany | |
TODO.md +2 −2
| @@ -13,9 +13,9 @@ | |||
| 13 | * **Реализовать отображение контента, отличного от картинок (видео, аудио, etc)** | 13 | * **Реализовать отображение контента, отличного от картинок (видео, аудио, etc)** |
| 14 | * Исправить баг с эмоджи, когда некоторые кастомные эмоции могут не отображаться | 14 | * Исправить баг с эмоджи, когда некоторые кастомные эмоции могут не отображаться |
| 15 | * ~~Добавить аргумент &filename, который будет выдавать файл с нормально выглядещем именем~~ ✔️ | 15 | * ~~Добавить аргумент &filename, который будет выдавать файл с нормально выглядещем именем~~ ✔️ |
| 16 | * ~~Улучшить систему кеширования: добавить рейтинг для удаления и копирование изображений в ОЗУ~~ | 16 | * ~~Улучшить систему кеширования: добавить рейтинг для удаления и копирование изображений в ОЗУ~~ BUG: почему-то всё переодически встаёт раком |
| 17 | # v1.4 | 17 | # v1.4 |
| 18 | * Реализовать API | 18 | * Реализовать API |
| 19 | * Реализовать темы | 19 | * Реализовать темы |
| 20 | * Перейти на арены в кеше | 20 | * Перейти на арены в кеше |
| 21 | * Реализовать многоязычный интерфейс\ No newline at end of file | 21 | * Реализовать многоязычный интерфейс |
app/api.go added +34
| @@ -0,0 +1,34 @@ | |||
| 1 | package app | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "math/rand" | ||
| 5 | "strings" | ||
| 6 | |||
| 7 | "git.macaw.me/skunky/devianter" | ||
| 8 | ) | ||
| 9 | |||
| 10 | type API struct { | ||
| 11 | skunkyartLink *skunkyart | ||
| 12 | } | ||
| 13 | |||
| 14 | func (a API) sendMedia(d *devianter.Deviation) { | ||
| 15 | 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()) | ||
| 22 | |||
| 23 | if len(mediaUrl) != 0 { | ||
| 24 | mediaUrl = mediaUrl[21:] | ||
| 25 | dot := strings.Index(mediaUrl, ".") | ||
| 26 | a.skunkyartLink.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:]) | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | 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))]) | ||
| 34 | } | ||
app/cache.go +17 −14
| @@ -26,9 +26,11 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { | |||
| 26 | url.WriteString(subdomain) | 26 | url.WriteString(subdomain) |
| 27 | url.WriteString(".wixmp.com/") | 27 | url.WriteString(".wixmp.com/") |
| 28 | url.WriteString(path) | 28 | url.WriteString(path) |
| 29 | url.WriteString("?token=") | 29 | if t := s.Args.Get("token"); t != "" { |
| 30 | url.WriteString(s.Args.Get("token")) | 30 | url.WriteString("?token=") |
| 31 | 31 | url.WriteString(t) | |
| 32 | } | ||
| 33 | |||
| 32 | var response []byte | 34 | var response []byte |
| 33 | 35 | ||
| 34 | switch { | 36 | switch { |
| @@ -40,17 +42,18 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { | |||
| 40 | if tempFS[fileName] == nil { | 42 | if tempFS[fileName] == nil { |
| 41 | tempFS[fileName] = &file{} | 43 | tempFS[fileName] = &file{} |
| 42 | } | 44 | } |
| 43 | f := *tempFS[fileName] | ||
| 44 | mx.Unlock() | 45 | mx.Unlock() |
| 45 | 46 | ||
| 46 | if f.Content != nil { | 47 | if tempFS[fileName].Content != nil { |
| 47 | f.Score += 2 | 48 | response = tempFS[fileName].Content |
| 49 | tempFS[fileName].Score += 2 | ||
| 50 | break | ||
| 48 | } else { | 51 | } else { |
| 49 | file, err := os.Open(filePath) | 52 | file, err := os.Open(filePath) |
| 50 | if err != nil { | 53 | if err != nil { |
| 51 | if dwnld := Download(url.String()); dwnld.Status == 200 && dwnld.Headers["Content-Type"][0][:5] == "image" { | 54 | if dwnld := Download(url.String()); dwnld.Status == 200 && dwnld.Headers["Content-Type"][0][:5] == "image" { |
| 52 | f.Content = dwnld.Body | 55 | response = dwnld.Body |
| 53 | try(os.WriteFile(filePath, f.Content, 0700)) | 56 | try(os.WriteFile(filePath, response, 0700)) |
| 54 | } else { | 57 | } else { |
| 55 | s.ReturnHTTPError(dwnld.Status) | 58 | s.ReturnHTTPError(dwnld.Status) |
| 56 | return | 59 | return |
| @@ -58,11 +61,16 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { | |||
| 58 | } else { | 61 | } else { |
| 59 | file, e := io.ReadAll(file) | 62 | file, e := io.ReadAll(file) |
| 60 | try(e) | 63 | try(e) |
| 61 | f.Content = file | 64 | response = file |
| 62 | } | 65 | } |
| 63 | 66 | ||
| 64 | go func() { | 67 | go func() { |
| 65 | defer restore() | 68 | defer restore() |
| 69 | |||
| 70 | mx.RLock() | ||
| 71 | tempFS[fileName].Content = response | ||
| 72 | mx.RUnlock() | ||
| 73 | |||
| 66 | for { | 74 | for { |
| 67 | time.Sleep(1 * time.Minute) | 75 | time.Sleep(1 * time.Minute) |
| 68 | 76 | ||
| @@ -77,11 +85,6 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { | |||
| 77 | } | 85 | } |
| 78 | }() | 86 | }() |
| 79 | } | 87 | } |
| 80 | |||
| 81 | mx.Lock() | ||
| 82 | tempFS[fileName] = &f | ||
| 83 | mx.Unlock() | ||
| 84 | response = f.Content | ||
| 85 | case CFG.Proxy: | 88 | case CFG.Proxy: |
| 86 | dwnld := Download(url.String()) | 89 | dwnld := Download(url.String()) |
| 87 | if dwnld.Status != 200 { | 90 | if dwnld.Status != 200 { |
app/parsers.go +4 −2
| @@ -82,8 +82,9 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con | |||
| 82 | 82 | ||
| 83 | for i, l := 0, len(devs); i < l; i++ { | 83 | for i, l := 0, len(devs); i < l; i++ { |
| 84 | data := &devs[i] | 84 | data := &devs[i] |
| 85 | if preview, fullview := ParseMedia(data.Media, data.Title, 320), ParseMedia(data.Media, data.Title); !(data.NSFW && !CFG.Nsfw) { | 85 | if preview, fullview := ParseMedia(data.Media, 320), ParseMedia(data.Media); !(data.NSFW && !CFG.Nsfw) { |
| 86 | if allowAtom && s.Atom { | 86 | if allowAtom && s.Atom { |
| 87 | s.Writer.Header().Add("Content-type", "application/atom+xml") | ||
| 87 | id := strconv.Itoa(data.ID) | 88 | id := strconv.Itoa(data.ID) |
| 88 | listContent.WriteString(`<entry><author><name>`) | 89 | listContent.WriteString(`<entry><author><name>`) |
| 89 | listContent.WriteString(data.Author.Username) | 90 | listContent.WriteString(data.Author.Username) |
| @@ -183,6 +184,7 @@ type text struct { | |||
| 183 | To int | 184 | To int |
| 184 | } | 185 | } |
| 185 | 186 | ||
| 187 | // переписать весь этот пиздец нахуй | ||
| 186 | func ParseDescription(dscr devianter.Text) string { | 188 | func ParseDescription(dscr devianter.Text) string { |
| 187 | var parsedDescription strings.Builder | 189 | var parsedDescription strings.Builder |
| 188 | TagBuilder := func(content string, tags ...string) string { | 190 | TagBuilder := func(content string, tags ...string) string { |
| @@ -286,7 +288,7 @@ func ParseDescription(dscr devianter.Text) string { | |||
| 286 | parsedDescription.WriteString(`<a href="`) | 288 | parsedDescription.WriteString(`<a href="`) |
| 287 | parsedDescription.WriteString(ConvertDeviantArtUrlToSkunkyArt(d.Url)) | 289 | parsedDescription.WriteString(ConvertDeviantArtUrlToSkunkyArt(d.Url)) |
| 288 | parsedDescription.WriteString(`"><img width="50%" src="`) | 290 | parsedDescription.WriteString(`"><img width="50%" src="`) |
| 289 | parsedDescription.WriteString(ParseMedia(d.Media, d.Title)) | 291 | parsedDescription.WriteString(ParseMedia(d.Media)) |
| 290 | parsedDescription.WriteString(`" title="`) | 292 | parsedDescription.WriteString(`" title="`) |
| 291 | parsedDescription.WriteString(d.Author.Username) | 293 | parsedDescription.WriteString(d.Author.Username) |
| 292 | parsedDescription.WriteString(" - ") | 294 | parsedDescription.WriteString(" - ") |
app/router.go +9 −1
| @@ -83,13 +83,15 @@ func Router() { | |||
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | skunky.Endpoint = path[1] | 85 | skunky.Endpoint = path[1] |
| 86 | skunky.API.skunkyartLink = &skunky | ||
| 86 | 87 | ||
| 87 | // пути | 88 | // пути |
| 88 | switch skunky.Endpoint { | 89 | switch skunky.Endpoint { |
| 89 | default: | 90 | default: |
| 90 | skunky.ReturnHTTPError(404) | 91 | skunky.ReturnHTTPError(404) |
| 92 | |||
| 91 | case "": | 93 | case "": |
| 92 | w.Write(open("html/index.htm")) | 94 | skunky.ExecuteTemplate("index.htm", "html", &CFG.URI) |
| 93 | case "post": | 95 | case "post": |
| 94 | skunky.Deviation(path[2], path[3]) | 96 | skunky.Deviation(path[2], path[3]) |
| 95 | case "search": | 97 | case "search": |
| @@ -120,6 +122,12 @@ func Router() { | |||
| 120 | w.Write(open("css/skunky.css")) | 122 | w.Write(open("css/skunky.css")) |
| 121 | case "favicon.ico": | 123 | case "favicon.ico": |
| 122 | w.Write(open("images/logo.png")) | 124 | w.Write(open("images/logo.png")) |
| 125 | |||
| 126 | case "api": | ||
| 127 | switch path[2] { | ||
| 128 | case "random": | ||
| 129 | skunky.API.Random() | ||
| 130 | } | ||
| 123 | } | 131 | } |
| 124 | } | 132 | } |
| 125 | 133 | ||
app/util.go +5 −4
| @@ -63,6 +63,8 @@ type skunkyart struct { | |||
| 63 | BasePath, Endpoint string | 63 | BasePath, Endpoint string |
| 64 | Query, QueryRaw string | 64 | Query, QueryRaw string |
| 65 | 65 | ||
| 66 | API API | ||
| 67 | |||
| 66 | Templates struct { | 68 | Templates struct { |
| 67 | About struct { | 69 | About struct { |
| 68 | Proxy bool | 70 | Proxy bool |
| @@ -182,13 +184,12 @@ func Download(urlString string) (d Downloaded) { | |||
| 182 | } | 184 | } |
| 183 | 185 | ||
| 184 | /* PARSING HELPERS */ | 186 | /* PARSING HELPERS */ |
| 185 | func ParseMedia(media devianter.Media, filename string, thumb ...int) string { | 187 | func ParseMedia(media devianter.Media, thumb ...int) string { |
| 186 | mediaUrl := devianter.UrlFromMedia(media, thumb...) | 188 | mediaUrl, filename := devianter.UrlFromMedia(media, thumb...) |
| 187 | if len(mediaUrl) != 0 && CFG.Proxy { | 189 | if len(mediaUrl) != 0 && CFG.Proxy { |
| 188 | mediaUrl = mediaUrl[21:] | 190 | mediaUrl = mediaUrl[21:] |
| 189 | dot := strings.Index(mediaUrl, ".") | 191 | dot := strings.Index(mediaUrl, ".") |
| 190 | 192 | return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename) | |
| 191 | return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", url.QueryEscape(filename)) | ||
| 192 | } | 193 | } |
| 193 | return mediaUrl | 194 | return mediaUrl |
| 194 | } | 195 | } |
app/wrapper.go +3 −3
| @@ -74,7 +74,7 @@ func (s skunkyart) GRUser() { | |||
| 74 | case "cover_deviation": | 74 | case "cover_deviation": |
| 75 | group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation | 75 | group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation |
| 76 | group.About.BGMeta.Url = ConvertDeviantArtUrlToSkunkyArt(group.About.BGMeta.Url) | 76 | group.About.BGMeta.Url = ConvertDeviantArtUrlToSkunkyArt(group.About.BGMeta.Url) |
| 77 | group.About.BG = ParseMedia(group.About.BGMeta.Media, group.About.BGMeta.Title) | 77 | group.About.BG = ParseMedia(group.About.BGMeta.Media) |
| 78 | case "group_admins": | 78 | case "group_admins": |
| 79 | var htm strings.Builder | 79 | var htm strings.Builder |
| 80 | for _, z := range x.ModuleData.GroupAdmins.Results { | 80 | for _, z := range x.ModuleData.GroupAdmins.Results { |
| @@ -122,7 +122,7 @@ func (s skunkyart) GRUser() { | |||
| 122 | folders.WriteString(`<a href="`) | 122 | folders.WriteString(`<a href="`) |
| 123 | folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url)) | 123 | folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url)) |
| 124 | folders.WriteString(`"><img loading="lazy" src="`) | 124 | folders.WriteString(`"><img loading="lazy" src="`) |
| 125 | folders.WriteString(ParseMedia(x.Thumb.Media, x.Thumb.Title)) | 125 | folders.WriteString(ParseMedia(x.Thumb.Media)) |
| 126 | folders.WriteString(`" title="`) | 126 | folders.WriteString(`" title="`) |
| 127 | folders.WriteString(x.Thumb.Title) | 127 | folders.WriteString(x.Thumb.Title) |
| 128 | folders.WriteString(`"></a>`) | 128 | folders.WriteString(`"></a>`) |
| @@ -185,7 +185,7 @@ func (s skunkyart) Deviation(author, postname string) { | |||
| 185 | } | 185 | } |
| 186 | // время публикации | 186 | // время публикации |
| 187 | post.StringTime = post.Post.Deviation.PublishedTime.UTC().String() | 187 | post.StringTime = post.Post.Deviation.PublishedTime.UTC().String() |
| 188 | post.Post.IMG = ParseMedia(post.Post.Deviation.Media, post.Post.Deviation.Title) | 188 | post.Post.IMG = ParseMedia(post.Post.Deviation.Media) |
| 189 | for _, x := range post.Post.Deviation.Extended.RelatedContent { | 189 | for _, x := range post.Post.Deviation.Extended.RelatedContent { |
| 190 | if len(x.Deviations) != 0 { | 190 | if len(x.Deviations) != 0 { |
| 191 | post.Related += s.DeviationList(x.Deviations, false) | 191 | post.Related += s.DeviationList(x.Deviations, false) |
config.example.json +2 −2
| @@ -1,6 +1,6 @@ | |||
| 1 | { | 1 | { |
| 2 | "listen": "0:3003", | 2 | "listen": "0.0.0.0:3003", |
| 3 | "uri": "/", | 3 | "uri": "/huy/", |
| 4 | "cache": { | 4 | "cache": { |
| 5 | "enabled": true, | 5 | "enabled": true, |
| 6 | "path": "cache", | 6 | "path": "cache", |
go.mod +2 −2
| @@ -1,8 +1,8 @@ | |||
| 1 | module skunkyart | 1 | module skunkyart |
| 2 | 2 | ||
| 3 | go 1.22.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 | 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 |
instances.json +7 −7
| @@ -35,10 +35,10 @@ | |||
| 35 | } | 35 | } |
| 36 | }, | 36 | }, |
| 37 | { | 37 | { |
| 38 | "title": "frontendfriendly.xyz", | 38 | "title": "lumaeris.com", |
| 39 | "country": "Finland", | 39 | "country": "Germany", |
| 40 | "urls": { | 40 | "urls": { |
| 41 | "clearnet": "https://skunkyart.frontendfriendly.xyz" | 41 | "clearnet": "https://skunkyart.lumaeris.com" |
| 42 | }, | 42 | }, |
| 43 | "settings": { | 43 | "settings": { |
| 44 | "nsfw": true, | 44 | "nsfw": true, |
| @@ -46,10 +46,10 @@ | |||
| 46 | } | 46 | } |
| 47 | }, | 47 | }, |
| 48 | { | 48 | { |
| 49 | "title": "lumaeris.com", | 49 | "title": "art.bloat.cat", |
| 50 | "country": "US", | 50 | "country": "Germany", |
| 51 | "urls": { | 51 | "urls": { |
| 52 | "clearnet": "https://skunkyart.lumaeris.com" | 52 | "clearnet": "https://art.bloat.cat" |
| 53 | }, | 53 | }, |
| 54 | "settings": { | 54 | "settings": { |
| 55 | "nsfw": true, | 55 | "nsfw": true, |
| @@ -57,4 +57,4 @@ | |||
| 57 | } | 57 | } |
| 58 | } | 58 | } |
| 59 | ] | 59 | ] |
| 60 | }\ No newline at end of file | 60 | } |
static/html/index.htm +4 −4
| @@ -2,12 +2,12 @@ | |||
| 2 | <html> | 2 | <html> |
| 3 | <head> | 3 | <head> |
| 4 | <title>SkunkyArt</title> | 4 | <title>SkunkyArt</title> |
| 5 | <link rel="stylesheet" href="stylesheet"/> | 5 | <link rel="stylesheet" href="{{.}}stylesheet"/> |
| 6 | <link rel="icon" type="image/x-icon" href="favicon.ico"> | 6 | <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico"> |
| 7 | </head> | 7 | </head> |
| 8 | <main> | 8 | <main> |
| 9 | <center> | 9 | <center> |
| 10 | <form method="get" action="search"> | 10 | <form method="get" action="{{.}}search"> |
| 11 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | 11 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
| 12 | <select name="type"> | 12 | <select name="type"> |
| 13 | <option value="all">All</option> | 13 | <option value="all">All</option> |
| @@ -16,7 +16,7 @@ | |||
| 16 | </select> | 16 | </select> |
| 17 | <button type="submit">Search!</button> | 17 | <button type="submit">Search!</button> |
| 18 | </form> | 18 | </form> |
| 19 | <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> | 19 | <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> |
| 20 | </center> | 20 | </center> |
| 21 | </main> | 21 | </main> |
| 22 | </html>\ No newline at end of file | 22 | </html>\ No newline at end of file |