Skip to content

Commit 3df11da

Browse files
committed
fix(allanime): keep fast4speed direct source fallback
1 parent 80ceaa5 commit 3df11da

4 files changed

Lines changed: 72 additions & 3 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/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)