Commit 7f1ab1b73d
Unsigned
Layout: unified · split
app/config.go +50 −19
| @@ -1,26 +1,31 @@ | |||
| 1 | package app | 1 | package app |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/json" | ||
| 5 | "errors" | ||
| 4 | "os" | 6 | "os" |
| 5 | ) | 7 | ) |
| 6 | 8 | ||
| 7 | type cache_config struct { | 9 | type cache_config struct { |
| 8 | Enabled bool | 10 | Enabled bool |
| 9 | Path string | 11 | Path string |
| 10 | Max_size, Lifetime int64 | 12 | MaxSize int64 `json:"max-size"` |
| 13 | Lifetime int64 | ||
| 11 | } | 14 | } |
| 12 | 15 | ||
| 13 | type config struct { | 16 | type config struct { |
| 14 | cfg string | 17 | cfg string |
| 15 | Listen, Base_uri string | 18 | Listen string |
| 16 | Cache cache_config | 19 | BasePath string `json:"base-path"` |
| 17 | Proxy, Nsfw bool | 20 | Cache cache_config |
| 21 | Proxy, Nsfw bool | ||
| 22 | WixmpProxy string `json:"wixmp-proxy"` | ||
| 18 | } | 23 | } |
| 19 | 24 | ||
| 20 | var CFG = config{ | 25 | var CFG = config{ |
| 21 | cfg: "config.json", | 26 | cfg: "config.json", |
| 22 | Listen: "127.0.0.1:3003", | 27 | Listen: "127.0.0.1:3003", |
| 23 | Base_uri: "/", | 28 | BasePath: "/", |
| 24 | Cache: cache_config{ | 29 | Cache: cache_config{ |
| 25 | Enabled: true, | 30 | Enabled: true, |
| 26 | Path: "cache", | 31 | Path: "cache", |
| @@ -29,20 +34,46 @@ var CFG = config{ | |||
| 29 | Nsfw: true, | 34 | Nsfw: true, |
| 30 | } | 35 | } |
| 31 | 36 | ||
| 32 | func execcfg() { | 37 | func ExecuteConfig() { |
| 38 | try := func(err error, exitcode int) { | ||
| 39 | if err != nil { | ||
| 40 | println(err.Error()) | ||
| 41 | os.Exit(exitcode) | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 33 | a := os.Args | 45 | a := os.Args |
| 34 | for num, val := range a { | 46 | if l := len(a); l > 1 { |
| 35 | switch val { | 47 | switch a[1] { |
| 36 | case "-conf": | 48 | case "-c", "--config": |
| 37 | CFG.cfg = a[num] | 49 | if l >= 3 { |
| 38 | case "-help": | 50 | CFG.cfg = a[2] |
| 39 | println(`SkunkyArt v 1.3 [refactoring] | 51 | } else { |
| 52 | try(errors.New("Not enought arguments"), 1) | ||
| 53 | } | ||
| 54 | case "-h", "--help": | ||
| 55 | try(errors.New(`SkunkyArt v1.3 [refactoring] | ||
| 40 | Usage: | 56 | Usage: |
| 41 | - -conf - path to config | 57 | - [-c|--config] - path to config |
| 42 | - -help this message | 58 | - [-h|--help] - returns this message |
| 43 | Example: | 59 | Example: |
| 44 | ./skunkyart -conf config.json | 60 | ./skunkyart -c config.json |
| 45 | Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`) | 61 | Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`), 0) |
| 62 | default: | ||
| 63 | try(errors.New("Unreconginzed argument: "+a[1]), 1) | ||
| 64 | } | ||
| 65 | if CFG.cfg != "" { | ||
| 66 | f, err := os.ReadFile(CFG.cfg) | ||
| 67 | try(err, 1) | ||
| 68 | |||
| 69 | try(json.Unmarshal(f, &CFG), 1) | ||
| 70 | if CFG.Cache.Enabled && !CFG.Proxy { | ||
| 71 | try(errors.New("Incompatible settings detected: cannot use caching media content without proxy"), 1) | ||
| 72 | } | ||
| 73 | |||
| 74 | if CFG.Cache.MaxSize != 0 || CFG.Cache.Lifetime != 0 { | ||
| 75 | go InitCacheSystem() | ||
| 76 | } | ||
| 46 | } | 77 | } |
| 47 | } | 78 | } |
| 48 | } | 79 | } |
app/router.go +21 −9
| @@ -9,11 +9,14 @@ import ( | |||
| 9 | "strings" | 9 | "strings" |
| 10 | ) | 10 | ) |
| 11 | 11 | ||
| 12 | const addr string = "0.0.0.0:3003" | ||
| 13 | |||
| 14 | // роутер | ||
| 15 | func Router() { | 12 | func Router() { |
| 16 | parsepath := func(path string) map[int]string { | 13 | parsepath := func(path string) map[int]string { |
| 14 | if l := len(CFG.BasePath); len(path) > l { | ||
| 15 | path = path[l-1:] | ||
| 16 | } else { | ||
| 17 | path = "/" | ||
| 18 | } | ||
| 19 | |||
| 17 | parsedpath := make(map[int]string) | 20 | parsedpath := make(map[int]string) |
| 18 | for x := 0; true; x++ { | 21 | for x := 0; true; x++ { |
| 19 | slash := strings.Index(path, "/") + 1 | 22 | slash := strings.Index(path, "/") + 1 |
| @@ -52,16 +55,20 @@ func Router() { | |||
| 52 | var skunky skunkyart | 55 | var skunky skunkyart |
| 53 | skunky.Args = r.URL.Query() | 56 | skunky.Args = r.URL.Query() |
| 54 | skunky.Writer = w | 57 | skunky.Writer = w |
| 58 | skunky.BasePath = CFG.BasePath | ||
| 55 | 59 | ||
| 56 | arg := skunky.Args.Get | 60 | arg := skunky.Args.Get |
| 57 | skunky.Query = u.QueryEscape(arg("q")) | 61 | skunky.QueryRaw = arg("q") |
| 62 | skunky.Query = u.QueryEscape(skunky.QueryRaw) | ||
| 63 | |||
| 58 | if t := arg("type"); len(t) > 0 { | 64 | if t := arg("type"); len(t) > 0 { |
| 59 | skunky.Type = rune(t[0]) | 65 | skunky.Type = rune(t[0]) |
| 60 | } | 66 | } |
| 61 | p, _ := strconv.Atoi(arg("p")) | 67 | p, _ := strconv.Atoi(arg("p")) |
| 62 | skunky.Page = p | 68 | skunky.Page = p |
| 69 | |||
| 63 | if arg("atom") == "true" { | 70 | if arg("atom") == "true" { |
| 64 | skunky.atom = true | 71 | skunky.Atom = true |
| 65 | } | 72 | } |
| 66 | 73 | ||
| 67 | // пути | 74 | // пути |
| @@ -69,7 +76,7 @@ func Router() { | |||
| 69 | default: | 76 | default: |
| 70 | skunky.ReturnHTTPError(404) | 77 | skunky.ReturnHTTPError(404) |
| 71 | case "": | 78 | case "": |
| 72 | open_n_send("html/index.htm") | 79 | skunky.ExecuteTemplate("html/index.htm", &CFG.BasePath) |
| 73 | case "post": | 80 | case "post": |
| 74 | skunky.Deviation(path[2], path[3]) | 81 | skunky.Deviation(path[2], path[3]) |
| 75 | case "search": | 82 | case "search": |
| @@ -80,9 +87,14 @@ func Router() { | |||
| 80 | skunky.GRUser() | 87 | skunky.GRUser() |
| 81 | 88 | ||
| 82 | case "media": | 89 | case "media": |
| 83 | skunky.Emojitar(path[2]) | 90 | switch path[2] { |
| 91 | case "file": | ||
| 92 | skunky.DownloadAndSendMedia(path[3], next(path, 4)) | ||
| 93 | case "emojitar": | ||
| 94 | skunky.Emojitar(path[3]) | ||
| 95 | } | ||
| 84 | case "about": | 96 | case "about": |
| 85 | open_n_send("html/about.htm") | 97 | skunky.About() |
| 86 | case "gui": | 98 | case "gui": |
| 87 | w.Header().Add("content-type", "text/css") | 99 | w.Header().Add("content-type", "text/css") |
| 88 | open_n_send(next(path, 2)) | 100 | open_n_send(next(path, 2)) |
| @@ -90,7 +102,7 @@ func Router() { | |||
| 90 | } | 102 | } |
| 91 | 103 | ||
| 92 | http.HandleFunc("/", handle) | 104 | http.HandleFunc("/", handle) |
| 93 | http.ListenAndServe(addr, nil) | 105 | http.ListenAndServe(CFG.Listen, nil) |
| 94 | } | 106 | } |
| 95 | 107 | ||
| 96 | func err(e error) { | 108 | func err(e error) { |
app/util.go +186 −71
| @@ -1,11 +1,17 @@ | |||
| 1 | package app | 1 | package app |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/base64" | ||
| 4 | "encoding/json" | 5 | "encoding/json" |
| 6 | "io" | ||
| 5 | "net/http" | 7 | "net/http" |
| 8 | u "net/url" | ||
| 9 | "os" | ||
| 6 | "strconv" | 10 | "strconv" |
| 7 | "strings" | 11 | "strings" |
| 12 | "syscall" | ||
| 8 | "text/template" | 13 | "text/template" |
| 14 | "time" | ||
| 9 | 15 | ||
| 10 | "git.macaw.me/skunky/devianter" | 16 | "git.macaw.me/skunky/devianter" |
| 11 | ) | 17 | ) |
| @@ -19,11 +25,15 @@ func (s skunkyart) ExecuteTemplate(file string, data any) { | |||
| 19 | wr(s.Writer, buf.String()) | 25 | wr(s.Writer, buf.String()) |
| 20 | } | 26 | } |
| 21 | 27 | ||
| 22 | func (s skunkyart) UrlBuilder(strs ...string) string { | 28 | func UrlBuilder(strs ...string) string { |
| 23 | var str strings.Builder | 29 | var str strings.Builder |
| 24 | for _, x := range strs { | 30 | l := len(strs) |
| 31 | str.WriteString(CFG.BasePath) | ||
| 32 | for n, x := range strs { | ||
| 25 | str.WriteString(x) | 33 | str.WriteString(x) |
| 26 | str.WriteString("/") | 34 | if n+1 < l && !(strs[n+1][0] == '?' || strs[n+1][0] == '&') && !(x[0] == '?' || x[0] == '&') { |
| 35 | str.WriteString("/") | ||
| 36 | } | ||
| 27 | } | 37 | } |
| 28 | return str.String() | 38 | return str.String() |
| 29 | } | 39 | } |
| @@ -31,10 +41,10 @@ func (s skunkyart) UrlBuilder(strs ...string) string { | |||
| 31 | func (s skunkyart) ReturnHTTPError(status int) { | 41 | func (s skunkyart) ReturnHTTPError(status int) { |
| 32 | s.Writer.WriteHeader(status) | 42 | s.Writer.WriteHeader(status) |
| 33 | 43 | ||
| 34 | // пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения | ||
| 35 | var msg strings.Builder | 44 | var msg strings.Builder |
| 36 | msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`) | 45 | msg.WriteString(`<html><link rel="stylesheet" href="`) |
| 37 | msg.WriteString("<h1>") | 46 | msg.WriteString(UrlBuilder("gui", "css", "skunky.css")) |
| 47 | msg.WriteString(`" /><h1>`) | ||
| 38 | msg.WriteString(strconv.Itoa(status)) | 48 | msg.WriteString(strconv.Itoa(status)) |
| 39 | msg.WriteString(" - ") | 49 | msg.WriteString(" - ") |
| 40 | msg.WriteString(http.StatusText(status)) | 50 | msg.WriteString(http.StatusText(status)) |
| @@ -43,6 +53,15 @@ func (s skunkyart) ReturnHTTPError(status int) { | |||
| 43 | wr(s.Writer, msg.String()) | 53 | wr(s.Writer, msg.String()) |
| 44 | } | 54 | } |
| 45 | 55 | ||
| 56 | func (s skunkyart) ConvertDeviantArtUrlToSkunkyArt(url string) (output string) { | ||
| 57 | if len(url) > 32 && url[27:32] != "stash" { | ||
| 58 | url = url[27:] | ||
| 59 | toart := strings.Index(url, "/art/") | ||
| 60 | output = UrlBuilder("post", url[:toart], url[toart+5:]) | ||
| 61 | } | ||
| 62 | return | ||
| 63 | } | ||
| 64 | |||
| 46 | type text struct { | 65 | type text struct { |
| 47 | TXT string | 66 | TXT string |
| 48 | from int | 67 | from int |
| @@ -200,80 +219,78 @@ func (s skunkyart) NavBase(c dlist) string { | |||
| 200 | 219 | ||
| 201 | func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string { | 220 | func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string { |
| 202 | var list strings.Builder | 221 | var list strings.Builder |
| 203 | if !s.atom { | 222 | if s.Atom && s.Page > 1 { |
| 204 | list.WriteString(`<div class="content">`) | 223 | s.ReturnHTTPError(400) |
| 205 | } else { | 224 | return "" |
| 206 | list.WriteString(`<?xml version="1.0" encoding="UTF-8"?><feed xmlns:media="http://search.yahoo.com/mrss" xmlns="http://www.w3.org/2005/Atom">`) | 225 | } else if s.Atom { |
| 226 | list.WriteString(`<?xml version="1.0" encoding="UTF-8"?><feed xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom">`) | ||
| 207 | list.WriteString(`<title>SkunkyArt</title>`) | 227 | list.WriteString(`<title>SkunkyArt</title>`) |
| 208 | // list.WriteString(`<link rel="alternate" href="HOMEPAGE_URL"/><link href="FEED_URL" rel="self"/>`) | 228 | // list.WriteString(`<link rel="alternate" href="HOMEPAGE_URL"/><link href="FEED_URL" rel="self"/>`) |
| 229 | } else { | ||
| 230 | list.WriteString(`<div class="content">`) | ||
| 209 | } | 231 | } |
| 210 | for _, data := range devs { | 232 | for _, data := range devs { |
| 211 | url := devianter.UrlFromMedia(data.Media) | 233 | if !(data.NSFW && !CFG.Nsfw) { |
| 212 | if s.atom { | 234 | url := s.ParseMedia(data.Media) |
| 213 | id := strconv.Itoa(data.ID) | 235 | if s.Atom { |
| 214 | list.WriteString(`<entry><author><name>`) | 236 | id := strconv.Itoa(data.ID) |
| 215 | list.WriteString(data.Author.Username) | 237 | list.WriteString(`<entry><author><name>`) |
| 216 | list.WriteString(`</name></author><title>`) | 238 | list.WriteString(data.Author.Username) |
| 217 | list.WriteString(data.Title) | 239 | list.WriteString(`</name></author><title>`) |
| 218 | list.WriteString(`</title><link rel="alternate" type="text/html" href="`) | 240 | list.WriteString(data.Title) |
| 219 | list.WriteString(CFG.Base_uri) | 241 | list.WriteString(`</title><link rel="alternate" type="text/html" href="`) |
| 220 | list.WriteString("/post/") | 242 | list.WriteString(UrlBuilder("post", data.Author.Username, "atom-"+id)) |
| 221 | list.WriteString(data.Author.Username) | 243 | list.WriteString(`"/><id>`) |
| 222 | list.WriteString("/atom-") | 244 | list.WriteString(id) |
| 223 | list.WriteString(id) | 245 | list.WriteString(`</id><published>`) |
| 224 | list.WriteString(`"/><id>`) | 246 | list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700")) |
| 225 | list.WriteString(id) | 247 | list.WriteString(`</published>`) |
| 226 | list.WriteString(`</id><published>`) | 248 | list.WriteString(`<media:group><media:title>`) |
| 227 | list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700")) | 249 | list.WriteString(data.Title) |
| 228 | list.WriteString(`</published>`) | 250 | list.WriteString(`</media:title><media:thumbinal url="`) |
| 229 | list.WriteString(`<media:group><media:title>`) | ||
| 230 | list.WriteString(data.Title) | ||
| 231 | list.WriteString(`</media:title><media:thumbinal url="`) | ||
| 232 | list.WriteString(url) | ||
| 233 | list.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`) | ||
| 234 | list.WriteString(data.Url) | ||
| 235 | list.WriteString(`"><img src="`) | ||
| 236 | list.WriteString(url) | ||
| 237 | list.WriteString(`"/></a><p>`) | ||
| 238 | list.WriteString(ParseDescription(data.TextContent)) | ||
| 239 | list.WriteString(`</p></div></content></entry>`) | ||
| 240 | } else { | ||
| 241 | list.WriteString(`<div class="block">`) | ||
| 242 | if url != "" { | ||
| 243 | list.WriteString(`<a title="open/download" href="`) | ||
| 244 | list.WriteString(url) | 251 | list.WriteString(url) |
| 252 | list.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`) | ||
| 253 | list.WriteString(data.Url) | ||
| 245 | list.WriteString(`"><img src="`) | 254 | list.WriteString(`"><img src="`) |
| 246 | list.WriteString(url) | 255 | list.WriteString(url) |
| 247 | list.WriteString(`" width="15%"></a>`) | 256 | list.WriteString(`"/></a><p>`) |
| 257 | list.WriteString(ParseDescription(data.TextContent)) | ||
| 258 | list.WriteString(`</p></div></content></entry>`) | ||
| 248 | } else { | 259 | } else { |
| 249 | list.WriteString(`<h1>[ TEXT ]</h1>`) | 260 | list.WriteString(`<div class="block">`) |
| 250 | } | 261 | if url != "" { |
| 251 | list.WriteString(`<br><a href="`) | 262 | list.WriteString(`<a title="open/download" href="`) |
| 252 | list.WriteString("/post/") | 263 | list.WriteString(url) |
| 253 | list.WriteString(data.Author.Username) | 264 | list.WriteString(`"><img src="`) |
| 254 | list.WriteString("/") | 265 | list.WriteString(url) |
| 255 | list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:]) | 266 | list.WriteString(`" width="15%"></a>`) |
| 256 | list.WriteString(`">`) | 267 | } else { |
| 257 | list.WriteString(data.Author.Username) | 268 | list.WriteString(`<h1>[ TEXT ]</h1>`) |
| 258 | list.WriteString(" - ") | 269 | } |
| 259 | list.WriteString(data.Title) | 270 | list.WriteString(`<br><a href="`) |
| 271 | list.WriteString(s.ConvertDeviantArtUrlToSkunkyArt(data.Url)) | ||
| 272 | list.WriteString(`">`) | ||
| 273 | list.WriteString(data.Author.Username) | ||
| 274 | list.WriteString(" - ") | ||
| 275 | list.WriteString(data.Title) | ||
| 276 | |||
| 277 | // шильдики нсфв, аи и ежедневного поста | ||
| 278 | if data.NSFW { | ||
| 279 | list.WriteString(` [<span class="nsfw">NSFW</span>]`) | ||
| 280 | } | ||
| 281 | if data.AI { | ||
| 282 | list.WriteString(" [🤖]") | ||
| 283 | } | ||
| 284 | if data.DD { | ||
| 285 | list.WriteString(` [<span class="dd">DD</span>]`) | ||
| 286 | } | ||
| 260 | 287 | ||
| 261 | // шильдики нсфв, аи и ежедневного поста | 288 | list.WriteString("</a></div>") |
| 262 | if data.NSFW { | ||
| 263 | list.WriteString(` [<span class="nsfw">NSFW</span>]`) | ||
| 264 | } | ||
| 265 | if data.AI { | ||
| 266 | list.WriteString(" [🤖]") | ||
| 267 | } | ||
| 268 | if data.DD { | ||
| 269 | list.WriteString(` [<span class="dd">DD</span>]`) | ||
| 270 | } | 289 | } |
| 271 | |||
| 272 | list.WriteString("</a></div>") | ||
| 273 | } | 290 | } |
| 274 | } | 291 | } |
| 275 | 292 | ||
| 276 | if s.atom { | 293 | if s.Atom { |
| 277 | list.WriteString("</feed>") | 294 | list.WriteString("</feed>") |
| 278 | s.Writer.Write([]byte(list.String())) | 295 | s.Writer.Write([]byte(list.String())) |
| 279 | return "" | 296 | return "" |
| @@ -301,10 +318,10 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { | |||
| 301 | } | 318 | } |
| 302 | cmmts.WriteString(`"><p id="`) | 319 | cmmts.WriteString(`"><p id="`) |
| 303 | cmmts.WriteString(strconv.Itoa(x.ID)) | 320 | cmmts.WriteString(strconv.Itoa(x.ID)) |
| 304 | cmmts.WriteString(`"><img src="/media/`) | 321 | cmmts.WriteString(`"><img src="`) |
| 305 | cmmts.WriteString(x.User.Username) | 322 | cmmts.WriteString(UrlBuilder("media", "emojitar", x.User.Username, "?type=a")) |
| 306 | cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/group_user?type=about&q=`) | 323 | cmmts.WriteString(`" width="30px" height="30px"><a href="`) |
| 307 | cmmts.WriteString(x.User.Username) | 324 | cmmts.WriteString(UrlBuilder("group_user", "?q=", x.User.Username, "&type=a")) |
| 308 | cmmts.WriteString(`"><b`) | 325 | cmmts.WriteString(`"><b`) |
| 309 | cmmts.WriteString(` class="`) | 326 | cmmts.WriteString(` class="`) |
| 310 | if x.User.Banned { | 327 | if x.User.Banned { |
| @@ -346,3 +363,101 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { | |||
| 346 | cmmts.WriteString("</details>") | 363 | cmmts.WriteString("</details>") |
| 347 | return cmmts.String() | 364 | return cmmts.String() |
| 348 | } | 365 | } |
| 366 | |||
| 367 | func (s skunkyart) ParseMedia(media devianter.Media) string { | ||
| 368 | url := devianter.UrlFromMedia(media) | ||
| 369 | if len(url) != 0 { | ||
| 370 | url = url[21:] | ||
| 371 | dot := strings.Index(url, ".") | ||
| 372 | |||
| 373 | return UrlBuilder("media", "file", url[:dot], "/", url[dot+10:]) | ||
| 374 | } | ||
| 375 | return "" | ||
| 376 | } | ||
| 377 | |||
| 378 | func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { | ||
| 379 | var url strings.Builder | ||
| 380 | url.WriteString("https://images-wixmp-") | ||
| 381 | url.WriteString(subdomain) | ||
| 382 | url.WriteString(".wixmp.com/") | ||
| 383 | url.WriteString(path) | ||
| 384 | url.WriteString("?token=") | ||
| 385 | url.WriteString(s.Args.Get("token")) | ||
| 386 | |||
| 387 | download := func() (body []byte, status int, headers http.Header) { | ||
| 388 | cli := &http.Client{} | ||
| 389 | if CFG.WixmpProxy != "" { | ||
| 390 | u, e := u.Parse(CFG.WixmpProxy) | ||
| 391 | err(e) | ||
| 392 | cli.Transport = &http.Transport{Proxy: http.ProxyURL(u)} | ||
| 393 | } | ||
| 394 | |||
| 395 | req, e := http.NewRequest("GET", url.String(), nil) | ||
| 396 | err(e) | ||
| 397 | req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0.0") | ||
| 398 | |||
| 399 | resp, e := cli.Do(req) | ||
| 400 | err(e) | ||
| 401 | defer resp.Body.Close() | ||
| 402 | |||
| 403 | b, e := io.ReadAll(resp.Body) | ||
| 404 | err(e) | ||
| 405 | return b, resp.StatusCode, resp.Header | ||
| 406 | } | ||
| 407 | |||
| 408 | if CFG.Cache.Enabled { | ||
| 409 | os.Mkdir(CFG.Cache.Path, 0700) | ||
| 410 | fname := CFG.Cache.Path + "/" + base64.StdEncoding.EncodeToString([]byte(subdomain+path)) | ||
| 411 | file, e := os.Open(fname) | ||
| 412 | |||
| 413 | if e != nil { | ||
| 414 | b, status, headers := download() | ||
| 415 | if status == 200 && headers["Content-Type"][0][:5] == "image" { | ||
| 416 | err(os.WriteFile(fname, b, 0700)) | ||
| 417 | s.Writer.Write(b) | ||
| 418 | } | ||
| 419 | } else { | ||
| 420 | file, e := io.ReadAll(file) | ||
| 421 | err(e) | ||
| 422 | s.Writer.Write(file) | ||
| 423 | } | ||
| 424 | } else if CFG.Proxy { | ||
| 425 | b, _, _ := download() | ||
| 426 | s.Writer.Write(b) | ||
| 427 | } else { | ||
| 428 | s.Writer.Write([]byte("Sorry, butt proxy on this instance disabled.")) | ||
| 429 | } | ||
| 430 | } | ||
| 431 | |||
| 432 | func InitCacheSystem() { | ||
| 433 | c := &CFG.Cache | ||
| 434 | for { | ||
| 435 | dir, e := os.Open(c.Path) | ||
| 436 | err(e) | ||
| 437 | stat, e := dir.Stat() | ||
| 438 | err(e) | ||
| 439 | |||
| 440 | dirnames, e := dir.Readdirnames(-1) | ||
| 441 | err(e) | ||
| 442 | for _, a := range dirnames { | ||
| 443 | a = c.Path + "/" + a | ||
| 444 | if c.Lifetime != 0 { | ||
| 445 | now := time.Now().UnixMilli() | ||
| 446 | |||
| 447 | f, _ := os.Stat(a) | ||
| 448 | stat := f.Sys().(*syscall.Stat_t) | ||
| 449 | time := time.Unix(stat.Ctim.Unix()).UnixMilli() | ||
| 450 | |||
| 451 | if time+c.Lifetime <= now { | ||
| 452 | os.RemoveAll(a) | ||
| 453 | } | ||
| 454 | } | ||
| 455 | if c.MaxSize != 0 && stat.Size() > c.MaxSize { | ||
| 456 | os.RemoveAll(a) | ||
| 457 | } | ||
| 458 | } | ||
| 459 | |||
| 460 | dir.Close() | ||
| 461 | time.Sleep(time.Second) | ||
| 462 | } | ||
| 463 | } | ||
app/wraper.go +58 −33
| @@ -15,13 +15,27 @@ import ( | |||
| 15 | var wr = io.WriteString | 15 | var wr = io.WriteString |
| 16 | 16 | ||
| 17 | type skunkyart struct { | 17 | type skunkyart struct { |
| 18 | Writer http.ResponseWriter | 18 | Writer http.ResponseWriter |
| 19 | Args url.Values | 19 | Args url.Values |
| 20 | Type rune | 20 | BasePath string |
| 21 | Query string | 21 | Type rune |
| 22 | Page int | 22 | Query, QueryRaw string |
| 23 | atom bool | 23 | Page int |
| 24 | Templates struct { | 24 | Atom bool |
| 25 | Templates struct { | ||
| 26 | About struct { | ||
| 27 | Proxy bool | ||
| 28 | Nsfw bool | ||
| 29 | } | ||
| 30 | |||
| 31 | SomeList string | ||
| 32 | Deviation struct { | ||
| 33 | Post devianter.Post | ||
| 34 | StringTime string | ||
| 35 | Tags string | ||
| 36 | Comments string | ||
| 37 | } | ||
| 38 | |||
| 25 | GroupUser struct { | 39 | GroupUser struct { |
| 26 | GR devianter.GRuser | 40 | GR devianter.GRuser |
| 27 | Admins string | 41 | Admins string |
| @@ -51,6 +65,19 @@ type skunkyart struct { | |||
| 51 | } | 65 | } |
| 52 | } | 66 | } |
| 53 | 67 | ||
| 68 | // var Templates struct { | ||
| 69 | // Index string | ||
| 70 | // About string | ||
| 71 | // | ||
| 72 | // GRuser string | ||
| 73 | // Deviation string | ||
| 74 | // List string | ||
| 75 | // Search string | ||
| 76 | // } | ||
| 77 | |||
| 78 | // //go:embed ../html/* | ||
| 79 | // var Templates embed.FS | ||
| 80 | |||
| 54 | func (s skunkyart) GRUser() { | 81 | func (s skunkyart) GRUser() { |
| 55 | if len(s.Query) < 1 { | 82 | if len(s.Query) < 1 { |
| 56 | s.ReturnHTTPError(400) | 83 | s.ReturnHTTPError(400) |
| @@ -65,7 +92,7 @@ func (s skunkyart) GRUser() { | |||
| 65 | switch s.Type { | 92 | switch s.Type { |
| 66 | case 'a': | 93 | case 'a': |
| 67 | g := group.GR | 94 | g := group.GR |
| 68 | s.atom = false | 95 | s.Atom = false |
| 69 | for _, x := range g.Gruser.Page.Modules { | 96 | for _, x := range g.Gruser.Page.Modules { |
| 70 | switch x.Name { | 97 | switch x.Name { |
| 71 | case "about", "group_about": | 98 | case "about", "group_about": |
| @@ -110,17 +137,15 @@ func (s skunkyart) GRUser() { | |||
| 110 | 137 | ||
| 111 | case "cover_deviation": | 138 | case "cover_deviation": |
| 112 | group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation | 139 | group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation |
| 113 | group.About.BG = devianter.UrlFromMedia(group.About.BGMeta.Media) | 140 | group.About.BGMeta.Url = s.ConvertDeviantArtUrlToSkunkyArt(group.About.BGMeta.Url) |
| 141 | group.About.BG = s.ParseMedia(group.About.BGMeta.Media) | ||
| 114 | case "group_admins": | 142 | case "group_admins": |
| 115 | var htm strings.Builder | 143 | var htm strings.Builder |
| 116 | for _, z := range x.ModuleData.GroupAdmins.Results { | 144 | for _, z := range x.ModuleData.GroupAdmins.Results { |
| 117 | htm.WriteString(`<p><img src="`) | 145 | htm.WriteString(`<p><img src="`) |
| 118 | htm.WriteString("/media/") | 146 | htm.WriteString(UrlBuilder("media", "emojitar", z.User.Username, "?type=a")) |
| 119 | htm.WriteString(z.User.Username) | ||
| 120 | htm.WriteString("?type=a") | ||
| 121 | htm.WriteString(`"><a href="`) | 147 | htm.WriteString(`"><a href="`) |
| 122 | htm.WriteString("/group_user?type=about&q=") | 148 | htm.WriteString(UrlBuilder("group_user", "?type=about&q=", z.User.Username)) |
| 123 | htm.WriteString(z.User.Username) | ||
| 124 | htm.WriteString(`">`) | 149 | htm.WriteString(`">`) |
| 125 | htm.WriteString(z.User.Username) | 150 | htm.WriteString(z.User.Username) |
| 126 | htm.WriteString(`</a></p>`) | 151 | htm.WriteString(`</a></p>`) |
| @@ -175,24 +200,18 @@ func (s skunkyart) GRUser() { | |||
| 175 | s.ReturnHTTPError(400) | 200 | s.ReturnHTTPError(400) |
| 176 | } | 201 | } |
| 177 | 202 | ||
| 178 | if !s.atom { | 203 | if !s.Atom { |
| 179 | s.ExecuteTemplate("html/gruser.htm", &s) | 204 | s.ExecuteTemplate("html/gruser.htm", &s) |
| 180 | } | 205 | } |
| 181 | } | 206 | } |
| 182 | 207 | ||
| 183 | // посты | 208 | // посты |
| 184 | func (s skunkyart) Deviation(author, postname string) { | 209 | func (s skunkyart) Deviation(author, postname string) { |
| 185 | // поиск ID | 210 | id_search := regexp.MustCompile("[0-9]+").FindAllString(postname, -1) |
| 186 | re := regexp.MustCompile("[0-9]+").FindAllString(postname, -1) | 211 | if len(id_search) >= 1 { |
| 187 | if len(re) >= 1 { | 212 | post := &s.Templates.Deviation |
| 188 | var post struct { | ||
| 189 | Post devianter.Post | ||
| 190 | StringTime string | ||
| 191 | Tags string | ||
| 192 | Comments string | ||
| 193 | } | ||
| 194 | 213 | ||
| 195 | id := re[len(re)-1] | 214 | id := id_search[len(id_search)-1] |
| 196 | post.Post = devianter.DeviationFunc(id, author) | 215 | post.Post = devianter.DeviationFunc(id, author) |
| 197 | 216 | ||
| 198 | post.Post.Description = ParseDescription(post.Post.Deviation.TextContent) | 217 | post.Post.Description = ParseDescription(post.Post.Deviation.TextContent) |
| @@ -202,9 +221,9 @@ func (s skunkyart) Deviation(author, postname string) { | |||
| 202 | // хештэги | 221 | // хештэги |
| 203 | for _, x := range post.Post.Deviation.Extended.Tags { | 222 | for _, x := range post.Post.Deviation.Extended.Tags { |
| 204 | var tag strings.Builder | 223 | var tag strings.Builder |
| 205 | tag.WriteString(` <a href="/search?q=`) | 224 | tag.WriteString(` <a href="`) |
| 206 | tag.WriteString(x.Name) | 225 | tag.WriteString(UrlBuilder("search", "?q=", x.Name, "&type=tag")) |
| 207 | tag.WriteString(`&type=tag">#`) | 226 | tag.WriteString(`">#`) |
| 208 | tag.WriteString(x.Name) | 227 | tag.WriteString(x.Name) |
| 209 | tag.WriteString("</a>") | 228 | tag.WriteString("</a>") |
| 210 | 229 | ||
| @@ -213,7 +232,7 @@ func (s skunkyart) Deviation(author, postname string) { | |||
| 213 | 232 | ||
| 214 | post.Comments = s.ParseComments(devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1)) | 233 | post.Comments = s.ParseComments(devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1)) |
| 215 | 234 | ||
| 216 | s.ExecuteTemplate("html/deviantion.htm", &post) | 235 | s.ExecuteTemplate("html/deviantion.htm", &s) |
| 217 | } else { | 236 | } else { |
| 218 | s.ReturnHTTPError(400) | 237 | s.ReturnHTTPError(400) |
| 219 | } | 238 | } |
| @@ -221,17 +240,17 @@ func (s skunkyart) Deviation(author, postname string) { | |||
| 221 | 240 | ||
| 222 | func (s skunkyart) DD() { | 241 | func (s skunkyart) DD() { |
| 223 | dd := devianter.DailyDeviationsFunc(s.Page) | 242 | dd := devianter.DailyDeviationsFunc(s.Page) |
| 224 | ddparsed := s.DeviationList(dd.Deviations, dlist{ | 243 | s.Templates.SomeList = s.DeviationList(dd.Deviations, dlist{ |
| 225 | Pages: 0, | 244 | Pages: 0, |
| 226 | More: dd.HasMore, | 245 | More: dd.HasMore, |
| 227 | }) | 246 | }) |
| 228 | if !s.atom { | 247 | if !s.Atom { |
| 229 | s.ExecuteTemplate("html/list.htm", &ddparsed) | 248 | s.ExecuteTemplate("html/list.htm", &s) |
| 230 | } | 249 | } |
| 231 | } | 250 | } |
| 232 | 251 | ||
| 233 | func (s skunkyart) Search() { | 252 | func (s skunkyart) Search() { |
| 234 | s.atom = false | 253 | s.Atom = false |
| 235 | var e error | 254 | var e error |
| 236 | ss := &s.Templates.Search | 255 | ss := &s.Templates.Search |
| 237 | switch s.Type { | 256 | switch s.Type { |
| @@ -263,3 +282,9 @@ func (s skunkyart) Emojitar(name string) { | |||
| 263 | s.ReturnHTTPError(400) | 282 | s.ReturnHTTPError(400) |
| 264 | } | 283 | } |
| 265 | } | 284 | } |
| 285 | |||
| 286 | func (s skunkyart) About() { | ||
| 287 | s.Templates.About.Nsfw = CFG.Nsfw | ||
| 288 | s.Templates.About.Proxy = CFG.Proxy | ||
| 289 | s.ExecuteTemplate("html/about.htm", &s) | ||
| 290 | } | ||
atom.xml deleted −43
| @@ -1,43 +0,0 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <feed xmlns:yt="http://www.youtube.com/xml/schemas/2015" xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"> | ||
| 3 | <link rel="self" href="https://inv.clovius.club/feed/channel/UCuxpxCCevIlF-k-K5YU8XPA"/> | ||
| 4 | <id>yt:channel:UCuxpxCCevIlF-k-K5YU8XPA</id> | ||
| 5 | <yt:channelId>UCuxpxCCevIlF-k-K5YU8XPA</yt:channelId> | ||
| 6 | <icon>https://yt3.googleusercontent.com/ytc/AIdro_n7bKr9iusclFjvYw3U8UR74iTCoMcdGTKI3h55SOG8mSsY=s900-c-k-c0x00ffffff-no-rj</icon> | ||
| 7 | <title>Test</title> | ||
| 8 | <link rel="alternate" href="https://skunky.ebloid.ru"/> | ||
| 9 | <author> | ||
| 10 | <name>lost+skunk</name> | ||
| 11 | <uri>https://skunky.ebloid.ru</uri> | ||
| 12 | </author> | ||
| 13 | <image> | ||
| 14 | <url>https://ebloid.ru/_matrix/media/v3/download/ebloid.ru/enfAtfdjzCQMRNBgLXmiURfJ?allow_redirect=true</url> | ||
| 15 | <title>lost+skunk</title> | ||
| 16 | <link rel="self" href="https://skunky.ebloid.ru"/> | ||
| 17 | </image> | ||
| 18 | <entry> | ||
| 19 | <title>Test</title> | ||
| 20 | <link rel="alternate" href="https://2ip.ua"/> | ||
| 21 | <author> | ||
| 22 | <name>lost+skunk</name> | ||
| 23 | <uri>https://skunky.ebloid.ru</uri> | ||
| 24 | </author> | ||
| 25 | <content type="xhtml"> | ||
| 26 | <div xmlns="http://www.w3.org/1999/xhtml"> | ||
| 27 | <a href="https://2ip.ua"> | ||
| 28 | <img src="https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/526e735c-e3da-4542-a339-72ccdf827a00/dh3mfuq-5bd6472c-68f4-428d-bb90-8d461fc21410.jpg/v1/fill/w_1920,h_1080/image.gif?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9MTA4MCIsInBhdGgiOiJcL2ZcLzUyNmU3MzVjLWUzZGEtNDU0Mi1hMzM5LTcyY2NkZjgyN2EwMFwvZGgzbWZ1cS01YmQ2NDcyYy02OGY0LTQyOGQtYmI5MC04ZDQ2MWZjMjE0MTAuanBnIiwid2lkdGgiOiI8PTE5MjAifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.S_1DGdd29F5l6Y46Q_bumtGlEEUf_WawZsmF8kBygBs"/> | ||
| 29 | </a> | ||
| 30 | <p>test</p> | ||
| 31 | </div> | ||
| 32 | </content> | ||
| 33 | <published>2024-07-04T00:37:55+00:00</published> | ||
| 34 | <media:group> | ||
| 35 | <media:title>GM Just Announced Their Shutting Down Production in America and Firing Their Workers</media:title> | ||
| 36 | <media:thumbnail url="https://ebloid.ru/_matrix/media/v3/download/ebloid.ru/enfAtfdjzCQMRNBgLXmiURfJ?allow_redirect=true" width="320" height="180"/> | ||
| 37 | <media:description>test</media:description> | ||
| 38 | </media:group> | ||
| 39 | <media:community> | ||
| 40 | <media:statistics views="91247"/> | ||
| 41 | </media:community> | ||
| 42 | </entry> | ||
| 43 | </feed> | ||
config.example.json +4 −3
| @@ -1,12 +1,13 @@ | |||
| 1 | { | 1 | { |
| 2 | "listen": "0.0.0.0:3003", | 2 | "listen": "0.0.0.0:3003", |
| 3 | "base_uri": null, | 3 | "base-path": "/skunky/", |
| 4 | "cache": { | 4 | "cache": { |
| 5 | "enabled": true, | 5 | "enabled": true, |
| 6 | "path": "cache", | 6 | "path": "cache", |
| 7 | "lifetime": null, | 7 | "lifetime": null, |
| 8 | "max_size": 10000 | 8 | "max-size": 100000 |
| 9 | }, | 9 | }, |
| 10 | "proxy": false, | 10 | "proxy": true, |
| 11 | "wixmp-proxy": "http://127.0.0.1:8080", | ||
| 11 | "nsfw": false | 12 | "nsfw": false |
| 12 | }\ No newline at end of file | 13 | }\ No newline at end of file |
css/skunky.css +4 −1
| @@ -28,9 +28,12 @@ form input, button, select { | |||
| 28 | border: 0px; | 28 | border: 0px; |
| 29 | border-radius: 1px; | 29 | border-radius: 1px; |
| 30 | } | 30 | } |
| 31 | .nsfw, .true { | 31 | .nsfw, .about-false { |
| 32 | color: red; | 32 | color: red; |
| 33 | } | 33 | } |
| 34 | .about-true { | ||
| 35 | color: green; | ||
| 36 | } | ||
| 34 | .author { | 37 | .author { |
| 35 | color: seagreen; | 38 | color: seagreen; |
| 36 | } | 39 | } |
html/about.htm +9 −3
| @@ -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="gui/css/skunky.css"> | 5 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> |
| 6 | </head> | 6 | </head> |
| 7 | <main> | 7 | <main> |
| 8 | <header> | 8 | <header> |
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | 9 | <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1> |
| 10 | <form method="get" action="/search"> | 10 | <form method="get" action="{{.BasePath}}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> |
| @@ -19,5 +19,11 @@ | |||
| 19 | <p> | 19 | <p> |
| 20 | SkunkyArt is an alternative frontend for deviantart.com, written in Go. | 20 | SkunkyArt is an alternative frontend for deviantart.com, written in Go. |
| 21 | </p> | 21 | </p> |
| 22 | <h2>Instance settings:</h2> | ||
| 23 | <ul> | ||
| 24 | <li><b>NSFW</b>: <span class="about-{{.Templates.About.Nsfw}}">{{if .Templates.About.Nsfw}}YES{{else}}NO{{end}}</span></li> | ||
| 25 | <li><b>Proxyfing</b>: <span class="about-{{.Templates.About.Proxy}}">{{if .Templates.About.Proxy}}YES{{else}}NO{{end}}</span></li> | ||
| 26 | </ul> | ||
| 27 | <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" target="_blank">SkunkyArt v1.3</a></p> | ||
| 22 | </main> | 28 | </main> |
| 23 | </html>\ No newline at end of file | 29 | </html>\ No newline at end of file |
html/deviantion.htm +19 −19
| @@ -1,13 +1,13 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html> | 2 | <html> |
| 3 | <head> | 3 | <head> |
| 4 | <title>SkunkyArt | {{.Post.Deviation.Author.Username}} - {{.Post.Deviation.Title}}</title> | 4 | <title>SkunkyArt | {{.Templates.Deviation.Post.Deviation.Author.Username}} - {{.Templates.Deviation.Post.Deviation.Title}}</title> |
| 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> | 5 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> |
| 6 | </head> | 6 | </head> |
| 7 | <main> | 7 | <main> |
| 8 | <header> | 8 | <header> |
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | 9 | <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1> |
| 10 | <form method="get" action="/search"> | 10 | <form method="get" action="{{.BasePath}}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> |
| @@ -17,29 +17,29 @@ | |||
| 17 | </form> | 17 | </form> |
| 18 | </header> | 18 | </header> |
| 19 | <figure> | 19 | <figure> |
| 20 | <img src="/media/{{.Post.Deviation.Author.Username}}?type=a" width="30px"> | 20 | <img src="{{.BasePath}}media/emojitar/{{.Templates.Deviation.Post.Deviation.Author.Username}}?type=a" width="30px"> |
| 21 | <span><strong><a href="/group_user?type=about&q={{.Post.Deviation.Author.Username}}">{{.Post.Deviation.Author.Username}}</a></strong> — {{if (.Post.Deviation.DD)}} | 21 | <span><strong><a href="{{.BasePath}}group_user?type=about&q={{.Templates.Deviation.Post.Deviation.Author.Username}}">{{.Templates.Deviation.Post.Deviation.Author.Username}}</a></strong> — {{if (.Templates.Deviation.Post.Deviation.DD)}} |
| 22 | <span class="dd" title="Daily Deviation!"><b>{{.Post.Deviation.Title}}</b></span> | 22 | <span class="dd" title="Daily Deviation!"><b>{{.Templates.Deviation.Post.Deviation.Title}}</b></span> |
| 23 | {{else}}{{.Post.Deviation.Title}}{{end}} | 23 | {{else}}{{.Templates.Deviation.Post.Deviation.Title}}{{end}} |
| 24 | {{if (ne .Post.Deviation.License "none")}}<mark title="License">{{.Post.Deviation.License}}</mark>{{end}} {{if (.Post.Deviation.AI)}}[🤖]{{end}} | 24 | {{if (ne .Templates.Deviation.Post.Deviation.License "none")}}<mark title="License">{{.Templates.Deviation.Post.Deviation.License}}</mark>{{end}} {{if (.Templates.Deviation.Post.Deviation.AI)}}[🤖]{{end}} |
| 25 | {{if (.Post.Deviation.NSFW)}}[<span class="nsfw">NSFW</span>]{{end}} | 25 | {{if (.Templates.Deviation.Post.Deviation.NSFW)}}[<span class="nsfw">NSFW</span>]{{end}} |
| 26 | </span> | 26 | </span> |
| 27 | <br> | 27 | <br> |
| 28 | {{if (ne .Post.IMG "")}} | 28 | {{if (ne .Templates.Deviation.Post.IMG "")}} |
| 29 | <a href="{{.Post.IMG}}" title="open/download image"><img src="{{.Post.IMG}}" width="50%"></a> | 29 | <a href="{{.Templates.Deviation.Post.IMG}}" title="open/download image"><img src="{{.Templates.Deviation.Post.IMG}}" width="50%"></a> |
| 30 | <br> | 30 | <br> |
| 31 | {{end}} | 31 | {{end}} |
| 32 | {{if (ne .Tags "")}} | 32 | {{if (ne .Templates.Deviation.Tags "")}} |
| 33 | {{.Tags}}<br> | 33 | {{.Templates.Deviation.Tags}}<br> |
| 34 | {{end}} | 34 | {{end}} |
| 35 | <span>Published: <strong>{{.StringTime}}</strong>; Views: <strong>{{.Post.Deviation.Stats.Views}}</strong>; Favourites: <strong>{{.Post.Deviation.Stats.Favourites}}</strong>; Downloads: <strong>{{.Post.Deviation.Stats.Downloads}}</strong></span> | 35 | <span>Published: <strong>{{.Templates.Deviation.StringTime}}</strong>; Views: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Views}}</strong>; Favourites: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Favourites}}</strong>; Downloads: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Downloads}}</strong></span> |
| 36 | {{if (ne .Post.Description "")}} | 36 | {{if (ne .Templates.Deviation.Post.Description "")}} |
| 37 | <figcaption> | 37 | <figcaption> |
| 38 | {{.Post.Description}} | 38 | {{.Templates.Deviation.Post.Description}} |
| 39 | </figcaption> | 39 | </figcaption> |
| 40 | {{end}} | 40 | {{end}} |
| 41 | {{if (ne .Comments "")}} | 41 | {{if (ne .Templates.Deviation.Comments "")}} |
| 42 | {{.Comments}} | 42 | {{.Templates.Deviation.Comments}} |
| 43 | {{end}} | 43 | {{end}} |
| 44 | </figure> | 44 | </figure> |
| 45 | </main> | 45 | </main> |
html/gruser.htm +4 −4
| @@ -8,14 +8,14 @@ | |||
| 8 | gallery of {{.Templates.GroupUser.GR.Owner.Username}} | 8 | gallery of {{.Templates.GroupUser.GR.Owner.Username}} |
| 9 | {{end}} | 9 | {{end}} |
| 10 | </title> | 10 | </title> |
| 11 | <link rel="stylesheet" href="/gui/css/skunky.css"> | 11 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> |
| 12 | </head> | 12 | </head> |
| 13 | <main> | 13 | <main> |
| 14 | <header> | 14 | <header> |
| 15 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a> | 15 | <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a> |
| 16 | | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a> | 16 | | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a> |
| 17 | | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a></h1> | 17 | | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a></h1> |
| 18 | <form method="get" action="/search"> | 18 | <form method="get" action="{{.BasePath}}search"> |
| 19 | <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | 19 | <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
| 20 | <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}"> | 20 | <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}"> |
| 21 | <select name="type"> | 21 | <select name="type"> |
| @@ -32,7 +32,7 @@ | |||
| 32 | {{.Templates.GroupUser.About.BGMeta.Author.Username}} - {{end}}{{.Templates.GroupUser.About.BGMeta.Title}}" src="{{.Templates.GroupUser.About.BG}}"></a> | 32 | {{.Templates.GroupUser.About.BGMeta.Author.Username}} - {{end}}{{.Templates.GroupUser.About.BGMeta.Title}}" src="{{.Templates.GroupUser.About.BG}}"></a> |
| 33 | {{end}} | 33 | {{end}} |
| 34 | 34 | ||
| 35 | <img src="/media/{{.Templates.GroupUser.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.Templates.GroupUser.GR.Owner.Username}}</b> | 35 | <img src="{{.BasePath}}media/emojitar/{{.Templates.GroupUser.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.Templates.GroupUser.GR.Owner.Username}}</b> |
| 36 | {{if (eq .Templates.GroupUser.About.A.Gender "male")}} | 36 | {{if (eq .Templates.GroupUser.About.A.Gender "male")}} |
| 37 | ♂️ | 37 | ♂️ |
| 38 | {{end}} | 38 | {{end}} |
html/index.htm +3 −3
| @@ -2,11 +2,11 @@ | |||
| 2 | <html> | 2 | <html> |
| 3 | <head> | 3 | <head> |
| 4 | <title>SkunkyArt</title> | 4 | <title>SkunkyArt</title> |
| 5 | <link rel="stylesheet" href="gui/css/skunky.css" | 5 | <link rel="stylesheet" href="{{.}}gui/css/skunky.css"/> |
| 6 | </head> | 6 | </head> |
| 7 | <main> | 7 | <main> |
| 8 | <center> | 8 | <center> |
| 9 | <form method="get" action="search"> | 9 | <form method="get" action="{{.}}search"> |
| 10 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | 10 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
| 11 | <select name="type"> | 11 | <select name="type"> |
| 12 | <option value="all">All</option> | 12 | <option value="all">All</option> |
| @@ -14,7 +14,7 @@ | |||
| 14 | </select> | 14 | </select> |
| 15 | <button type="submit">Search!</button> | 15 | <button type="submit">Search!</button> |
| 16 | </form> | 16 | </form> |
| 17 | <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> | 17 | <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> |
| 18 | </center> | 18 | </center> |
| 19 | </main> | 19 | </main> |
| 20 | </html>\ No newline at end of file | 20 | </html>\ No newline at end of file |
html/list.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="gui/css/skunky.css"> | 5 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> |
| 6 | </head> | 6 | </head> |
| 7 | <main> | 7 | <main> |
| 8 | <header> | 8 | <header> |
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a> | <a href="?atom=true">RSS</a></h1> | 9 | <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a> | <a href="?atom=true">RSS</a></h1> |
| 10 | <form method="get" action="/search"> | 10 | <form method="get" action="{{.BasePath}}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,6 +16,6 @@ | |||
| 16 | <button type="submit">Search!</button> | 16 | <button type="submit">Search!</button> |
| 17 | </form> | 17 | </form> |
| 18 | </header> | 18 | </header> |
| 19 | {{.}} | 19 | {{.Templates.SomeList}} |
| 20 | </main> | 20 | </main> |
| 21 | </html>\ No newline at end of file | 21 | </html>\ No newline at end of file |
html/search.htm +3 −3
| @@ -1,12 +1,12 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html> | 2 | <html> |
| 3 | <head> | 3 | <head> |
| 4 | <title>SkunkyArt | Search "{{.Query}}"</title> | 4 | <title>SkunkyArt | Search "{{.QueryRaw}}"</title> |
| 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> | 5 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> |
| 6 | </head> | 6 | </head> |
| 7 | <main> | 7 | <main> |
| 8 | <header> | 8 | <header> |
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | 9 | <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1> |
| 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"> |
main.go +1
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | ) | 7 | ) |
| 8 | 8 | ||
| 9 | func main() { | 9 | func main() { |
| 10 | app.ExecuteConfig() | ||
| 10 | err := devianter.UpdateCSRF() | 11 | err := devianter.UpdateCSRF() |
| 11 | if err != nil { | 12 | if err != nil { |
| 12 | println(err.Error()) | 13 | println(err.Error()) |