diff options
| author | lost+skunk <[email protected]> | 2024-07-06 00:46:25 +0300 |
|---|---|---|
| committer | lost+skunk <[email protected]> | 2024-07-06 00:46:25 +0300 |
| commit | 7f1ab1b73d1e7a0d8a730354bbc3136c5584fea3 (patch) | |
| tree | cd44fa1c741570f05d2031288afa7b0d6b858a7a /app | |
| parent | 8b839f94063704782872cc11251975cfeae4584c (diff) | |
| download | skunky-art-7f1ab1b73d1e7a0d8a730354bbc3136c5584fea3.tar.gz skunky-art-7f1ab1b73d1e7a0d8a730354bbc3136c5584fea3.tar.bz2 skunky-art-7f1ab1b73d1e7a0d8a730354bbc3136c5584fea3.zip | |
Реализация конфига
Diffstat (limited to 'app')
| -rw-r--r-- | app/config.go | 69 | ||||
| -rw-r--r-- | app/router.go | 30 | ||||
| -rw-r--r-- | app/util.go | 257 | ||||
| -rw-r--r-- | app/wraper.go | 91 |
4 files changed, 315 insertions, 132 deletions
diff --git a/app/config.go b/app/config.go index 9a5aef1..1df6aaf 100644 --- a/app/config.go +++ b/app/config.go @@ -1,26 +1,31 @@ package app import ( + "encoding/json" + "errors" "os" ) type cache_config struct { - Enabled bool - Path string - Max_size, Lifetime int64 + Enabled bool + Path string + MaxSize int64 `json:"max-size"` + Lifetime int64 } type config struct { - cfg string - Listen, Base_uri string - Cache cache_config - Proxy, Nsfw bool + cfg string + Listen string + BasePath string `json:"base-path"` + Cache cache_config + Proxy, Nsfw bool + WixmpProxy string `json:"wixmp-proxy"` } var CFG = config{ cfg: "config.json", Listen: "127.0.0.1:3003", - Base_uri: "/", + BasePath: "/", Cache: cache_config{ Enabled: true, Path: "cache", @@ -29,20 +34,46 @@ var CFG = config{ Nsfw: true, } -func execcfg() { +func ExecuteConfig() { + try := func(err error, exitcode int) { + if err != nil { + println(err.Error()) + os.Exit(exitcode) + } + } + a := os.Args - for num, val := range a { - switch val { - case "-conf": - CFG.cfg = a[num] - case "-help": - println(`SkunkyArt v 1.3 [refactoring] + if l := len(a); l > 1 { + switch a[1] { + case "-c", "--config": + if l >= 3 { + CFG.cfg = a[2] + } else { + try(errors.New("Not enought arguments"), 1) + } + case "-h", "--help": + try(errors.New(`SkunkyArt v1.3 [refactoring] Usage: - - -conf - path to config - - -help this message + - [-c|--config] - path to config + - [-h|--help] - returns this message Example: - ./skunkyart -conf config.json -Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`) + ./skunkyart -c config.json +Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`), 0) + default: + try(errors.New("Unreconginzed argument: "+a[1]), 1) + } + if CFG.cfg != "" { + f, err := os.ReadFile(CFG.cfg) + try(err, 1) + + try(json.Unmarshal(f, &CFG), 1) + if CFG.Cache.Enabled && !CFG.Proxy { + try(errors.New("Incompatible settings detected: cannot use caching media content without proxy"), 1) + } + + if CFG.Cache.MaxSize != 0 || CFG.Cache.Lifetime != 0 { + go InitCacheSystem() + } } } } diff --git a/app/router.go b/app/router.go index d7f48b8..e833628 100644 --- a/app/router.go +++ b/app/router.go @@ -9,11 +9,14 @@ import ( "strings" ) -const addr string = "0.0.0.0:3003" - -// роутер func Router() { parsepath := func(path string) map[int]string { + if l := len(CFG.BasePath); len(path) > l { + path = path[l-1:] + } else { + path = "/" + } + parsedpath := make(map[int]string) for x := 0; true; x++ { slash := strings.Index(path, "/") + 1 @@ -52,16 +55,20 @@ func Router() { var skunky skunkyart skunky.Args = r.URL.Query() skunky.Writer = w + skunky.BasePath = CFG.BasePath arg := skunky.Args.Get - skunky.Query = u.QueryEscape(arg("q")) + skunky.QueryRaw = arg("q") + skunky.Query = u.QueryEscape(skunky.QueryRaw) + if t := arg("type"); len(t) > 0 { skunky.Type = rune(t[0]) } p, _ := strconv.Atoi(arg("p")) skunky.Page = p + if arg("atom") == "true" { - skunky.atom = true + skunky.Atom = true } // пути @@ -69,7 +76,7 @@ func Router() { default: skunky.ReturnHTTPError(404) case "": - open_n_send("html/index.htm") + skunky.ExecuteTemplate("html/index.htm", &CFG.BasePath) case "post": skunky.Deviation(path[2], path[3]) case "search": @@ -80,9 +87,14 @@ func Router() { skunky.GRUser() case "media": - skunky.Emojitar(path[2]) + switch path[2] { + case "file": + skunky.DownloadAndSendMedia(path[3], next(path, 4)) + case "emojitar": + skunky.Emojitar(path[3]) + } case "about": - open_n_send("html/about.htm") + skunky.About() case "gui": w.Header().Add("content-type", "text/css") open_n_send(next(path, 2)) @@ -90,7 +102,7 @@ func Router() { } http.HandleFunc("/", handle) - http.ListenAndServe(addr, nil) + http.ListenAndServe(CFG.Listen, nil) } func err(e error) { diff --git a/app/util.go b/app/util.go index 0d36fd8..9e1784d 100644 --- a/app/util.go +++ b/app/util.go @@ -1,11 +1,17 @@ package app import ( + "encoding/base64" "encoding/json" + "io" "net/http" + u "net/url" + "os" "strconv" "strings" + "syscall" "text/template" + "time" "git.macaw.me/skunky/devianter" ) @@ -19,11 +25,15 @@ func (s skunkyart) ExecuteTemplate(file string, data any) { wr(s.Writer, buf.String()) } -func (s skunkyart) UrlBuilder(strs ...string) string { +func UrlBuilder(strs ...string) string { var str strings.Builder - for _, x := range strs { + l := len(strs) + str.WriteString(CFG.BasePath) + for n, x := range strs { str.WriteString(x) - str.WriteString("/") + if n+1 < l && !(strs[n+1][0] == '?' || strs[n+1][0] == '&') && !(x[0] == '?' || x[0] == '&') { + str.WriteString("/") + } } return str.String() } @@ -31,10 +41,10 @@ func (s skunkyart) UrlBuilder(strs ...string) string { func (s skunkyart) ReturnHTTPError(status int) { s.Writer.WriteHeader(status) - // пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения var msg strings.Builder - msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`) - msg.WriteString("<h1>") + msg.WriteString(`<html><link rel="stylesheet" href="`) + msg.WriteString(UrlBuilder("gui", "css", "skunky.css")) + msg.WriteString(`" /><h1>`) msg.WriteString(strconv.Itoa(status)) msg.WriteString(" - ") msg.WriteString(http.StatusText(status)) @@ -43,6 +53,15 @@ func (s skunkyart) ReturnHTTPError(status int) { wr(s.Writer, msg.String()) } +func (s skunkyart) ConvertDeviantArtUrlToSkunkyArt(url string) (output string) { + if len(url) > 32 && url[27:32] != "stash" { + url = url[27:] + toart := strings.Index(url, "/art/") + output = UrlBuilder("post", url[:toart], url[toart+5:]) + } + return +} + type text struct { TXT string from int @@ -200,80 +219,78 @@ func (s skunkyart) NavBase(c dlist) string { func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string { var list strings.Builder - if !s.atom { - list.WriteString(`<div class="content">`) - } else { - list.WriteString(`<?xml version="1.0" encoding="UTF-8"?><feed xmlns:media="http://search.yahoo.com/mrss" xmlns="http://www.w3.org/2005/Atom">`) + if s.Atom && s.Page > 1 { + s.ReturnHTTPError(400) + return "" + } else if s.Atom { + list.WriteString(`<?xml version="1.0" encoding="UTF-8"?><feed xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom">`) list.WriteString(`<title>SkunkyArt</title>`) // list.WriteString(`<link rel="alternate" href="HOMEPAGE_URL"/><link href="FEED_URL" rel="self"/>`) + } else { + list.WriteString(`<div class="content">`) } for _, data := range devs { - url := devianter.UrlFromMedia(data.Media) - if s.atom { - id := strconv.Itoa(data.ID) - list.WriteString(`<entry><author><name>`) - list.WriteString(data.Author.Username) - list.WriteString(`</name></author><title>`) - list.WriteString(data.Title) - list.WriteString(`</title><link rel="alternate" type="text/html" href="`) - list.WriteString(CFG.Base_uri) - list.WriteString("/post/") - list.WriteString(data.Author.Username) - list.WriteString("/atom-") - list.WriteString(id) - list.WriteString(`"/><id>`) - list.WriteString(id) - list.WriteString(`</id><published>`) - list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700")) - list.WriteString(`</published>`) - list.WriteString(`<media:group><media:title>`) - list.WriteString(data.Title) - list.WriteString(`</media:title><media:thumbinal url="`) - list.WriteString(url) - list.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`) - list.WriteString(data.Url) - list.WriteString(`"><img src="`) - list.WriteString(url) - list.WriteString(`"/></a><p>`) - list.WriteString(ParseDescription(data.TextContent)) - list.WriteString(`</p></div></content></entry>`) - } else { - list.WriteString(`<div class="block">`) - if url != "" { - list.WriteString(`<a title="open/download" href="`) + if !(data.NSFW && !CFG.Nsfw) { + url := s.ParseMedia(data.Media) + if s.Atom { + id := strconv.Itoa(data.ID) + list.WriteString(`<entry><author><name>`) + list.WriteString(data.Author.Username) + list.WriteString(`</name></author><title>`) + list.WriteString(data.Title) + list.WriteString(`</title><link rel="alternate" type="text/html" href="`) + list.WriteString(UrlBuilder("post", data.Author.Username, "atom-"+id)) + list.WriteString(`"/><id>`) + list.WriteString(id) + list.WriteString(`</id><published>`) + list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700")) + list.WriteString(`</published>`) + list.WriteString(`<media:group><media:title>`) + list.WriteString(data.Title) + list.WriteString(`</media:title><media:thumbinal url="`) list.WriteString(url) + list.WriteString(`"/></media:group><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><a href="`) + list.WriteString(data.Url) list.WriteString(`"><img src="`) list.WriteString(url) - list.WriteString(`" width="15%"></a>`) + list.WriteString(`"/></a><p>`) + list.WriteString(ParseDescription(data.TextContent)) + list.WriteString(`</p></div></content></entry>`) } else { - list.WriteString(`<h1>[ TEXT ]</h1>`) - } - list.WriteString(`<br><a href="`) - list.WriteString("/post/") - list.WriteString(data.Author.Username) - list.WriteString("/") - list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:]) - list.WriteString(`">`) - list.WriteString(data.Author.Username) - list.WriteString(" - ") - list.WriteString(data.Title) + list.WriteString(`<div class="block">`) + if url != "" { + list.WriteString(`<a title="open/download" href="`) + list.WriteString(url) + list.WriteString(`"><img src="`) + list.WriteString(url) + list.WriteString(`" width="15%"></a>`) + } else { + list.WriteString(`<h1>[ TEXT ]</h1>`) + } + list.WriteString(`<br><a href="`) + list.WriteString(s.ConvertDeviantArtUrlToSkunkyArt(data.Url)) + list.WriteString(`">`) + list.WriteString(data.Author.Username) + list.WriteString(" - ") + list.WriteString(data.Title) + + // шильдики нсфв, аи и ежедневного поста + if data.NSFW { + list.WriteString(` [<span class="nsfw">NSFW</span>]`) + } + if data.AI { + list.WriteString(" [🤖]") + } + if data.DD { + list.WriteString(` [<span class="dd">DD</span>]`) + } - // шильдики нсфв, аи и ежедневного поста - if data.NSFW { - list.WriteString(` [<span class="nsfw">NSFW</span>]`) - } - if data.AI { - list.WriteString(" [🤖]") - } - if data.DD { - list.WriteString(` [<span class="dd">DD</span>]`) + list.WriteString("</a></div>") } - - list.WriteString("</a></div>") } } - if s.atom { + if s.Atom { list.WriteString("</feed>") s.Writer.Write([]byte(list.String())) return "" @@ -301,10 +318,10 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { } cmmts.WriteString(`"><p id="`) cmmts.WriteString(strconv.Itoa(x.ID)) - cmmts.WriteString(`"><img src="/media/`) - cmmts.WriteString(x.User.Username) - cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/group_user?type=about&q=`) - cmmts.WriteString(x.User.Username) + cmmts.WriteString(`"><img src="`) + cmmts.WriteString(UrlBuilder("media", "emojitar", x.User.Username, "?type=a")) + cmmts.WriteString(`" width="30px" height="30px"><a href="`) + cmmts.WriteString(UrlBuilder("group_user", "?q=", x.User.Username, "&type=a")) cmmts.WriteString(`"><b`) cmmts.WriteString(` class="`) if x.User.Banned { @@ -346,3 +363,101 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { cmmts.WriteString("</details>") return cmmts.String() } + +func (s skunkyart) ParseMedia(media devianter.Media) string { + url := devianter.UrlFromMedia(media) + if len(url) != 0 { + url = url[21:] + dot := strings.Index(url, ".") + + return UrlBuilder("media", "file", url[:dot], "/", url[dot+10:]) + } + return "" +} + +func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { + var url strings.Builder + url.WriteString("https://images-wixmp-") + url.WriteString(subdomain) + url.WriteString(".wixmp.com/") + url.WriteString(path) + url.WriteString("?token=") + url.WriteString(s.Args.Get("token")) + + download := func() (body []byte, status int, headers http.Header) { + cli := &http.Client{} + if CFG.WixmpProxy != "" { + u, e := u.Parse(CFG.WixmpProxy) + err(e) + cli.Transport = &http.Transport{Proxy: http.ProxyURL(u)} + } + + req, e := http.NewRequest("GET", url.String(), nil) + err(e) + req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0.0") + + resp, e := cli.Do(req) + err(e) + defer resp.Body.Close() + + b, e := io.ReadAll(resp.Body) + err(e) + return b, resp.StatusCode, resp.Header + } + + if CFG.Cache.Enabled { + os.Mkdir(CFG.Cache.Path, 0700) + fname := CFG.Cache.Path + "/" + base64.StdEncoding.EncodeToString([]byte(subdomain+path)) + file, e := os.Open(fname) + + if e != nil { + b, status, headers := download() + if status == 200 && headers["Content-Type"][0][:5] == "image" { + err(os.WriteFile(fname, b, 0700)) + s.Writer.Write(b) + } + } else { + file, e := io.ReadAll(file) + err(e) + s.Writer.Write(file) + } + } else if CFG.Proxy { + b, _, _ := download() + s.Writer.Write(b) + } else { + s.Writer.Write([]byte("Sorry, butt proxy on this instance disabled.")) + } +} + +func InitCacheSystem() { + c := &CFG.Cache + for { + dir, e := os.Open(c.Path) + err(e) + stat, e := dir.Stat() + err(e) + + dirnames, e := dir.Readdirnames(-1) + err(e) + for _, a := range dirnames { + a = c.Path + "/" + a + if c.Lifetime != 0 { + now := time.Now().UnixMilli() + + f, _ := os.Stat(a) + stat := f.Sys().(*syscall.Stat_t) + time := time.Unix(stat.Ctim.Unix()).UnixMilli() + + if time+c.Lifetime <= now { + os.RemoveAll(a) + } + } + if c.MaxSize != 0 && stat.Size() > c.MaxSize { + os.RemoveAll(a) + } + } + + dir.Close() + time.Sleep(time.Second) + } +} diff --git a/app/wraper.go b/app/wraper.go index 757602c..f42a9af 100644 --- a/app/wraper.go +++ b/app/wraper.go @@ -15,13 +15,27 @@ import ( var wr = io.WriteString type skunkyart struct { - Writer http.ResponseWriter - Args url.Values - Type rune - Query string - Page int - atom bool - Templates struct { + Writer http.ResponseWriter + Args url.Values + BasePath string + Type rune + Query, QueryRaw string + Page int + Atom bool + Templates struct { + About struct { + Proxy bool + Nsfw bool + } + + SomeList string + Deviation struct { + Post devianter.Post + StringTime string + Tags string + Comments string + } + GroupUser struct { GR devianter.GRuser Admins string @@ -51,6 +65,19 @@ type skunkyart struct { } } +// var Templates struct { +// Index string +// About string +// +// GRuser string +// Deviation string +// List string +// Search string +// } + +// //go:embed ../html/* +// var Templates embed.FS + func (s skunkyart) GRUser() { if len(s.Query) < 1 { s.ReturnHTTPError(400) @@ -65,7 +92,7 @@ func (s skunkyart) GRUser() { switch s.Type { case 'a': g := group.GR - s.atom = false + s.Atom = false for _, x := range g.Gruser.Page.Modules { switch x.Name { case "about", "group_about": @@ -110,17 +137,15 @@ func (s skunkyart) GRUser() { case "cover_deviation": group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation - group.About.BG = devianter.UrlFromMedia(group.About.BGMeta.Media) + group.About.BGMeta.Url = s.ConvertDeviantArtUrlToSkunkyArt(group.About.BGMeta.Url) + group.About.BG = s.ParseMedia(group.About.BGMeta.Media) case "group_admins": var htm strings.Builder for _, z := range x.ModuleData.GroupAdmins.Results { htm.WriteString(`<p><img src="`) - htm.WriteString("/media/") - htm.WriteString(z.User.Username) - htm.WriteString("?type=a") + htm.WriteString(UrlBuilder("media", "emojitar", z.User.Username, "?type=a")) htm.WriteString(`"><a href="`) - htm.WriteString("/group_user?type=about&q=") - htm.WriteString(z.User.Username) + htm.WriteString(UrlBuilder("group_user", "?type=about&q=", z.User.Username)) htm.WriteString(`">`) htm.WriteString(z.User.Username) htm.WriteString(`</a></p>`) @@ -175,24 +200,18 @@ func (s skunkyart) GRUser() { s.ReturnHTTPError(400) } - if !s.atom { + if !s.Atom { s.ExecuteTemplate("html/gruser.htm", &s) } } // посты func (s skunkyart) Deviation(author, postname string) { - // поиск ID - re := regexp.MustCompile("[0-9]+").FindAllString(postname, -1) - if len(re) >= 1 { - var post struct { - Post devianter.Post - StringTime string - Tags string - Comments string - } + id_search := regexp.MustCompile("[0-9]+").FindAllString(postname, -1) + if len(id_search) >= 1 { + post := &s.Templates.Deviation - id := re[len(re)-1] + id := id_search[len(id_search)-1] post.Post = devianter.DeviationFunc(id, author) post.Post.Description = ParseDescription(post.Post.Deviation.TextContent) @@ -202,9 +221,9 @@ func (s skunkyart) Deviation(author, postname string) { // хештэги for _, x := range post.Post.Deviation.Extended.Tags { var tag strings.Builder - tag.WriteString(` <a href="/search?q=`) - tag.WriteString(x.Name) - tag.WriteString(`&type=tag">#`) + tag.WriteString(` <a href="`) + tag.WriteString(UrlBuilder("search", "?q=", x.Name, "&type=tag")) + tag.WriteString(`">#`) tag.WriteString(x.Name) tag.WriteString("</a>") @@ -213,7 +232,7 @@ func (s skunkyart) Deviation(author, postname string) { post.Comments = s.ParseComments(devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1)) - s.ExecuteTemplate("html/deviantion.htm", &post) + s.ExecuteTemplate("html/deviantion.htm", &s) } else { s.ReturnHTTPError(400) } @@ -221,17 +240,17 @@ func (s skunkyart) Deviation(author, postname string) { func (s skunkyart) DD() { dd := devianter.DailyDeviationsFunc(s.Page) - ddparsed := s.DeviationList(dd.Deviations, dlist{ + s.Templates.SomeList = s.DeviationList(dd.Deviations, dlist{ Pages: 0, More: dd.HasMore, }) - if !s.atom { - s.ExecuteTemplate("html/list.htm", &ddparsed) + if !s.Atom { + s.ExecuteTemplate("html/list.htm", &s) } } func (s skunkyart) Search() { - s.atom = false + s.Atom = false var e error ss := &s.Templates.Search switch s.Type { @@ -263,3 +282,9 @@ func (s skunkyart) Emojitar(name string) { s.ReturnHTTPError(400) } } + +func (s skunkyart) About() { + s.Templates.About.Nsfw = CFG.Nsfw + s.Templates.About.Proxy = CFG.Proxy + s.ExecuteTemplate("html/about.htm", &s) +} |
