From 7f1ab1b73d1e7a0d8a730354bbc3136c5584fea3 Mon Sep 17 00:00:00 2001 From: lost+skunk Date: Sat, 6 Jul 2024 00:46:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config.go | 69 ++++++++---- app/router.go | 30 ++++-- app/util.go | 257 ++++++++++++++++++++++++++++++++------------ app/wraper.go | 91 ++++++++++------ atom.xml | 43 -------- config.example.json | 7 +- css/skunky.css | 5 +- html/about.htm | 12 ++- html/deviantion.htm | 38 +++---- html/gruser.htm | 8 +- html/index.htm | 6 +- html/list.htm | 8 +- html/search.htm | 6 +- main.go | 1 + 14 files changed, 366 insertions(+), 215 deletions(-) delete mode 100644 atom.xml 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(``) - msg.WriteString("

") + msg.WriteString(`

`) 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(`
`) - } else { - list.WriteString(``) + if s.Atom && s.Page > 1 { + s.ReturnHTTPError(400) + return "" + } else if s.Atom { + list.WriteString(``) list.WriteString(`SkunkyArt`) // list.WriteString(``) + } else { + list.WriteString(`
`) } for _, data := range devs { - url := devianter.UrlFromMedia(data.Media) - if s.atom { - id := strconv.Itoa(data.ID) - list.WriteString(``) - list.WriteString(data.Author.Username) - list.WriteString(``) - list.WriteString(data.Title) - list.WriteString(``) - list.WriteString(id) - list.WriteString(``) - list.WriteString(data.PublishedTime.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700")) - list.WriteString(``) - list.WriteString(``) - list.WriteString(data.Title) - list.WriteString(`

`) - list.WriteString(ParseDescription(data.TextContent)) - list.WriteString(`

`) - } else { - list.WriteString(`") + } } } - if s.atom { + if s.Atom { list.WriteString("") s.Writer.Write([]byte(list.String())) return "" @@ -301,10 +318,10 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { } cmmts.WriteString(`">

") 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(`

`) htm.WriteString(z.User.Username) htm.WriteString(`

`) @@ -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(` #`) + tag.WriteString(` #`) tag.WriteString(x.Name) tag.WriteString("") @@ -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) +} diff --git a/atom.xml b/atom.xml deleted file mode 100644 index 784f37d..0000000 --- a/atom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - yt:channel:UCuxpxCCevIlF-k-K5YU8XPA - UCuxpxCCevIlF-k-K5YU8XPA - https://yt3.googleusercontent.com/ytc/AIdro_n7bKr9iusclFjvYw3U8UR74iTCoMcdGTKI3h55SOG8mSsY=s900-c-k-c0x00ffffff-no-rj - Test - - - lost+skunk - https://skunky.ebloid.ru - - - https://ebloid.ru/_matrix/media/v3/download/ebloid.ru/enfAtfdjzCQMRNBgLXmiURfJ?allow_redirect=true - lost+skunk - - - - Test - - - lost+skunk - https://skunky.ebloid.ru - - -
- - - -

test

-
-
- 2024-07-04T00:37:55+00:00 - - GM Just Announced Their Shutting Down Production in America and Firing Their Workers - - test - - - - -
-
diff --git a/config.example.json b/config.example.json index 1d979bd..56b5218 100644 --- a/config.example.json +++ b/config.example.json @@ -1,12 +1,13 @@ { "listen": "0.0.0.0:3003", - "base_uri": null, + "base-path": "/skunky/", "cache": { "enabled": true, "path": "cache", "lifetime": null, - "max_size": 10000 + "max-size": 100000 }, - "proxy": false, + "proxy": true, + "wixmp-proxy": "http://127.0.0.1:8080", "nsfw": false } \ No newline at end of file diff --git a/css/skunky.css b/css/skunky.css index 9e38325..b440f5b 100644 --- a/css/skunky.css +++ b/css/skunky.css @@ -28,9 +28,12 @@ form input, button, select { border: 0px; border-radius: 1px; } -.nsfw, .true { +.nsfw, .about-false { color: red; } +.about-true { + color: green; +} .author { color: seagreen; } diff --git a/html/about.htm b/html/about.htm index 2c43bbe..4a1b316 100644 --- a/html/about.htm +++ b/html/about.htm @@ -2,12 +2,12 @@ SkunkyArt - +
-

HOME | DD

-
+

HOME | DD

+
-

Daily Deviations | About | Source Code

+

Daily Deviations | About | Source Code

\ No newline at end of file diff --git a/html/list.htm b/html/list.htm index 23d48e5..57b4659 100644 --- a/html/list.htm +++ b/html/list.htm @@ -2,12 +2,12 @@ SkunkyArt - +
-

HOME | DD | RSS

-
+

HOME | DD | RSS

+