package gallery import ( "crypto/md5" "encoding/json" "fmt" "io/fs" "os" "path/filepath" "strings" "time" ) type Gallery struct { images []string uuid string title string jpTitle string tags []Tag source Source CTime time.Time artists []string groups []string parodies []string } func (g *Gallery) Name() string { return g.title } func (g *Gallery) JpName() string { return g.jpTitle } func (g *Gallery) Uuid() string { return g.uuid } func (g *Gallery) Images() []string { return g.images } func (g *Gallery) Tags() []Tag { return g.tags } func (g *Gallery) Source() Source { return g.source } func (g *Gallery) Artists() []string { return g.artists } func (g *Gallery) Groups() []string { return g.groups } func (g *Gallery) Parodies() []string { return g.parodies } func newGallery( images []string, uuid string, title string, jpTitle string, tags []Tag, source Source, CTime time.Time, artists []string, groups []string, parodies []string, ) Gallery { return Gallery{ images: images, uuid: uuid, title: title, jpTitle: jpTitle, tags: tags, source: source, CTime: CTime, artists: artists, groups: groups, parodies: parodies, } } type Source string const ( Nhentai Source = "nhentai" Exhentai = "exhentai" Hitomi = "hitomi" ) type Gender string const ( Male Gender = "male" Female Gender = "female" Any Gender = "any" ) type Tag struct { Name string Sex Gender } func newTag(name string, sex Gender) Tag { return Tag{ Name: strings.ToLower(name), Sex: sex, } } func genUuid(title string) string { return fmt.Sprintf("%x", md5.Sum([]byte(title))) } func filterImages(root string, galleryDir []fs.DirEntry) []string { var paths []string for _, file := range galleryDir { if file.IsDir() { continue } name := file.Name() // fixme isJpeg := strings.HasSuffix(name, ".jpeg") || strings.HasSuffix(name, ".jpg") isPng := strings.HasSuffix(name, ".png") isWebp := strings.HasSuffix(name, ".webp") isJson := strings.HasSuffix(name, ".json") if isJson { continue } if isJpeg || isPng || isWebp { paths = append(paths, filepath.Join(root, name)) } } return paths } func NewNhentaiGallery(infoBinary []byte, root string, galleryDir []fs.DirEntry) (Gallery, error) { var data struct { Title string Title_en string `json:"title_en"` Title_ja string // Gallery_id string `json:"gallery_id"` // troublesome Media_id int Date int64 Scanlator string Artist, Group, Parody, Characters, Tags []string Type, Lang, Language, Category, Subcategory string Count int } err := json.Unmarshal(infoBinary, &data) if err != nil { return Gallery{}, err } var tags []Tag for _, t := range data.Tags { tags = append(tags, newTag(t, Any)) } images := filterImages(root, galleryDir) d, err := os.Stat(root) if err != nil { return Gallery{}, err } g := newGallery( images, genUuid(data.Title), data.Title, data.Title_ja, tags, Nhentai, d.ModTime(), data.Artist, data.Group, data.Parody, ) return g, nil } func NewExhentaiGallery(infoBinary []byte, root string, galleryDir []fs.DirEntry) (Gallery, error) { var data struct { Gid int Token string Thumb string Title string Title_jpn string Eh_category string Uploader string Date string Parent string Language string Filecount string Favorites string Rating string Torrentcount string Lang string Category string Subcategory string Expunged bool Filesize int Tags []string } err := json.Unmarshal(infoBinary, &data) if err != nil { return Gallery{}, err } var ( tags []Tag artists []string groups []string parodies []string ) for _, t := range data.Tags { switch { case strings.HasPrefix(t, "female:"): tags = append(tags, newTag(t[7:], Female)) case strings.HasPrefix(t, "male:"): tags = append(tags, newTag(t[5:], Male)) case strings.HasPrefix(t, "artist:"): artists = append(artists, t[7:]) case strings.HasPrefix(t, "group:"): groups = append(groups, t[6:]) case strings.HasPrefix(t, "parody:"): parodies = append(parodies, t[7:]) } } d, err := os.Stat(root) if err != nil { return Gallery{}, err } g := newGallery( filterImages(root, galleryDir), genUuid(data.Title), data.Title, data.Title_jpn, tags, Exhentai, d.ModTime(), artists, groups, parodies, ) return g, nil } func NewHitomiGallery(infoBinary []byte, root string, galleryDir []fs.DirEntry) (Gallery, error) { var data struct { Gallery_id int Title string Title_jpn string Type string Language string Lang string Date string Tags []string Artist []string Group []string Parody []string Characters []string Count int Category string Subcategory string } err := json.Unmarshal(infoBinary, &data) if err != nil { return Gallery{}, err } var tags []Tag for _, t := range data.Tags { sex := Any name := t if strings.HasSuffix(t, "♀") { sex = Female name = t[:len(t)-3] } else if strings.HasSuffix(t, "♂") { sex = Male name = t[:len(t)-3] } tags = append(tags, newTag(name, sex)) } d, err := os.Stat(root) if err != nil { return Gallery{}, err } g := newGallery( filterImages(root, galleryDir), genUuid(data.Title), data.Title, data.Title_jpn, tags, Hitomi, d.ModTime(), data.Artist, data.Group, data.Parody, ) return g, nil } func NewGallery(source Source, infoBinary []byte, root string, galleryDir []fs.DirEntry) (Gallery, error) { switch source { case Nhentai: return NewNhentaiGallery(infoBinary, root, galleryDir) case Exhentai: return NewExhentaiGallery(infoBinary, root, galleryDir) case Hitomi: return NewHitomiGallery(infoBinary, root, galleryDir) } panic(source) }