omakase/main.go
2025-07-24 19:31:03 -03:00

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