fix(base): add override hooks for masonry, madara, keyoapp
Madara: - Add PopularURL/LatestURL Config hooks for custom URL building (needed by hentai4free which uses search-based popular/latest URLs) Masonry: - Replace CSS :not(:has(a[href*=/video/])) with programmatic filtering. goquery/cascadia doesn't support :has() + attribute selectors (Jsoup does, hence Kotlin works but Go didn't) Keyoapp: - Add overridable selector fields (PopularSelector, DescriptionSelector, StatusSelector, AuthorSelector, ArtistSelector) to Config
This commit is contained in:
@@ -21,6 +21,17 @@ type Config struct {
|
||||
Name string
|
||||
BaseURL string
|
||||
Lang string
|
||||
|
||||
// Override popular manga selector. Empty means use default.
|
||||
PopularSelector string
|
||||
// Override description selector. Empty means use default.
|
||||
DescriptionSelector string
|
||||
// Override status selector. Empty means use default.
|
||||
StatusSelector string
|
||||
// Override author selector. Empty means use default.
|
||||
AuthorSelector string
|
||||
// Override artist selector. Empty means use default.
|
||||
ArtistSelector string
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
@@ -116,12 +127,13 @@ func (s *Source) mangaFromElement(el *goquery.Selection) source.SManga {
|
||||
return m
|
||||
}
|
||||
|
||||
var popularSelectors = []string{"Popular", "Popularie", "Trending"}
|
||||
|
||||
func popularSelector() string {
|
||||
func (s *Source) popularSelector() string {
|
||||
if s.cfg.PopularSelector != "" {
|
||||
return s.cfg.PopularSelector
|
||||
}
|
||||
var parts []string
|
||||
for _, s := range popularSelectors {
|
||||
parts = append(parts, fmt.Sprintf("div:contains(%s) + div .group.overflow-hidden.grid", s))
|
||||
for _, label := range []string{"Popular", "Popularie", "Trending"} {
|
||||
parts = append(parts, fmt.Sprintf("div:contains(%s) + div .group.overflow-hidden.grid", label))
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
@@ -132,7 +144,7 @@ func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
|
||||
return source.MangasPage{}, err
|
||||
}
|
||||
var mangas []source.SManga
|
||||
doc.Find(popularSelector()).Each(func(_ int, el *goquery.Selection) {
|
||||
doc.Find(s.popularSelector()).Each(func(_ int, el *goquery.Selection) {
|
||||
m := s.mangaFromElement(el)
|
||||
if m.URL != "" && m.Title != "" {
|
||||
mangas = append(mangas, m)
|
||||
@@ -213,10 +225,29 @@ func (s *Source) GetMangaDetails(manga source.SManga) (source.SManga, error) {
|
||||
|
||||
// Thumbnail from div[class*=photoURL] background-image style
|
||||
result.ThumbnailURL = getImageURL(doc.Find("div[class*=photoURL]").First(), s.cfg.BaseURL)
|
||||
result.Description = strings.TrimSpace(doc.Find("div:containsOwn(Synopsis) ~ div").First().Text())
|
||||
result.Status = parseStatus(doc.Find("div:has(span:containsOwn(Status)) ~ div").First())
|
||||
result.Author = strings.TrimSpace(doc.Find("div:has(span:containsOwn(Author)) ~ div").First().Text())
|
||||
result.Artist = strings.TrimSpace(doc.Find("div:has(span:containsOwn(Artist)) ~ div").First().Text())
|
||||
descSel := "div:containsOwn(Synopsis) ~ div"
|
||||
if s.cfg.DescriptionSelector != "" {
|
||||
descSel = s.cfg.DescriptionSelector
|
||||
}
|
||||
result.Description = strings.TrimSpace(doc.Find(descSel).First().Text())
|
||||
|
||||
statusSel := "div:has(span:containsOwn(Status)) ~ div"
|
||||
if s.cfg.StatusSelector != "" {
|
||||
statusSel = s.cfg.StatusSelector
|
||||
}
|
||||
result.Status = parseStatus(doc.Find(statusSel).First())
|
||||
|
||||
authorSel := "div:has(span:containsOwn(Author)) ~ div"
|
||||
if s.cfg.AuthorSelector != "" {
|
||||
authorSel = s.cfg.AuthorSelector
|
||||
}
|
||||
result.Author = strings.TrimSpace(doc.Find(authorSel).First().Text())
|
||||
|
||||
artistSel := "div:has(span:containsOwn(Artist)) ~ div"
|
||||
if s.cfg.ArtistSelector != "" {
|
||||
artistSel = s.cfg.ArtistSelector
|
||||
}
|
||||
result.Artist = strings.TrimSpace(doc.Find(artistSel).First().Text())
|
||||
|
||||
// Title from h1 inside the series header
|
||||
result.Title = strings.TrimSpace(doc.Find("h1").First().Text())
|
||||
|
||||
@@ -30,6 +30,13 @@ type Config struct {
|
||||
// UseNewChapterEndpoint: use /ajax/chapters instead of admin-ajax.php.
|
||||
UseNewChapterEndpoint bool
|
||||
|
||||
// PopularURL overrides the URL for GetPopularManga. If nil, default is used.
|
||||
// Takes page number (1-indexed), returns full URL string.
|
||||
PopularURL func(page int) string
|
||||
|
||||
// LatestURL overrides the URL for GetLatestUpdates. If nil, default is used.
|
||||
LatestURL func(page int) string
|
||||
|
||||
// Overridable selectors — leave empty to use defaults.
|
||||
PopularMangaSelector string
|
||||
PopularMangaURLSelector string
|
||||
@@ -196,10 +203,7 @@ func (s *Source) parseSearchMangaFromElement(el *goquery.Selection) source.SMang
|
||||
}
|
||||
|
||||
func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
|
||||
pageStr := s.searchPage(page)
|
||||
rawURL := fmt.Sprintf("%s/%s/%s?m_orderby=views",
|
||||
strings.TrimRight(s.cfg.BaseURL, "/"), s.cfg.MangaSubString, pageStr)
|
||||
|
||||
rawURL := s.popularURL(page)
|
||||
doc, err := s.get(context.Background(), rawURL)
|
||||
if err != nil {
|
||||
return source.MangasPage{}, err
|
||||
@@ -208,10 +212,7 @@ func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
|
||||
}
|
||||
|
||||
func (s *Source) GetLatestUpdates(page int) (source.MangasPage, error) {
|
||||
pageStr := s.searchPage(page)
|
||||
rawURL := fmt.Sprintf("%s/%s/%s?m_orderby=latest",
|
||||
strings.TrimRight(s.cfg.BaseURL, "/"), s.cfg.MangaSubString, pageStr)
|
||||
|
||||
rawURL := s.latestURL(page)
|
||||
doc, err := s.get(context.Background(), rawURL)
|
||||
if err != nil {
|
||||
return source.MangasPage{}, err
|
||||
@@ -219,6 +220,24 @@ func (s *Source) GetLatestUpdates(page int) (source.MangasPage, error) {
|
||||
return s.parseMangaList(doc, s.cfg.PopularMangaSelector, true), nil
|
||||
}
|
||||
|
||||
func (s *Source) popularURL(page int) string {
|
||||
if s.cfg.PopularURL != nil {
|
||||
return s.cfg.PopularURL(page)
|
||||
}
|
||||
pageStr := s.searchPage(page)
|
||||
return fmt.Sprintf("%s/%s/%s?m_orderby=views",
|
||||
strings.TrimRight(s.cfg.BaseURL, "/"), s.cfg.MangaSubString, pageStr)
|
||||
}
|
||||
|
||||
func (s *Source) latestURL(page int) string {
|
||||
if s.cfg.LatestURL != nil {
|
||||
return s.cfg.LatestURL(page)
|
||||
}
|
||||
pageStr := s.searchPage(page)
|
||||
return fmt.Sprintf("%s/%s/%s?m_orderby=latest",
|
||||
strings.TrimRight(s.cfg.BaseURL, "/"), s.cfg.MangaSubString, pageStr)
|
||||
}
|
||||
|
||||
func (s *Source) GetSearchManga(page int, query string, filters []source.Filter) (source.MangasPage, error) {
|
||||
base := strings.TrimRight(s.cfg.BaseURL, "/")
|
||||
searchURL := fmt.Sprintf("%s/?s=%s&post_type=wp-manga&paged=%d",
|
||||
|
||||
@@ -69,10 +69,14 @@ func imgAttr(img *goquery.Selection) string {
|
||||
}
|
||||
|
||||
func (s *Source) parseMangaList(doc *goquery.Document) source.MangasPage {
|
||||
// Exclude static galleries and broken entries
|
||||
const sel = ".list-gallery:not(.static) figure:not(:has(a[href*=cdn.]))"
|
||||
var mangas []source.SManga
|
||||
doc.Find(sel).Each(func(_ int, el *goquery.Selection) {
|
||||
// goquery's :has() doesn't support attribute selectors like [href*=/video/],
|
||||
// so we filter programmatically instead of relying on CSS :not(:has(...)).
|
||||
doc.Find(".list-gallery:not(.static) figure").Each(func(_ int, el *goquery.Selection) {
|
||||
// Skip video entries (matching Kotlin's :not(:has(a[href*=/video/])))
|
||||
if hasAttr(el, "a", "href", "/video/") {
|
||||
return
|
||||
}
|
||||
a := el.Find("a").First()
|
||||
if a.Length() == 0 {
|
||||
return
|
||||
@@ -94,6 +98,19 @@ func (s *Source) parseMangaList(doc *goquery.Document) source.MangasPage {
|
||||
return source.MangasPage{Mangas: mangas, HasNextPage: hasNext}
|
||||
}
|
||||
|
||||
// hasAttr checks if any descendant matching tag has an href containing substr.
|
||||
func hasAttr(el *goquery.Selection, tag, attr, substr string) bool {
|
||||
found := false
|
||||
el.Find(tag).EachWithBreak(func(_ int, a *goquery.Selection) bool {
|
||||
if v, ok := a.Attr(attr); ok && strings.Contains(v, substr) {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
|
||||
var u string
|
||||
switch page {
|
||||
|
||||
Reference in New Issue
Block a user