node build fixed
This commit is contained in:
376
seanime-2.9.10/internal/manga/providers/comick.go
Normal file
376
seanime-2.9.10/internal/manga/providers/comick.go
Normal file
@@ -0,0 +1,376 @@
|
||||
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 (
|
||||
ComicK struct {
|
||||
Url string
|
||||
Client *req.Client
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
ComicKResultItem struct {
|
||||
ID int `json:"id"`
|
||||
HID string `json:"hid"`
|
||||
Slug string `json:"slug"`
|
||||
Title string `json:"title"`
|
||||
Country string `json:"country"`
|
||||
Rating string `json:"rating"`
|
||||
BayesianRating string `json:"bayesian_rating"`
|
||||
RatingCount int `json:"rating_count"`
|
||||
FollowCount int `json:"follow_count"`
|
||||
Description string `json:"desc"`
|
||||
Status int `json:"status"`
|
||||
LastChapter float64 `json:"last_chapter"`
|
||||
TranslationCompleted bool `json:"translation_completed"`
|
||||
ViewCount int `json:"view_count"`
|
||||
ContentRating string `json:"content_rating"`
|
||||
Demographic int `json:"demographic"`
|
||||
UploadedAt string `json:"uploaded_at"`
|
||||
Genres []int `json:"genres"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UserFollowCount int `json:"user_follow_count"`
|
||||
Year int `json:"year"`
|
||||
MuComics struct {
|
||||
Year int `json:"year"`
|
||||
} `json:"mu_comics"`
|
||||
MdTitles []struct {
|
||||
Title string `json:"title"`
|
||||
} `json:"md_titles"`
|
||||
MdCovers []struct {
|
||||
W int `json:"w"`
|
||||
H int `json:"h"`
|
||||
B2Key string `json:"b2key"`
|
||||
} `json:"md_covers"`
|
||||
Highlight string `json:"highlight"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewComicK(logger *zerolog.Logger) *ComicK {
|
||||
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 &ComicK{
|
||||
Url: "https://api.comick.fun",
|
||||
Client: client,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// DEVNOTE: Each chapter ID is a unique string provided by ComicK
|
||||
|
||||
func (c *ComicK) GetSettings() hibikemanga.Settings {
|
||||
return hibikemanga.Settings{
|
||||
SupportsMultiScanlator: false,
|
||||
SupportsMultiLanguage: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ComicK) Search(opts hibikemanga.SearchOptions) ([]*hibikemanga.SearchResult, error) {
|
||||
searchUrl := fmt.Sprintf("%s/v1.0/search?q=%s&limit=25&page=1", c.Url, url.QueryEscape(opts.Query))
|
||||
if opts.Year != 0 {
|
||||
searchUrl += fmt.Sprintf("&from=%d&to=%d", opts.Year, opts.Year)
|
||||
}
|
||||
|
||||
c.logger.Debug().Str("searchUrl", searchUrl).Msg("comick: Searching manga")
|
||||
|
||||
var data []*ComicKResultItem
|
||||
resp, err := c.Client.R().
|
||||
SetSuccessResult(&data).
|
||||
Get(searchUrl)
|
||||
|
||||
if err != nil {
|
||||
c.logger.Error().Err(err).Msg("comick: Failed to send request")
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
c.logger.Error().Str("status", resp.Status).Msg("comick: Request failed")
|
||||
return nil, fmt.Errorf("failed to reach API: status %s", resp.Status)
|
||||
}
|
||||
|
||||
results := make([]*hibikemanga.SearchResult, 0)
|
||||
for _, result := range data {
|
||||
|
||||
// Skip fan-colored manga
|
||||
if strings.Contains(result.Slug, "fan-colored") {
|
||||
continue
|
||||
}
|
||||
|
||||
var coverURL string
|
||||
if len(result.MdCovers) > 0 && result.MdCovers[0].B2Key != "" {
|
||||
coverURL = "https://meo.comick.pictures/" + result.MdCovers[0].B2Key
|
||||
}
|
||||
|
||||
altTitles := make([]string, len(result.MdTitles))
|
||||
for j, title := range result.MdTitles {
|
||||
altTitles[j] = title.Title
|
||||
}
|
||||
|
||||
// DEVNOTE: We don't compare to alt titles because ComicK's synonyms aren't good
|
||||
compRes, _ := comparison.FindBestMatchWithSorensenDice(&opts.Query, []*string{&result.Title})
|
||||
|
||||
results = append(results, &hibikemanga.SearchResult{
|
||||
ID: result.HID,
|
||||
Title: cmp.Or(result.Title, result.Slug),
|
||||
Synonyms: altTitles,
|
||||
Image: coverURL,
|
||||
Year: result.Year,
|
||||
SearchRating: compRes.Rating,
|
||||
Provider: ComickProvider,
|
||||
})
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
c.logger.Warn().Msg("comick: No results found")
|
||||
return nil, ErrNoChapters
|
||||
}
|
||||
|
||||
c.logger.Info().Int("count", len(results)).Msg("comick: Found results")
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (c *ComicK) FindChapters(id string) ([]*hibikemanga.ChapterDetails, error) {
|
||||
ret := make([]*hibikemanga.ChapterDetails, 0)
|
||||
|
||||
c.logger.Debug().Str("mangaId", id).Msg("comick: Fetching chapters")
|
||||
|
||||
uri := fmt.Sprintf("%s/comic/%s/chapters?lang=en&page=0&limit=1000000&chap-order=1", c.Url, id)
|
||||
|
||||
var data struct {
|
||||
Chapters []*ComicChapter `json:"chapters"`
|
||||
}
|
||||
|
||||
resp, err := c.Client.R().
|
||||
SetSuccessResult(&data).
|
||||
Get(uri)
|
||||
|
||||
if err != nil {
|
||||
c.logger.Error().Err(err).Msg("comick: Failed to send request")
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
c.logger.Error().Str("status", resp.Status).Msg("comick: Request failed")
|
||||
return nil, fmt.Errorf("failed to decode response: status %s", resp.Status)
|
||||
}
|
||||
|
||||
chapters := make([]*hibikemanga.ChapterDetails, 0)
|
||||
chaptersMap := make(map[string]*hibikemanga.ChapterDetails)
|
||||
count := 0
|
||||
for _, chapter := range data.Chapters {
|
||||
if chapter.Chap == "" || chapter.Lang != "en" {
|
||||
continue
|
||||
}
|
||||
title := "Chapter " + chapter.Chap + " "
|
||||
|
||||
if title == "" {
|
||||
if chapter.Title == "" {
|
||||
title = "Oneshot"
|
||||
} else {
|
||||
title = chapter.Title
|
||||
}
|
||||
}
|
||||
title = strings.TrimSpace(title)
|
||||
|
||||
prev, ok := chaptersMap[chapter.Chap]
|
||||
rating := chapter.UpCount - chapter.DownCount
|
||||
|
||||
if !ok || rating > prev.Rating {
|
||||
if !ok {
|
||||
count++
|
||||
}
|
||||
chaptersMap[chapter.Chap] = &hibikemanga.ChapterDetails{
|
||||
Provider: ComickProvider,
|
||||
ID: chapter.HID,
|
||||
Title: title,
|
||||
Index: uint(count),
|
||||
URL: fmt.Sprintf("%s/chapter/%s", c.Url, chapter.HID),
|
||||
Chapter: chapter.Chap,
|
||||
Rating: rating,
|
||||
UpdatedAt: chapter.UpdatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, chapter := range chaptersMap {
|
||||
chapters = append(chapters, chapter)
|
||||
}
|
||||
|
||||
// Sort chapters by index
|
||||
slices.SortStableFunc(chapters, func(i, j *hibikemanga.ChapterDetails) int {
|
||||
return cmp.Compare(i.Index, j.Index)
|
||||
})
|
||||
|
||||
ret = append(ret, chapters...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
c.logger.Warn().Msg("comick: No chapters found")
|
||||
return nil, ErrNoChapters
|
||||
}
|
||||
|
||||
c.logger.Info().Int("count", len(ret)).Msg("comick: Found chapters")
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (c *ComicK) FindChapterPages(id string) ([]*hibikemanga.ChapterPage, error) {
|
||||
ret := make([]*hibikemanga.ChapterPage, 0)
|
||||
|
||||
c.logger.Debug().Str("chapterId", id).Msg("comick: Finding chapter pages")
|
||||
|
||||
uri := fmt.Sprintf("%s/chapter/%s", c.Url, id)
|
||||
|
||||
var data struct {
|
||||
Chapter *ComicChapter `json:"chapter"`
|
||||
}
|
||||
|
||||
resp, err := c.Client.R().
|
||||
SetHeader("User-Agent", util.GetRandomUserAgent()).
|
||||
SetSuccessResult(&data).
|
||||
Get(uri)
|
||||
|
||||
if err != nil {
|
||||
c.logger.Error().Err(err).Msg("comick: Failed to send request")
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
|
||||
if !resp.IsSuccessState() {
|
||||
c.logger.Error().Str("status", resp.Status).Msg("comick: Request failed")
|
||||
return nil, fmt.Errorf("failed to decode response: status %s", resp.Status)
|
||||
}
|
||||
|
||||
if data.Chapter == nil {
|
||||
c.logger.Error().Msg("comick: Chapter not found")
|
||||
return nil, fmt.Errorf("chapter not found")
|
||||
}
|
||||
|
||||
for index, image := range data.Chapter.MdImages {
|
||||
ret = append(ret, &hibikemanga.ChapterPage{
|
||||
Provider: ComickProvider,
|
||||
URL: fmt.Sprintf("https://meo.comick.pictures/%s", image.B2Key),
|
||||
Index: index,
|
||||
Headers: make(map[string]string),
|
||||
})
|
||||
}
|
||||
|
||||
if len(ret) == 0 {
|
||||
c.logger.Warn().Msg("comick: No pages found")
|
||||
return nil, ErrNoPages
|
||||
}
|
||||
|
||||
c.logger.Info().Int("count", len(ret)).Msg("comick: Found pages")
|
||||
|
||||
return ret, nil
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type Comic struct {
|
||||
ID int `json:"id"`
|
||||
HID string `json:"hid"`
|
||||
Title string `json:"title"`
|
||||
Country string `json:"country"`
|
||||
Status int `json:"status"`
|
||||
Links struct {
|
||||
AL string `json:"al"`
|
||||
AP string `json:"ap"`
|
||||
BW string `json:"bw"`
|
||||
KT string `json:"kt"`
|
||||
MU string `json:"mu"`
|
||||
AMZ string `json:"amz"`
|
||||
CDJ string `json:"cdj"`
|
||||
EBJ string `json:"ebj"`
|
||||
MAL string `json:"mal"`
|
||||
RAW string `json:"raw"`
|
||||
} `json:"links"`
|
||||
LastChapter interface{} `json:"last_chapter"`
|
||||
ChapterCount int `json:"chapter_count"`
|
||||
Demographic int `json:"demographic"`
|
||||
Hentai bool `json:"hentai"`
|
||||
UserFollowCount int `json:"user_follow_count"`
|
||||
FollowRank int `json:"follow_rank"`
|
||||
CommentCount int `json:"comment_count"`
|
||||
FollowCount int `json:"follow_count"`
|
||||
Description string `json:"desc"`
|
||||
Parsed string `json:"parsed"`
|
||||
Slug string `json:"slug"`
|
||||
Mismatch interface{} `json:"mismatch"`
|
||||
Year int `json:"year"`
|
||||
BayesianRating interface{} `json:"bayesian_rating"`
|
||||
RatingCount int `json:"rating_count"`
|
||||
ContentRating string `json:"content_rating"`
|
||||
TranslationCompleted bool `json:"translation_completed"`
|
||||
RelateFrom []interface{} `json:"relate_from"`
|
||||
Mies interface{} `json:"mies"`
|
||||
MdTitles []struct {
|
||||
Title string `json:"title"`
|
||||
} `json:"md_titles"`
|
||||
MdComicMdGenres []struct {
|
||||
MdGenres struct {
|
||||
Name string `json:"name"`
|
||||
Type interface{} `json:"type"`
|
||||
Slug string `json:"slug"`
|
||||
Group string `json:"group"`
|
||||
} `json:"md_genres"`
|
||||
} `json:"md_comic_md_genres"`
|
||||
MuComics struct {
|
||||
LicensedInEnglish interface{} `json:"licensed_in_english"`
|
||||
MuComicCategories []struct {
|
||||
MuCategories struct {
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
} `json:"mu_categories"`
|
||||
PositiveVote int `json:"positive_vote"`
|
||||
NegativeVote int `json:"negative_vote"`
|
||||
} `json:"mu_comic_categories"`
|
||||
} `json:"mu_comics"`
|
||||
MdCovers []struct {
|
||||
Vol interface{} `json:"vol"`
|
||||
W int `json:"w"`
|
||||
H int `json:"h"`
|
||||
B2Key string `json:"b2key"`
|
||||
} `json:"md_covers"`
|
||||
Iso6391 string `json:"iso639_1"`
|
||||
LangName string `json:"lang_name"`
|
||||
LangNative string `json:"lang_native"`
|
||||
}
|
||||
|
||||
type ComicChapter struct {
|
||||
ID int `json:"id"`
|
||||
Chap string `json:"chap"`
|
||||
Title string `json:"title"`
|
||||
Vol string `json:"vol,omitempty"`
|
||||
Lang string `json:"lang"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
UpCount int `json:"up_count"`
|
||||
DownCount int `json:"down_count"`
|
||||
GroupName []string `json:"group_name"`
|
||||
HID string `json:"hid"`
|
||||
MdImages []struct {
|
||||
Name string `json:"name"`
|
||||
W int `json:"w"`
|
||||
H int `json:"h"`
|
||||
S int `json:"s"`
|
||||
B2Key string `json:"b2key"`
|
||||
} `json:"md_images"`
|
||||
}
|
||||
Reference in New Issue
Block a user