node build fixed

This commit is contained in:
ra_ma committed 2025-09-20 14:08:38 +01:00
1 parent c6ebbe069d
commit 3d298fa434
1516 files changed
+535727 -2

No files matched your search

@@ -0,0 +1,614 @@
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"github.com/goccy/go-json"
"github.com/gocolly/colly"
"github.com/rs/zerolog"
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
)
const (
DefaultServer = "default"
GogoanimeProvider = "gogoanime-external"
GogocdnServer = "gogocdn"
VidstreamingServer = "vidstreaming"
StreamSBServer = "streamsb"
)
type Gogoanime struct {
BaseURL string
AjaxURL string
Client http.Client
UserAgent string
logger *zerolog.Logger
}
func NewProvider(logger *zerolog.Logger) hibikeonlinestream.Provider {
return &Gogoanime{
BaseURL: "https://anitaku.to",
AjaxURL: "https://ajax.gogocdn.net",
Client: http.Client{},
UserAgent: util.GetRandomUserAgent(),
logger: logger,
}
}
func (g *Gogoanime) GetEpisodeServers() []string {
return []string{GogocdnServer, VidstreamingServer}
}
func (g *Gogoanime) Search(query string, dubbed bool) ([]*hibikeonlinestream.SearchResult, error) {
var results []*hibikeonlinestream.SearchResult
g.logger.Debug().Str("query", query).Bool("dubbed", dubbed).Msg("gogoanime: Searching anime")
c := colly.NewCollector(
colly.UserAgent(g.UserAgent),
)
c.OnHTML(".last_episodes > ul > li", func(e *colly.HTMLElement) {
id := ""
idParts := strings.Split(e.ChildAttr("p.name > a", "href"), "/")
if len(idParts) > 2 {
id = idParts[2]
}
title := e.ChildText("p.name > a")
url := g.BaseURL + e.ChildAttr("p.name > a", "href")
subOrDub := hibikeonlinestream.Sub
if strings.Contains(strings.ToLower(e.ChildText("p.name > a")), "dub") {
subOrDub = hibikeonlinestream.Dub
}
results = append(results, &hibikeonlinestream.SearchResult{
ID: id,
Title: title,
URL: url,
SubOrDub: subOrDub,
})
})
searchURL := g.BaseURL + "/search.html?keyword=" + url.QueryEscape(query)
if dubbed {
searchURL += "%20(Dub)"
}
err := c.Visit(searchURL)
if err != nil {
return nil, err
}
g.logger.Debug().Int("count", len(results)).Msg("gogoanime: Fetched anime")
return results, nil
}
func (g *Gogoanime) FindEpisode(id string) ([]*hibikeonlinestream.EpisodeDetails, error) {
var episodes []*hibikeonlinestream.EpisodeDetails
g.logger.Debug().Str("id", id).Msg("gogoanime: Fetching episodes")
if !strings.Contains(id, "gogoanime") {
id = fmt.Sprintf("%s/category/%s", g.BaseURL, id)
}
c := colly.NewCollector(
colly.UserAgent(g.UserAgent),
)
var epStart, epEnd, movieID, alias string
c.OnHTML("#episode_page > li > a", func(e *colly.HTMLElement) {
if epStart == "" {
epStart = e.Attr("ep_start")
}
epEnd = e.Attr("ep_end")
})
c.OnHTML("#movie_id", func(e *colly.HTMLElement) {
movieID = e.Attr("value")
})
c.OnHTML("#alias", func(e *colly.HTMLElement) {
alias = e.Attr("value")
})
err := c.Visit(id)
if err != nil {
g.logger.Error().Err(err).Msg("gogoanime: Failed to fetch episodes")
return nil, err
}
c2 := colly.NewCollector(
colly.UserAgent(g.UserAgent),
)
c2.OnHTML("#episode_related > li", func(e *colly.HTMLElement) {
episodeIDParts := strings.Split(e.ChildAttr("a", "href"), "/")
if len(episodeIDParts) < 2 {
return
}
episodeID := strings.TrimSpace(episodeIDParts[1])
episodeNumberStr := strings.TrimPrefix(e.ChildText("div.name"), "EP ")
episodeNumber, err := strconv.Atoi(episodeNumberStr)
if err != nil {
g.logger.Error().Err(err).Str("episodeID", episodeID).Msg("failed to parse episode number")
return
}
episodes = append(episodes, &hibikeonlinestream.EpisodeDetails{
Provider: GogoanimeProvider,
ID: episodeID,
Number: episodeNumber,
URL: g.BaseURL + "/" + episodeID,
})
})
ajaxURL := fmt.Sprintf("%s/ajax/load-list-episode", g.AjaxURL)
ajaxParams := url.Values{
"ep_start": {epStart},
"ep_end": {epEnd},
"id": {movieID},
"alias": {alias},
"default_ep": {"0"},
}
ajaxURLWithParams := fmt.Sprintf("%s?%s", ajaxURL, ajaxParams.Encode())
err = c2.Visit(ajaxURLWithParams)
if err != nil {
g.logger.Error().Err(err).Msg("gogoanime: Failed to fetch episodes")
return nil, err
}
g.logger.Debug().Int("count", len(episodes)).Msg("gogoanime: Fetched episodes")
return episodes, nil
}
func (g *Gogoanime) FindEpisodeServer(episodeInfo *hibikeonlinestream.EpisodeDetails, server string) (*hibikeonlinestream.EpisodeServer, error) {
var source *hibikeonlinestream.EpisodeServer
if server == DefaultServer {
server = GogocdnServer
}
g.logger.Debug().Str("server", string(server)).Str("episodeID", episodeInfo.ID).Msg("gogoanime: Fetching server sources")
c := colly.NewCollector()
switch server {
case VidstreamingServer:
c.OnHTML(".anime_muti_link > ul > li.vidcdn > a", func(e *colly.HTMLElement) {
src := e.Attr("data-video")
gogocdn := NewGogoCDN()
videoSources, err := gogocdn.Extract(src)
if err == nil {
source = &hibikeonlinestream.EpisodeServer{
Provider: GogoanimeProvider,
Server: server,
Headers: map[string]string{
"Referer": g.BaseURL + "/" + episodeInfo.ID,
},
VideoSources: videoSources,
}
}
})
case GogocdnServer, "":
c.OnHTML("#load_anime > div > div > iframe", func(e *colly.HTMLElement) {
src := e.Attr("src")
gogocdn := NewGogoCDN()
videoSources, err := gogocdn.Extract(src)
if err == nil {
source = &hibikeonlinestream.EpisodeServer{
Provider: GogoanimeProvider,
Server: server,
Headers: map[string]string{
"Referer": g.BaseURL + "/" + episodeInfo.ID,
},
VideoSources: videoSources,
}
}
})
case StreamSBServer:
c.OnHTML(".anime_muti_link > ul > li.streamsb > a", func(e *colly.HTMLElement) {
src := e.Attr("data-video")
streamsb := NewStreamSB()
videoSources, err := streamsb.Extract(src)
if err == nil {
source = &hibikeonlinestream.EpisodeServer{
Provider: GogoanimeProvider,
Server: server,
Headers: map[string]string{
"Referer": g.BaseURL + "/" + episodeInfo.ID,
"watchsb": "streamsb",
"User-Agent": g.UserAgent,
},
VideoSources: videoSources,
}
}
})
}
err := c.Visit(g.BaseURL + "/" + episodeInfo.ID)
if err != nil {
return nil, err
}
if source == nil {
g.logger.Warn().Str("server", string(server)).Msg("gogoanime: No sources found")
return nil, fmt.Errorf("no sources found")
}
g.logger.Debug().Str("server", string(server)).Int("videoSources", len(source.VideoSources)).Msg("gogoanime: Fetched server sources")
return source, nil
}
type cdnKeys struct {
key []byte
secondKey []byte
iv []byte
}
type GogoCDN struct {
client *http.Client
serverName string
keys cdnKeys
referrer string
}
func NewGogoCDN() *GogoCDN {
return &GogoCDN{
client: &http.Client{},
serverName: "goload",
keys: cdnKeys{
key: []byte("37911490979715163134003223491201"),
secondKey: []byte("54674138327930866480207815084989"),
iv: []byte("3134003223491201"),
},
}
}
// Extract fetches and extracts video sources from the provided URI.
func (g *GogoCDN) Extract(uri string) (vs []*hibikeonlinestream.VideoSource, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("failed to extract video sources")
}
}()
// Instantiate a new collector
c := colly.NewCollector(
// Allow visiting the same page multiple times
colly.AllowURLRevisit(),
)
ur, err := url.Parse(uri)
if err != nil {
return nil, err
}
// Variables to hold extracted values
var scriptValue, id string
id = ur.Query().Get("id")
// Find and extract the script value and id
c.OnHTML("script[data-name='episode']", func(e *colly.HTMLElement) {
scriptValue = e.Attr("data-value")
})
// Start scraping
err = c.Visit(uri)
if err != nil {
return nil, err
}
// Check if scriptValue and id are found
if scriptValue == "" || id == "" {
return nil, errors.New("script value or id not found")
}
// Extract video sources
ajaxUrl := fmt.Sprintf("%s://%s/encrypt-ajax.php?%s", ur.Scheme, ur.Host, g.generateEncryptedAjaxParams(id, scriptValue))
req, err := http.NewRequest("GET", ajaxUrl, nil)
if err != nil {
return nil, err
}
req.Header.Set("X-Requested-With", "XMLHttpRequest")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
encryptedData, err := g.client.Do(req)
if err != nil {
return nil, err
}
defer encryptedData.Body.Close()
encryptedDataBytesRes, err := io.ReadAll(encryptedData.Body)
if err != nil {
return nil, err
}
var encryptedDataBytes map[string]string
err = json.Unmarshal(encryptedDataBytesRes, &encryptedDataBytes)
if err != nil {
return nil, err
}
data, err := g.decryptAjaxData(encryptedDataBytes["data"])
source, ok := data["source"].([]interface{})
// Check if source is found
if !ok {
return nil, errors.New("source not found")
}
var results []*hibikeonlinestream.VideoSource
urls := make([]string, 0)
for _, src := range source {
s := src.(map[string]interface{})
urls = append(urls, s["file"].(string))
}
sourceBK, ok := data["source_bk"].([]interface{})
if ok {
for _, src := range sourceBK {
s := src.(map[string]interface{})
urls = append(urls, s["file"].(string))
}
}
for _, url := range urls {
vs, ok := g.urlToVideoSource(url, source, sourceBK)
if ok {
results = append(results, vs...)
}
}
return results, nil
}
func (g *GogoCDN) urlToVideoSource(url string, source []interface{}, sourceBK []interface{}) (vs []*hibikeonlinestream.VideoSource, ok bool) {
defer func() {
if r := recover(); r != nil {
ok = false
}
}()
ret := make([]*hibikeonlinestream.VideoSource, 0)
if strings.Contains(url, ".m3u8") {
resResult, err := http.Get(url)
if err != nil {
return nil, false
}
defer resResult.Body.Close()
bodyBytes, err := io.ReadAll(resResult.Body)
if err != nil {
return nil, false
}
bodyString := string(bodyBytes)
resolutions := regexp.MustCompile(`(RESOLUTION=)(.*)(\s*?)(\s.*)`).FindAllStringSubmatch(bodyString, -1)
baseURL := url[:strings.LastIndex(url, "/")]
for _, res := range resolutions {
quality := strings.Split(strings.Split(res[2], "x")[1], ",")[0]
url := fmt.Sprintf("%s/%s", baseURL, strings.TrimSpace(res[4]))
ret = append(ret, &hibikeonlinestream.VideoSource{URL: url, Type: hibikeonlinestream.VideoSourceM3U8, Quality: quality + "p"})
}
ret = append(ret, &hibikeonlinestream.VideoSource{URL: url, Type: hibikeonlinestream.VideoSourceM3U8, Quality: "default"})
} else {
for _, src := range source {
s := src.(map[string]interface{})
if s["file"].(string) == url {
quality := strings.Split(s["label"].(string), " ")[0] + "p"
ret = append(ret, &hibikeonlinestream.VideoSource{URL: url, Type: hibikeonlinestream.VideoSourceMP4, Quality: quality})
}
}
if sourceBK != nil {
for _, src := range sourceBK {
s := src.(map[string]interface{})
if s["file"].(string) == url {
ret = append(ret, &hibikeonlinestream.VideoSource{URL: url, Type: hibikeonlinestream.VideoSourceMP4, Quality: "backup"})
}
}
}
}
return ret, true
}
// generateEncryptedAjaxParams generates encrypted AJAX parameters.
func (g *GogoCDN) generateEncryptedAjaxParams(id, scriptValue string) string {
encryptedKey := g.encrypt(id, g.keys.iv, g.keys.key)
decryptedToken := g.decrypt(scriptValue, g.keys.iv, g.keys.key)
return fmt.Sprintf("id=%s&alias=%s", encryptedKey, decryptedToken)
}
// encrypt encrypts the given text using AES CBC mode.
func (g *GogoCDN) encrypt(text string, iv []byte, key []byte) string {
block, _ := aes.NewCipher(key)
textBytes := []byte(text)
textBytes = pkcs7Padding(textBytes, aes.BlockSize)
cipherText := make([]byte, len(textBytes))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(cipherText, textBytes)
return base64.StdEncoding.EncodeToString(cipherText)
}
// decrypt decrypts the given text using AES CBC mode.
func (g *GogoCDN) decrypt(text string, iv []byte, key []byte) string {
block, _ := aes.NewCipher(key)
cipherText, _ := base64.StdEncoding.DecodeString(text)
plainText := make([]byte, len(cipherText))
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(plainText, cipherText)
plainText = pkcs7Trimming(plainText)
return string(plainText)
}
func (g *GogoCDN) decryptAjaxData(encryptedData string) (map[string]interface{}, error) {
decodedData, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(g.keys.secondKey)
if err != nil {
return nil, err
}
if len(decodedData) < aes.BlockSize {
return nil, fmt.Errorf("cipher text too short")
}
iv := g.keys.iv
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(decodedData, decodedData)
// Remove padding
decodedData = pkcs7Trimming(decodedData)
var data map[string]interface{}
err = json.Unmarshal(decodedData, &data)
if err != nil {
return nil, err
}
return data, nil
}
// pkcs7Padding pads the text to be a multiple of blockSize using Pkcs7 padding.
func pkcs7Padding(text []byte, blockSize int) []byte {
padding := blockSize - len(text)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(text, padText...)
}
// pkcs7Trimming removes Pkcs7 padding from the text.
func pkcs7Trimming(text []byte) []byte {
length := len(text)
unpadding := int(text[length-1])
return text[:(length - unpadding)]
}
type StreamSB struct {
Host string
Host2 string
UserAgent string
}
func NewStreamSB() *StreamSB {
return &StreamSB{
Host: "https://streamsss.net/sources50",
Host2: "https://watchsb.com/sources50",
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
}
}
func (s *StreamSB) Payload(hex string) string {
return "566d337678566f743674494a7c7c" + hex + "7c7c346b6767586d6934774855537c7c73747265616d7362/6565417268755339773461447c7c346133383438333436313335376136323337373433383634376337633465366534393338373136643732373736343735373237613763376334363733353737303533366236333463353333363534366137633763373337343732363536313664373336327c7c6b586c3163614468645a47617c7c73747265616d7362"
}
func (s *StreamSB) Extract(uri string) (vs []*hibikeonlinestream.VideoSource, err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New("failed to extract video sources")
}
}()
var ret []*hibikeonlinestream.VideoSource
id := strings.Split(uri, "/e/")[1]
if strings.Contains(id, "html") {
id = strings.Split(id, ".html")[0]
}
if id == "" {
return nil, errors.New("cannot find ID")
}
client := &http.Client{}
req, _ := http.NewRequest("GET", fmt.Sprintf("%s/%s", s.Host, s.Payload(hex.EncodeToString([]byte(id)))), nil)
req.Header.Add("watchsb", "sbstream")
req.Header.Add("User-Agent", s.UserAgent)
req.Header.Add("Referer", uri)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
var jsonResponse map[string]interface{}
err = json.Unmarshal(body, &jsonResponse)
if err != nil {
return nil, err
}
streamData, ok := jsonResponse["stream_data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("stream data not found")
}
m3u8Urls, err := client.Get(streamData["file"].(string))
if err != nil {
return nil, err
}
defer m3u8Urls.Body.Close()
m3u8Body, err := io.ReadAll(m3u8Urls.Body)
if err != nil {
return nil, err
}
videoList := strings.Split(string(m3u8Body), "#EXT-X-STREAM-INF:")
for _, video := range videoList {
if !strings.Contains(video, "m3u8") {
continue
}
url := strings.Split(video, "\n")[1]
quality := strings.Split(strings.Split(video, "RESOLUTION=")[1], ",")[0]
quality = strings.Split(quality, "x")[1]
ret = append(ret, &hibikeonlinestream.VideoSource{
URL: url,
Quality: quality + "p",
Type: hibikeonlinestream.VideoSourceM3U8,
})
}
ret = append(ret, &hibikeonlinestream.VideoSource{
URL: streamData["file"].(string),
Quality: "auto",
Type: map[bool]hibikeonlinestream.VideoSourceType{true: hibikeonlinestream.VideoSourceM3U8, false: hibikeonlinestream.VideoSourceMP4}[strings.Contains(streamData["file"].(string), ".m3u8")],
})
return ret, nil
}
@@ -0,0 +1,226 @@
package main
import (
"fmt"
"github.com/5rahim/hibike/pkg/util/bypass"
"github.com/5rahim/hibike/pkg/util/common"
"github.com/5rahim/hibike/pkg/util/similarity"
"github.com/gocolly/colly"
"github.com/rs/zerolog"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
const MangapillProvider = "mangapill-external"
type (
Mangapill struct {
Url string
Client *http.Client
UserAgent string
logger *zerolog.Logger
}
)
func NewProvider(logger *zerolog.Logger) manga.Provider {
c := &http.Client{
Timeout: 60 * time.Second,
}
c.Transport = bypass.AddCloudFlareByPass(c.Transport)
return &Mangapill{
Url: "https://mangapill.com",
Client: c,
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
logger: logger,
}
}
// DEVNOTE: Unique ID
// Each chapter ID has this format: {number}${slug} -- e.g. 6502-10004000$gokurakugai-chapter-4
// The chapter ID is split by the $ character to reconstruct the chapter URL for subsequent requests
func (mp *Mangapill) Search(opts manga.SearchOptions) (ret []*manga.SearchResult, err error) {
ret = make([]*manga.SearchResult, 0)
mp.logger.Debug().Str("query", opts.Query).Msg("mangapill: Searching manga")
uri := fmt.Sprintf("%s/search?q=%s", mp.Url, url.QueryEscape(opts.Query))
c := colly.NewCollector(
colly.UserAgent(mp.UserAgent),
)
c.WithTransport(mp.Client.Transport)
c.OnHTML("div.container div.my-3.justify-end > div", func(e *colly.HTMLElement) {
defer func() {
if r := recover(); r != nil {
}
}()
result := &manga.SearchResult{
Provider: "mangapill",
}
result.ID = strings.Split(e.ChildAttr("a", "href"), "/manga/")[1]
result.ID = strings.Replace(result.ID, "/", "$", -1)
title := e.DOM.Find("div > a > div.mt-3").Text()
result.Title = strings.TrimSpace(title)
altTitles := e.DOM.Find("div > a > div.text-xs.text-secondary").Text()
if altTitles != "" {
result.Synonyms = []string{strings.TrimSpace(altTitles)}
}
compTitles := []string{result.Title}
if len(result.Synonyms) > 0 {
compTitles = append(compTitles, result.Synonyms[0])
}
compRes, _ := similarity.FindBestMatchWithSorensenDice(opts.Query, compTitles)
result.SearchRating = compRes.Rating
result.Image = e.ChildAttr("a img", "data-src")
yearStr := e.DOM.Find("div > div.flex > div").Eq(1).Text()
year, err := strconv.Atoi(strings.TrimSpace(yearStr))
if err != nil {
result.Year = 0
} else {
result.Year = year
}
ret = append(ret, result)
})
err = c.Visit(uri)
if err != nil {
mp.logger.Error().Err(err).Msg("mangapill: Failed to visit")
return nil, err
}
// code
if len(ret) == 0 {
mp.logger.Error().Str("query", opts.Query).Msg("mangapill: No results found")
return nil, fmt.Errorf("no results found")
}
mp.logger.Info().Int("count", len(ret)).Msg("mangapill: Found results")
return ret, nil
}
func (mp *Mangapill) FindChapters(id string) (ret []*manga.ChapterDetails, err error) {
ret = make([]*manga.ChapterDetails, 0)
mp.logger.Debug().Str("mangaId", id).Msg("mangapill: Finding chapters")
uriId := strings.Replace(id, "$", "/", -1)
uri := fmt.Sprintf("%s/manga/%s", mp.Url, uriId)
c := colly.NewCollector(
colly.UserAgent(mp.UserAgent),
)
c.WithTransport(mp.Client.Transport)
c.OnHTML("div.container div.border-border div#chapters div.grid-cols-1 a", func(e *colly.HTMLElement) {
defer func() {
if r := recover(); r != nil {
}
}()
chapter := &manga.ChapterDetails{
Provider: MangapillProvider,
}
chapter.ID = strings.Split(e.Attr("href"), "/chapters/")[1]
chapter.ID = strings.Replace(chapter.ID, "/", "$", -1)
chapter.Title = strings.TrimSpace(e.Text)
splitTitle := strings.Split(chapter.Title, "Chapter ")
if len(splitTitle) < 2 {
return
}
chapter.Chapter = splitTitle[1]
ret = append(ret, chapter)
})
err = c.Visit(uri)
if err != nil {
mp.logger.Error().Err(err).Msg("mangapill: Failed to visit")
return nil, err
}
if len(ret) == 0 {
mp.logger.Error().Str("mangaId", id).Msg("mangapill: No chapters found")
return nil, fmt.Errorf("no chapters found")
}
common.Reverse(ret)
for i, chapter := range ret {
chapter.Index = uint(i)
}
mp.logger.Info().Int("count", len(ret)).Msg("mangapill: Found chapters")
return ret, nil
}
func (mp *Mangapill) FindChapterPages(id string) (ret []*manga.ChapterPage, err error) {
ret = make([]*manga.ChapterPage, 0)
mp.logger.Debug().Str("chapterId", id).Msg("mangapill: Finding chapter pages")
uriId := strings.Replace(id, "$", "/", -1)
uri := fmt.Sprintf("%s/chapters/%s", mp.Url, uriId)
c := colly.NewCollector(
colly.UserAgent(mp.UserAgent),
)
c.WithTransport(mp.Client.Transport)
c.OnHTML("chapter-page", func(e *colly.HTMLElement) {
defer func() {
if r := recover(); r != nil {
}
}()
page := &manga.ChapterPage{}
page.URL = e.DOM.Find("div picture img").AttrOr("data-src", "")
if page.URL == "" {
return
}
indexStr := e.DOM.Find("div[data-summary] > div").Text()
index, _ := strconv.Atoi(strings.Split(strings.Split(indexStr, "page ")[1], "/")[0])
page.Index = index - 1
page.Headers = map[string]string{
"Referer": mp.Url,
}
ret = append(ret, page)
})
err = c.Visit(uri)
if err != nil {
mp.logger.Error().Err(err).Msg("mangapill: Failed to visit")
return nil, err
}
if len(ret) == 0 {
mp.logger.Error().Str("chapterId", id).Msg("mangapill: No pages found")
return nil, fmt.Errorf("no pages found")
}
mp.logger.Info().Int("count", len(ret)).Msg("mangapill: Found pages")
return ret, nil
}
@@ -0,0 +1,69 @@
package main
import (
"net/http"
"time"
bypass "github.com/5rahim/hibike/pkg/util/bypass"
"github.com/rs/zerolog"
torrent "seanime/internal/extension/hibike/torrent"
)
type (
MyAnimeTorrentProvider struct {
url string
client *http.Client
logger *zerolog.Logger
}
)
func NewProvider(logger *zerolog.Logger) torrent.AnimeProvider {
c := &http.Client{
Timeout: 60 * time.Second,
}
c.Transport = bypass.AddCloudFlareByPass(c.Transport)
return &MyAnimeTorrentProvider{
url: "https://example.com",
client: c,
logger: logger,
}
}
func (m *MyAnimeTorrentProvider) Search(opts torrent.AnimeSearchOptions) ([]*torrent.AnimeTorrent, error) {
//TODO implement me
panic("implement me")
}
func (m *MyAnimeTorrentProvider) SmartSearch(opts torrent.AnimeSmartSearchOptions) ([]*torrent.AnimeTorrent, error) {
//TODO implement me
panic("implement me")
}
func (m *MyAnimeTorrentProvider) GetTorrentInfoHash(torrent *torrent.AnimeTorrent) (string, error) {
//TODO implement me
panic("implement me")
}
func (m *MyAnimeTorrentProvider) GetTorrentMagnetLink(torrent *torrent.AnimeTorrent) (string, error) {
//TODO implement me
panic("implement me")
}
func (m *MyAnimeTorrentProvider) GetLatest() ([]*torrent.AnimeTorrent, error) {
//TODO implement me
panic("implement me")
}
func (m *MyAnimeTorrentProvider) GetSettings() torrent.AnimeProviderSettings {
return torrent.AnimeProviderSettings{
CanSmartSearch: true,
SmartSearchFilters: []torrent.AnimeProviderSmartSearchFilter{
torrent.AnimeProviderSmartSearchFilterEpisodeNumber,
torrent.AnimeProviderSmartSearchFilterResolution,
torrent.AnimeProviderSmartSearchFilterQuery,
torrent.AnimeProviderSmartSearchFilterBatch,
},
SupportsAdult: false,
Type: "main",
}
}
@@ -0,0 +1,49 @@
package main
import (
"net/http"
"time"
bypass "github.com/5rahim/hibike/pkg/util/bypass"
"github.com/rs/zerolog"
onlinestream "seanime/internal/extension/hibike/onlinestream"
)
type (
Provider struct {
url string
client *http.Client
logger *zerolog.Logger
}
)
func NewProvider(logger *zerolog.Logger) onlinestream.Provider {
c := &http.Client{
Timeout: 60 * time.Second,
}
c.Transport = bypass.AddCloudFlareByPass(c.Transport)
return &Provider{
url: "https://example.com",
client: c,
logger: logger,
}
}
func (p *Provider) Search(query string, dub bool) ([]*onlinestream.SearchResult, error) {
//TODO implement me
panic("implement me")
}
func (p *Provider) FindEpisode(id string) ([]*onlinestream.EpisodeDetails, error) {
//TODO implement me
panic("implement me")
}
func (p *Provider) FindEpisodeServer(episode *onlinestream.EpisodeDetails, server string) (*onlinestream.EpisodeServer, error) {
//TODO implement me
panic("implement me")
}
func (p *Provider) GetEpisodeServers() []string {
return []string{"server1", "server2"}
}
@@ -0,0 +1,11 @@
{
"id": "gogoanime-external",
"name": "Gogoanime (External)",
"description": "",
"version": "0.0.1",
"type": "onlinestream-provider",
"manifestURI": "",
"language": "go",
"author": "Seanime",
"payload": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\tbrowser \"github.com/EDDYCJY/fake-useragent\"\n\t\"github.com/goccy/go-json\"\n\t\"github.com/gocolly/colly\"\n\t\"github.com/rs/zerolog\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\thibikeonlinestream \"github.com/5rahim/hibike/pkg/extension/onlinestream\"\n)\n\nconst (\n\tDefaultServer = \"default\"\n\tGogoanimeProvider = \"gogoanime-external\"\n\tGogocdnServer = \"gogocdn\"\n\tVidstreamingServer = \"vidstreaming\"\n\tStreamSBServer = \"streamsb\"\n)\n\ntype Gogoanime struct {\n\tBaseURL string\n\tAjaxURL string\n\tClient http.Client\n\tUserAgent string\n\tlogger *zerolog.Logger\n}\n\nfunc NewProvider(logger *zerolog.Logger) hibikeonlinestream.Provider {\n\treturn &Gogoanime{\n\t\tBaseURL: \"https://anitaku.to\",\n\t\tAjaxURL: \"https://ajax.gogocdn.net\",\n\t\tClient: http.Client{},\n\t\tUserAgent: browser.Firefox(),\n\t\tlogger: logger,\n\t}\n}\n\nfunc (g *Gogoanime) GetEpisodeServers() []string {\n\treturn []string{GogocdnServer, VidstreamingServer}\n}\n\nfunc (g *Gogoanime) Search(query string, dubbed bool) ([]*hibikeonlinestream.SearchResult, error) {\n\tvar results []*hibikeonlinestream.SearchResult\n\n\tg.logger.Debug().Str(\"query\", query).Bool(\"dubbed\", dubbed).Msg(\"gogoanime: Searching anime\")\n\n\tc := colly.NewCollector(\n\t\tcolly.UserAgent(g.UserAgent),\n\t)\n\n\tc.OnHTML(\".last_episodes > ul > li\", func(e *colly.HTMLElement) {\n\t\tid := \"\"\n\t\tidParts := strings.Split(e.ChildAttr(\"p.name > a\", \"href\"), \"/\")\n\t\tif len(idParts) > 2 {\n\t\t\tid = idParts[2]\n\t\t}\n\t\ttitle := e.ChildText(\"p.name > a\")\n\t\turl := g.BaseURL + e.ChildAttr(\"p.name > a\", \"href\")\n\t\tsubOrDub := hibikeonlinestream.Sub\n\t\tif strings.Contains(strings.ToLower(e.ChildText(\"p.name > a\")), \"dub\") {\n\t\t\tsubOrDub = hibikeonlinestream.Dub\n\t\t}\n\t\tresults = append(results, &hibikeonlinestream.SearchResult{\n\t\t\tID: id,\n\t\t\tTitle: title,\n\t\t\tURL: url,\n\t\t\tSubOrDub: subOrDub,\n\t\t})\n\t})\n\n\tsearchURL := g.BaseURL + \"/search.html?keyword=\" + url.QueryEscape(query)\n\tif dubbed {\n\t\tsearchURL += \"%20(Dub)\"\n\t}\n\n\terr := c.Visit(searchURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tg.logger.Debug().Int(\"count\", len(results)).Msg(\"gogoanime: Fetched anime\")\n\n\treturn results, nil\n}\n\nfunc (g *Gogoanime) FindEpisode(id string) ([]*hibikeonlinestream.EpisodeDetails, error) {\n\tvar episodes []*hibikeonlinestream.EpisodeDetails\n\n\tg.logger.Debug().Str(\"id\", id).Msg(\"gogoanime: Fetching episodes\")\n\n\tif !strings.Contains(id, \"gogoanime\") {\n\t\tid = fmt.Sprintf(\"%s/category/%s\", g.BaseURL, id)\n\t}\n\n\tc := colly.NewCollector(\n\t\tcolly.UserAgent(g.UserAgent),\n\t)\n\n\tvar epStart, epEnd, movieID, alias string\n\n\tc.OnHTML(\"#episode_page > li > a\", func(e *colly.HTMLElement) {\n\t\tif epStart == \"\" {\n\t\t\tepStart = e.Attr(\"ep_start\")\n\t\t}\n\t\tepEnd = e.Attr(\"ep_end\")\n\t})\n\n\tc.OnHTML(\"#movie_id\", func(e *colly.HTMLElement) {\n\t\tmovieID = e.Attr(\"value\")\n\t})\n\n\tc.OnHTML(\"#alias\", func(e *colly.HTMLElement) {\n\t\talias = e.Attr(\"value\")\n\t})\n\n\terr := c.Visit(id)\n\tif err != nil {\n\t\tg.logger.Error().Err(err).Msg(\"gogoanime: Failed to fetch episodes\")\n\t\treturn nil, err\n\t}\n\n\tc2 := colly.NewCollector(\n\t\tcolly.UserAgent(g.UserAgent),\n\t)\n\n\tc2.OnHTML(\"#episode_related > li\", func(e *colly.HTMLElement) {\n\t\tepisodeIDParts := strings.Split(e.ChildAttr(\"a\", \"href\"), \"/\")\n\t\tif len(episodeIDParts) < 2 {\n\t\t\treturn\n\t\t}\n\t\tepisodeID := strings.TrimSpace(episodeIDParts[1])\n\t\tepisodeNumberStr := strings.TrimPrefix(e.ChildText(\"div.name\"), \"EP \")\n\t\tepisodeNumber, err := strconv.Atoi(episodeNumberStr)\n\t\tif err != nil {\n\t\t\tg.logger.Error().Err(err).Str(\"episodeID\", episodeID).Msg(\"failed to parse episode number\")\n\t\t\treturn\n\t\t}\n\t\tepisodes = append(episodes, &hibikeonlinestream.EpisodeDetails{\n\t\t\tProvider: GogoanimeProvider,\n\t\t\tID: episodeID,\n\t\t\tNumber: episodeNumber,\n\t\t\tURL: g.BaseURL + \"/\" + episodeID,\n\t\t})\n\t})\n\n\tajaxURL := fmt.Sprintf(\"%s/ajax/load-list-episode\", g.AjaxURL)\n\tajaxParams := url.Values{\n\t\t\"ep_start\": {epStart},\n\t\t\"ep_end\": {epEnd},\n\t\t\"id\": {movieID},\n\t\t\"alias\": {alias},\n\t\t\"default_ep\": {\"0\"},\n\t}\n\tajaxURLWithParams := fmt.Sprintf(\"%s?%s\", ajaxURL, ajaxParams.Encode())\n\n\terr = c2.Visit(ajaxURLWithParams)\n\tif err != nil {\n\t\tg.logger.Error().Err(err).Msg(\"gogoanime: Failed to fetch episodes\")\n\t\treturn nil, err\n\t}\n\n\tg.logger.Debug().Int(\"count\", len(episodes)).Msg(\"gogoanime: Fetched episodes\")\n\n\treturn episodes, nil\n}\n\nfunc (g *Gogoanime) FindEpisodeServer(episodeInfo *Line truncated
}
@@ -0,0 +1,11 @@
{
"id": "mangapill-external",
"name": "Mangapill (External)",
"description": "",
"version": "0.0.1",
"type": "manga-provider",
"manifestURI": "",
"language": "go",
"author": "Seanime",
"payload": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/5rahim/hibike/pkg/extension/manga\"\n\t\"github.com/5rahim/hibike/pkg/util/bypass\"\n\t\"github.com/5rahim/hibike/pkg/util/common\"\n\t\"github.com/5rahim/hibike/pkg/util/similarity\"\n\t\"github.com/gocolly/colly\"\n\t\"github.com/rs/zerolog\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst MangapillProvider = \"mangapill-external\"\n\ntype (\n\tMangapill struct {\n\t\tUrl string\n\t\tClient *http.Client\n\t\tUserAgent string\n\t\tlogger *zerolog.Logger\n\t}\n)\n\nfunc NewProvider(logger *zerolog.Logger) manga.Provider {\n\tc := &http.Client{\n\t\tTimeout: 60 * time.Second,\n\t}\n\tc.Transport = bypass.AddCloudFlareByPass(c.Transport)\n\treturn &Mangapill{\n\t\tUrl: \"https://mangapill.com\",\n\t\tClient: c,\n\t\tUserAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3\",\n\t\tlogger: logger,\n\t}\n}\n\n// DEVNOTE: Unique ID\n// Each chapter ID has this format: {number}${slug} -- e.g. 6502-10004000$gokurakugai-chapter-4\n// The chapter ID is split by the $ character to reconstruct the chapter URL for subsequent requests\n\nfunc (mp *Mangapill) Search(opts manga.SearchOptions) (ret []*manga.SearchResult, err error) {\n\tret = make([]*manga.SearchResult, 0)\n\n\tmp.logger.Debug().Str(\"query\", opts.Query).Msg(\"mangapill: Searching manga\")\n\n\turi := fmt.Sprintf(\"%s/search?q=%s\", mp.Url, url.QueryEscape(opts.Query))\n\n\tc := colly.NewCollector(\n\t\tcolly.UserAgent(mp.UserAgent),\n\t)\n\n\tc.WithTransport(mp.Client.Transport)\n\n\tc.OnHTML(\"div.container div.my-3.justify-end > div\", func(e *colly.HTMLElement) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t}\n\t\t}()\n\t\tresult := &manga.SearchResult{\n\t\t\tProvider: \"mangapill\",\n\t\t}\n\n\t\tresult.ID = strings.Split(e.ChildAttr(\"a\", \"href\"), \"/manga/\")[1]\n\t\tresult.ID = strings.Replace(result.ID, \"/\", \"$\", -1)\n\n\t\ttitle := e.DOM.Find(\"div > a > div.mt-3\").Text()\n\t\tresult.Title = strings.TrimSpace(title)\n\n\t\taltTitles := e.DOM.Find(\"div > a > div.text-xs.text-secondary\").Text()\n\t\tif altTitles != \"\" {\n\t\t\tresult.Synonyms = []string{strings.TrimSpace(altTitles)}\n\t\t}\n\n\t\tcompTitles := []string{result.Title}\n\t\tif len(result.Synonyms) > 0 {\n\t\t\tcompTitles = append(compTitles, result.Synonyms[0])\n\t\t}\n\t\tcompRes, _ := similarity.FindBestMatchWithSorensenDice(opts.Query, compTitles)\n\t\tresult.SearchRating = compRes.Rating\n\n\t\tresult.Image = e.ChildAttr(\"a img\", \"data-src\")\n\n\t\tyearStr := e.DOM.Find(\"div > div.flex > div\").Eq(1).Text()\n\t\tyear, err := strconv.Atoi(strings.TrimSpace(yearStr))\n\t\tif err != nil {\n\t\t\tresult.Year = 0\n\t\t} else {\n\t\t\tresult.Year = year\n\t\t}\n\n\t\tret = append(ret, result)\n\t})\n\n\terr = c.Visit(uri)\n\tif err != nil {\n\t\tmp.logger.Error().Err(err).Msg(\"mangapill: Failed to visit\")\n\t\treturn nil, err\n\t}\n\n\t// code\n\n\tif len(ret) == 0 {\n\t\tmp.logger.Error().Str(\"query\", opts.Query).Msg(\"mangapill: No results found\")\n\t\treturn nil, fmt.Errorf(\"no results found\")\n\t}\n\n\tmp.logger.Info().Int(\"count\", len(ret)).Msg(\"mangapill: Found results\")\n\n\treturn ret, nil\n}\n\nfunc (mp *Mangapill) FindChapters(id string) (ret []*manga.ChapterDetails, err error) {\n\tret = make([]*manga.ChapterDetails, 0)\n\n\tmp.logger.Debug().Str(\"mangaId\", id).Msg(\"mangapill: Finding chapters\")\n\n\turiId := strings.Replace(id, \"$\", \"/\", -1)\n\turi := fmt.Sprintf(\"%s/manga/%s\", mp.Url, uriId)\n\n\tc := colly.NewCollector(\n\t\tcolly.UserAgent(mp.UserAgent),\n\t)\n\n\tc.WithTransport(mp.Client.Transport)\n\n\tc.OnHTML(\"div.container div.border-border div#chapters div.grid-cols-1 a\", func(e *colly.HTMLElement) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t}\n\t\t}()\n\t\tchapter := &manga.ChapterDetails{\n\t\t\tProvider: MangapillProvider,\n\t\t}\n\n\t\tchapter.ID = strings.Split(e.Attr(\"href\"), \"/chapters/\")[1]\n\t\tchapter.ID = strings.Replace(chapter.ID, \"/\", \"$\", -1)\n\n\t\tchapter.Title = strings.TrimSpace(e.Text)\n\n\t\tsplitTitle := strings.Split(chapter.Title, \"Chapter \")\n\t\tif len(splitTitle) < 2 {\n\t\t\treturn\n\t\t}\n\t\tchapter.Chapter = splitTitle[1]\n\n\t\tret = append(ret, chapter)\n\t})\n\n\terr = c.Visit(uri)\n\tif err != nil {\n\t\tmp.logger.Error().Err(err).Msg(\"mangapill: Failed to visit\")\n\t\treturn nil, err\n\t}\n\n\tif len(ret) == 0 {\n\t\tmp.logger.Error().Str(\"mangaId\", id).Msg(\"mangapill: No chapters found\")\n\t\treturn nil, fmt.Errorf(\"no chapters found\")\n\t}\n\n\tcommon.Reverse(ret)\n\n\tfor i, chapter := range ret {\n\t\tchapter.Index = uint(i)\n\t}\n\n\tmp.logger.Info().Int(\"count\", len(ret)).Msg(\"mangapill: Found chapters\")\n\n\treturn ret, nil\n}\n\nfunc (mp *Mangapill) FindChapterPages(id string) (ret []*manga.ChapterPage, err error) {\n\tret =Line truncated
}
@@ -0,0 +1 @@
package extension_repo_test