refactor: consolidate http and utls clients, switch to post with aa lease
This commit is contained in:
@@ -150,17 +150,17 @@ func TestGraphqlRequestWithHash_Plain(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
provider := &AllAnimeProvider{
|
provider := &AllAnimeProvider{
|
||||||
utlsClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
if req.Method != http.MethodGet {
|
if req.Method != http.MethodPost {
|
||||||
t.Errorf("method = %q, want GET", req.Method)
|
t.Errorf("method = %q, want POST", req.Method)
|
||||||
}
|
|
||||||
if !strings.Contains(req.URL.String(), episodeQueryHash) {
|
|
||||||
t.Errorf("url should contain hash, got %q", req.URL.String())
|
|
||||||
}
|
}
|
||||||
if req.Header.Get("Referer") != allAnimeReferer {
|
if req.Header.Get("Referer") != allAnimeReferer {
|
||||||
t.Errorf("Referer = %q", req.Header.Get("Referer"))
|
t.Errorf("Referer = %q", req.Header.Get("Referer"))
|
||||||
}
|
}
|
||||||
|
if req.Header.Get("x-build-id") != "9" {
|
||||||
|
t.Errorf("x-build-id = %q", req.Header.Get("x-build-id"))
|
||||||
|
}
|
||||||
return mockStringResponse(http.StatusOK, `{"data":{"episode":{"sourceUrls":[{"sourceUrl":"https://example.test/v.mp4","sourceName":"default"}]}}}`), nil
|
return mockStringResponse(http.StatusOK, `{"data":{"episode":{"sourceUrls":[{"sourceUrl":"https://example.test/v.mp4","sourceName":"default"}]}}}`), nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -190,7 +190,7 @@ func TestGraphqlRequestWithHash_Encrypted(t *testing.T) {
|
|||||||
encryptedPayload := buildEncryptedTobeparsedPayload(t, []byte(`{"sourceUrls":[{"sourceUrl":"https://e.test/v.mp4","sourceName":"default"}]}`))
|
encryptedPayload := buildEncryptedTobeparsedPayload(t, []byte(`{"sourceUrls":[{"sourceUrl":"https://e.test/v.mp4","sourceName":"default"}]}`))
|
||||||
|
|
||||||
provider := &AllAnimeProvider{
|
provider := &AllAnimeProvider{
|
||||||
utlsClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encryptedPayload+`"}}`), nil
|
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encryptedPayload+`"}}`), nil
|
||||||
}),
|
}),
|
||||||
@@ -205,7 +205,11 @@ func TestGraphqlRequestWithHash_Encrypted(t *testing.T) {
|
|||||||
t.Fatalf("graphqlRequestWithHash: %v", err)
|
t.Fatalf("graphqlRequestWithHash: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sources := nestedSlice(result, "episode", "sourceUrls")
|
ep, ok := result["episode"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("result missing episode key")
|
||||||
|
}
|
||||||
|
sources := nestedSlice(ep, "sourceUrls")
|
||||||
if len(sources) != 1 {
|
if len(sources) != 1 {
|
||||||
t.Fatalf("got %d sources, want 1", len(sources))
|
t.Fatalf("got %d sources, want 1", len(sources))
|
||||||
}
|
}
|
||||||
@@ -215,7 +219,7 @@ func TestGraphqlRequestWithHash_Non200(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
provider := &AllAnimeProvider{
|
provider := &AllAnimeProvider{
|
||||||
utlsClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
return mockStringResponse(http.StatusNotFound, `not found`), nil
|
return mockStringResponse(http.StatusNotFound, `not found`), nil
|
||||||
}),
|
}),
|
||||||
@@ -235,7 +239,7 @@ func TestGraphqlRequestWithHash_EmptyData(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
provider := &AllAnimeProvider{
|
provider := &AllAnimeProvider{
|
||||||
utlsClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
return mockStringResponse(http.StatusOK, `{"data":{}}`), nil
|
return mockStringResponse(http.StatusOK, `{"data":{}}`), nil
|
||||||
}),
|
}),
|
||||||
@@ -258,12 +262,6 @@ func TestGetEpisodeSources_EncryptedHash(t *testing.T) {
|
|||||||
|
|
||||||
provider := &AllAnimeProvider{
|
provider := &AllAnimeProvider{
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
||||||
t.Error("fallback POST should not be called")
|
|
||||||
return nil, nil
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
utlsClient: &http.Client{
|
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encrypted+`"}}`), nil
|
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encrypted+`"}}`), nil
|
||||||
}),
|
}),
|
||||||
@@ -292,15 +290,15 @@ func TestGetEpisodeSources_FallbackPost(t *testing.T) {
|
|||||||
provider := &AllAnimeProvider{
|
provider := &AllAnimeProvider{
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
body, _ := io.ReadAll(req.Body)
|
||||||
|
req.Body.Close()
|
||||||
|
if strings.Contains(string(body), `"extensions":`) {
|
||||||
|
return mockStringResponse(http.StatusOK, `{"data":{}}`), nil
|
||||||
|
}
|
||||||
fallbackCalled = true
|
fallbackCalled = true
|
||||||
return mockStringResponse(http.StatusOK, sourceResponse), nil
|
return mockStringResponse(http.StatusOK, sourceResponse), nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
utlsClient: &http.Client{
|
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
||||||
return mockStringResponse(http.StatusNotFound, `not found`), nil
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
extractor: newProviderExtractor(),
|
extractor: newProviderExtractor(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,11 +323,6 @@ func TestGetEpisodeSources_BothFail(t *testing.T) {
|
|||||||
return mockStringResponse(http.StatusNotFound, `not found`), nil
|
return mockStringResponse(http.StatusNotFound, `not found`), nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
utlsClient: &http.Client{
|
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
||||||
return mockStringResponse(http.StatusNotFound, `not found`), nil
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
extractor: newProviderExtractor(),
|
extractor: newProviderExtractor(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,11 +457,11 @@ func TestGetStreams_FullSuccess(t *testing.T) {
|
|||||||
provider := &AllAnimeProvider{
|
provider := &AllAnimeProvider{
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
return mockStringResponse(http.StatusOK, searchBody), nil
|
body, _ := io.ReadAll(req.Body)
|
||||||
}),
|
req.Body.Close()
|
||||||
},
|
if strings.Contains(string(body), `"search":`) {
|
||||||
utlsClient: &http.Client{
|
return mockStringResponse(http.StatusOK, searchBody), nil
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
}
|
||||||
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encrypted+`"}}`), nil
|
return mockStringResponse(http.StatusOK, `{"data":{"tobeparsed":"`+encrypted+`"}}`), nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -499,12 +492,6 @@ func TestGetStreams_ShowNotFound(t *testing.T) {
|
|||||||
return mockStringResponse(http.StatusOK, `{"data":{"shows":{"edges":[]}}}`), nil
|
return mockStringResponse(http.StatusOK, `{"data":{"shows":{"edges":[]}}}`), nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
utlsClient: &http.Client{
|
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
||||||
t.Error("should not call episode sources when show not found")
|
|
||||||
return nil, nil
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
extractor: newProviderExtractor(),
|
extractor: newProviderExtractor(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,11 +507,11 @@ func TestGetStreams_NoSources(t *testing.T) {
|
|||||||
provider := &AllAnimeProvider{
|
provider := &AllAnimeProvider{
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
return mockStringResponse(http.StatusOK, `{"data":{"shows":{"edges":[{"_id":"showX","malId":"1","name":"Anime"}]}}}`), nil
|
body, _ := io.ReadAll(req.Body)
|
||||||
}),
|
req.Body.Close()
|
||||||
},
|
if strings.Contains(string(body), `"search":`) {
|
||||||
utlsClient: &http.Client{
|
return mockStringResponse(http.StatusOK, `{"data":{"shows":{"edges":[{"_id":"showX","malId":"1","name":"Anime"}]}}}`), nil
|
||||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
}
|
||||||
return mockStringResponse(http.StatusNotFound, `not found`), nil
|
return mockStringResponse(http.StatusNotFound, `not found`), nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package allanime
|
package allanime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -172,11 +173,15 @@ func isHTTPURL(value string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildStreamSource(url, sourceType, provider string) StreamSource {
|
func buildStreamSource(url, sourceType, provider string) StreamSource {
|
||||||
|
referer := allAnimeReferer
|
||||||
|
if strings.Contains(strings.ToLower(provider), "yt-mp4") {
|
||||||
|
referer = allAnimeSiteURL
|
||||||
|
}
|
||||||
return StreamSource{
|
return StreamSource{
|
||||||
URL: url,
|
URL: url,
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
Type: sourceType,
|
Type: sourceType,
|
||||||
Referer: allAnimeReferer,
|
Referer: referer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,28 +245,24 @@ func stringMapValue(item map[string]any, key string) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *AllAnimeProvider) graphqlRequestWithHash(ctx context.Context, showID, episode, mode string) (map[string]any, error) {
|
func (c *AllAnimeProvider) graphqlRequestWithHash(ctx context.Context, showID, episode, mode string) (map[string]any, error) {
|
||||||
req, err := newHashRequest(ctx, showID, episode, mode)
|
req, err := c.newHashRequest(ctx, showID, episode, mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create GET request: %w", err)
|
return nil, fmt.Errorf("create POST request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", defaultUserAgent)
|
req.Header.Set("User-Agent", defaultUserAgent)
|
||||||
req.Header.Set("Accept", "*/*")
|
|
||||||
req.Header.Set("Accept-Language", "en-US,en;q=0.5")
|
|
||||||
req.Header.Set("Accept-Encoding", "identity")
|
|
||||||
req.Header.Set("Referer", allAnimeReferer)
|
req.Header.Set("Referer", allAnimeReferer)
|
||||||
req.Header.Set("Origin", allAnimeOrigin)
|
req.Header.Set("Origin", allAnimeOrigin)
|
||||||
req.Header.Set("Sec-Fetch-Dest", "empty")
|
req.Header.Set("x-build-id", "9")
|
||||||
req.Header.Set("Sec-Fetch-Mode", "cors")
|
|
||||||
req.Header.Set("Sec-Fetch-Site", "cross-site")
|
|
||||||
|
|
||||||
statusCode, respBody, err := executeAndReadResponse(c.utlsClient, req, "execute GET request", "read response")
|
statusCode, respBody, err := executeAndReadResponse(c.httpClient, req, "execute POST request (aaReq)", "read response")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusCode != http.StatusOK {
|
if statusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("GET status %d: %s", statusCode, string(respBody))
|
return nil, fmt.Errorf("POST status %d: %s", statusCode, string(respBody))
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed, err := parseGraphQLResponse(respBody, "decode response")
|
parsed, err := parseGraphQLResponse(respBody, "decode response")
|
||||||
@@ -289,15 +290,33 @@ func (c *AllAnimeProvider) graphqlRequestWithHash(ctx context.Context, showID, e
|
|||||||
return nil, errors.New("no usable data in response")
|
return nil, errors.New("no usable data in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHashRequest(ctx context.Context, showID, episode, mode string) (*http.Request, error) {
|
func (c *AllAnimeProvider) newHashRequest(ctx context.Context, showID, episode, mode string) (*http.Request, error) {
|
||||||
varsJSON := fmt.Sprintf(`{"showId":"%s","translationType":"%s","episodeString":"%s"}`, showID, strings.ToLower(mode), episode)
|
aaReq, err := makeAALease(episodeQueryHash)
|
||||||
extJSON := fmt.Sprintf(`{"persistedQuery":{"version":1,"sha256Hash":"%s"}}`, episodeQueryHash)
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create aa lease: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
params := url.Values{}
|
payload := map[string]any{
|
||||||
params.Set("variables", varsJSON)
|
"variables": map[string]any{
|
||||||
params.Set("extensions", extJSON)
|
"showId": showID,
|
||||||
|
"translationType": strings.ToLower(mode),
|
||||||
|
"episodeString": episode,
|
||||||
|
},
|
||||||
|
"extensions": map[string]any{
|
||||||
|
"persistedQuery": map[string]any{
|
||||||
|
"version": 1,
|
||||||
|
"sha256Hash": episodeQueryHash,
|
||||||
|
},
|
||||||
|
"aaReq": aaReq,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
return http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api?%s", allAnimeBaseURL, params.Encode()), nil)
|
body, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshal payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api", allAnimeBaseURL), bytes.NewReader(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectStreamType(sourceURL string) string {
|
func detectStreamType(sourceURL string) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user