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