369 lines
9.2 KiB
Go
369 lines
9.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"log"
|
|
"math/big"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"sort"
|
|
"strconv"
|
|
|
|
"goreader/gallery"
|
|
"goreader/state"
|
|
view "goreader/views"
|
|
|
|
"image"
|
|
_ "image/gif"
|
|
_ "image/jpeg"
|
|
"image/png"
|
|
|
|
"golang.org/x/image/draw"
|
|
|
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
|
)
|
|
|
|
var searchGalleriesError = fmt.Errorf("failure reading archives")
|
|
|
|
func searchGalleryDl(state *state.State) error {
|
|
defer func() {
|
|
log.Println("done searching galleries")
|
|
state.Done = true
|
|
}()
|
|
sourceDirectories, err := os.ReadDir(state.Root)
|
|
if err != nil {
|
|
return errors.Join(searchGalleriesError, err)
|
|
}
|
|
for _, source := range sourceDirectories {
|
|
log.Println("attemping", source)
|
|
if !source.IsDir() {
|
|
continue
|
|
}
|
|
sourcePath := filepath.Join(state.Root, source.Name())
|
|
sourceDir, err := os.ReadDir(sourcePath)
|
|
if err != nil {
|
|
return errors.Join(searchGalleriesError, err)
|
|
}
|
|
for _, galleryDir := range sourceDir {
|
|
|
|
galleryPath := filepath.Join(sourcePath, galleryDir.Name())
|
|
galleryFiles, err := os.ReadDir(galleryPath)
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
|
|
infoIndex := slices.IndexFunc(galleryFiles, func(a fs.DirEntry) bool {
|
|
return a.Name() == "info.json"
|
|
})
|
|
if infoIndex == -1 {
|
|
log.Println(fmt.Errorf("no index file: %s", galleryPath))
|
|
continue
|
|
}
|
|
infoPath := filepath.Join(galleryPath, "info.json")
|
|
infoBinary, err := os.ReadFile(infoPath)
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
|
|
var from gallery.Source
|
|
switch source.Name() {
|
|
case "nhentai":
|
|
from = gallery.Nhentai
|
|
case "exhentai":
|
|
from = gallery.Exhentai
|
|
case "hitomi":
|
|
from = gallery.Hitomi
|
|
default:
|
|
log.Printf("unrecognized source: \"%s\"\n", source.Name())
|
|
continue
|
|
}
|
|
|
|
info, err := gallery.NewGallery(from, infoBinary, galleryPath, galleryFiles)
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
|
|
err = state.AddGallery(info)
|
|
if os.IsNotExist(err) {
|
|
log.Println(err)
|
|
} else if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
|
|
state := &state.State{
|
|
Port: ":2323",
|
|
Root: "/home/xi/seed/gallery-dl/",
|
|
Galleries: make([]gallery.Gallery, 0, 50),
|
|
UniqueTags: make(map[string]int),
|
|
UniqueArtists: make(map[string]int),
|
|
UniqueGroups: make(map[string]int),
|
|
UniqueParodies: make(map[string]int),
|
|
Filtered: make([]gallery.Tag, 0),
|
|
}
|
|
|
|
fmt.Printf("live at http://localhost%s\n", state.Port)
|
|
|
|
server := &http.Server{
|
|
Addr: state.Port,
|
|
Handler: Logging(http.DefaultServeMux),
|
|
}
|
|
|
|
ln, err := net.Listen("tcp", server.Addr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
go func() {
|
|
// defer func() {
|
|
// if err := recover(); err != nil {
|
|
// e, ok := err.(error)
|
|
// if !ok {
|
|
// log.Println(err)
|
|
// } else {
|
|
// log.Println(e.Error())
|
|
// }
|
|
// }
|
|
// }()
|
|
err := searchGalleryDl(state)
|
|
if err != nil {
|
|
log.Println(err)
|
|
log.Println()
|
|
}
|
|
}()
|
|
|
|
cacheDir, err := os.UserCacheDir()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
state.CacheDir = filepath.Join(cacheDir, "omakase")
|
|
err = os.MkdirAll(state.CacheDir, 0o755)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
log.Println("cache directory path:", state.CacheDir)
|
|
|
|
http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" && r.URL.Path != "index.html" {
|
|
log.Println("not found:", r.URL.Path)
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
page, err := strconv.Atoi(r.URL.Query().Get("page"))
|
|
if err != nil {
|
|
page = 1
|
|
}
|
|
view.Index(state, page).Render(w)
|
|
})
|
|
|
|
// http.DefaultServeMux.HandleFunc("/static/styles.css", func(w http.ResponseWriter, r *http.Request) {
|
|
// file, err := os.Open("./views/static/styles.css")
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
// io.Copy(w, file)
|
|
// })
|
|
|
|
http.DefaultServeMux.HandleFunc("/read/{uuid}", func(w http.ResponseWriter, r *http.Request) {
|
|
uuid := r.PathValue("uuid")
|
|
if uuid == "" {
|
|
panic(fmt.Errorf("could not get gallery UUID. empty string."))
|
|
}
|
|
page := "1"
|
|
pageString := r.URL.Query().Get("page")
|
|
if pageString != "" {
|
|
page = pageString
|
|
}
|
|
found, err := state.FindTitle(uuid)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
_ = view.Reader(found, page).Render(w)
|
|
})
|
|
|
|
http.DefaultServeMux.HandleFunc("/random", func(w http.ResponseWriter, r *http.Request) {
|
|
f := state.FilterGalleries()
|
|
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(f))))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
http.Redirect(w, r, fmt.Sprintf("/read/%s", f[n.Int64()].Uuid()), http.StatusFound)
|
|
})
|
|
|
|
http.DefaultServeMux.HandleFunc("/page/{uuid}/{page}", func(w http.ResponseWriter, r *http.Request) {
|
|
uuid := r.PathValue("uuid")
|
|
pageString := r.PathValue("page")
|
|
if uuid == "" || pageString == "" {
|
|
panic(fmt.Errorf("uuid or page could not be found: %s", r.URL.Path))
|
|
}
|
|
page, err := strconv.Atoi(pageString)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
found, err := state.FindTitle(uuid)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
image, err := os.Open(found.Images()[page-1])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
_, _ = io.Copy(w, image)
|
|
})
|
|
|
|
http.DefaultServeMux.HandleFunc("/thumb/{uuid}", func(w http.ResponseWriter, r *http.Request) {
|
|
uuid := r.PathValue("uuid")
|
|
if uuid == "" {
|
|
panic(fmt.Errorf("uuid could not be found: %s", r.URL.Path))
|
|
}
|
|
found, err := state.FindTitle(uuid)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if len(found.Images()) == 0 {
|
|
panic(fmt.Errorf("why does gallery contain no images? \"%s\"", uuid))
|
|
}
|
|
imageCacheFilepath := filepath.Join(state.CacheDir, uuid)
|
|
imageCache, err := os.Open(imageCacheFilepath)
|
|
if err == nil {
|
|
_, _ = io.Copy(w, imageCache)
|
|
imageCache.Close()
|
|
return
|
|
}
|
|
if len(found.Images()) == 0 {
|
|
panic(fmt.Errorf("why does gallery contain no images? \"%s\"", uuid))
|
|
}
|
|
coverImagePath := found.Images()[0]
|
|
imageStream, err := os.Open(coverImagePath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer imageStream.Close()
|
|
sourceImage, _, err := image.Decode(imageStream)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
sourceSize := sourceImage.Bounds().Size()
|
|
aspectRatio := float64(sourceSize.X) / float64(sourceSize.Y)
|
|
destWidth := 148
|
|
destHeight := int(float64(destWidth) / aspectRatio)
|
|
scaler := draw.BiLinear.NewScaler(destWidth, destHeight, sourceSize.X, sourceSize.Y)
|
|
destRect := image.Rect(0, 0, destWidth, destHeight)
|
|
destImage := image.NewRGBA(destRect)
|
|
scaler.Scale(destImage, destRect, sourceImage, sourceImage.Bounds(), draw.Src, nil)
|
|
imageCache, err = os.Create(imageCacheFilepath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer imageCache.Close()
|
|
_ = png.Encode(io.MultiWriter(imageCache, w), destImage)
|
|
})
|
|
|
|
// http.DefaultServeMux.HandleFunc("GET /filter/{tag}/{sex}", func(w http.ResponseWriter, r *http.Request) {
|
|
// tagLookup := r.PathValue("tag")
|
|
// if tagLookup == "" {
|
|
// panic(fmt.Errorf("tag could not be found: %s", r.URL.Path))
|
|
// }
|
|
// if tagLookup == "clear" {
|
|
// state.Filtered = []gallery.Tag{}
|
|
// view.SearchResults(state.Galleries).Render(w)
|
|
// return
|
|
// }
|
|
// tagSexLookup := r.PathValue("sex")
|
|
// if tagSexLookup == "" {
|
|
// panic(fmt.Errorf("tag sex could not be found: %s", r.URL.Path))
|
|
// }
|
|
// sex := gallery.Gender(tagSexLookup)
|
|
// i := slices.IndexFunc(state.TagKeys, func(a gallery.Tag) bool {
|
|
// return a.Name == tagLookup && a.Sex == sex
|
|
// })
|
|
// if i < 0 {
|
|
// panic(fmt.Errorf("tag could not be found: %s", r.URL.Path))
|
|
// }
|
|
// tag := state.TagKeys[i]
|
|
// i = slices.Index(state.Filtered, tag)
|
|
// if i < 0 {
|
|
// state.Filtered = append(state.Filtered, tag)
|
|
// } else {
|
|
// state.Filtered = slices.Delete(state.Filtered, i, i+1)
|
|
// }
|
|
// view.SearchResults(state.FilterGalleries()).Render(w)
|
|
// })
|
|
|
|
http.DefaultServeMux.HandleFunc("GET /stats", func(w http.ResponseWriter, r *http.Request) {
|
|
view.Stats(state).Render(w)
|
|
})
|
|
|
|
http.DefaultServeMux.HandleFunc("GET /details/{uuid}", func(w http.ResponseWriter, r *http.Request) {
|
|
uuid := r.PathValue("uuid")
|
|
if uuid == "" {
|
|
panic(fmt.Errorf("uuid could not be found: %s", r.URL.Path))
|
|
}
|
|
g, err := state.FindTitle(uuid)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
view.InspectorInfo(g, state).Render(w)
|
|
})
|
|
|
|
// FIXME
|
|
http.DefaultServeMux.HandleFunc("POST /search", func(w http.ResponseWriter, r *http.Request) {
|
|
query := r.FormValue("search")
|
|
log.Println("search form result:", query)
|
|
if query == "" {
|
|
view.SearchResults(state.Galleries).Render(w)
|
|
return
|
|
}
|
|
// ranks := fuzzy.RankFindFold(search, state.GalleryNames)
|
|
var ranks fuzzy.Ranks
|
|
for i, g := range state.Galleries {
|
|
en := fuzzy.RankMatchFold(query, g.Name())
|
|
rank := fuzzy.Rank{
|
|
Source: query,
|
|
Target: g.JpName(),
|
|
Distance: en,
|
|
OriginalIndex: i,
|
|
}
|
|
if jp := fuzzy.RankMatchFold(query, g.JpName()); jp >= 0 && jp < en {
|
|
rank = fuzzy.Rank{
|
|
Source: query,
|
|
Target: g.JpName(),
|
|
Distance: jp,
|
|
OriginalIndex: i,
|
|
}
|
|
}
|
|
if rank.Distance < 0 {
|
|
continue
|
|
}
|
|
ranks = append(ranks, rank)
|
|
}
|
|
sort.Sort(ranks)
|
|
var galleries []gallery.Gallery
|
|
for i, r := range ranks {
|
|
if i == 30 {
|
|
break
|
|
}
|
|
galleries = append(galleries, state.Galleries[r.OriginalIndex])
|
|
}
|
|
view.SearchResults(galleries).Render(w)
|
|
})
|
|
|
|
log.Fatal(server.Serve(ln))
|
|
}
|