diff options
| -rw-r--r-- | app/config.go | 48 | ||||
| -rw-r--r-- | app/router.go | 81 | ||||
| -rw-r--r-- | app/wraper.go | 192 | ||||
| -rw-r--r-- | config.json | 12 | ||||
| -rw-r--r-- | css/skunky.css | 81 | ||||
| -rw-r--r-- | go.mod | 7 | ||||
| -rw-r--r-- | go.sum | 0 | ||||
| -rw-r--r-- | html/about.htm | 23 | ||||
| -rw-r--r-- | html/deviantion.htm | 44 | ||||
| -rw-r--r-- | html/index.htm | 20 | ||||
| -rw-r--r-- | html/search.htm | 29 | ||||
| -rw-r--r-- | main.go | 7 |
12 files changed, 544 insertions, 0 deletions
diff --git a/app/config.go b/app/config.go new file mode 100644 index 0000000..9a5aef1 --- /dev/null +++ b/app/config.go @@ -0,0 +1,48 @@ +package app + +import ( + "os" +) + +type cache_config struct { + Enabled bool + Path string + Max_size, Lifetime int64 +} + +type config struct { + cfg string + Listen, Base_uri string + Cache cache_config + Proxy, Nsfw bool +} + +var CFG = config{ + cfg: "config.json", + Listen: "127.0.0.1:3003", + Base_uri: "/", + Cache: cache_config{ + Enabled: true, + Path: "cache", + }, + Proxy: true, + Nsfw: true, +} + +func execcfg() { + a := os.Args + for num, val := range a { + switch val { + case "-conf": + CFG.cfg = a[num] + case "-help": + println(`SkunkyArt v 1.3 [refactoring] +Usage: + - -conf - path to config + - -help this message +Example: + ./skunkyart -conf config.json +Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`) + } + } +} diff --git a/app/router.go b/app/router.go new file mode 100644 index 0000000..b4af3f1 --- /dev/null +++ b/app/router.go @@ -0,0 +1,81 @@ +package app + +import ( + "io" + "net/http" + u "net/url" + "os" + "strconv" + "strings" +) + +const addr string = "0.0.0.0:3003" + +// роутер +func Router() { + // расшифровка эндпоинта из урл + endpoint := func(url string) (string, string) { + if CFG.Base_uri != "" { + url = strings.Replace(url, CFG.Base_uri, "/", 1) + } + + end := strings.Index(url[1:], "/") + if end == -1 { + return url[1:], "" + } + return url[1 : end+1], url[end+2:] + } + + // функция, что управляет всем + handle := func(w http.ResponseWriter, r *http.Request) { + e, url := endpoint(r.URL.Path) + var wr = io.WriteString + open_n_send := func(name string) { + f, e := os.ReadFile(name) + err(e) + wr(w, string(f)) + } + + // структура с функциями + var skunky skunkyart + skunky.Args = r.URL.Query() + skunky.Writer = w + + arg := skunky.Args.Get + skunky.Query = u.QueryEscape(arg("q")) + if t := arg("type"); len(t) > 0 { + skunky.Type = rune(t[0]) + } + p, _ := strconv.Atoi(arg("p")) + skunky.Page = p + + // пути + switch e { + default: + skunky.httperr(404) + case "/", "": + open_n_send("html/index.htm") + case "post": + slash := strings.Index(url, "/") + skunky.Deviation(url[:slash], url[slash+1:]) + case "search": + skunky.Search() + case "media": + skunky.Emojitar(url) + case "about": + open_n_send("html/about.htm") + case "gui": + w.Header().Add("content-type", "text/css") + open_n_send(url) + } + } + + http.HandleFunc("/", handle) + http.ListenAndServe(addr, nil) +} + +func err(e error) { + if e != nil { + println(e.Error()) + } +} diff --git a/app/wraper.go b/app/wraper.go new file mode 100644 index 0000000..d8a459f --- /dev/null +++ b/app/wraper.go @@ -0,0 +1,192 @@ +package app + +import ( + "bytes" + "io" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + "text/template" + + "git.macaw.me/skunky/devianter" +) + +var wr = io.WriteString + +type skunkyart struct { + Writer http.ResponseWriter + Args url.Values + Type rune + Query string + Page int +} + +// парсинг темплейтов +func (s skunkyart) exe(file string, data any) { + var buf bytes.Buffer + tmp, e := template.ParseFiles(file) + err(e) + tmp.Execute(&buf, &data) + wr(s.Writer, buf.String()) +} + +func (s skunkyart) httperr(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(strconv.Itoa(status)) + msg.WriteString(" - ") + msg.WriteString(http.StatusText(status)) + msg.WriteString("</h1></html>") + + wr(s.Writer, msg.String()) +} + +func (s skunkyart) DeviationList(devs []devianter.Deviation) string { + var list strings.Builder + list.WriteString(`<div class="content">`) + for _, data := range devs { + url := devianter.UrlFromMedia(data.Media) + + list.WriteString(`<a title="open/download" href="`) + list.WriteString(url) + list.WriteString(`"><div class="block"><img src="`) + list.WriteString(url) + list.WriteString(`" width="15%"></a><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) + + // шильдики нсфв, аи и ежедневного поста + 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("</div>") + return list.String() +} + +// посты +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 := re[len(re)-1] + post.Post = devianter.DeviationFunc(id, author) + + // время публикации + post.StringTime = post.Post.Deviation.PublishedTime.UTC().String() + + println(post.Post.Description) + // хештэги + 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(x.Name) + tag.WriteString("</a>") + + post.Tags += tag.String() + } + + // генерация комментов + var cmmts strings.Builder + var replied map[int]string + _ = replied + c := devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1) + + cmmts.WriteString("<details><summary>Comments: <b>") + cmmts.WriteString(strconv.Itoa(c.Total)) + cmmts.WriteString("</b></summary>") + for _, x := range c.Thread { + cmmts.WriteString(`<div class="msg"><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="/user/`) + cmmts.WriteString(x.User.Username) + cmmts.WriteString(`"><b`) + if x.User.Banned { + cmmts.WriteString(` class="banned"`) + } + if x.Author { + cmmts.WriteString(` class="author"`) + } + cmmts.WriteString(">") + cmmts.WriteString(x.User.Username) + cmmts.WriteString("</b></a> ") + cmmts.WriteString(x.Posted.UTC().String()) + cmmts.WriteString("<p>") + cmmts.WriteString(x.Comment) + cmmts.WriteString("<p>👍: ") + cmmts.WriteString(strconv.Itoa(x.Likes)) + cmmts.WriteString(" ⏩: ") + cmmts.WriteString(strconv.Itoa(x.Replies)) + cmmts.WriteString("</p></div>\n") + } + cmmts.WriteString("</details>") + post.Comments = cmmts.String() + + s.exe("html/deviantion.htm", &post) + } else { + s.httperr(400) + } +} + +func (s skunkyart) Search() { + // тут всё и так понятно + if s.Type == 'a' || s.Type == 't' || s.Type == 'g' { + var srch struct { + Search devianter.Search + List string + } + + var e error + srch.Search, e = devianter.SearchFunc(s.Query, s.Page, s.Type) + err(e) + srch.List = s.DeviationList(srch.Search.Results) + + s.exe("html/search.htm", &srch) + } else { + s.httperr(400) + } +} + +func (s skunkyart) Emojitar(name string) { + if name != "" && (s.Type == 'a' || s.Type == 'e') { + ae, e := devianter.AEmedia(name, s.Type) + if e != nil { + s.httperr(404) + println(e.Error()) + } + wr(s.Writer, ae) + } else { + s.httperr(400) + } +} diff --git a/config.json b/config.json new file mode 100644 index 0000000..1d979bd --- /dev/null +++ b/config.json @@ -0,0 +1,12 @@ +{ + "listen": "0.0.0.0:3003", + "base_uri": null, + "cache": { + "enabled": true, + "path": "cache", + "lifetime": null, + "max_size": 10000 + }, + "proxy": false, + "nsfw": false +}
\ No newline at end of file diff --git a/css/skunky.css b/css/skunky.css new file mode 100644 index 0000000..ef281ce --- /dev/null +++ b/css/skunky.css @@ -0,0 +1,81 @@ +html { + font-family: ubuntu; + background-color:black; + color: rgb(234, 216, 216); +} +a { + text-decoration: none; + color: cadetblue; +} +a:hover { + color: red; + transition: 400ms; +} +header h1 { + padding-right: 0.2%; +} +header form { + align-self: center; +} +header { + display: flex; +} +form input, button, select { + background-color: #134134; + padding: 5px; + color: whitesmoke; + border: 0px; + border-radius: 1px; +} +.nsfw, .true { + color: red; +} +.author { + color: seagreen; +} +.msg { + background-color: #091f19; + color: whitesmoke; + width: fit-content; + max-width: 90%; + padding: 4px; + border-radius: 2px; + margin-top: 6px; + text-wrap: pretty; + transition: 350ms; +} +.msg:hover { + background-color: #134134; +} +.dd { + color: rgb(160, 0, 147); +} +.content { + border-radius: 3px; + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.block { + max-width: 20%; + height: 0%; + padding: 4px; + border-radius: 2px; + border: 3px solid #091f19; + word-break: break-all; + background-color: #091f19; + margin-left: 5px; + margin-top: 5px; + text-align: center; +} +.block:hover { + border: 3px solid #4d27d6; + transition: 400ms; +} +.block img { + width: 100%; +} +.block p { + word-break: break-all; +} + @@ -0,0 +1,7 @@ +module skunkyart + +go 1.22.3 + +replace git.macaw.me/skunky/devianter v0.1.0 => /home/skunk/projects/devianter + +require git.macaw.me/skunky/devianter v0.1.0 diff --git a/html/about.htm b/html/about.htm new file mode 100644 index 0000000..2c43bbe --- /dev/null +++ b/html/about.htm @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + <head> + <title>SkunkyArt</title> + <link rel="stylesheet" href="gui/css/skunky.css"> + </head> + <main> + <header> + <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> + <form method="get" action="/search"> + <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> + <select name="type"> + <option value="all">All</option> + <option value="tag">Tag</option> + </select> + <button type="submit">Search!</button> + </form> + </header> + <p> + SkunkyArt is an alternative frontend for deviantart.com, written in Go. + </p> + </main> +</html>
\ No newline at end of file diff --git a/html/deviantion.htm b/html/deviantion.htm new file mode 100644 index 0000000..8516965 --- /dev/null +++ b/html/deviantion.htm @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> + <head> + <title>SkunkyArt | {{.Post.Deviation.Author.Username}} - {{.Post.Deviation.Title}}</title> + <link rel="stylesheet" href="/gui/css/skunky.css"> + </head> + <main> + <header> + <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> + <form method="get" action="/search"> + <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> + <select name="type"> + <option value="all">All</option> + <option value="tag">Tag</option> + </select> + <button type="submit">Search!</button> + </form> + </header> + <figure> + <img src="/media/{{.Post.Deviation.Author.Username}}?type=a" width="30px"> + <span><strong><a href="/user/{{.Post.Deviation.Author.Username}}">{{.Post.Deviation.Author.Username}}</a></strong> — {{if (.Post.Deviation.DD)}} + <span class="dd" title="Daily Deviation!"><b>{{.Post.Deviation.Title}}</b></span> + {{else}}{{.Post.Deviation.Title}}{{end}} + {{if (ne .Post.Deviation.License "none")}}<mark title="License">{{.Post.Deviation.License}}</mark>{{end}} {{if (.Post.Deviation.AI)}}[🤖]{{end}} + {{if (.Post.Deviation.NSFW)}}[<span class="nsfw">NSFW</span>]{{end}} + </span> + <br> + {{if (ne .Post.IMG "")}} + <a href="{{.Post.IMG}}" title="open/download image"><img src="{{.Post.IMG}}" width="50%"></a> + <br> + {{end}} + {{if (ne .Tags "")}} + {{.Tags}}<br> + {{end}} + <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> + {{if (ne .Post.Description "")}} + {{.Post.Description}} + {{end}} + {{if (ne .Comments "")}} + {{.Comments}} + {{end}} + </figure> + </main> +</html>
\ No newline at end of file diff --git a/html/index.htm b/html/index.htm new file mode 100644 index 0000000..e754884 --- /dev/null +++ b/html/index.htm @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> + <head> + <title>SkunkyArt</title> + <link rel="stylesheet" href="gui/css/skunky.css" + </head> + <main> + <center> + <form method="get" action="search"> + <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> + <select name="type"> + <option value="all">All</option> + <option value="tag">Tag</option> + </select> + <button type="submit">Search!</button> + </form> + <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> + </center> + </main> +</html>
\ No newline at end of file diff --git a/html/search.htm b/html/search.htm new file mode 100644 index 0000000..4ef9fbd --- /dev/null +++ b/html/search.htm @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> + <head> + <title>SkunkyArt | Search</title> + <link rel="stylesheet" href="/gui/css/skunky.css"> + </head> + <main> + <header> + <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> + <form method="get" action="search"> + <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> + <select name="type"> + <option value="all">All</option> + <option value="tag">Tag</option> + </select> + <button type="submit">Search!</button> + </form> + </header> + {{if (ne .List "")}} + {{if (ne .Search.Total 0)}} + <h1>Total resuls: {{.Search.Total}}</h1> + {{end}} + {{.List}} + {{.Search.Pages}} + {{else}} + <p>No results :(</p> + {{end}} + </main> +</html>
\ No newline at end of file @@ -0,0 +1,7 @@ +package main + +import "skunkyart/app" + +func main() { + app.Router() +} |
