Files
seanime-docker/seanime-2.9.10/internal/manga/providers/manganato.go
2025-09-20 14:08:38 +01:00

303 lines
7.6 KiB
Go

package manga_providers
import (
"bytes"
"fmt"
"net/url"
hibikemanga "seanime/internal/extension/hibike/manga"
"seanime/internal/util"
"seanime/internal/util/comparison"
"slices"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/imroc/req/v3"
"github.com/rs/zerolog"
)
type (
Manganato struct {
Url string
Client *req.Client
logger *zerolog.Logger
}
ManganatoSearchResult struct {
ID string `json:"id"`
Name string `json:"name"`
NameUnsigned string `json:"nameunsigned"`
LastChapter string `json:"lastchapter"`
Image string `json:"image"`
Author string `json:"author"`
StoryLink string `json:"story_link"`
}
)
func NewManganato(logger *zerolog.Logger) *Manganato {
client := req.C().
SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36").
SetTimeout(60 * time.Second).
EnableInsecureSkipVerify().
ImpersonateSafari()
return &Manganato{
Url: "https://natomanga.com",
Client: client,
logger: logger,
}
}
func (mp *Manganato) GetSettings() hibikemanga.Settings {
return hibikemanga.Settings{
SupportsMultiScanlator: false,
SupportsMultiLanguage: false,
}
}
func (mp *Manganato) Search(opts hibikemanga.SearchOptions) (ret []*hibikemanga.SearchResult, err error) {
ret = make([]*hibikemanga.SearchResult, 0)
mp.logger.Debug().Str("query", opts.Query).Msg("manganato: Searching manga")
q := opts.Query
q = strings.ReplaceAll(q, " ", "_")
q = strings.ToLower(q)
q = strings.TrimSpace(q)
q = url.QueryEscape(q)
uri := fmt.Sprintf("https://natomanga.com/search/story/%s", q)
resp, err := mp.Client.R().
SetHeader("User-Agent", util.GetRandomUserAgent()).
Get(uri)
if err != nil {
mp.logger.Error().Err(err).Str("uri", uri).Msg("manganato: Failed to send request")
return nil, err
}
if !resp.IsSuccessState() {
mp.logger.Error().Str("status", resp.Status).Str("uri", uri).Msg("manganato: Request failed")
return nil, fmt.Errorf("failed to fetch search results: status %s", resp.Status)
}
bodyBytes := resp.Bytes()
//mp.logger.Debug().Str("body", string(bodyBytes)).Msg("manganato: Response body")
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))
if err != nil {
mp.logger.Error().Err(err).Msg("manganato: Failed to parse HTML")
return nil, err
}
doc.Find("div.story_item").Each(func(i int, s *goquery.Selection) {
defer func() {
if r := recover(); r != nil {
}
}()
result := &hibikemanga.SearchResult{
Provider: string(ManganatoProvider),
}
href, exists := s.Find("a").Attr("href")
if !exists {
return
}
if !strings.HasPrefix(href, "https://natomanga.com/") &&
!strings.HasPrefix(href, "https://www.natomanga.com/") &&
!strings.HasPrefix(href, "https://www.chapmanganato.com/") &&
!strings.HasPrefix(href, "https://chapmanganato.com/") {
return
}
result.ID = href
splitHref := strings.Split(result.ID, "/")
if strings.Contains(href, "chapmanganato") {
result.ID = "chapmanganato$"
} else {
result.ID = "manganato$"
}
if len(splitHref) > 4 {
result.ID += splitHref[4]
}
result.Title = s.Find("h3.story_name").Text()
result.Title = strings.TrimSpace(result.Title)
result.Image, _ = s.Find("img").Attr("src")
compRes, _ := comparison.FindBestMatchWithSorensenDice(&opts.Query, []*string{&result.Title})
result.SearchRating = compRes.Rating
ret = append(ret, result)
})
if len(ret) == 0 {
mp.logger.Error().Str("query", opts.Query).Msg("manganato: No results found")
return nil, ErrNoResults
}
mp.logger.Info().Int("count", len(ret)).Msg("manganato: Found results")
return ret, nil
}
func (mp *Manganato) FindChapters(id string) (ret []*hibikemanga.ChapterDetails, err error) {
ret = make([]*hibikemanga.ChapterDetails, 0)
mp.logger.Debug().Str("mangaId", id).Msg("manganato: Finding chapters")
splitId := strings.Split(id, "$")
if len(splitId) != 2 {
mp.logger.Error().Str("mangaId", id).Msg("manganato: Invalid manga ID")
return nil, ErrNoChapters
}
uri := ""
if splitId[0] == "manganato" {
uri = fmt.Sprintf("https://natomanga.com/manga/%s", splitId[1])
} else if splitId[0] == "chapmanganato" {
uri = fmt.Sprintf("https://chapmanganato.com/manga/%s", splitId[1])
}
resp, err := mp.Client.R().
SetHeader("User-Agent", util.GetRandomUserAgent()).
Get(uri)
if err != nil {
mp.logger.Error().Err(err).Str("uri", uri).Msg("manganato: Failed to send request")
return nil, err
}
if !resp.IsSuccessState() {
mp.logger.Error().Str("status", resp.Status).Str("uri", uri).Msg("manganato: Request failed")
return nil, fmt.Errorf("failed to fetch chapters: status %s", resp.Status)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
mp.logger.Error().Err(err).Msg("manganato: Failed to parse HTML")
return nil, err
}
doc.Find(".chapter-list .row").Each(func(i int, s *goquery.Selection) {
defer func() {
if r := recover(); r != nil {
}
}()
name := s.Find("a").Text()
if strings.HasPrefix(name, "Vol.") {
split := strings.Split(name, " ")
name = strings.Join(split[1:], " ")
}
chStr := strings.TrimSpace(strings.Split(name, " ")[1])
chStr = strings.TrimSuffix(chStr, ":")
href, exists := s.Find("a").Attr("href")
if !exists {
return
}
hrefParts := strings.Split(href, "/")
if len(hrefParts) < 6 {
return
}
chapterId := hrefParts[5]
chapter := &hibikemanga.ChapterDetails{
Provider: string(ManganatoProvider),
ID: splitId[1] + "$" + chapterId,
URL: href,
Title: strings.TrimSpace(name),
Chapter: chStr,
}
ret = append(ret, chapter)
})
slices.Reverse(ret)
for i, chapter := range ret {
chapter.Index = uint(i)
}
if len(ret) == 0 {
mp.logger.Error().Str("mangaId", id).Msg("manganato: No chapters found")
return nil, ErrNoChapters
}
mp.logger.Info().Int("count", len(ret)).Msg("manganato: Found chapters")
return ret, nil
}
func (mp *Manganato) FindChapterPages(id string) (ret []*hibikemanga.ChapterPage, err error) {
ret = make([]*hibikemanga.ChapterPage, 0)
mp.logger.Debug().Str("chapterId", id).Msg("manganato: Finding chapter pages")
splitId := strings.Split(id, "$")
if len(splitId) != 2 {
mp.logger.Error().Str("chapterId", id).Msg("manganato: Invalid chapter ID")
return nil, ErrNoPages
}
uri := fmt.Sprintf("https://natomanga.com/manga/%s/%s", splitId[0], splitId[1])
resp, err := mp.Client.R().
SetHeader("User-Agent", util.GetRandomUserAgent()).
SetHeader("Referer", "https://natomanga.com/").
Get(uri)
if err != nil {
mp.logger.Error().Err(err).Str("uri", uri).Msg("manganato: Failed to send request")
return nil, err
}
if !resp.IsSuccessState() {
mp.logger.Error().Str("status", resp.Status).Str("uri", uri).Msg("manganato: Request failed")
return nil, fmt.Errorf("failed to fetch chapter pages: status %s", resp.Status)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
mp.logger.Error().Err(err).Msg("manganato: Failed to parse HTML")
return nil, err
}
doc.Find(".container-chapter-reader img").Each(func(i int, s *goquery.Selection) {
defer func() {
if r := recover(); r != nil {
}
}()
src, exists := s.Attr("src")
if !exists || src == "" {
return
}
page := &hibikemanga.ChapterPage{
Provider: string(ManganatoProvider),
URL: src,
Index: len(ret),
Headers: map[string]string{
"Referer": "https://natomanga.com/",
},
}
ret = append(ret, page)
})
if len(ret) == 0 {
mp.logger.Error().Str("chapterId", id).Msg("manganato: No pages found")
return nil, ErrNoPages
}
mp.logger.Info().Int("count", len(ret)).Msg("manganato: Found pages")
return ret, nil
}