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
|
Name string
|
||||||
BaseURL string
|
BaseURL string
|
||||||
Lang 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 {
|
type Source struct {
|
||||||
@@ -116,12 +127,13 @@ func (s *Source) mangaFromElement(el *goquery.Selection) source.SManga {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
var popularSelectors = []string{"Popular", "Popularie", "Trending"}
|
func (s *Source) popularSelector() string {
|
||||||
|
if s.cfg.PopularSelector != "" {
|
||||||
func popularSelector() string {
|
return s.cfg.PopularSelector
|
||||||
|
}
|
||||||
var parts []string
|
var parts []string
|
||||||
for _, s := range popularSelectors {
|
for _, label := range []string{"Popular", "Popularie", "Trending"} {
|
||||||
parts = append(parts, fmt.Sprintf("div:contains(%s) + div .group.overflow-hidden.grid", s))
|
parts = append(parts, fmt.Sprintf("div:contains(%s) + div .group.overflow-hidden.grid", label))
|
||||||
}
|
}
|
||||||
return strings.Join(parts, ", ")
|
return strings.Join(parts, ", ")
|
||||||
}
|
}
|
||||||
@@ -132,7 +144,7 @@ func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
|
|||||||
return source.MangasPage{}, err
|
return source.MangasPage{}, err
|
||||||
}
|
}
|
||||||
var mangas []source.SManga
|
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)
|
m := s.mangaFromElement(el)
|
||||||
if m.URL != "" && m.Title != "" {
|
if m.URL != "" && m.Title != "" {
|
||||||
mangas = append(mangas, m)
|
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
|
// Thumbnail from div[class*=photoURL] background-image style
|
||||||
result.ThumbnailURL = getImageURL(doc.Find("div[class*=photoURL]").First(), s.cfg.BaseURL)
|
result.ThumbnailURL = getImageURL(doc.Find("div[class*=photoURL]").First(), s.cfg.BaseURL)
|
||||||
result.Description = strings.TrimSpace(doc.Find("div:containsOwn(Synopsis) ~ div").First().Text())
|
descSel := "div:containsOwn(Synopsis) ~ div"
|
||||||
result.Status = parseStatus(doc.Find("div:has(span:containsOwn(Status)) ~ div").First())
|
if s.cfg.DescriptionSelector != "" {
|
||||||
result.Author = strings.TrimSpace(doc.Find("div:has(span:containsOwn(Author)) ~ div").First().Text())
|
descSel = s.cfg.DescriptionSelector
|
||||||
result.Artist = strings.TrimSpace(doc.Find("div:has(span:containsOwn(Artist)) ~ div").First().Text())
|
}
|
||||||
|
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
|
// Title from h1 inside the series header
|
||||||
result.Title = strings.TrimSpace(doc.Find("h1").First().Text())
|
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: use /ajax/chapters instead of admin-ajax.php.
|
||||||
UseNewChapterEndpoint bool
|
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.
|
// Overridable selectors — leave empty to use defaults.
|
||||||
PopularMangaSelector string
|
PopularMangaSelector string
|
||||||
PopularMangaURLSelector 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) {
|
func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
|
||||||
pageStr := s.searchPage(page)
|
rawURL := s.popularURL(page)
|
||||||
rawURL := fmt.Sprintf("%s/%s/%s?m_orderby=views",
|
|
||||||
strings.TrimRight(s.cfg.BaseURL, "/"), s.cfg.MangaSubString, pageStr)
|
|
||||||
|
|
||||||
doc, err := s.get(context.Background(), rawURL)
|
doc, err := s.get(context.Background(), rawURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return source.MangasPage{}, err
|
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) {
|
func (s *Source) GetLatestUpdates(page int) (source.MangasPage, error) {
|
||||||
pageStr := s.searchPage(page)
|
rawURL := s.latestURL(page)
|
||||||
rawURL := fmt.Sprintf("%s/%s/%s?m_orderby=latest",
|
|
||||||
strings.TrimRight(s.cfg.BaseURL, "/"), s.cfg.MangaSubString, pageStr)
|
|
||||||
|
|
||||||
doc, err := s.get(context.Background(), rawURL)
|
doc, err := s.get(context.Background(), rawURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return source.MangasPage{}, err
|
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
|
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) {
|
func (s *Source) GetSearchManga(page int, query string, filters []source.Filter) (source.MangasPage, error) {
|
||||||
base := strings.TrimRight(s.cfg.BaseURL, "/")
|
base := strings.TrimRight(s.cfg.BaseURL, "/")
|
||||||
searchURL := fmt.Sprintf("%s/?s=%s&post_type=wp-manga&paged=%d",
|
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 {
|
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
|
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()
|
a := el.Find("a").First()
|
||||||
if a.Length() == 0 {
|
if a.Length() == 0 {
|
||||||
return
|
return
|
||||||
@@ -94,6 +98,19 @@ func (s *Source) parseMangaList(doc *goquery.Document) source.MangasPage {
|
|||||||
return source.MangasPage{Mangas: mangas, HasNextPage: hasNext}
|
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) {
|
func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
|
||||||
var u string
|
var u string
|
||||||
switch page {
|
switch page {
|
||||||
|
|||||||
Reference in New Issue
Block a user