Files
goyomi/sources/base/vercomics/vercomics.go
T
2026-05-11 06:48:23 +00:00

231 lines
6.0 KiB
Go
Executable File

package vercomics
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"github.com/PuerkitoBio/goquery"
"goyomi/internal/httpclient"
"goyomi/internal/source"
)
type Config struct {
Name string
BaseURL string
Lang string
URLSuffix string
GenreSuffix string
}
type Source struct {
cfg Config
client *httpclient.Client
id int64
}
func New(cfg Config) *Source {
c := httpclient.NewClient()
return &Source{
cfg: cfg,
client: c,
id: source.GenerateSourceID(cfg.Name, cfg.Lang),
}
}
func (s *Source) ID() int64 { return s.id }
func (s *Source) Name() string { return s.cfg.Name }
func (s *Source) Lang() string { return s.cfg.Lang }
func (s *Source) SupportsLatest() bool { return false }
func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
url := s.cfg.BaseURL + "/" + s.cfg.URLSuffix + fmt.Sprintf("/page/%d", page)
doc, err := s.fetchDoc(url)
if err != nil {
return source.MangasPage{}, err
}
mangas := s.mangaListFromDoc(doc)
hasNext := doc.Find("div.wp-pagenavi > span.current + a").Length() > 0
return source.MangasPage{Mangas: mangas, HasNextPage: hasNext}, nil
}
func (s *Source) mangaListFromDoc(doc *goquery.Document) []source.SManga {
mangas := make([]source.SManga, 0)
doc.Find("header:has(h1) ~ * .entry").Each(func(_ int, sel *goquery.Selection) {
link := sel.Find("a.popimg").First()
if link.Length() == 0 {
return
}
title := link.Find("img").AttrOr("alt", "")
href := link.AttrOr("href", "")
thumb := s.imgAttr(link.Find("img"))
mangas = append(mangas, source.SManga{
URL: href,
Title: strings.TrimSpace(title),
ThumbnailURL: thumb,
})
})
return mangas
}
func (s *Source) imgAttr(img *goquery.Selection) string {
if img.Length() == 0 {
return ""
}
if src, ok := img.Attr("data-src"); ok {
if abs, ok := img.Attr("abs:data-src"); ok {
return abs
}
return src
}
if src, ok := img.Attr("data-lazy-src"); ok {
if abs, ok := img.Attr("abs:data-lazy-src"); ok {
return abs
}
return src
}
if srcset, ok := img.Attr("srcset"); ok {
parts := strings.Split(srcset, " ")
for _, p := range parts {
if strings.HasPrefix(p, "http") {
return p
}
}
}
if src, ok := img.Attr("data-cfsrc"); ok {
if abs, ok := img.Attr("abs:data-cfsrc"); ok {
return abs
}
return src
}
return img.AttrOr("abs:src", "")
}
func (s *Source) GetLatestUpdates(page int) (source.MangasPage, error) {
return source.MangasPage{}, fmt.Errorf("not supported")
}
func (s *Source) GetSearchManga(page int, query string, filters []source.Filter) (source.MangasPage, error) {
if query != "" {
url := s.cfg.BaseURL
if s.cfg.URLSuffix != "" {
url += "/" + s.cfg.URLSuffix
}
url += fmt.Sprintf("/page/%d?s=%s", page, query)
doc, err := s.fetchDoc(url)
if err != nil {
return source.MangasPage{}, err
}
return source.MangasPage{Mangas: s.mangaListFromDoc(doc), HasNextPage: false}, nil
}
for _, f := range filters {
if sel, ok := f.(*source.SelectFilter); ok && sel.Selected > 0 && len(sel.Values) > sel.Selected {
uriPart := sel.Values[sel.Selected]
if uriPart != "" {
url := s.cfg.BaseURL + "/" + s.cfg.GenreSuffix + "/" + uriPart + fmt.Sprintf("/page/%d", page)
doc, err := s.fetchDoc(url)
if err != nil {
return source.MangasPage{}, err
}
return source.MangasPage{Mangas: s.mangaListFromDoc(doc), HasNextPage: false}, nil
}
}
}
return s.GetPopularManga(page)
}
func (s *Source) GetMangaDetails(manga source.SManga) (source.SManga, error) {
doc, err := s.fetchDoc(manga.URL)
if err != nil {
return manga, err
}
genreList := doc.Find("div.tax_box:has(div.title:contains(Etiquetas)) a[rel=tag]")
genres := make([]string, 0)
genreList.Each(func(_ int, sel *goquery.Selection) {
text := sel.Text()
if text != "" {
first := strings.ToUpper(string(text[0]))
genres = append(genres, first+text[1:])
}
})
manga.Genre = strings.Join(genres, ", ")
manga.Status = 2
manga.Initialized = true
return manga, nil
}
func (s *Source) GetChapterList(manga source.SManga) ([]source.SChapter, error) {
return []source.SChapter{
{
URL: manga.URL,
Name: manga.Title,
},
}, nil
}
func (s *Source) GetPageList(chapter source.SChapter) ([]source.Page, error) {
doc, err := s.fetchDoc(chapter.URL)
if err != nil {
return nil, err
}
pages := make([]source.Page, 0)
doc.Find("div.wp-content p > img:not(noscript img), div.wp-content div#lector > img:not(noscript img), div.wp-content > figure img:not(noscript img), div.wp-content > img, div.wp-content > p img, div.post-imgs > img").Each(func(i int, sel *goquery.Selection) {
imgURL := s.imgAttr(sel)
if imgURL != "" {
pages = append(pages, source.Page{Index: i, ImageURL: imgURL})
}
})
return pages, nil
}
func (s *Source) GetImageURL(page source.Page) (string, error) {
return page.ImageURL, nil
}
func (s *Source) GetFilterList() []source.Filter {
genres := []string{"", "accion", "animacion", "artes-marciales", "aventures", "carreras", "ciencia-ficcion", "comedia", "demonios", "deportes", "drama", "ecchi", "escuela", "espacio", "fantasia", "gore", "historico", "horror", "juego", "magia", "mecha", "militar", "misterio", "musica", "ninja", "parodia", "policia", "psicologico", "romance", "samurai", "sci-fi", "seinen", "shoujo", "shoujo-ai", "shounen", "shounen-ai"}
filters := []source.Filter{
&source.TextFilter{FilterName: "Los filtros serán ignorados si la búsqueda no está vacía."},
&source.SelectFilter{FilterName: "Filtrar por género", Values: genres, Selected: 0},
}
return filters
}
func (s *Source) fetchDoc(rawURL string) (*goquery.Document, error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, rawURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Referer", s.cfg.BaseURL+"/")
resp, err := s.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
html, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return goquery.NewDocumentFromReader(strings.NewReader(string(html)))
}
var _ source.CatalogueSource = (*Source)(nil)