Skip to content

Commit bbc4636

Browse files
Merge pull request #163 from Vittuu/fix/allanime-fast4speed-direct
fix: keep AllAnime fast4speed direct fallback
2 parents 54b5119 + ea06510 commit bbc4636

6 files changed

Lines changed: 143 additions & 11 deletions

File tree

docs/SCRAPING_INTEGRATION.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ type UnifiedScraper interface {
8484
5. **Error Handling with Fallbacks**
8585
6. **Metadata Extraction**
8686

87+
### AllAnime Provider Notes
88+
89+
The AllAnime implementation follows the current `ani-cli` provider behavior for
90+
source resolution:
91+
92+
- GraphQL requests use `https://api.allanime.day/api`.
93+
- Provider and playback requests use `https://allmanga.to` as the referer.
94+
- Encoded `/clock` source URLs are decoded with the ani-cli hex substitution
95+
table and normalized to `/clock.json`.
96+
- `tools.fast4speed.rsvp` entries are treated as direct playable URLs. Some
97+
shows expose this provider while the `/apivtwo/clock.json` providers return
98+
server errors, so the resolver keeps the direct URL as a fallback instead of
99+
requiring it to return a secondary JSON link list.
100+
101+
Regression coverage:
102+
103+
```bash
104+
go test ./internal/scraper -run TestProcessSourceURLsConcurrentFallsBackToFast4SpeedDirectSource -count=1
105+
```
106+
87107

88108

89109
### Command Equivalents

internal/api/allanime_enhanced.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ func GetAllAnimeEpisodeURLDirect(anime *models.Anime, episodeNumber string, qual
106106
if err != nil {
107107
return "", nil, fmt.Errorf("failed to get episode URL: %w", err)
108108
}
109+
if referer, ok := metadata["referer"]; ok && referer != "" {
110+
util.SetGlobalReferer(referer)
111+
}
109112

110113
// Add additional metadata
111114
metadata["navigator"] = "allanime"

internal/player/playvideo.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@ var ErrBackToDownloadOptions = errors.New("back to download options")
3636
// dubSubTagRe strips parenthesized dub/sub tags from anime names for display
3737
var dubSubTagRe = regexp.MustCompile(`\s*\((?i:Dublado|Legendado|SUB|DUB|Subbed|Dubbed)\)\s*`)
3838

39+
const defaultHLSReferer = "https://streameeeeee.site/"
40+
41+
func appendPlaybackRefererArgs(mpvArgs []string, videoURL string, isHLSStream bool) ([]string, string) {
42+
lowerURL := strings.ToLower(strings.TrimSpace(videoURL))
43+
if !strings.HasPrefix(lowerURL, "http://") && !strings.HasPrefix(lowerURL, "https://") {
44+
return mpvArgs, ""
45+
}
46+
47+
referer := util.GetGlobalReferer()
48+
if referer == "" && isHLSStream {
49+
referer = defaultHLSReferer
50+
}
51+
if referer == "" {
52+
return mpvArgs, ""
53+
}
54+
55+
return append(mpvArgs, fmt.Sprintf("--http-header-fields=Referer: %s", referer)), referer
56+
}
57+
3958
// waitForVideoReady waits for the HLS video to be ready for playback
4059
// Returns true if video is ready, false if timeout
4160
func waitForVideoReady(socketPath string) bool {
@@ -350,17 +369,20 @@ func playVideo(
350369
is9Anime = updater.GetAnime().Source == "9Anime"
351370
}
352371

372+
mpvArgs, playbackReferer := appendPlaybackRefererArgs(mpvArgs, videoURL, isHLSStream)
373+
if playbackReferer != "" {
374+
if isHLSStream {
375+
util.Debugf("HLS stream detected - Referer: %s", playbackReferer)
376+
} else {
377+
util.Debugf("HTTP stream detected - Referer: %s", playbackReferer)
378+
}
379+
}
380+
353381
if isHLSStream {
354-
// Use the stored global referer if available (set by source-specific stream resolvers),
355-
// otherwise fall back to the default referer for legacy sources
356-
referer := util.GetGlobalReferer()
382+
referer := playbackReferer
357383
if referer == "" {
358-
referer = "https://streameeeeee.site/"
384+
referer = defaultHLSReferer
359385
}
360-
mpvArgs = append(mpvArgs,
361-
fmt.Sprintf("--http-header-fields=Referer: %s", referer),
362-
)
363-
util.Debugf("HLS stream detected - Referer: %s", referer)
364386

365387
// For 9Anime (and other Cloudflare-protected CDNs), route playback through
366388
// yt-dlp with Chrome TLS impersonation to bypass Cloudflare fingerprint checks.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package player
2+
3+
import (
4+
"testing"
5+
6+
"github.com/alvarorichard/Goanime/internal/util"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestAppendPlaybackRefererArgsAddsGlobalRefererForDirectHTTP(t *testing.T) {
11+
restore := snapshotGlobalReferer()
12+
defer restore()
13+
util.SetGlobalReferer("https://allmanga.to")
14+
15+
args, referer := appendPlaybackRefererArgs(nil, "https://tools.fast4speed.rsvp//media9/videos/id/sub/4?v=22", false)
16+
17+
assert.Equal(t, "https://allmanga.to", referer)
18+
assert.Contains(t, args, "--http-header-fields=Referer: https://allmanga.to")
19+
}
20+
21+
func TestAppendPlaybackRefererArgsKeepsHLSFallbackReferer(t *testing.T) {
22+
restore := snapshotGlobalReferer()
23+
defer restore()
24+
util.ClearGlobalReferer()
25+
26+
args, referer := appendPlaybackRefererArgs(nil, "https://cdn.example.com/master.m3u8", true)
27+
28+
assert.Equal(t, defaultHLSReferer, referer)
29+
assert.Contains(t, args, "--http-header-fields=Referer: "+defaultHLSReferer)
30+
}
31+
32+
func TestAppendPlaybackRefererArgsSkipsLocalFiles(t *testing.T) {
33+
restore := snapshotGlobalReferer()
34+
defer restore()
35+
util.SetGlobalReferer("https://allmanga.to")
36+
37+
args, referer := appendPlaybackRefererArgs([]string{"--cache=yes"}, "/tmp/episode.mp4", false)
38+
39+
assert.Empty(t, referer)
40+
assert.Equal(t, []string{"--cache=yes"}, args)
41+
}

internal/scraper/allanime.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,16 @@ func (c *AllAnimeClient) processSourceURLsConcurrent(sourceURLs []string, qualit
676676

677677
for i, sourceURL := range sourceURLs {
678678
go func(idx int, url string) {
679+
if c.isDirectProviderURL(url) {
680+
results <- result{
681+
index: idx,
682+
sourceURL: url,
683+
links: map[string]string{
684+
"direct": url,
685+
},
686+
}
687+
return
688+
}
679689

680690
links, err := c.getLinks(url)
681691
if err != nil {
@@ -778,6 +788,10 @@ func (c *AllAnimeClient) getPriorityScore(url string) int {
778788
return 0
779789
}
780790

791+
func (c *AllAnimeClient) isDirectProviderURL(sourceURL string) bool {
792+
return strings.Contains(sourceURL, "tools.fast4speed.rsvp")
793+
}
794+
781795
// extractSourceURLs extracts source URLs from the API response
782796
func (c *AllAnimeClient) extractSourceURLs(response string) []string {
783797
// Check if the response contains a "tobeparsed" blob (AES-encrypted source URLs).
@@ -905,8 +919,8 @@ func (c *AllAnimeClient) getLinks(sourceURL string) (map[string]string, error) {
905919
return nil, fmt.Errorf("failed to create request: %w", err)
906920
}
907921

908-
// Use the same headers as Curd for better compatibility
909-
req.Header.Set("Referer", "https://allanime.to")
922+
// Match ani-cli: AllAnime's current providers expect the allmanga referer.
923+
req.Header.Set("Referer", c.referer)
910924
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0")
911925

912926
resp, err := c.client.Do(req) // #nosec G704
@@ -1079,6 +1093,13 @@ func (c *AllAnimeClient) selectQuality(links map[string]string, requestedQuality
10791093
return url, metadata
10801094
}
10811095

1096+
if url, exists := links["direct"]; exists {
1097+
metadata["quality"] = "direct"
1098+
metadata["type"] = "direct"
1099+
metadata["referer"] = c.referer
1100+
return url, metadata
1101+
}
1102+
10821103
// Return first priority link available
10831104
for quality, url := range links {
10841105
if before, ok := strings.CutSuffix(quality, "_priority"); ok {

internal/scraper/allanime_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1369,7 +1369,7 @@ func TestGetLinksVerifiesRefererHeader(t *testing.T) {
13691369
defer server.Close()
13701370

13711371
_, _ = newTestClient(server.URL).getLinks(server.URL)
1372-
assert.Equal(t, "https://allanime.to", capturedReferer)
1372+
assert.Equal(t, AllAnimeReferer, capturedReferer)
13731373
}
13741374

13751375
// ---------------------------------------------------------------------------
@@ -1518,6 +1518,31 @@ func TestProcessSourceURLsConcurrentPartialFailure(t *testing.T) {
15181518
assert.Less(t, time.Since(startedAt), 2*time.Second, "successful fallback should not wait for the global timeout")
15191519
}
15201520

1521+
func TestProcessSourceURLsConcurrentFallsBackToFast4SpeedDirectSource(t *testing.T) {
1522+
t.Parallel()
1523+
1524+
failServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
1525+
w.WriteHeader(http.StatusInternalServerError)
1526+
}))
1527+
defer failServer.Close()
1528+
1529+
client := newTestClient(failServer.URL)
1530+
directURL := "https://tools.fast4speed.rsvp//media9/videos/gHQe2eBBh57QdC9hZ/sub/1"
1531+
1532+
url, meta, err := client.processSourceURLsConcurrent(
1533+
[]string{failServer.URL + "/clock.json", directURL},
1534+
"worst", "gHQe2eBBh57QdC9hZ", "1",
1535+
)
1536+
1537+
require.NoError(t, err)
1538+
assert.Equal(t, directURL, url)
1539+
assert.Equal(t, "direct", meta["quality"])
1540+
assert.Equal(t, "direct", meta["type"])
1541+
assert.Equal(t, AllAnimeReferer, meta["referer"])
1542+
assert.Equal(t, "gHQe2eBBh57QdC9hZ", meta["anime_id"])
1543+
assert.Equal(t, "1", meta["episode"])
1544+
}
1545+
15211546
func TestProcessSourceURLsConcurrentHighPriorityWins(t *testing.T) {
15221547
t.Parallel()
15231548

0 commit comments

Comments
 (0)