Commit 7f1ab1b73d
Unsigned
Layout: unified · split
app/config.go +50 −19
| @@ -1,26 +1,31 @@ | ||
| 1 | 1 | package app |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "encoding/json" | |
| 5 | "errors" | |
| 4 | 6 | "os" |
| 5 | 7 | ) |
| 6 | 8 | |
| 7 | 9 | type cache_config struct { |
| 8 | Enabled bool | |
| 9 | Path string | |
| 10 | Max_size, Lifetime int64 | |
| 10 | Enabled bool | |
| 11 | Path string | |
| 12 | MaxSize int64 `json:"max-size"` | |
| 13 | Lifetime int64 | |
| 11 | 14 | } |
| 12 | 15 | |
| 13 | 16 | type config struct { |
| 14 | cfg string | |
| 15 | Listen, Base_uri string | |
| 16 | Cache cache_config | |
| 17 | Proxy, Nsfw bool | |
| 17 | cfg string | |
| 18 | Listen string | |
| 19 | BasePath string `json:"base-path"` | |
| 20 | Cache cache_config | |
| 21 | Proxy, Nsfw bool | |
| 22 | WixmpProxy string `json:"wixmp-proxy"` | |
| 18 | 23 | } |
| 19 | 24 | |
| 20 | 25 | var CFG = config{ |
| 21 | 26 | cfg: "config.json", |
| 22 | 27 | Listen: "127.0.0.1:3003", |
| 23 | Base_uri: "/", | |
| 28 | BasePath: "/", | |
| 24 | 29 | Cache: cache_config{ |
| 25 | 30 | Enabled: true, |
| 26 | 31 | Path: "cache", |
| @@ -29,20 +34,46 @@ var CFG = config{ | ||
| 29 | 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 | 45 | a := os.Args |
| 34 | for num, val := range a { | |
| 35 | switch val { | |
| 36 | case "-conf": | |
| 37 | CFG.cfg = a[num] | |
| 38 | case "-help": | |
| 39 | println(`SkunkyArt v 1.3 [refactoring] | |
| 46 | if l := len(a); l > 1 { | |
| 47 | switch a[1] { | |
| 48 | case "-c", "--config": | |
| 49 | if l >= 3 { | |
| 50 | CFG.cfg = a[2] | |
| 51 | } else { | |
| 52 | try(errors.New("Not enought arguments"), 1) | |
| 53 | } | |
| 54 | case "-h", "--help": | |
| 55 | try(errors.New(`SkunkyArt v1.3 [refactoring] | |
| 40 | 56 | Usage: |
| 41 | - -conf - path to config | |
| 42 | - -help this message | |
| 57 | - [-c|--config] - path to config | |
| 58 | - [-h|--help] - returns this message | |
| 43 | 59 | Example: |
| 44 | ./skunkyart -conf config.json | |
| 45 | Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`) | |
| 60 | ./skunkyart -c config.json | |
| 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 | 9 | "strings" |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | const addr string = "0.0.0.0:3003" | |
| 13 | ||
| 14 | // роутер | |
| 15 | 12 | func Router() { |
| 16 | 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 | 20 | parsedpath := make(map[int]string) |
| 18 | 21 | for x := 0; true; x++ { |
| 19 | 22 | slash := strings.Index(path, "/") + 1 |
| @@ -52,16 +55,20 @@ func Router() { | ||
| 52 | 55 | var skunky skunkyart |
| 53 | 56 | skunky.Args = r.URL.Query() |
| 54 | 57 | skunky.Writer = w |
| 58 | skunky.BasePath = CFG.BasePath | |
| 55 | 59 | |
| 56 | 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 | 64 | if t := arg("type"); len(t) > 0 { |
| 59 | 65 | skunky.Type = rune(t[0]) |
| 60 | 66 | } |
| 61 | 67 | p, _ := strconv.Atoi(arg("p")) |
| 62 | 68 | skunky.Page = p |
| 69 | ||
| 63 | 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 | 76 | default: |
| 70 | 77 | skunky.ReturnHTTPError(404) |
| 71 | 78 | case "": |
| 72 | open_n_send("html/index.htm") | |
| 79 | skunky.ExecuteTemplate("html/index.htm", &CFG.BasePath) | |
| 73 | 80 | case "post": |
| 74 | 81 | skunky.Deviation(path[2], path[3]) |
| 75 | 82 | case "search": |
| @@ -80,9 +87,14 @@ func Router() { | ||
| 80 | 87 | skunky.GRUser() |
| 81 | 88 | |
| 82 | 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 | 96 | case "about": |
| 85 | open_n_send("html/about.htm") | |
| 97 | skunky.About() | |
| 86 | 98 | case "gui": |
| 87 | 99 | w.Header().Add("content-type", "text/css") |
| 88 | 100 | open_n_send(next(path, 2)) |
| @@ -90,7 +102,7 @@ func Router() { | ||
| 90 | 102 | } |
| 91 | 103 | |
| 92 | 104 | http.HandleFunc("/", handle) |
| 93 | http.ListenAndServe(addr, nil) | |
| 105 | http.ListenAndServe(CFG.Listen, nil) | |
| 94 | 106 | } |
| 95 | 107 | |
| 96 | 108 | func err(e error) { |
app/util.go +186 −71
| @@ -1,11 +1,17 @@ | ||
| 1 | 1 | package app |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "encoding/base64" | |
| 4 | 5 | "encoding/json" |
| 6 | "io" | |
| 5 | 7 | "net/http" |
| 8 | u "net/url" | |
| 9 | "os" | |
| 6 | 10 | "strconv" |
| 7 | 11 | "strings" |
| 12 | "syscall" | |
| 8 | 13 | "text/template" |
| 14 | "time" | |
| 9 | 15 | |
| 10 | 16 | "git.macaw.me/skunky/devianter" |
| 11 | 17 | ) |
| @@ -19,11 +25,15 @@ func (s skunkyart) ExecuteTemplate(file string, data any) { | ||
| 19 | 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 | 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 | 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 | 38 | return str.String() |
| 29 | 39 | } |
| @@ -31,10 +41,10 @@ func (s skunkyart) UrlBuilder(strs ...string) string { | ||
| 31 | 41 | func (s skunkyart) ReturnHTTPError(status int) { |
| 32 | 42 | s.Writer.WriteHeader(status) |
| 33 | 43 | |
| 34 | // пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения | |
| 35 | 44 | var msg strings.Builder |
| 36 | msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`) | |
| 37 | msg.WriteString("<h1>") | |
| 45 | msg.WriteString(`<html><link rel="stylesheet" href="`) | |
| 46 | msg.WriteString(UrlBuilder("gui", "css", "skunky.css")) | |
| 47 | msg.WriteString(`" /><h1>`) | |
| 38 | 48 | msg.WriteString(strconv.Itoa(status)) |
| 39 | 49 | msg.WriteString(" - ") |
| 40 | 50 | msg.WriteString(http.StatusText(status)) |
| @@ -43,6 +53,15 @@ func (s skunkyart) ReturnHTTPError(status int) { | ||
| 43 | 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 | 65 | type text struct { |
| 47 | 66 | TXT string |
| 48 | 67 | from int |
| @@ -200,80 +219,78 @@ func (s skunkyart) NavBase(c dlist) string { | ||
| 200 | 219 | |
| 201 | 220 | func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string { |
| 202 | 221 | var list strings.Builder |
| 203 | if !s.atom { | |
| 204 | list.WriteString(`<div class="content">`) | |
| 205 | } else { | |
| 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">`) | |
| 222 | if s.Atom && s.Page > 1 { | |
| 223 | s.ReturnHTTPError(400) | |
| 224 | return "" | |
| 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 | 227 | list.WriteString(`<title>SkunkyArt</title>`) |
| 208 | 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 | 232 | for _, data := range devs { |
| 211 | url := devianter.UrlFromMedia(data.Media) | |
| 212 | if s.atom { | |
| 213 | id := strconv.Itoa(data.ID) | |
| 214 | list.WriteString(`<entry><author><name>`) | |
| 215 | list.WriteString(data.Author.Username) | |
| 216 | list.WriteString(`</name></author><title>`) | |
| 217 | list.WriteString(data.Title) | |
| 218 | list.WriteString(`</title><link rel="alternate" type="text/html" href="`) | |
| 219 | list.WriteString(CFG.Base_uri) | |
| 220 | list.WriteString("/post/") | |
| 221 | list.WriteString(data.Author.Username) | |
| 222 | list.WriteString("/atom-") | |
| 223 | list.WriteString(id) | |
| 224 | list.WriteString(`"/><id>`) | |
| 225 | list.WriteString(id) | |
| 226 | list.WriteString(`</id><published>`) | |
| 227 | list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700")) | |
| 228 | list.WriteString(`</published>`) | |
| 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="`) | |
| 233 | if !(data.NSFW && !CFG.Nsfw) { | |
| 234 | url := s.ParseMedia(data.Media) | |
| 235 | if s.Atom { | |
| 236 | id := strconv.Itoa(data.ID) | |
| 237 | list.WriteString(`<entry><author><name>`) | |
| 238 | list.WriteString(data.Author.Username) | |
| 239 | list.WriteString(`</name></author><title>`) | |
| 240 | list.WriteString(data.Title) | |
| 241 | list.WriteString(`</title><link rel="alternate" type="text/html" href="`) | |
| 242 | list.WriteString(UrlBuilder("post", data.Author.Username, "atom-"+id)) | |
| 243 | list.WriteString(`"/><id>`) | |
| 244 | list.WriteString(id) | |
| 245 | list.WriteString(`</id><published>`) | |
| 246 | list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700")) | |
| 247 | list.WriteString(`</published>`) | |
| 248 | list.WriteString(`<media:group><media:title>`) | |
| 249 | list.WriteString(data.Title) | |
| 250 | list.WriteString(`</media:title><media:thumbinal url="`) | |
| 244 | 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 | 254 | list.WriteString(`"><img src="`) |
| 246 | 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 | 259 | } else { |
| 249 | list.WriteString(`<h1>[ TEXT ]</h1>`) | |
| 250 | } | |
| 251 | list.WriteString(`<br><a href="`) | |
| 252 | list.WriteString("/post/") | |
| 253 | list.WriteString(data.Author.Username) | |
| 254 | list.WriteString("/") | |
| 255 | list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:]) | |
| 256 | list.WriteString(`">`) | |
| 257 | list.WriteString(data.Author.Username) | |
| 258 | list.WriteString(" - ") | |
| 259 | list.WriteString(data.Title) | |
| 260 | list.WriteString(`<div class="block">`) | |
| 261 | if url != "" { | |
| 262 | list.WriteString(`<a title="open/download" href="`) | |
| 263 | list.WriteString(url) | |
| 264 | list.WriteString(`"><img src="`) | |
| 265 | list.WriteString(url) | |
| 266 | list.WriteString(`" width="15%"></a>`) | |
| 267 | } else { | |
| 268 | list.WriteString(`<h1>[ TEXT ]</h1>`) | |
| 269 | } | |
| 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 | // шильдики нсфв, аи и ежедневного поста | |
| 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>]`) | |
| 288 | list.WriteString("</a></div>") | |
| 270 | 289 | } |
| 271 | ||
| 272 | list.WriteString("</a></div>") | |
| 273 | 290 | } |
| 274 | 291 | } |
| 275 | 292 | |
| 276 | if s.atom { | |
| 293 | if s.Atom { | |
| 277 | 294 | list.WriteString("</feed>") |
| 278 | 295 | s.Writer.Write([]byte(list.String())) |
| 279 | 296 | return "" |
| @@ -301,10 +318,10 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { | ||
| 301 | 318 | } |
| 302 | 319 | cmmts.WriteString(`"><p id="`) |
| 303 | 320 | cmmts.WriteString(strconv.Itoa(x.ID)) |
| 304 | cmmts.WriteString(`"><img src="/media/`) | |
| 305 | cmmts.WriteString(x.User.Username) | |
| 306 | cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/group_user?type=about&q=`) | |
| 307 | cmmts.WriteString(x.User.Username) | |
| 321 | cmmts.WriteString(`"><img src="`) | |
| 322 | cmmts.WriteString(UrlBuilder("media", "emojitar", x.User.Username, "?type=a")) | |
| 323 | cmmts.WriteString(`" width="30px" height="30px"><a href="`) | |
| 324 | cmmts.WriteString(UrlBuilder("group_user", "?q=", x.User.Username, "&type=a")) | |
| 308 | 325 | cmmts.WriteString(`"><b`) |
| 309 | 326 | cmmts.WriteString(` class="`) |
| 310 | 327 | if x.User.Banned { |
| @@ -346,3 +363,101 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { | ||
| 346 | 363 | cmmts.WriteString("</details>") |
| 347 | 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 | 15 | var wr = io.WriteString |
| 16 | 16 | |
| 17 | 17 | type skunkyart struct { |
| 18 | Writer http.ResponseWriter | |
| 19 | Args url.Values | |
| 20 | Type rune | |
| 21 | Query string | |
| 22 | Page int | |
| 23 | atom bool | |
| 24 | Templates struct { | |
| 18 | Writer http.ResponseWriter | |
| 19 | Args url.Values | |
| 20 | BasePath string | |
| 21 | Type rune | |
| 22 | Query, QueryRaw string | |
| 23 | Page int | |
| 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 | 39 | GroupUser struct { |
| 26 | 40 | GR devianter.GRuser |
| 27 | 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 | 81 | func (s skunkyart) GRUser() { |
| 55 | 82 | if len(s.Query) < 1 { |
| 56 | 83 | s.ReturnHTTPError(400) |
| @@ -65,7 +92,7 @@ func (s skunkyart) GRUser() { | ||
| 65 | 92 | switch s.Type { |
| 66 | 93 | case 'a': |
| 67 | 94 | g := group.GR |
| 68 | s.atom = false | |
| 95 | s.Atom = false | |
| 69 | 96 | for _, x := range g.Gruser.Page.Modules { |
| 70 | 97 | switch x.Name { |
| 71 | 98 | case "about", "group_about": |
| @@ -110,17 +137,15 @@ func (s skunkyart) GRUser() { | ||
| 110 | 137 | |
| 111 | 138 | case "cover_deviation": |
| 112 | 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 | 142 | case "group_admins": |
| 115 | 143 | var htm strings.Builder |
| 116 | 144 | for _, z := range x.ModuleData.GroupAdmins.Results { |
| 117 | 145 | htm.WriteString(`<p><img src="`) |
| 118 | htm.WriteString("/media/") | |
| 119 | htm.WriteString(z.User.Username) | |
| 120 | htm.WriteString("?type=a") | |
| 146 | htm.WriteString(UrlBuilder("media", "emojitar", z.User.Username, "?type=a")) | |
| 121 | 147 | htm.WriteString(`"><a href="`) |
| 122 | htm.WriteString("/group_user?type=about&q=") | |
| 123 | htm.WriteString(z.User.Username) | |
| 148 | htm.WriteString(UrlBuilder("group_user", "?type=about&q=", z.User.Username)) | |
| 124 | 149 | htm.WriteString(`">`) |
| 125 | 150 | htm.WriteString(z.User.Username) |
| 126 | 151 | htm.WriteString(`</a></p>`) |
| @@ -175,24 +200,18 @@ func (s skunkyart) GRUser() { | ||
| 175 | 200 | s.ReturnHTTPError(400) |
| 176 | 201 | } |
| 177 | 202 | |
| 178 | if !s.atom { | |
| 203 | if !s.Atom { | |
| 179 | 204 | s.ExecuteTemplate("html/gruser.htm", &s) |
| 180 | 205 | } |
| 181 | 206 | } |
| 182 | 207 | |
| 183 | 208 | // посты |
| 184 | 209 | func (s skunkyart) Deviation(author, postname string) { |
| 185 | // поиск ID | |
| 186 | re := regexp.MustCompile("[0-9]+").FindAllString(postname, -1) | |
| 187 | if len(re) >= 1 { | |
| 188 | var post struct { | |
| 189 | Post devianter.Post | |
| 190 | StringTime string | |
| 191 | Tags string | |
| 192 | Comments string | |
| 193 | } | |
| 210 | id_search := regexp.MustCompile("[0-9]+").FindAllString(postname, -1) | |
| 211 | if len(id_search) >= 1 { | |
| 212 | post := &s.Templates.Deviation | |
| 194 | 213 | |
| 195 | id := re[len(re)-1] | |
| 214 | id := id_search[len(id_search)-1] | |
| 196 | 215 | post.Post = devianter.DeviationFunc(id, author) |
| 197 | 216 | |
| 198 | 217 | post.Post.Description = ParseDescription(post.Post.Deviation.TextContent) |
| @@ -202,9 +221,9 @@ func (s skunkyart) Deviation(author, postname string) { | ||
| 202 | 221 | // хештэги |
| 203 | 222 | for _, x := range post.Post.Deviation.Extended.Tags { |
| 204 | 223 | var tag strings.Builder |
| 205 | tag.WriteString(` <a href="/search?q=`) | |
| 206 | tag.WriteString(x.Name) | |
| 207 | tag.WriteString(`&type=tag">#`) | |
| 224 | tag.WriteString(` <a href="`) | |
| 225 | tag.WriteString(UrlBuilder("search", "?q=", x.Name, "&type=tag")) | |
| 226 | tag.WriteString(`">#`) | |
| 208 | 227 | tag.WriteString(x.Name) |
| 209 | 228 | tag.WriteString("</a>") |
| 210 | 229 | |
| @@ -213,7 +232,7 @@ func (s skunkyart) Deviation(author, postname string) { | ||
| 213 | 232 | |
| 214 | 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 | 236 | } else { |
| 218 | 237 | s.ReturnHTTPError(400) |
| 219 | 238 | } |
| @@ -221,17 +240,17 @@ func (s skunkyart) Deviation(author, postname string) { | ||
| 221 | 240 | |
| 222 | 241 | func (s skunkyart) DD() { |
| 223 | 242 | dd := devianter.DailyDeviationsFunc(s.Page) |
| 224 | ddparsed := s.DeviationList(dd.Deviations, dlist{ | |
| 243 | s.Templates.SomeList = s.DeviationList(dd.Deviations, dlist{ | |
| 225 | 244 | Pages: 0, |
| 226 | 245 | More: dd.HasMore, |
| 227 | 246 | }) |
| 228 | if !s.atom { | |
| 229 | s.ExecuteTemplate("html/list.htm", &ddparsed) | |
| 247 | if !s.Atom { | |
| 248 | s.ExecuteTemplate("html/list.htm", &s) | |
| 230 | 249 | } |
| 231 | 250 | } |
| 232 | 251 | |
| 233 | 252 | func (s skunkyart) Search() { |
| 234 | s.atom = false | |
| 253 | s.Atom = false | |
| 235 | 254 | var e error |
| 236 | 255 | ss := &s.Templates.Search |
| 237 | 256 | switch s.Type { |
| @@ -263,3 +282,9 @@ func (s skunkyart) Emojitar(name string) { | ||
| 263 | 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 | 2 | "listen": "0.0.0.0:3003", |
| 3 | "base_uri": null, | |
| 3 | "base-path": "/skunky/", | |
| 4 | 4 | "cache": { |
| 5 | 5 | "enabled": true, |
| 6 | 6 | "path": "cache", |
| 7 | 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 | 12 | "nsfw": false |
| 12 | 13 | } |
| \ No newline at end of file | ||
css/skunky.css +4 −1
| @@ -28,9 +28,12 @@ form input, button, select { | ||
| 28 | 28 | border: 0px; |
| 29 | 29 | border-radius: 1px; |
| 30 | 30 | } |
| 31 | .nsfw, .true { | |
| 31 | .nsfw, .about-false { | |
| 32 | 32 | color: red; |
| 33 | 33 | } |
| 34 | .about-true { | |
| 35 | color: green; | |
| 36 | } | |
| 34 | 37 | .author { |
| 35 | 38 | color: seagreen; |
| 36 | 39 | } |
html/about.htm +9 −3
| @@ -2,12 +2,12 @@ | ||
| 2 | 2 | <html> |
| 3 | 3 | <head> |
| 4 | 4 | <title>SkunkyArt</title> |
| 5 | <link rel="stylesheet" href="gui/css/skunky.css"> | |
| 5 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> | |
| 6 | 6 | </head> |
| 7 | 7 | <main> |
| 8 | 8 | <header> |
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | |
| 10 | <form method="get" action="/search"> | |
| 9 | <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1> | |
| 10 | <form method="get" action="{{.BasePath}}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> |
| @@ -19,5 +19,11 @@ | ||
| 19 | 19 | <p> |
| 20 | 20 | SkunkyArt is an alternative frontend for deviantart.com, written in Go. |
| 21 | 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 | 28 | </main> |
| 23 | 29 | </html> |
| \ No newline at end of file | ||
html/deviantion.htm +19 −19
| @@ -1,13 +1,13 @@ | ||
| 1 | 1 | <!DOCTYPE html> |
| 2 | 2 | <html> |
| 3 | 3 | <head> |
| 4 | <title>SkunkyArt | {{.Post.Deviation.Author.Username}} - {{.Post.Deviation.Title}}</title> | |
| 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> | |
| 4 | <title>SkunkyArt | {{.Templates.Deviation.Post.Deviation.Author.Username}} - {{.Templates.Deviation.Post.Deviation.Title}}</title> | |
| 5 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> | |
| 6 | 6 | </head> |
| 7 | 7 | <main> |
| 8 | 8 | <header> |
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | |
| 10 | <form method="get" action="/search"> | |
| 9 | <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a></h1> | |
| 10 | <form method="get" action="{{.BasePath}}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> |
| @@ -17,29 +17,29 @@ | ||
| 17 | 17 | </form> |
| 18 | 18 | </header> |
| 19 | 19 | <figure> |
| 20 | <img src="/media/{{.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)}} | |
| 22 | <span class="dd" title="Daily Deviation!"><b>{{.Post.Deviation.Title}}</b></span> | |
| 23 | {{else}}{{.Post.Deviation.Title}}{{end}} | |
| 24 | {{if (ne .Post.Deviation.License "none")}}<mark title="License">{{.Post.Deviation.License}}</mark>{{end}} {{if (.Post.Deviation.AI)}}[🤖]{{end}} | |
| 25 | {{if (.Post.Deviation.NSFW)}}[<span class="nsfw">NSFW</span>]{{end}} | |
| 20 | <img src="{{.BasePath}}media/emojitar/{{.Templates.Deviation.Post.Deviation.Author.Username}}?type=a" width="30px"> | |
| 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>{{.Templates.Deviation.Post.Deviation.Title}}</b></span> | |
| 23 | {{else}}{{.Templates.Deviation.Post.Deviation.Title}}{{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 (.Templates.Deviation.Post.Deviation.NSFW)}}[<span class="nsfw">NSFW</span>]{{end}} | |
| 26 | 26 | </span> |
| 27 | 27 | <br> |
| 28 | {{if (ne .Post.IMG "")}} | |
| 29 | <a href="{{.Post.IMG}}" title="open/download image"><img src="{{.Post.IMG}}" width="50%"></a> | |
| 28 | {{if (ne .Templates.Deviation.Post.IMG "")}} | |
| 29 | <a href="{{.Templates.Deviation.Post.IMG}}" title="open/download image"><img src="{{.Templates.Deviation.Post.IMG}}" width="50%"></a> | |
| 30 | 30 | <br> |
| 31 | 31 | {{end}} |
| 32 | {{if (ne .Tags "")}} | |
| 33 | {{.Tags}}<br> | |
| 32 | {{if (ne .Templates.Deviation.Tags "")}} | |
| 33 | {{.Templates.Deviation.Tags}}<br> | |
| 34 | 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> | |
| 36 | {{if (ne .Post.Description "")}} | |
| 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 .Templates.Deviation.Post.Description "")}} | |
| 37 | 37 | <figcaption> |
| 38 | {{.Post.Description}} | |
| 38 | {{.Templates.Deviation.Post.Description}} | |
| 39 | 39 | </figcaption> |
| 40 | 40 | {{end}} |
| 41 | {{if (ne .Comments "")}} | |
| 42 | {{.Comments}} | |
| 41 | {{if (ne .Templates.Deviation.Comments "")}} | |
| 42 | {{.Templates.Deviation.Comments}} | |
| 43 | 43 | {{end}} |
| 44 | 44 | </figure> |
| 45 | 45 | </main> |
html/gruser.htm +4 −4
| @@ -8,14 +8,14 @@ | ||
| 8 | 8 | gallery of {{.Templates.GroupUser.GR.Owner.Username}} |
| 9 | 9 | {{end}} |
| 10 | 10 | </title> |
| 11 | <link rel="stylesheet" href="/gui/css/skunky.css"> | |
| 11 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> | |
| 12 | 12 | </head> |
| 13 | 13 | <main> |
| 14 | 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 | 16 | | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a> |
| 17 | 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 | 19 | <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
| 20 | 20 | <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}"> |
| 21 | 21 | <select name="type"> |
| @@ -32,7 +32,7 @@ | ||
| 32 | 32 | {{.Templates.GroupUser.About.BGMeta.Author.Username}} - {{end}}{{.Templates.GroupUser.About.BGMeta.Title}}" src="{{.Templates.GroupUser.About.BG}}"></a> |
| 33 | 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 | 36 | {{if (eq .Templates.GroupUser.About.A.Gender "male")}} |
| 37 | 37 | ♂️ |
| 38 | 38 | {{end}} |
html/index.htm +3 −3
| @@ -2,11 +2,11 @@ | ||
| 2 | 2 | <html> |
| 3 | 3 | <head> |
| 4 | 4 | <title>SkunkyArt</title> |
| 5 | <link rel="stylesheet" href="gui/css/skunky.css" | |
| 5 | <link rel="stylesheet" href="{{.}}gui/css/skunky.css"/> | |
| 6 | 6 | </head> |
| 7 | 7 | <main> |
| 8 | 8 | <center> |
| 9 | <form method="get" action="search"> | |
| 9 | <form method="get" action="{{.}}search"> | |
| 10 | 10 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> |
| 11 | 11 | <select name="type"> |
| 12 | 12 | <option value="all">All</option> |
| @@ -14,7 +14,7 @@ | ||
| 14 | 14 | </select> |
| 15 | 15 | <button type="submit">Search!</button> |
| 16 | 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 | 18 | </center> |
| 19 | 19 | </main> |
| 20 | 20 | </html> |
| \ No newline at end of file | ||
html/list.htm +4 −4
| @@ -2,12 +2,12 @@ | ||
| 2 | 2 | <html> |
| 3 | 3 | <head> |
| 4 | 4 | <title>SkunkyArt</title> |
| 5 | <link rel="stylesheet" href="gui/css/skunky.css"> | |
| 5 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> | |
| 6 | 6 | </head> |
| 7 | 7 | <main> |
| 8 | 8 | <header> |
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a> | <a href="?atom=true">RSS</a></h1> | |
| 10 | <form method="get" action="/search"> | |
| 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="{{.BasePath}}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,6 +16,6 @@ | ||
| 16 | 16 | <button type="submit">Search!</button> |
| 17 | 17 | </form> |
| 18 | 18 | </header> |
| 19 | {{.}} | |
| 19 | {{.Templates.SomeList}} | |
| 20 | 20 | </main> |
| 21 | 21 | </html> |
| \ No newline at end of file | ||
html/search.htm +3 −3
| @@ -1,12 +1,12 @@ | ||
| 1 | 1 | <!DOCTYPE html> |
| 2 | 2 | <html> |
| 3 | 3 | <head> |
| 4 | <title>SkunkyArt | Search "{{.Query}}"</title> | |
| 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> | |
| 4 | <title>SkunkyArt | Search "{{.QueryRaw}}"</title> | |
| 5 | <link rel="stylesheet" href="{{.BasePath}}gui/css/skunky.css"> | |
| 6 | 6 | </head> |
| 7 | 7 | <main> |
| 8 | 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 | 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"> |
main.go +1
| @@ -7,6 +7,7 @@ import ( | ||
| 7 | 7 | ) |
| 8 | 8 | |
| 9 | 9 | func main() { |
| 10 | app.ExecuteConfig() | |
| 10 | 11 | err := devianter.UpdateCSRF() |
| 11 | 12 | if err != nil { |
| 12 | 13 | println(err.Error()) |