krz/skunky-art

Alternative privacy frontend for DeviantArt.

clone: git clone https://gitbay.org/krz/skunky-art.git

2f8c35ba322e3c8c6e0bff8f7818d4eedf002928

unsigned

author: lost+skunk <skunky@ebloid.ru> · 2024-08-14T16:18:06Z

user favourites
 INSTANCES.md           |   2 +-
 TODO.md                |   2 +-
 app/cache.go           |  34 ++++++------
 app/util.go            |  59 ++++++++++++++++++++
 app/wrapper.go         | 144 +++++++++++++++++--------------------------------
 go.mod                 |   1 +
 instances.json         |   2 +-
 static/html/gruser.htm |  17 ++++--
 static/html/head.htm   |  11 ++--
 9 files changed, 149 insertions(+), 123 deletions(-)

diff --git a/INSTANCES.md b/INSTANCES.md
index 1637971..d032d24 100644
--- a/INSTANCES.md
+++ b/INSTANCES.md
@@ -2,6 +2,6 @@
 |:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:|
 |[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | No | No | Russia |
 |[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden |
-|[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Romania |
+|[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Germany |
 |[frontendfriendly.xyz](https://skunkyart.frontendfriendly.xyz)|No|No|No| Yes | Yes | No | Finland |
 |[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | US |
\ No newline at end of file
diff --git a/TODO.md b/TODO.md
index 84678b0..5a0da71 100644
--- a/TODO.md
+++ b/TODO.md
@@ -6,7 +6,7 @@
 * ~~Исправить баг с навигацией по страницам~~ ✔️
 * Сделать нормальное отображение ошибок
 * ~~Сделать единицы в конфиге более понятными~~ ✔️
-* Добавить просмотр понравившихся артов пользователю
+* ~~Добавить просмотр понравившихся артов пользователю~~ ✔️
 * Добавить возможность включить темплейты в бинарник [P]
 * ~~Реализовать миниатюры и оптимизировать CSS под маленькие экраны~~ ✔️
 * Написать Makefile и скрипт для автоматического развёртывания инстанса
diff --git a/app/cache.go b/app/cache.go
index dedba83..6506cfc 100644
--- a/app/cache.go
+++ b/app/cache.go
@@ -99,34 +99,36 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) {
 
 func InitCacheSystem() {
 	c := &CFG.Cache
-	os.Mkdir(c.Path, 0700)
 	for {
-		dir, e := os.Open(c.Path)
-		try(e)
-		stat, e := dir.Stat()
-		try(e)
-
-		dirnames, e := dir.Readdirnames(-1)
-		try(e)
-		for _, a := range dirnames {
-			a = c.Path + "/" + a
+		dir, err := os.ReadDir(c.Path)
+		if err != nil {
+			if os.IsNotExist(err) {
+				os.Mkdir(c.Path, 0700)
+				continue
+			}
+			println(err.Error())
+		}
+
+		for _, file := range dir {
+			fileName := c.Path + "/" + file.Name()
+			fileInfo, err := file.Info()
+			try(err)
+
 			if c.Lifetime != "" {
 				now := time.Now().UnixMilli()
 
-				f, _ := os.Stat(a)
-				stat := f.Sys().(*syscall.Stat_t)
+				stat := fileInfo.Sys().(*syscall.Stat_t)
 				time := statTime(stat)
 
 				if time+lifetimeParsed <= now {
-					try(os.RemoveAll(a))
+					try(os.RemoveAll(fileName))
 				}
 			}
-			if c.MaxSize != 0 && stat.Size() > c.MaxSize {
-				try(os.RemoveAll(a))
+			if c.MaxSize != 0 && fileInfo.Size() > c.MaxSize {
+				try(os.RemoveAll(fileName))
 			}
 		}
 
-		dir.Close()
 		time.Sleep(time.Second * time.Duration(c.UpdateInterval))
 	}
 }
diff --git a/app/util.go b/app/util.go
index a5b978d..8e8ef6e 100644
--- a/app/util.go
+++ b/app/util.go
@@ -16,6 +16,8 @@ import (
 )
 
 /* INTERNAL */
+var wr = io.WriteString
+
 func exit(msg string, code int) {
 	println(msg)
 	os.Exit(code)
@@ -50,6 +52,63 @@ func RefreshInstances() {
 }
 
 // some crap for frontend
+type skunkyart struct {
+	Writer http.ResponseWriter
+
+	Args url.Values
+	Page int
+	Type rune
+	Atom bool
+
+	BasePath, Endpoint string
+	Query, QueryRaw    string
+
+	Templates struct {
+		About struct {
+			Proxy     bool
+			Nsfw      bool
+			Instances []settings
+		}
+
+		SomeList  string
+		DDStrips  string
+		Deviation struct {
+			Post       devianter.Post
+			Related    string
+			StringTime string
+			Tags       string
+			Comments   string
+		}
+
+		GroupUser struct {
+			GR           devianter.GRuser
+			Admins       string
+			Group        bool
+			CreationDate string
+
+			About struct {
+				A devianter.About
+
+				DescriptionFormatted string
+				Interests, Social    string
+				Comments             string
+				BG                   string
+				BGMeta               devianter.Deviation
+			}
+
+			Gallery struct {
+				Folders string
+				Pages   int
+				List    string
+			}
+		}
+		Search struct {
+			Content devianter.Search
+			List    string
+		}
+	}
+}
+
 func (s skunkyart) ExecuteTemplate(file, dir string, data any) {
 	var buf strings.Builder
 	tmp := template.New(file)
diff --git a/app/wrapper.go b/app/wrapper.go
index 5e8b6c1..e0e5054 100644
--- a/app/wrapper.go
+++ b/app/wrapper.go
@@ -2,9 +2,6 @@ package app
 
 import (
 	"encoding/json"
-	"io"
-	"net/http"
-	"net/url"
 	"regexp"
 	"strconv"
 	"strings"
@@ -14,65 +11,6 @@ import (
 	"golang.org/x/net/html"
 )
 
-var wr = io.WriteString
-
-type skunkyart struct {
-	Writer http.ResponseWriter
-
-	Args url.Values
-	Page int
-	Type rune
-	Atom bool
-
-	BasePath, Endpoint string
-	Query, QueryRaw    string
-
-	Templates struct {
-		About struct {
-			Proxy     bool
-			Nsfw      bool
-			Instances []settings
-		}
-
-		SomeList  string
-		DDStrips  string
-		Deviation struct {
-			Post       devianter.Post
-			Related    string
-			StringTime string
-			Tags       string
-			Comments   string
-		}
-
-		GroupUser struct {
-			GR           devianter.GRuser
-			Admins       string
-			Group        bool
-			CreationDate string
-
-			About struct {
-				A devianter.About
-
-				DescriptionFormatted string
-				Interests, Social    string
-				Comments             string
-				BG                   string
-				BGMeta               devianter.Deviation
-			}
-
-			Gallery struct {
-				Folders string
-				Pages   int
-				List    string
-			}
-		}
-		Search struct {
-			Content devianter.Search
-			List    string
-		}
-	}
-}
-
 func (s skunkyart) GRUser() {
 	if len(s.Query) < 1 {
 		s.ReturnHTTPError(400)
@@ -82,7 +20,7 @@ func (s skunkyart) GRUser() {
 	var g devianter.Group
 	g.Name = s.Query
 	var err error
-	s.Templates.GroupUser.GR, err = g.GetGroup()
+	s.Templates.GroupUser.GR, err = g.Get()
 	try(err)
 
 	group := &s.Templates.GroupUser
@@ -146,51 +84,65 @@ func (s skunkyart) GRUser() {
 			}
 
 		}
-	case 'g':
+	case 'g', 'f':
+		var all bool
+		var content devianter.Group
+
 		folderid, _ := strconv.Atoi(s.Args.Get("folder"))
+
+		if a := s.Args.Get("all"); a == "true" {
+			all = true
+		}
+
 		if s.Page == 0 {
 			s.Page++
 		}
 
-		gallery, err := g.GetGallery(s.Page, folderid)
-		try(err)
+		if s.Type == 'f' {
+			content = g.Favourites(s.Page, all, folderid)
+		} else {
+			content, err = g.Gallery(s.Page, folderid)
+			try(err)
+		}
 
-		if folderid > 0 {
-			group.Gallery.List = s.DeviationList(gallery.Content.Results, true, DeviationList{
-				More: gallery.Content.HasMore,
+		if folderid > 0 || (s.Type == 'f' && all) {
+			group.Gallery.List = s.DeviationList(content.Content.Results, true, DeviationList{
+				More: content.Content.HasMore,
 			})
 		} else {
-			for _, x := range gallery.Content.Gruser.Page.Modules {
+			for _, x := range content.Content.Gruser.Page.Modules {
 				if l := len(x.ModuleData.Folders.Results); l != 0 {
 					var folders strings.Builder
 					folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`)
 					for _, x := range x.ModuleData.Folders.Results {
-						folders.WriteString(`<div class="block folder-item">`)
-
-						if !(x.Thumb.NSFW && !CFG.Nsfw) {
-							folders.WriteString(`<a href="`)
-							folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url))
-							folders.WriteString(`"><img loading="lazy" src="`)
-							folders.WriteString(ParseMedia(x.Thumb.Media, x.Thumb.Title))
-							folders.WriteString(`" title="`)
-							folders.WriteString(x.Thumb.Title)
-							folders.WriteString(`"></a>`)
-						} else {
-							folders.WriteString(`<h1>[ <span class="nsfw">NSFW</span> ]</h1>`)
+						if x.FolderId != -1 && x.Size != 0 {
+							folders.WriteString(`<div class="block folder-item">`)
+
+							if !(x.Thumb.NSFW && !CFG.Nsfw) {
+								folders.WriteString(`<a href="`)
+								folders.WriteString(ConvertDeviantArtUrlToSkunkyArt(x.Thumb.Url))
+								folders.WriteString(`"><img loading="lazy" src="`)
+								folders.WriteString(ParseMedia(x.Thumb.Media, x.Thumb.Title))
+								folders.WriteString(`" title="`)
+								folders.WriteString(x.Thumb.Title)
+								folders.WriteString(`"></a>`)
+							} else {
+								folders.WriteString(`<h1>[ <span class="nsfw">NSFW</span> ]</h1>`)
+							}
+							folders.WriteString("<br>")
+
+							folders.WriteString(`<a href="group_user?folder=`)
+							folders.WriteString(strconv.Itoa(x.FolderId))
+							folders.WriteString("&q=")
+							folders.WriteString(s.Query)
+							folders.WriteString("&type=")
+							folders.WriteString(string(s.Type))
+							folders.WriteString(`">`)
+							folders.WriteString(x.Name)
+							folders.WriteString(`</a>`)
+
+							folders.WriteString("</div>")
 						}
-						folders.WriteString("<br>")
-
-						folders.WriteString(`<a href="group_user?folder=`)
-						folders.WriteString(strconv.Itoa(x.FolderId))
-						folders.WriteString("&q=")
-						folders.WriteString(s.Query)
-						folders.WriteString("&type=")
-						folders.WriteString(string(s.Type))
-						folders.WriteString(`">`)
-						folders.WriteString(x.Name)
-						folders.WriteString(`</a>`)
-
-						folders.WriteString("</div>")
 					}
 					folders.WriteString(`</div><h1 id="content"><a href="#content">#</a> Content</h1>`)
 					group.Gallery.Folders = folders.String()
@@ -296,7 +248,7 @@ func (s skunkyart) Search() {
 	switch s.Type {
 	case 'a', 't':
 		ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type)
-	case 'g':
+	case 'g', 'f':
 		ss.Content, err = devianter.PerformSearch(s.Query, s.Page, s.Type, s.Args.Get("usr"))
 	case 'r': // скраппер, поскольку девиантартовцы зажопили гостевое API для поиска групп
 		var (
diff --git a/go.mod b/go.mod
index 8016fa3..2b4844e 100644
--- a/go.mod
+++ b/go.mod
@@ -2,6 +2,7 @@ module skunkyart
 
 go 1.22.3
 
+replace git.macaw.me/skunky/devianter v0.2.5 => /home/skunk/projects/devianter
 require (
 	git.macaw.me/skunky/devianter v0.2.5
 	golang.org/x/net v0.27.0
diff --git a/instances.json b/instances.json
index cd95b78..74049be 100644
--- a/instances.json
+++ b/instances.json
@@ -25,7 +25,7 @@
         },
         {
             "title": "bloat.cat",
-            "country": "Romania",
+            "country": "Germany",
             "urls": {
                 "clearnet": "https://skunky.bloat.cat"
             },
diff --git a/static/html/gruser.htm b/static/html/gruser.htm
index c8227ef..6593951 100644
--- a/static/html/gruser.htm
+++ b/static/html/gruser.htm
@@ -3,14 +3,25 @@
     {{template "head" . }}
     <main>
         <header>
-            <h1><a href="{{.BasePath}}">HOME</a> | <a href="{{.BasePath}}dd">DD</a>
-            | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a>
-             | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a></h1>
+            <h1>
+                <a href="{{.BasePath}}">HOME</a>
+                | <a href="{{.BasePath}}dd">DD</a>
+                {{if ne .Type 'f'}}
+                    | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery{{else}}about">About{{end}}</a>
+                    | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=favourites">Favourites</a>
+                {{else}}
+                    | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=about">About</a>
+                    | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery">Gallery</a>
+                    | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=favourites">Favourites</a>
+                {{end}}
+                | <a href="group_user?q={{.Templates.GroupUser.GR.Owner.Username}}&type=gallery&atom=true">RSS</a>
+            </h1>
              <form method="get" action="{{.BasePath}}search">
                 <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
                 <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}">
                 <select name="type">
                     <option value="gallery">Gallery</option>
+                    <option value="folders">Folders</option>
                     <option value="all">All</option>
                     <option value="tag">Tag</option>
                     <option value="r">Groups</option>
diff --git a/static/html/head.htm b/static/html/head.htm
index f7f6b91..08ecf45 100644
--- a/static/html/head.htm
+++ b/static/html/head.htm
@@ -5,12 +5,13 @@
             "{{.QueryRaw}}"
         {{else if eq .Endpoint "post"}}
             {{.Templates.Deviation.Post.Deviation.Author.Username}} — {{.Templates.Deviation.Post.Deviation.Title}}
-        {{else if eq .Type 'a'}}
-            {{if .Templates.GroupUser.GR.Owner.Username}}
-                {{.Templates.GroupUser.GR.Owner.Username}}
-            {{else}}
-                gallery of {{.Templates.GroupUser.GR.Owner.Username}}
+        {{else if eq .Endpoint "group_user"}}
+            {{if eq .Type 'g'}}
+                gallery of
+            {{else if eq .Type 'f'}}
+                favourites of
             {{end}}
+            {{.Templates.GroupUser.GR.Owner.Username}}
         {{else}}
             {{.Endpoint}}
         {{end}}