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

355 lines
8.8 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package uzaymanga
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"goyomi/internal/httpclient"
"goyomi/internal/source"
)
type Config struct {
Name string
BaseURL string
Lang string
CdnURL string
}
type Source struct {
cfg Config
client *httpclient.Client
id int64
}
func New(cfg Config) *Source {
c := httpclient.NewClient(httpclient.WithRateLimit(3, 1))
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 true }
type SearchResult struct {
ID int `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
}
func (s *Source) GetPopularManga(page int) (source.MangasPage, error) {
doc, err := s.fetchDoc(fmt.Sprintf("%s/search?page=%d&search=&order=4", s.cfg.BaseURL, page))
if err != nil {
return source.MangasPage{}, err
}
mangas := make([]source.SManga, 0)
doc.Find("section[aria-label='series area'] .card").Each(func(_ int, sel *goquery.Selection) {
title := sel.Find("h2").Text()
img := sel.Find("img")
thumb := img.AttrOr("abs:src", "")
link := sel.Find("a").AttrOr("abs:href", "")
mangas = append(mangas, source.SManga{
URL: link,
Title: strings.TrimSpace(title),
ThumbnailURL: thumb,
})
})
return source.MangasPage{Mangas: mangas, HasNextPage: len(mangas) > 0}, nil
}
func (s *Source) GetLatestUpdates(page int) (source.MangasPage, error) {
doc, err := s.fetchDoc(fmt.Sprintf("%s/?page=%d", s.cfg.BaseURL, page))
if err != nil {
return source.MangasPage{}, err
}
var grid *goquery.Selection
if header := doc.Find("div.header:has(h2:contains(En Son Yüklenen))"); header.Length() > 0 {
grid = header.Next()
} else if grid = doc.Find("div.grid.grid-cols-1"); grid.Length() == 0 {
grid = doc.Find("div.grid").First()
}
if grid == nil || grid.Length() == 0 {
return source.MangasPage{Mangas: []source.SManga{}, HasNextPage: false}, nil
}
mangas := make([]source.SManga, 0)
grid.Find("> div").Each(func(_ int, sel *goquery.Selection) {
link := sel.Find("h2").Parent()
if link.Length() == 0 {
link = sel.Find("a[href*='/manga/']")
}
if link.Length() == 0 {
return
}
title := sel.Find("h2").Text()
img := sel.Find(".card-image img")
if img.Length() == 0 {
img = sel.Find("img")
}
thumb := img.AttrOr("abs:src", "")
href := link.AttrOr("abs:href", "")
mangas = append(mangas, source.SManga{
URL: href,
Title: strings.TrimSpace(title),
ThumbnailURL: thumb,
})
})
return source.MangasPage{Mangas: mangas, HasNextPage: len(mangas) > 0}, nil
}
func (s *Source) GetSearchManga(page int, query string, filters []source.Filter) (source.MangasPage, error) {
if strings.HasPrefix(query, "slug:") {
slug := strings.TrimPrefix(query, "slug:")
urlStr := fmt.Sprintf("%s/manga/%s", s.cfg.BaseURL, slug)
doc, err := s.fetchDoc(urlStr)
if err != nil {
return source.MangasPage{}, err
}
if s.isMangaPage(doc) {
manga, err := s.parseMangaDetails(doc, urlStr)
if err != nil {
return source.MangasPage{}, err
}
return source.MangasPage{Mangas: []source.SManga{manga}, HasNextPage: false}, nil
}
return source.MangasPage{Mangas: []source.SManga{}, HasNextPage: false}, nil
}
u, _ := url.Parse(s.cfg.BaseURL + "/api/series/search/navbar")
q := u.Query()
q.Set("search", query)
u.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, u.String(), nil)
if err != nil {
return source.MangasPage{}, err
}
resp, err := s.client.Do(req)
if err != nil {
return source.MangasPage{}, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if strings.Contains(string(body), "[]") || strings.TrimSpace(string(body)) == "" {
return source.MangasPage{Mangas: []source.SManga{}, HasNextPage: false}, nil
}
var results []SearchResult
if err := json.Unmarshal(body, &results); err != nil {
return source.MangasPage{}, err
}
mangas := make([]source.SManga, len(results))
baseImage := s.cfg.CdnURL
if baseImage == "" {
baseImage = strings.TrimSuffix(s.cfg.BaseURL, "/")
}
for i, r := range results {
imgURL := r.Image
if !strings.HasPrefix(imgURL, "http") {
imgURL = baseImage + "/" + strings.TrimLeft(imgURL, "/")
}
slug := strings.ToLower(r.Name)
slug = strings.ReplaceAll(slug, "ı", "i")
slug = strings.ReplaceAll(slug, "ğ", "g")
slug = strings.ReplaceAll(slug, "ü", "u")
slug = strings.ReplaceAll(slug, "ş", "s")
slug = strings.ReplaceAll(slug, "ö", "o")
slug = strings.ReplaceAll(slug, "ç", "c")
re := regexp.MustCompile(`[^a-z0-9\s]`)
slug = re.ReplaceAllString(slug, "")
slug = strings.TrimSpace(slug)
re = regexp.MustCompile(`\s+`)
slug = re.ReplaceAllString(slug, "-")
mangas[i] = source.SManga{
URL: fmt.Sprintf("/manga/%d/%s", r.ID, slug),
Title: r.Name,
ThumbnailURL: imgURL,
}
}
return source.MangasPage{Mangas: mangas, HasNextPage: false}, nil
}
func (s *Source) isMangaPage(doc *goquery.Document) bool {
return doc.Find("div.grid h2 + p").Length() > 0
}
func (s *Source) parseMangaDetails(doc *goquery.Document, url string) (source.SManga, error) {
content := doc.Find("#content")
if content.Length() == 0 {
return source.SManga{}, fmt.Errorf("content not found")
}
title := content.Find("h1").Text()
img := content.Find("img")
thumb := img.AttrOr("abs:src", "")
genres := content.Find("a[href^='search?categories']").Map(func(_ int, sel *goquery.Selection) string {
return sel.Text()
})
desc := content.Find("div.grid h2 + p").Text()
pageStatus := content.Find("span:contains(Durum) + span").Text()
status := 0
switch {
case strings.Contains(pageStatus, "Devam Ediyor") || strings.Contains(pageStatus, "Birakildi"):
status = 1
case strings.Contains(pageStatus, "Tamamlandi"):
status = 2
case strings.Contains(pageStatus, "Ara Veridi"):
status = 5
}
return source.SManga{
URL: url,
Title: strings.TrimSpace(title),
ThumbnailURL: thumb,
Genre: joinStrings(genres, ", "),
Description: strings.TrimSpace(desc),
Status: status,
Initialized: true,
}, nil
}
func (s *Source) GetMangaDetails(manga source.SManga) (source.SManga, error) {
doc, err := s.fetchDoc(manga.URL)
if err != nil {
return manga, err
}
return s.parseMangaDetails(doc, manga.URL)
}
func (s *Source) GetChapterList(manga source.SManga) ([]source.SChapter, error) {
doc, err := s.fetchDoc(manga.URL)
if err != nil {
return nil, err
}
chapters := make([]source.SChapter, 0)
dateFormat := "Jan 2 ,2006"
doc.Find("div.list-episode a").Each(func(_ int, sel *goquery.Selection) {
name := sel.Find("h3").Text()
dateText := sel.Find("span").Text()
href := sel.AttrOr("abs:href", "")
var dateUpload int64
if t, err := time.Parse(dateFormat, strings.TrimSpace(dateText)); err == nil {
dateUpload = t.UnixMilli()
}
chapters = append(chapters, source.SChapter{
URL: href,
Name: strings.TrimSpace(name),
DateUpload: dateUpload,
})
})
return chapters, 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)
pageRegex := regexp.MustCompile(`"path":"([^"]+)"`)
doc.Find("script").Each(func(_ int, sel *goquery.Selection) {
html, err := sel.Html()
if err != nil {
return
}
matches := pageRegex.FindAllStringSubmatch(html, -1)
for i, m := range matches {
if len(m) > 1 {
imgPath := m[1]
cdnURL := s.cfg.CdnURL
if cdnURL == "" {
cdnURL = s.cfg.BaseURL
}
pages = append(pages, source.Page{
Index: i,
URL: chapter.URL,
ImageURL: cdnURL + "/" + imgPath,
})
}
}
})
return pages, nil
}
func (s *Source) GetImageURL(page source.Page) (string, error) {
return page.ImageURL, nil
}
func (s *Source) GetFilterList() []source.Filter {
return nil
}
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)))
}
func joinStrings(ss []string, sep string) string {
if len(ss) == 0 {
return ""
}
result := ss[0]
for i := 1; i < len(ss); i++ {
result += sep + ss[i]
}
return result
}
var _ source.CatalogueSource = (*Source)(nil)