node build fixed
This commit is contained in:
446
seanime-2.9.10/internal/onlinestream/providers/_animepahe.go
Normal file
446
seanime-2.9.10/internal/onlinestream/providers/_animepahe.go
Normal file
@@ -0,0 +1,446 @@
|
||||
package onlinestream_providers
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gocolly/colly"
|
||||
"github.com/rs/zerolog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
|
||||
onlinestream_sources "seanime/internal/onlinestream/sources"
|
||||
"seanime/internal/util"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type (
|
||||
Animepahe struct {
|
||||
BaseURL string
|
||||
Client http.Client
|
||||
UserAgent string
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
AnimepaheSearchResult struct {
|
||||
Data []struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Year int `json:"year"`
|
||||
Poster string `json:"poster"`
|
||||
Type string `json:"type"`
|
||||
Session string `json:"session"`
|
||||
} `json:"data"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewAnimepahe(logger *zerolog.Logger) hibikeonlinestream.Provider {
|
||||
return &Animepahe{
|
||||
BaseURL: "https://animepahe.ru",
|
||||
Client: http.Client{},
|
||||
UserAgent: util.GetRandomUserAgent(),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Animepahe) GetSettings() hibikeonlinestream.Settings {
|
||||
return hibikeonlinestream.Settings{
|
||||
EpisodeServers: []string{"animepahe"},
|
||||
SupportsDub: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Animepahe) Search(opts hibikeonlinestream.SearchOptions) ([]*hibikeonlinestream.SearchResult, error) {
|
||||
var results []*hibikeonlinestream.SearchResult
|
||||
|
||||
query := opts.Query
|
||||
dubbed := opts.Dub
|
||||
|
||||
g.logger.Debug().Str("query", query).Bool("dubbed", dubbed).Msg("animepahe: Searching anime")
|
||||
|
||||
q := url.QueryEscape(query)
|
||||
request, err := http.NewRequest("GET", g.BaseURL+fmt.Sprintf("/api?m=search&q=%s", q), nil)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to create request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("User-Agent", g.UserAgent)
|
||||
request.Header.Set("Cookie", "__ddg1_=;__ddg2_=;")
|
||||
|
||||
response, err := g.Client.Do(request)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to send request")
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var searchResult AnimepaheSearchResult
|
||||
err = json.NewDecoder(response.Body).Decode(&searchResult)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to decode response")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, data := range searchResult.Data {
|
||||
results = append(results, &hibikeonlinestream.SearchResult{
|
||||
ID: cmp.Or(fmt.Sprintf("%d", data.ID), data.Session),
|
||||
Title: data.Title,
|
||||
URL: fmt.Sprintf("%s/anime/%d", g.BaseURL, data.ID),
|
||||
SubOrDub: hibikeonlinestream.Sub,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (g *Animepahe) FindEpisodes(id string) ([]*hibikeonlinestream.EpisodeDetails, error) {
|
||||
var episodes []*hibikeonlinestream.EpisodeDetails
|
||||
|
||||
q1 := fmt.Sprintf("/anime/%s", id)
|
||||
if !strings.Contains(id, "-") {
|
||||
q1 = fmt.Sprintf("/a/%s", id)
|
||||
}
|
||||
c := colly.NewCollector(
|
||||
colly.UserAgent(g.UserAgent),
|
||||
)
|
||||
|
||||
c.OnRequest(func(r *colly.Request) {
|
||||
r.Headers.Set("Cookie", "__ddg1_=;__ddg2_=")
|
||||
})
|
||||
|
||||
var tempId string
|
||||
c.OnHTML("head > meta[property='og:url']", func(e *colly.HTMLElement) {
|
||||
parts := strings.Split(e.Attr("content"), "/")
|
||||
tempId = parts[len(parts)-1]
|
||||
})
|
||||
|
||||
err := c.Visit(g.BaseURL + q1)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to fetch episodes")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// { last_page: number; data: { id: number; episode: number; title: string; snapshot: string; filler: number; created_at?: string }[] }
|
||||
type data struct {
|
||||
LastPage int `json:"last_page"`
|
||||
Data []struct {
|
||||
ID int `json:"id"`
|
||||
Episode int `json:"episode"`
|
||||
Title string `json:"title"`
|
||||
Snapshot string `json:"snapshot"`
|
||||
Filler int `json:"filler"`
|
||||
Session string `json:"session"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
q2 := fmt.Sprintf("/api?m=release&id=%s&sort=episode_asc&page=1", tempId)
|
||||
request, err := http.NewRequest("GET", g.BaseURL+q2, nil)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to create request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("User-Agent", g.UserAgent)
|
||||
request.Header.Set("Cookie", "__ddg1_=;__ddg2_=")
|
||||
|
||||
response, err := g.Client.Do(request)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to send request")
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var d data
|
||||
err = json.NewDecoder(response.Body).Decode(&d)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to decode response")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, e := range d.Data {
|
||||
episodes = append(episodes, &hibikeonlinestream.EpisodeDetails{
|
||||
Provider: "animepahe",
|
||||
ID: fmt.Sprintf("%d$%s", e.ID, id),
|
||||
Number: e.Episode,
|
||||
URL: fmt.Sprintf("%s/anime/%s/%d", g.BaseURL, id, e.Episode),
|
||||
Title: cmp.Or(e.Title, "Episode "+fmt.Sprintf("%d", e.Episode)),
|
||||
})
|
||||
}
|
||||
|
||||
var pageNumbers []int
|
||||
|
||||
for i := 2; i <= d.LastPage; i++ {
|
||||
pageNumbers = append(pageNumbers, i)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(pageNumbers))
|
||||
mu := sync.Mutex{}
|
||||
|
||||
for _, p := range pageNumbers {
|
||||
go func(p int) {
|
||||
defer wg.Done()
|
||||
q2 := fmt.Sprintf("/api?m=release&id=%s&sort=episode_asc&page=%d", tempId, p)
|
||||
request, err := http.NewRequest("GET", g.BaseURL+q2, nil)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to create request")
|
||||
return
|
||||
}
|
||||
|
||||
request.Header.Set("User-Agent", g.UserAgent)
|
||||
request.Header.Set("Cookie", "__ddg1_=;__ddg2_=")
|
||||
|
||||
response, err := g.Client.Do(request)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to send request")
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var d data
|
||||
err = json.NewDecoder(response.Body).Decode(&d)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to decode response")
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
for _, e := range d.Data {
|
||||
episodes = append(episodes, &hibikeonlinestream.EpisodeDetails{
|
||||
Provider: "animepahe",
|
||||
ID: fmt.Sprintf("%d$%s", e.ID, id),
|
||||
Number: e.Episode,
|
||||
URL: fmt.Sprintf("%s/anime/%s/%d", g.BaseURL, id, e.Episode),
|
||||
Title: cmp.Or(e.Title, "Episode "+fmt.Sprintf("%d", e.Episode)),
|
||||
})
|
||||
}
|
||||
mu.Unlock()
|
||||
}(p)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
g.logger.Debug().Int("count", len(episodes)).Msg("animepahe: Fetched episodes")
|
||||
|
||||
sort.Slice(episodes, func(i, j int) bool {
|
||||
return episodes[i].Number < episodes[j].Number
|
||||
})
|
||||
|
||||
if len(episodes) == 0 {
|
||||
return nil, fmt.Errorf("no episodes found")
|
||||
}
|
||||
|
||||
// Normalize episode numbers
|
||||
offset := episodes[0].Number + 1
|
||||
for i, e := range episodes {
|
||||
episodes[i].Number = e.Number - offset
|
||||
}
|
||||
|
||||
return episodes, nil
|
||||
}
|
||||
|
||||
func (g *Animepahe) FindEpisodeServer(episodeInfo *hibikeonlinestream.EpisodeDetails, server string) (*hibikeonlinestream.EpisodeServer, error) {
|
||||
var source *hibikeonlinestream.EpisodeServer
|
||||
|
||||
parts := strings.Split(episodeInfo.ID, "$")
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("animepahe: Invalid episode ID")
|
||||
}
|
||||
|
||||
episodeID := parts[0]
|
||||
animeID := parts[1]
|
||||
|
||||
q1 := fmt.Sprintf("/anime/%s", animeID)
|
||||
if !strings.Contains(animeID, "-") {
|
||||
q1 = fmt.Sprintf("/a/%s", animeID)
|
||||
}
|
||||
c := colly.NewCollector(
|
||||
colly.UserAgent(g.UserAgent),
|
||||
)
|
||||
|
||||
var reqUrl *url.URL
|
||||
|
||||
c.OnRequest(func(r *colly.Request) {
|
||||
r.Headers.Set("Cookie", "__ddg1_=;__ddg2_=")
|
||||
})
|
||||
|
||||
c.OnResponse(func(r *colly.Response) {
|
||||
reqUrl = r.Request.URL
|
||||
})
|
||||
|
||||
var tempId string
|
||||
c.OnHTML("head > meta[property='og:url']", func(e *colly.HTMLElement) {
|
||||
parts := strings.Split(e.Attr("content"), "/")
|
||||
tempId = parts[len(parts)-1]
|
||||
})
|
||||
|
||||
err := c.Visit(g.BaseURL + q1)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to fetch episodes")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sessionId string
|
||||
// retain url without query
|
||||
reqUrlStr := reqUrl.Path
|
||||
reqUrlStrParts := strings.Split(reqUrlStr, "/anime/")
|
||||
sessionId = reqUrlStrParts[len(reqUrlStrParts)-1]
|
||||
|
||||
// { last_page: number; data: { id: number; episode: number; title: string; snapshot: string; filler: number; created_at?: string }[] }
|
||||
type data struct {
|
||||
LastPage int `json:"last_page"`
|
||||
Data []struct {
|
||||
ID int `json:"id"`
|
||||
Session string `json:"session"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
q2 := fmt.Sprintf("/api?m=release&id=%s&sort=episode_asc&page=1", tempId)
|
||||
request, err := http.NewRequest("GET", g.BaseURL+q2, nil)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to create request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("User-Agent", g.UserAgent)
|
||||
request.Header.Set("Cookie", "__ddg1_=;__ddg2_=")
|
||||
|
||||
response, err := g.Client.Do(request)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to send request")
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var d data
|
||||
err = json.NewDecoder(response.Body).Decode(&d)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to decode response")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
episodeSession := ""
|
||||
|
||||
for _, e := range d.Data {
|
||||
if fmt.Sprintf("%d", e.ID) == episodeID {
|
||||
episodeSession = e.Session
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var pageNumbers []int
|
||||
|
||||
for i := 1; i <= d.LastPage; i++ {
|
||||
pageNumbers = append(pageNumbers, i)
|
||||
}
|
||||
|
||||
if episodeSession == "" {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(pageNumbers))
|
||||
mu := sync.Mutex{}
|
||||
|
||||
for _, p := range pageNumbers {
|
||||
go func(p int) {
|
||||
defer wg.Done()
|
||||
q2 := fmt.Sprintf("/api?m=release&id=%s&sort=episode_asc&page=%d", tempId, p)
|
||||
request, err := http.NewRequest("GET", g.BaseURL+q2, nil)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to create request")
|
||||
return
|
||||
}
|
||||
|
||||
request.Header.Set("User-Agent", g.UserAgent)
|
||||
request.Header.Set("Cookie", "__ddg1_=;__ddg2_=")
|
||||
|
||||
response, err := g.Client.Do(request)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to send request")
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var d data
|
||||
err = json.NewDecoder(response.Body).Decode(&d)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to decode response")
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
for _, e := range d.Data {
|
||||
if fmt.Sprintf("%d", e.ID) == episodeID {
|
||||
episodeSession = e.Session
|
||||
break
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
}(p)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
if episodeSession == "" {
|
||||
return nil, fmt.Errorf("animepahe: Episode not found")
|
||||
}
|
||||
|
||||
q3 := fmt.Sprintf("/play/%s/%s", sessionId, episodeSession)
|
||||
request2, err := http.NewRequest("GET", g.BaseURL+q3, nil)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to create request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request2.Header.Set("User-Agent", g.UserAgent)
|
||||
request2.Header.Set("Cookie", "__ddg1_=;__ddg2_=")
|
||||
|
||||
response2, err := g.Client.Do(request2)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to send request")
|
||||
return nil, err
|
||||
}
|
||||
defer response2.Body.Close()
|
||||
|
||||
htmlString := ""
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(response2.Body)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to parse response")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
htmlString = doc.Text()
|
||||
|
||||
//const regex = /https:\/\/kwik\.si\/e\/\w+/g;
|
||||
// const matches = watchReq.match(regex);
|
||||
//
|
||||
// if (matches === null) return undefined;
|
||||
|
||||
re := regexp.MustCompile(`https:\/\/kwik\.si\/e\/\w+`)
|
||||
matches := re.FindAllString(htmlString, -1)
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("animepahe: Failed to find episode source")
|
||||
}
|
||||
|
||||
kwik := onlinestream_sources.NewKwik()
|
||||
videoSources, err := kwik.Extract(matches[0])
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("animepahe: Failed to extract video sources")
|
||||
return nil, fmt.Errorf("animepahe: Failed to extract video sources, %w", err)
|
||||
}
|
||||
|
||||
source = &hibikeonlinestream.EpisodeServer{
|
||||
Provider: "animepahe",
|
||||
Server: KwikServer,
|
||||
Headers: map[string]string{"Referer": "https://kwik.si/"},
|
||||
VideoSources: videoSources,
|
||||
}
|
||||
|
||||
return source, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package onlinestream_providers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnimepahe_Search(t *testing.T) {
|
||||
|
||||
ap := NewAnimepahe(util.NewLogger())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
dubbed bool
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
query: "One Piece",
|
||||
dubbed: false,
|
||||
},
|
||||
{
|
||||
name: "Blue Lock Season 2",
|
||||
query: "Blue Lock Season 2",
|
||||
dubbed: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
results, err := ap.Search(hibikeonlinestream.SearchOptions{
|
||||
Query: tt.query,
|
||||
Dub: tt.dubbed,
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, results)
|
||||
|
||||
for _, r := range results {
|
||||
assert.NotEmpty(t, r.ID, "ID is empty")
|
||||
assert.NotEmpty(t, r.Title, "Title is empty")
|
||||
assert.NotEmpty(t, r.URL, "URL is empty")
|
||||
}
|
||||
|
||||
util.Spew(results)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAnimepahe_FetchEpisodes(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
id: "4",
|
||||
},
|
||||
{
|
||||
name: "Blue Lock Season 2",
|
||||
id: "5648",
|
||||
},
|
||||
}
|
||||
|
||||
ap := NewAnimepahe(util.NewLogger())
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
episodes, err := ap.FindEpisodes(tt.id)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, episodes)
|
||||
|
||||
for _, e := range episodes {
|
||||
assert.NotEmpty(t, e.ID, "ID is empty")
|
||||
assert.NotEmpty(t, e.Number, "Number is empty")
|
||||
assert.NotEmpty(t, e.URL, "URL is empty")
|
||||
}
|
||||
|
||||
util.Spew(episodes)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAnimepahe_FetchSources(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
episode *hibikeonlinestream.EpisodeDetails
|
||||
server string
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "63391$4",
|
||||
Number: 1115,
|
||||
URL: "",
|
||||
},
|
||||
server: KwikServer,
|
||||
},
|
||||
{
|
||||
name: "Blue Lock Season 2 - Episode 1",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "64056$5648",
|
||||
Number: 1,
|
||||
URL: "",
|
||||
},
|
||||
server: KwikServer,
|
||||
},
|
||||
}
|
||||
ap := NewAnimepahe(util.NewLogger())
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
sources, err := ap.FindEpisodeServer(tt.episode, tt.server)
|
||||
if err != nil {
|
||||
if !errors.Is(err, ErrSourceNotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Skip("Source not found")
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, sources)
|
||||
|
||||
for _, s := range sources.VideoSources {
|
||||
assert.NotEmpty(t, s, "Source is empty")
|
||||
}
|
||||
|
||||
util.Spew(sources)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
25
seanime-2.9.10/internal/onlinestream/providers/common.go
Normal file
25
seanime-2.9.10/internal/onlinestream/providers/common.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package onlinestream_providers
|
||||
|
||||
import "errors"
|
||||
|
||||
// Built-in
|
||||
const (
|
||||
GogoanimeProvider string = "gogoanime"
|
||||
ZoroProvider string = "zoro"
|
||||
)
|
||||
|
||||
// Built-in
|
||||
const (
|
||||
DefaultServer = "default"
|
||||
GogocdnServer = "gogocdn"
|
||||
VidstreamingServer = "vidstreaming"
|
||||
StreamSBServer = "streamsb"
|
||||
VidcloudServer = "vidcloud"
|
||||
StreamtapeServer = "streamtape"
|
||||
KwikServer = "kwik"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSourceNotFound = errors.New("video source not found")
|
||||
ErrServerNotFound = errors.New("server not found")
|
||||
)
|
||||
247
seanime-2.9.10/internal/onlinestream/providers/gogoanime.go
Normal file
247
seanime-2.9.10/internal/onlinestream/providers/gogoanime.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package onlinestream_providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gocolly/colly"
|
||||
"github.com/rs/zerolog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"seanime/internal/onlinestream/sources"
|
||||
"seanime/internal/util"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
|
||||
)
|
||||
|
||||
type Gogoanime struct {
|
||||
BaseURL string
|
||||
AjaxURL string
|
||||
Client http.Client
|
||||
UserAgent string
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
func NewGogoanime(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) GetSettings() hibikeonlinestream.Settings {
|
||||
return hibikeonlinestream.Settings{
|
||||
EpisodeServers: []string{GogocdnServer, VidstreamingServer},
|
||||
SupportsDub: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gogoanime) Search(opts hibikeonlinestream.SearchOptions) ([]*hibikeonlinestream.SearchResult, error) {
|
||||
var results []*hibikeonlinestream.SearchResult
|
||||
|
||||
query := opts.Query
|
||||
dubbed := opts.Dub
|
||||
|
||||
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) FindEpisodes(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 := onlinestream_sources.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 := onlinestream_sources.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 := onlinestream_sources.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", server).Msg("gogoanime: No sources found")
|
||||
return nil, ErrSourceNotFound
|
||||
}
|
||||
|
||||
g.logger.Debug().Str("server", server).Int("videoSources", len(source.VideoSources)).Msg("gogoanime: Fetched server sources")
|
||||
|
||||
return source, nil
|
||||
|
||||
}
|
||||
172
seanime-2.9.10/internal/onlinestream/providers/gogoanime_test.go
Normal file
172
seanime-2.9.10/internal/onlinestream/providers/gogoanime_test.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package onlinestream_providers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/stretchr/testify/assert"
|
||||
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGogoanime_Search(t *testing.T) {
|
||||
|
||||
gogo := NewGogoanime(util.NewLogger())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
dubbed bool
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
query: "One Piece",
|
||||
dubbed: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
results, err := gogo.Search(hibikeonlinestream.SearchOptions{
|
||||
Query: tt.query,
|
||||
Dub: tt.dubbed,
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, results)
|
||||
|
||||
for _, r := range results {
|
||||
assert.NotEmpty(t, r.ID, "ID is empty")
|
||||
assert.NotEmpty(t, r.Title, "Title is empty")
|
||||
assert.NotEmpty(t, r.URL, "URL is empty")
|
||||
}
|
||||
|
||||
spew.Dump(results)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGogoanime_FetchEpisodes(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
id: "one-piece",
|
||||
},
|
||||
{
|
||||
name: "One Piece (Dub)",
|
||||
id: "one-piece-dub",
|
||||
},
|
||||
}
|
||||
|
||||
gogo := NewGogoanime(util.NewLogger())
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
episodes, err := gogo.FindEpisodes(tt.id)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, episodes)
|
||||
|
||||
for _, e := range episodes {
|
||||
assert.NotEmpty(t, e.ID, "ID is empty")
|
||||
assert.NotEmpty(t, e.Number, "Number is empty")
|
||||
assert.NotEmpty(t, e.URL, "URL is empty")
|
||||
}
|
||||
|
||||
spew.Dump(episodes)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGogoanime_FetchSources(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
episode *hibikeonlinestream.EpisodeDetails
|
||||
server string
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "one-piece-episode-1075",
|
||||
Number: 1075,
|
||||
URL: "https://anitaku.to/one-piece-episode-1075",
|
||||
},
|
||||
server: VidstreamingServer,
|
||||
},
|
||||
{
|
||||
name: "One Piece",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "one-piece-episode-1075",
|
||||
Number: 1075,
|
||||
URL: "https://anitaku.to/one-piece-episode-1075",
|
||||
},
|
||||
server: StreamSBServer,
|
||||
},
|
||||
{
|
||||
name: "One Piece",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "one-piece-episode-1075",
|
||||
Number: 1075,
|
||||
URL: "https://anitaku.to/one-piece-episode-1075",
|
||||
},
|
||||
server: GogocdnServer,
|
||||
},
|
||||
{
|
||||
name: "Bocchi the Rock!",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "bocchi-the-rock-episode-1",
|
||||
Number: 1075,
|
||||
URL: "https://anitaku.to/bocchi-the-rock-episode-1",
|
||||
},
|
||||
server: GogocdnServer,
|
||||
},
|
||||
}
|
||||
gogo := NewGogoanime(util.NewLogger())
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
sources, err := gogo.FindEpisodeServer(tt.episode, tt.server)
|
||||
if err != nil {
|
||||
if !errors.Is(err, ErrSourceNotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Skip("Source not found")
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, sources)
|
||||
|
||||
for _, s := range sources.VideoSources {
|
||||
assert.NotEmpty(t, s, "Source is empty")
|
||||
}
|
||||
|
||||
spew.Dump(sources)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
342
seanime-2.9.10/internal/onlinestream/providers/zoro.go
Normal file
342
seanime-2.9.10/internal/onlinestream/providers/zoro.go
Normal file
@@ -0,0 +1,342 @@
|
||||
package onlinestream_providers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gocolly/colly"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/samber/lo"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"seanime/internal/onlinestream/sources"
|
||||
"seanime/internal/util"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
|
||||
)
|
||||
|
||||
type Zoro struct {
|
||||
BaseURL string
|
||||
Client *http.Client
|
||||
UserAgent string
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
func NewZoro(logger *zerolog.Logger) hibikeonlinestream.Provider {
|
||||
return &Zoro{
|
||||
BaseURL: "https://hianime.to",
|
||||
UserAgent: util.GetRandomUserAgent(),
|
||||
Client: &http.Client{},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (z *Zoro) GetSettings() hibikeonlinestream.Settings {
|
||||
return hibikeonlinestream.Settings{
|
||||
EpisodeServers: []string{VidcloudServer, VidstreamingServer},
|
||||
SupportsDub: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (z *Zoro) Search(opts hibikeonlinestream.SearchOptions) ([]*hibikeonlinestream.SearchResult, error) {
|
||||
var results []*hibikeonlinestream.SearchResult
|
||||
|
||||
query := opts.Query
|
||||
dubbed := opts.Dub
|
||||
|
||||
z.logger.Debug().Str("query", query).Bool("dubbed", dubbed).Msg("zoro: Searching anime")
|
||||
|
||||
c := colly.NewCollector()
|
||||
|
||||
c.OnHTML(".flw-item", func(e *colly.HTMLElement) {
|
||||
id := strings.Split(strings.Split(e.ChildAttr(".film-name a", "href"), "/")[1], "?")[0]
|
||||
title := e.ChildText(".film-name a")
|
||||
url := strings.Split(z.BaseURL+e.ChildAttr(".film-name a", "href"), "?")[0]
|
||||
subOrDub := hibikeonlinestream.Sub
|
||||
foundSub := false
|
||||
foundDub := false
|
||||
if e.ChildText(".tick-item.tick-dub") != "" {
|
||||
foundDub = true
|
||||
}
|
||||
if e.ChildText(".tick-item.tick-sub") != "" {
|
||||
foundSub = true
|
||||
}
|
||||
if foundSub && foundDub {
|
||||
subOrDub = hibikeonlinestream.SubAndDub
|
||||
} else if foundDub {
|
||||
subOrDub = hibikeonlinestream.Dub
|
||||
}
|
||||
results = append(results, &hibikeonlinestream.SearchResult{
|
||||
ID: id,
|
||||
Title: title,
|
||||
URL: url,
|
||||
SubOrDub: subOrDub,
|
||||
})
|
||||
})
|
||||
|
||||
searchURL := z.BaseURL + "/search?keyword=" + url.QueryEscape(query)
|
||||
|
||||
err := c.Visit(searchURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dubbed {
|
||||
results = lo.Filter(results, func(r *hibikeonlinestream.SearchResult, _ int) bool {
|
||||
return r.SubOrDub == hibikeonlinestream.Dub || r.SubOrDub == hibikeonlinestream.SubAndDub
|
||||
})
|
||||
}
|
||||
|
||||
z.logger.Debug().Int("count", len(results)).Msg("zoro: Fetched anime")
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (z *Zoro) FindEpisodes(id string) ([]*hibikeonlinestream.EpisodeDetails, error) {
|
||||
var episodes []*hibikeonlinestream.EpisodeDetails
|
||||
|
||||
z.logger.Debug().Str("id", id).Msg("zoro: Fetching episodes")
|
||||
|
||||
c := colly.NewCollector()
|
||||
|
||||
subOrDub := hibikeonlinestream.Sub
|
||||
|
||||
c.OnHTML("div.film-stats > div.tick", func(e *colly.HTMLElement) {
|
||||
if e.ChildText(".tick-item.tick-dub") != "" {
|
||||
subOrDub = hibikeonlinestream.Dub
|
||||
}
|
||||
if e.ChildText(".tick-item.tick-sub") != "" {
|
||||
if subOrDub == hibikeonlinestream.Dub {
|
||||
subOrDub = hibikeonlinestream.SubAndDub
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watchUrl := fmt.Sprintf("%s/watch/%s", z.BaseURL, id)
|
||||
err := c.Visit(watchUrl)
|
||||
if err != nil {
|
||||
z.logger.Error().Err(err).Msg("zoro: Failed to fetch episodes")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get episodes
|
||||
|
||||
splitId := strings.Split(id, "-")
|
||||
idNum := splitId[len(splitId)-1]
|
||||
ajaxUrl := fmt.Sprintf("%s/ajax/v2/episode/list/%s", z.BaseURL, idNum)
|
||||
|
||||
c2 := colly.NewCollector(
|
||||
colly.UserAgent(z.UserAgent),
|
||||
)
|
||||
|
||||
c2.OnRequest(func(r *colly.Request) {
|
||||
r.Headers.Set("X-Requested-With", "XMLHttpRequest")
|
||||
r.Headers.Set("Referer", watchUrl)
|
||||
})
|
||||
|
||||
c2.OnResponse(func(r *colly.Response) {
|
||||
var jsonResponse map[string]interface{}
|
||||
err = json.Unmarshal(r.Body, &jsonResponse)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(jsonResponse["html"].(string)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content := doc.Find(".detail-infor-content")
|
||||
content.Find("a").Each(func(i int, s *goquery.Selection) {
|
||||
id := s.AttrOr("href", "")
|
||||
if id == "" {
|
||||
return
|
||||
}
|
||||
hrefParts := strings.Split(s.AttrOr("href", ""), "/")
|
||||
if len(hrefParts) < 2 {
|
||||
return
|
||||
}
|
||||
if subOrDub == hibikeonlinestream.SubAndDub {
|
||||
subOrDub = "both"
|
||||
}
|
||||
id = fmt.Sprintf("%s$%s", strings.Replace(hrefParts[2], "?ep=", "$episode$", 1), subOrDub)
|
||||
epNumber, _ := strconv.Atoi(s.AttrOr("data-number", ""))
|
||||
url := z.BaseURL + s.AttrOr("href", "")
|
||||
title := s.AttrOr("title", "")
|
||||
episodes = append(episodes, &hibikeonlinestream.EpisodeDetails{
|
||||
Provider: ZoroProvider,
|
||||
ID: id,
|
||||
Number: epNumber,
|
||||
URL: url,
|
||||
Title: title,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
err = c2.Visit(ajaxUrl)
|
||||
if err != nil {
|
||||
z.logger.Error().Err(err).Msg("zoro: Failed to fetch episodes")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
z.logger.Debug().Int("count", len(episodes)).Msg("zoro: Fetched episodes")
|
||||
|
||||
return episodes, nil
|
||||
}
|
||||
|
||||
func (z *Zoro) FindEpisodeServer(episodeInfo *hibikeonlinestream.EpisodeDetails, server string) (*hibikeonlinestream.EpisodeServer, error) {
|
||||
var source *hibikeonlinestream.EpisodeServer
|
||||
|
||||
if server == DefaultServer {
|
||||
server = VidcloudServer
|
||||
}
|
||||
|
||||
z.logger.Debug().Str("server", server).Str("episodeID", episodeInfo.ID).Msg("zoro: Fetching server sources")
|
||||
|
||||
episodeParts := strings.Split(episodeInfo.ID, "$")
|
||||
|
||||
if len(episodeParts) < 3 {
|
||||
return nil, errors.New("invalid episode id")
|
||||
}
|
||||
|
||||
episodeID := fmt.Sprintf("%s?ep=%s", episodeParts[0], episodeParts[2])
|
||||
subOrDub := hibikeonlinestream.Sub
|
||||
if episodeParts[len(episodeParts)-1] == "dub" {
|
||||
subOrDub = hibikeonlinestream.Dub
|
||||
}
|
||||
|
||||
// Get server
|
||||
|
||||
var serverId string
|
||||
|
||||
c := colly.NewCollector(
|
||||
colly.UserAgent(z.UserAgent),
|
||||
)
|
||||
c.OnRequest(func(r *colly.Request) {
|
||||
r.Headers.Set("X-Requested-With", "XMLHttpRequest")
|
||||
})
|
||||
|
||||
c.OnResponse(func(r *colly.Response) {
|
||||
var jsonResponse map[string]interface{}
|
||||
err := json.Unmarshal(r.Body, &jsonResponse)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(jsonResponse["html"].(string)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch server {
|
||||
case VidcloudServer:
|
||||
serverId = z.findServerId(doc, 4, subOrDub)
|
||||
case VidstreamingServer:
|
||||
serverId = z.findServerId(doc, 4, subOrDub)
|
||||
case StreamSBServer:
|
||||
serverId = z.findServerId(doc, 4, subOrDub)
|
||||
case StreamtapeServer:
|
||||
serverId = z.findServerId(doc, 4, subOrDub)
|
||||
}
|
||||
})
|
||||
|
||||
ajaxEpisodeUrl := fmt.Sprintf("%s/ajax/v2/episode/servers?episodeId=%s", z.BaseURL, strings.Split(episodeID, "?ep=")[1])
|
||||
if err := c.Visit(ajaxEpisodeUrl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if serverId == "" {
|
||||
return nil, ErrServerNotFound
|
||||
}
|
||||
|
||||
c2 := colly.NewCollector(
|
||||
colly.UserAgent(z.UserAgent),
|
||||
)
|
||||
c2.OnRequest(func(r *colly.Request) {
|
||||
r.Headers.Set("X-Requested-With", "XMLHttpRequest")
|
||||
})
|
||||
|
||||
c2.OnResponse(func(r *colly.Response) {
|
||||
var jsonResponse map[string]interface{}
|
||||
err := json.Unmarshal(r.Body, &jsonResponse)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, ok := jsonResponse["link"].(string); !ok {
|
||||
return
|
||||
}
|
||||
switch server {
|
||||
case VidcloudServer, VidstreamingServer:
|
||||
megacloud := onlinestream_sources.NewMegaCloud()
|
||||
sources, err := megacloud.Extract(jsonResponse["link"].(string))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
source = &hibikeonlinestream.EpisodeServer{
|
||||
Provider: ZoroProvider,
|
||||
Server: server,
|
||||
Headers: map[string]string{},
|
||||
VideoSources: sources,
|
||||
}
|
||||
case StreamtapeServer:
|
||||
streamtape := onlinestream_sources.NewStreamtape()
|
||||
sources, err := streamtape.Extract(jsonResponse["link"].(string))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
source = &hibikeonlinestream.EpisodeServer{
|
||||
Provider: ZoroProvider,
|
||||
Server: server,
|
||||
Headers: map[string]string{
|
||||
"Referer": jsonResponse["link"].(string),
|
||||
"User-Agent": z.UserAgent,
|
||||
},
|
||||
VideoSources: sources,
|
||||
}
|
||||
case StreamSBServer:
|
||||
streamsb := onlinestream_sources.NewStreamSB()
|
||||
sources, err := streamsb.Extract(jsonResponse["link"].(string))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
source = &hibikeonlinestream.EpisodeServer{
|
||||
Provider: ZoroProvider,
|
||||
Server: server,
|
||||
Headers: map[string]string{
|
||||
"Referer": jsonResponse["link"].(string),
|
||||
"watchsb": "streamsb",
|
||||
"User-Agent": z.UserAgent,
|
||||
},
|
||||
VideoSources: sources,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Get sources
|
||||
serverSourceUrl := fmt.Sprintf("%s/ajax/v2/episode/sources?id=%s", z.BaseURL, serverId)
|
||||
if err := c2.Visit(serverSourceUrl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if source == nil {
|
||||
z.logger.Warn().Str("server", server).Msg("zoro: No sources found")
|
||||
return nil, ErrSourceNotFound
|
||||
}
|
||||
|
||||
z.logger.Debug().Str("server", server).Int("videoSources", len(source.VideoSources)).Msg("zoro: Fetched server sources")
|
||||
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func (z *Zoro) findServerId(doc *goquery.Document, idx int, subOrDub hibikeonlinestream.SubOrDub) string {
|
||||
var serverId string
|
||||
doc.Find(fmt.Sprintf("div.ps_-block.ps_-block-sub.servers-%s > div.ps__-list > div", subOrDub)).Each(func(i int, s *goquery.Selection) {
|
||||
_serverId := s.AttrOr("data-server-id", "")
|
||||
if serverId == "" {
|
||||
if _serverId == strconv.Itoa(idx) {
|
||||
serverId = s.AttrOr("data-id", "")
|
||||
}
|
||||
}
|
||||
})
|
||||
return serverId
|
||||
}
|
||||
194
seanime-2.9.10/internal/onlinestream/providers/zoro_test.go
Normal file
194
seanime-2.9.10/internal/onlinestream/providers/zoro_test.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package onlinestream_providers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/stretchr/testify/assert"
|
||||
hibikeonlinestream "seanime/internal/extension/hibike/onlinestream"
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestZoro_Search(t *testing.T) {
|
||||
|
||||
logger := util.NewLogger()
|
||||
zoro := NewZoro(logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
dubbed bool
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
query: "One Piece",
|
||||
dubbed: false,
|
||||
},
|
||||
{
|
||||
name: "Dungeon Meshi",
|
||||
query: "Dungeon Meshi",
|
||||
dubbed: false,
|
||||
},
|
||||
{
|
||||
name: "Omoi, Omoware, Furi, Furare",
|
||||
query: "Omoi, Omoware, Furi, Furare",
|
||||
dubbed: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
results, err := zoro.Search(hibikeonlinestream.SearchOptions{
|
||||
Query: tt.query,
|
||||
Dub: tt.dubbed,
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, results)
|
||||
|
||||
for _, r := range results {
|
||||
assert.NotEmpty(t, r.ID, "ID is empty")
|
||||
assert.NotEmpty(t, r.Title, "Title is empty")
|
||||
assert.NotEmpty(t, r.URL, "URL is empty")
|
||||
}
|
||||
|
||||
spew.Dump(results)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestZoro_FetchEpisodes(t *testing.T) {
|
||||
logger := util.NewLogger()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
id: "one-piece-100",
|
||||
},
|
||||
{
|
||||
name: "The Apothecary Diaries",
|
||||
id: "the-apothecary-diaries-18578",
|
||||
},
|
||||
}
|
||||
|
||||
zoro := NewZoro(logger)
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
episodes, err := zoro.FindEpisodes(tt.id)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, episodes)
|
||||
|
||||
for _, e := range episodes {
|
||||
assert.NotEmpty(t, e.ID, "ID is empty")
|
||||
assert.NotEmpty(t, e.Number, "Number is empty")
|
||||
assert.NotEmpty(t, e.URL, "URL is empty")
|
||||
}
|
||||
|
||||
spew.Dump(episodes)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestZoro_FetchSources(t *testing.T) {
|
||||
logger := util.NewLogger()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
episode *hibikeonlinestream.EpisodeDetails
|
||||
server string
|
||||
}{
|
||||
{
|
||||
name: "One Piece",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "one-piece-100$episode$120118$both",
|
||||
Number: 1095,
|
||||
URL: "https://hianime.to/watch/one-piece-100?ep=120118",
|
||||
},
|
||||
server: VidcloudServer,
|
||||
},
|
||||
{
|
||||
name: "One Piece",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "one-piece-100$episode$120118$both",
|
||||
Number: 1095,
|
||||
URL: "https://hianime.to/watch/one-piece-100?ep=120118",
|
||||
},
|
||||
server: VidstreamingServer,
|
||||
},
|
||||
{
|
||||
name: "One Piece",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "one-piece-100$episode$120118$both",
|
||||
Number: 1095,
|
||||
URL: "https://hianime.to/watch/one-piece-100?ep=120118",
|
||||
},
|
||||
server: StreamtapeServer,
|
||||
},
|
||||
{
|
||||
name: "One Piece",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "one-piece-100$episode$120118$both",
|
||||
Number: 1095,
|
||||
URL: "https://hianime.to/watch/one-piece-100?ep=120118",
|
||||
},
|
||||
server: StreamSBServer,
|
||||
},
|
||||
{
|
||||
name: "Apothecary Diaries",
|
||||
episode: &hibikeonlinestream.EpisodeDetails{
|
||||
ID: "the-apothecary-diaries-18578$episode$122954$sub",
|
||||
Number: 24,
|
||||
URL: "https://hianime.to/watch/the-apothecary-diaries-18578?ep=122954",
|
||||
},
|
||||
server: StreamSBServer,
|
||||
},
|
||||
}
|
||||
zoro := NewZoro(logger)
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
serverSources, err := zoro.FindEpisodeServer(tt.episode, tt.server)
|
||||
if err != nil {
|
||||
if !errors.Is(err, ErrSourceNotFound) && !errors.Is(err, ErrServerNotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Skip(err.Error())
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, serverSources)
|
||||
|
||||
for _, s := range serverSources.VideoSources {
|
||||
assert.NotEmpty(t, s, "Source is empty")
|
||||
}
|
||||
|
||||
spew.Dump(serverSources)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user