231 lines
6.0 KiB
Go
Executable File
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) |