альфа: работают только посты и поиск

This commit is contained in:
lost+skunk 2024-06-13 23:56:03 +03:00
commit 6f872fe2f7
12 changed files with 544 additions and 0 deletions

48
app/config.go Normal file
View File

@ -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`)
}
}
}

81
app/router.go Normal file
View File

@ -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())
}
}

192
app/wraper.go Normal file
View File

@ -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)
}
}

12
config.json Normal file
View File

@ -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
}

81
css/skunky.css Normal file
View File

@ -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;
}

7
go.mod Normal file
View File

@ -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

0
go.sum Normal file
View File

23
html/about.htm Normal file
View File

@ -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>

44
html/deviantion.htm Normal file
View File

@ -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>

20
html/index.htm Normal file
View File

@ -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>

29
html/search.htm Normal file
View File

@ -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>

7
main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "skunkyart/app"
func main() {
app.Router()
}