node build fixed
This commit is contained in:
388
seanime-2.9.10/internal/manga/providers/mangadex.go
Normal file
388
seanime-2.9.10/internal/manga/providers/mangadex.go
Normal file
@@ -0,0 +1,388 @@
|
||||
package manga_providers
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"net/url"
|
||||
hibikemanga "seanime/internal/extension/hibike/manga"
|
||||
"seanime/internal/util"
|
||||
"seanime/internal/util/comparison"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type (
|
||||
Mangadex struct {
|
||||
Url string
|
||||
BaseUrl string
|
||||
UserAgent string
|
||||
Client *req.Client
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
MangadexManga struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Attributes MangadexMangeAttributes
|
||||
Relationships []MangadexMangaRelationship `json:"relationships"`
|
||||
}
|
||||
|
||||
MangadexMangeAttributes struct {
|
||||
AltTitles []map[string]string `json:"altTitles"`
|
||||
Title map[string]string `json:"title"`
|
||||
Year int `json:"year"`
|
||||
}
|
||||
|
||||
MangadexMangaRelationship struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Related string `json:"related"`
|
||||
Attributes map[string]interface{} `json:"attributes"`
|
||||
}
|
||||
|
||||
MangadexErrorResponse struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Code string `json:"code"`
|
||||
Title string `json:"title"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
MangadexChapterData struct {
|
||||
ID string `json:"id"`
|
||||
Attributes MangadexChapterAttributes `json:"attributes"`
|
||||
}
|
||||
|
||||
MangadexChapterAttributes struct {
|
||||
Title string `json:"title"`
|
||||
Volume string `json:"volume"`
|
||||
Chapter string `json:"chapter"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
}
|
||||
)
|
||||
|
||||
// DEVNOTE: Each chapter ID is a unique string provided by Mangadex
|
||||
|
||||
func NewMangadex(logger *zerolog.Logger) *Mangadex {
|
||||
client := req.C().
|
||||
SetUserAgent(util.GetRandomUserAgent()).
|
||||
SetTimeout(60 * time.Second).
|
||||
EnableInsecureSkipVerify().
|
||||
ImpersonateChrome()
|
||||
|
||||
return &Mangadex{
|
||||
Url: "https://api.mangadex.org",
|
||||
BaseUrl: "https://mangadex.org",
|
||||
Client: client,
|
||||
UserAgent: util.GetRandomUserAgent(),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (md *Mangadex) GetSettings() hibikemanga.Settings {
|
||||
return hibikemanga.Settings{
|
||||
SupportsMultiScanlator: false,
|
||||
SupportsMultiLanguage: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (md *Mangadex) Search(opts hibikemanga.SearchOptions) ([]*hibikemanga.SearchResult, error) {
|
||||
ret := make([]*hibikemanga.SearchResult, 0)
|
||||
|
||||
retManga := make([]*MangadexManga, 0)
|
||||
|
||||
for i := range 1 {
|
||||
uri := fmt.Sprintf("%s/manga?title=%s&limit=25&offset=%d&order[relevance]=desc&contentRating[]=safe&contentRating[]=suggestive&includes[]=cover_art", md.Url, url.QueryEscape(opts.Query), 25*i)
|
||||
|
||||
var data struct {
|
||||
Data []*MangadexManga `json:"data"`
|
||||
}
|
||||
|
||||
resp, err := md.Client.R().
|
||||
SetHeader("Referer", "https://google.com").
|
||||
SetSuccessResult(&data).
|
||||
Get(uri)
|
||||
|
||||
if err != nil {
|
||||
md.logger.Error().Err(err).Msg("mangadex: Failed to send request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
md.logger.Error().Str("status", resp.Status).Msg("mangadex: Request failed")
|
||||
return nil, fmt.Errorf("failed to decode response: status %s", resp.Status)
|
||||
}
|
||||
|
||||
retManga = append(retManga, data.Data...)
|
||||
}
|
||||
|
||||
for _, manga := range retManga {
|
||||
var altTitles []string
|
||||
for _, title := range manga.Attributes.AltTitles {
|
||||
altTitle, ok := title["en"]
|
||||
if ok {
|
||||
altTitles = append(altTitles, altTitle)
|
||||
}
|
||||
altTitle, ok = title["jp"]
|
||||
if ok {
|
||||
altTitles = append(altTitles, altTitle)
|
||||
}
|
||||
altTitle, ok = title["ja"]
|
||||
if ok {
|
||||
altTitles = append(altTitles, altTitle)
|
||||
}
|
||||
}
|
||||
t := getTitle(manga.Attributes)
|
||||
|
||||
var img string
|
||||
for _, relation := range manga.Relationships {
|
||||
if relation.Type == "cover_art" {
|
||||
fn, ok := relation.Attributes["fileName"].(string)
|
||||
if ok {
|
||||
img = fmt.Sprintf("%s/covers/%s/%s.512.jpg", md.BaseUrl, manga.ID, fn)
|
||||
} else {
|
||||
img = fmt.Sprintf("%s/covers/%s/%s.jpg.512.jpg", md.BaseUrl, manga.ID, relation.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
format := strings.ToUpper(manga.Type)
|
||||
if format == "ADAPTATION" {
|
||||
format = "MANGA"
|
||||
}
|
||||
|
||||
compRes, _ := comparison.FindBestMatchWithSorensenDice(&opts.Query, []*string{&t})
|
||||
|
||||
result := &hibikemanga.SearchResult{
|
||||
ID: manga.ID,
|
||||
Title: t,
|
||||
Synonyms: altTitles,
|
||||
Image: img,
|
||||
Year: manga.Attributes.Year,
|
||||
SearchRating: compRes.Rating,
|
||||
Provider: string(MangadexProvider),
|
||||
}
|
||||
|
||||
ret = append(ret, result)
|
||||
}
|
||||
|
||||
if len(ret) == 0 {
|
||||
md.logger.Error().Msg("mangadex: No results found")
|
||||
return nil, ErrNoResults
|
||||
}
|
||||
|
||||
md.logger.Info().Int("count", len(ret)).Msg("mangadex: Found results")
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (md *Mangadex) FindChapters(id string) ([]*hibikemanga.ChapterDetails, error) {
|
||||
ret := make([]*hibikemanga.ChapterDetails, 0)
|
||||
|
||||
md.logger.Debug().Str("mangaId", id).Msg("mangadex: Finding chapters")
|
||||
|
||||
for page := 0; page <= 1; page++ {
|
||||
uri := fmt.Sprintf("%s/manga/%s/feed?limit=500&translatedLanguage%%5B%%5D=en&includes[]=scanlation_group&includes[]=user&order[volume]=desc&order[chapter]=desc&offset=%d&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic", md.Url, id, 500*page)
|
||||
|
||||
var data struct {
|
||||
Result string `json:"result"`
|
||||
Errors []MangadexErrorResponse `json:"errors"`
|
||||
Data []MangadexChapterData `json:"data"`
|
||||
}
|
||||
|
||||
resp, err := md.Client.R().
|
||||
SetSuccessResult(&data).
|
||||
Get(uri)
|
||||
|
||||
if err != nil {
|
||||
md.logger.Error().Err(err).Msg("mangadex: Failed to send request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
md.logger.Error().Str("status", resp.Status).Msg("mangadex: Request failed")
|
||||
return nil, fmt.Errorf("failed to decode response: status %s", resp.Status)
|
||||
}
|
||||
|
||||
if data.Result == "error" {
|
||||
md.logger.Error().Str("error", data.Errors[0].Title).Str("detail", data.Errors[0].Detail).Msg("mangadex: Could not find chapters")
|
||||
return nil, fmt.Errorf("could not find chapters: %s", data.Errors[0].Detail)
|
||||
}
|
||||
|
||||
slices.Reverse(data.Data)
|
||||
|
||||
chapterMap := make(map[string]*hibikemanga.ChapterDetails)
|
||||
idx := uint(len(ret))
|
||||
for _, chapter := range data.Data {
|
||||
|
||||
if chapter.Attributes.Chapter == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
title := "Chapter " + fmt.Sprintf("%s", chapter.Attributes.Chapter) + " "
|
||||
|
||||
if _, ok := chapterMap[chapter.Attributes.Chapter]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
chapterMap[chapter.Attributes.Chapter] = &hibikemanga.ChapterDetails{
|
||||
ID: chapter.ID,
|
||||
Title: title,
|
||||
Index: idx,
|
||||
Chapter: chapter.Attributes.Chapter,
|
||||
UpdatedAt: chapter.Attributes.UpdatedAt,
|
||||
Provider: string(MangadexProvider),
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
chapters := make([]*hibikemanga.ChapterDetails, 0, len(chapterMap))
|
||||
for _, chapter := range chapterMap {
|
||||
chapters = append(chapters, chapter)
|
||||
}
|
||||
|
||||
slices.SortStableFunc(chapters, func(i, j *hibikemanga.ChapterDetails) int {
|
||||
return cmp.Compare(i.Index, j.Index)
|
||||
})
|
||||
|
||||
if len(chapters) > 0 {
|
||||
ret = append(ret, chapters...)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(ret) == 0 {
|
||||
md.logger.Error().Msg("mangadex: No chapters found")
|
||||
return nil, ErrNoChapters
|
||||
}
|
||||
|
||||
md.logger.Info().Int("count", len(ret)).Msg("mangadex: Found chapters")
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (md *Mangadex) FindChapterPages(id string) ([]*hibikemanga.ChapterPage, error) {
|
||||
ret := make([]*hibikemanga.ChapterPage, 0)
|
||||
|
||||
md.logger.Debug().Str("chapterId", id).Msg("mangadex: Finding chapter pages")
|
||||
|
||||
uri := fmt.Sprintf("%s/at-home/server/%s", md.Url, id)
|
||||
|
||||
var data struct {
|
||||
BaseUrl string `json:"baseUrl"`
|
||||
Chapter struct {
|
||||
Hash string `json:"hash"`
|
||||
Data []string `json:"data"`
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := md.Client.R().
|
||||
SetHeader("User-Agent", util.GetRandomUserAgent()).
|
||||
SetSuccessResult(&data).
|
||||
Get(uri)
|
||||
|
||||
if err != nil {
|
||||
md.logger.Error().Err(err).Msg("mangadex: Failed to get chapter pages")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
md.logger.Error().Str("status", resp.Status).Msg("mangadex: Request failed")
|
||||
return nil, fmt.Errorf("failed to decode response: status %s", resp.Status)
|
||||
}
|
||||
|
||||
for i, page := range data.Chapter.Data {
|
||||
ret = append(ret, &hibikemanga.ChapterPage{
|
||||
Provider: string(MangadexProvider),
|
||||
URL: fmt.Sprintf("%s/data/%s/%s", data.BaseUrl, data.Chapter.Hash, page),
|
||||
Index: i,
|
||||
Headers: map[string]string{
|
||||
"Referer": "https://mangadex.org",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if len(ret) == 0 {
|
||||
md.logger.Error().Msg("mangadex: No pages found")
|
||||
return nil, ErrNoPages
|
||||
}
|
||||
|
||||
md.logger.Info().Int("count", len(ret)).Msg("mangadex: Found pages")
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func getTitle(attributes MangadexMangeAttributes) string {
|
||||
altTitles := attributes.AltTitles
|
||||
title := attributes.Title
|
||||
|
||||
enTitle := title["en"]
|
||||
if enTitle != "" {
|
||||
return enTitle
|
||||
}
|
||||
|
||||
var enAltTitle string
|
||||
for _, altTitle := range altTitles {
|
||||
if value, ok := altTitle["en"]; ok {
|
||||
enAltTitle = value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if enAltTitle != "" && util.IsMostlyLatinString(enAltTitle) {
|
||||
return enAltTitle
|
||||
}
|
||||
|
||||
// Check for other language titles
|
||||
if jaRoTitle, ok := title["ja-ro"]; ok {
|
||||
return jaRoTitle
|
||||
}
|
||||
if jpRoTitle, ok := title["jp-ro"]; ok {
|
||||
return jpRoTitle
|
||||
}
|
||||
if jpTitle, ok := title["jp"]; ok {
|
||||
return jpTitle
|
||||
}
|
||||
if jaTitle, ok := title["ja"]; ok {
|
||||
return jaTitle
|
||||
}
|
||||
if koTitle, ok := title["ko"]; ok {
|
||||
return koTitle
|
||||
}
|
||||
|
||||
// Check alt titles for other languages
|
||||
for _, altTitle := range altTitles {
|
||||
if value, ok := altTitle["ja-ro"]; ok {
|
||||
return value
|
||||
}
|
||||
}
|
||||
for _, altTitle := range altTitles {
|
||||
if value, ok := altTitle["jp-ro"]; ok {
|
||||
return value
|
||||
}
|
||||
}
|
||||
for _, altTitle := range altTitles {
|
||||
if value, ok := altTitle["jp"]; ok {
|
||||
return value
|
||||
}
|
||||
}
|
||||
for _, altTitle := range altTitles {
|
||||
if value, ok := altTitle["ja"]; ok {
|
||||
return value
|
||||
}
|
||||
}
|
||||
for _, altTitle := range altTitles {
|
||||
if value, ok := altTitle["ko"]; ok {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user