ca609ccae7
Ports bases from previous session: util (shared helpers), bakkin, fmreader, foolslide, gigaviewer, gmanga, grouple, guya, heancms, hentaihand, kemono, madara, madtheme, mangadventure, mangahub, mangathemesia, mangaworld, mmrcms, senkuro, wpcomics.
180 lines
5.0 KiB
Go
180 lines
5.0 KiB
Go
// Package guya implements the Guya/Manga4Life reader base.
|
|
// GET {base}/api/get_all_series/ returns all manga as a JSON map.
|
|
package guya
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"goyomi/internal/httpclient"
|
|
"goyomi/internal/source"
|
|
)
|
|
|
|
type Config struct {
|
|
Name string
|
|
BaseURL string
|
|
Lang string
|
|
}
|
|
|
|
type seriesEntry struct {
|
|
Title string `json:"title"`
|
|
CoverVol string `json:"cover_vol"`
|
|
Groups map[string]string `json:"groups"`
|
|
Chapters map[string]chapterEntry `json:"chapters"`
|
|
}
|
|
|
|
type chapterEntry struct {
|
|
Title string `json:"title"`
|
|
Date int64 `json:"date"`
|
|
Groups map[string][]string `json:"groups"`
|
|
}
|
|
|
|
type Source struct {
|
|
cfg Config
|
|
client *httpclient.Client
|
|
id int64
|
|
}
|
|
|
|
func New(cfg Config) *Source {
|
|
c := httpclient.NewClient(httpclient.WithRateLimit(1, 2))
|
|
return &Source{cfg: cfg, client: c, id: source.GenerateSourceID(cfg.Name, cfg.Lang)}
|
|
}
|
|
|
|
func (s *Source) ID() int64 { return s.id }
|
|
func (s *Source) Name() string { return s.cfg.Name }
|
|
func (s *Source) Lang() string { return s.cfg.Lang }
|
|
func (s *Source) SupportsLatest() bool { return true }
|
|
|
|
func (s *Source) base() string { return strings.TrimRight(s.cfg.BaseURL, "/") }
|
|
|
|
func (s *Source) getAllSeries(ctx context.Context) (map[string]seriesEntry, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.base()+"/api/get_all_series/", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := s.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("guya: HTTP %d", resp.StatusCode)
|
|
}
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result map[string]seriesEntry
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Source) toSManga(slug string, entry seriesEntry) source.SManga {
|
|
thumb := ""
|
|
if entry.CoverVol != "" {
|
|
thumb = fmt.Sprintf("%s/media/manga/%s/volume-covers/%s", s.base(), slug, entry.CoverVol)
|
|
}
|
|
return source.SManga{
|
|
URL: fmt.Sprintf("/reader/series/%s/", slug),
|
|
Title: entry.Title,
|
|
ThumbnailURL: thumb,
|
|
}
|
|
}
|
|
|
|
func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
|
|
if page > 1 {
|
|
return source.MangasPage{}, nil
|
|
}
|
|
series, err := s.getAllSeries(context.Background())
|
|
if err != nil {
|
|
return source.MangasPage{}, err
|
|
}
|
|
var mangas []source.SManga
|
|
for slug, entry := range series {
|
|
mangas = append(mangas, s.toSManga(slug, entry))
|
|
}
|
|
return source.MangasPage{Mangas: mangas, HasNextPage: false}, nil
|
|
}
|
|
|
|
func (s *Source) GetLatestUpdates(page int) (source.MangasPage, error) {
|
|
return s.GetPopularManga(page)
|
|
}
|
|
|
|
func (s *Source) GetSearchManga(page int, query string, filters []source.Filter) (source.MangasPage, error) {
|
|
mp, err := s.GetPopularManga(1)
|
|
if err != nil {
|
|
return source.MangasPage{}, err
|
|
}
|
|
q := strings.ToLower(query)
|
|
var matched []source.SManga
|
|
for _, m := range mp.Mangas {
|
|
if strings.Contains(strings.ToLower(m.Title), q) {
|
|
matched = append(matched, m)
|
|
}
|
|
}
|
|
return source.MangasPage{Mangas: matched, HasNextPage: false}, nil
|
|
}
|
|
|
|
func (s *Source) GetMangaDetails(manga source.SManga) (source.SManga, error) {
|
|
slug := strings.Trim(strings.TrimPrefix(manga.URL, "/reader/series/"), "/")
|
|
apiURL := fmt.Sprintf("%s/api/series/%s/", s.base(), slug)
|
|
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil)
|
|
if err != nil {
|
|
return manga, err
|
|
}
|
|
resp, err := s.client.Do(req)
|
|
if err != nil {
|
|
return manga, err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(resp.Body)
|
|
var entry seriesEntry
|
|
if err := json.Unmarshal(body, &entry); err != nil {
|
|
return manga, err
|
|
}
|
|
return s.toSManga(slug, entry), nil
|
|
}
|
|
|
|
func (s *Source) GetChapterList(manga source.SManga) ([]source.SChapter, error) {
|
|
slug := strings.Trim(strings.TrimPrefix(manga.URL, "/reader/series/"), "/")
|
|
apiURL := fmt.Sprintf("%s/api/series/%s/", s.base(), slug)
|
|
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, apiURL, nil)
|
|
resp, err := s.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(resp.Body)
|
|
var entry seriesEntry
|
|
if err := json.Unmarshal(body, &entry); err != nil {
|
|
return nil, err
|
|
}
|
|
var chapters []source.SChapter
|
|
for chNum, ch := range entry.Chapters {
|
|
name := "Chapter " + chNum
|
|
if ch.Title != "" {
|
|
name += " - " + ch.Title
|
|
}
|
|
chapters = append(chapters, source.SChapter{
|
|
URL: fmt.Sprintf("%s/reader/series/%s/%s/", s.base(), slug, chNum),
|
|
Name: name,
|
|
DateUpload: ch.Date * 1000,
|
|
})
|
|
}
|
|
return chapters, nil
|
|
}
|
|
|
|
func (s *Source) GetPageList(chapter source.SChapter) ([]source.Page, error) {
|
|
// Pages are served as images at known paths; minimal impl returns empty
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *Source) GetImageURL(page source.Page) (string, error) { return page.ImageURL, nil }
|
|
func (s *Source) GetFilterList() []source.Filter { return nil }
|