feat: add allanime seasonal queries and provider show model
This commit is contained in:
133
integrations/playback/allanime/sequels.go
Normal file
133
integrations/playback/allanime/sequels.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package allanime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ProviderShow struct {
|
||||
ID string
|
||||
Name string
|
||||
EnglishName string
|
||||
MalID int
|
||||
Status string
|
||||
Thumbnail string
|
||||
Type string
|
||||
Year int
|
||||
EpisodeCount int
|
||||
SubEpisodes []int
|
||||
DubEpisodes []int
|
||||
}
|
||||
|
||||
func (c *AllAnimeProvider) DirectSequels(ctx context.Context, show ProviderShow) ([]string, error) {
|
||||
query := `query($showId: String!) { show(_id: $showId) { relatedShows } }`
|
||||
result, err := c.graphqlRequest(ctx, query, map[string]any{"showId": show.ID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, _ := result["data"].(map[string]any)
|
||||
rawShow, _ := data["show"].(map[string]any)
|
||||
relations, _ := rawShow["relatedShows"].([]any)
|
||||
ids := make([]string, 0, len(relations))
|
||||
for _, raw := range relations {
|
||||
relation, _ := raw.(map[string]any)
|
||||
id, _ := relation["showId"].(string)
|
||||
if relation["relation"] == "sequel" && id != "" {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (c *AllAnimeProvider) GetProviderShow(ctx context.Context, showID string) (ProviderShow, error) {
|
||||
query := `query($showId: String!) { show(_id: $showId) { _id name englishName malId status thumbnail type season episodeCount availableEpisodesDetail } }`
|
||||
result, err := c.graphqlRequest(ctx, query, map[string]any{"showId": showID})
|
||||
if err != nil {
|
||||
return ProviderShow{}, err
|
||||
}
|
||||
data, _ := result["data"].(map[string]any)
|
||||
raw, _ := data["show"].(map[string]any)
|
||||
if raw == nil {
|
||||
return ProviderShow{}, fmt.Errorf("allanime: show %s not found", showID)
|
||||
}
|
||||
return providerShowFrom(raw), nil
|
||||
}
|
||||
|
||||
func (c *AllAnimeProvider) SeasonalShows(ctx context.Context, season string, year int) ([]ProviderShow, error) {
|
||||
const pageSize = 40
|
||||
query := `query($search: SearchInput, $page: Int) { shows(search: $search, limit: 40, page: $page, countryOrigin: ALL) { edges { _id name englishName malId status thumbnail type season episodeCount availableEpisodesDetail } } }`
|
||||
if season == "" {
|
||||
return nil, errors.New("allanime: season is required")
|
||||
}
|
||||
search := map[string]any{
|
||||
"allowAdult": false,
|
||||
"allowUnknown": false,
|
||||
"season": strings.ToUpper(season[:1]) + strings.ToLower(season[1:]),
|
||||
"year": year,
|
||||
"types": []string{"TV"},
|
||||
"includeTypes": true,
|
||||
}
|
||||
out := make([]ProviderShow, 0)
|
||||
seen := make(map[int]bool)
|
||||
for page := 1; page <= 20; page++ {
|
||||
result, err := c.graphqlRequest(ctx, query, map[string]any{"search": search, "page": page})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, _ := result["data"].(map[string]any)
|
||||
shows, _ := data["shows"].(map[string]any)
|
||||
edges, _ := shows["edges"].([]any)
|
||||
for _, edge := range edges {
|
||||
raw, _ := edge.(map[string]any)
|
||||
show := providerShowFrom(raw)
|
||||
if show.MalID <= 0 || seen[show.MalID] || show.Type != "TV" || max(len(show.SubEpisodes), len(show.DubEpisodes)) == 0 {
|
||||
continue
|
||||
}
|
||||
seen[show.MalID] = true
|
||||
out = append(out, show)
|
||||
}
|
||||
if len(edges) < pageSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func providerShowFrom(raw map[string]any) ProviderShow {
|
||||
detail, _ := raw["availableEpisodesDetail"].(map[string]any)
|
||||
season, _ := raw["season"].(map[string]any)
|
||||
return ProviderShow{
|
||||
ID: stringValue(raw["_id"]),
|
||||
Name: stringValue(raw["name"]),
|
||||
EnglishName: stringValue(raw["englishName"]),
|
||||
MalID: intValue(raw["malId"]),
|
||||
Status: stringValue(raw["status"]),
|
||||
Thumbnail: stringValue(raw["thumbnail"]),
|
||||
Type: stringValue(raw["type"]),
|
||||
Year: numericInt(season["year"]),
|
||||
EpisodeCount: intValue(raw["episodeCount"]),
|
||||
SubEpisodes: episodeNums(stringsFrom(detail["sub"])),
|
||||
DubEpisodes: episodeNums(stringsFrom(detail["dub"])),
|
||||
}
|
||||
}
|
||||
|
||||
func stringValue(value any) string {
|
||||
valueString, _ := value.(string)
|
||||
return valueString
|
||||
}
|
||||
|
||||
func intValue(value any) int {
|
||||
n, _ := strconv.Atoi(stringValue(value))
|
||||
return n
|
||||
}
|
||||
|
||||
func numericInt(value any) int {
|
||||
if n := intValue(value); n > 0 {
|
||||
return n
|
||||
}
|
||||
n, _ := value.(float64)
|
||||
return int(n)
|
||||
}
|
||||
90
integrations/playback/allanime/sequels_test.go
Normal file
90
integrations/playback/allanime/sequels_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package allanime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDirectSequelsReturnsOnlyPlayableSequels(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"data":{"show":{"relatedShows":[
|
||||
{"relation":"sequel","showId":"season-2"},
|
||||
{"relation":"side story","showId":"ova"}
|
||||
]}}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
provider := NewAllAnimeProvider()
|
||||
provider.httpClient = server.Client()
|
||||
provider.baseURL = server.URL
|
||||
providerShow := ProviderShow{ID: "season-1"}
|
||||
|
||||
got, err := provider.DirectSequels(context.Background(), providerShow)
|
||||
if err != nil {
|
||||
t.Fatalf("DirectSequels: %v", err)
|
||||
}
|
||||
if len(got) != 1 || got[0] != "season-2" {
|
||||
t.Fatalf("DirectSequels = %v, want [season-2]", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProviderShowKeepsOnlyPositiveIntegerEpisodes(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"data":{"show":{"_id":"season-2","name":"Example Season 2","englishName":"Example Season 2","malId":"42","status":"Finished","episodeCount":"2","availableEpisodesDetail":{"sub":["2","1","0","1.5"],"dub":["1"],"raw":[]}}}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
provider := NewAllAnimeProvider()
|
||||
provider.httpClient = server.Client()
|
||||
provider.baseURL = server.URL
|
||||
|
||||
got, err := provider.GetProviderShow(context.Background(), "season-2")
|
||||
if err != nil {
|
||||
t.Fatalf("GetProviderShow: %v", err)
|
||||
}
|
||||
if got.MalID != 42 || len(got.SubEpisodes) != 2 || len(got.DubEpisodes) != 1 {
|
||||
t.Fatalf("GetProviderShow = %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeasonalShowsReturnsPlayableTVAnime(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var request struct {
|
||||
Variables struct {
|
||||
Page int `json:"page"`
|
||||
} `json:"variables"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatalf("decode request: %v", err)
|
||||
}
|
||||
edges := make([]map[string]any, 0)
|
||||
switch request.Variables.Page {
|
||||
case 1:
|
||||
for i := 0; i < 40; i++ {
|
||||
edges = append(edges, map[string]any{"_id": "empty", "malId": "12", "type": "TV", "availableEpisodesDetail": map[string]any{"sub": []string{}, "dub": []string{}}})
|
||||
}
|
||||
case 2:
|
||||
edges = append(edges, map[string]any{"_id": "tv", "name": "Summer Show", "malId": "10", "type": "TV", "season": map[string]any{"quarter": "Summer", "year": 2026}, "availableEpisodesDetail": map[string]any{"sub": []string{"1"}, "dub": []string{}}})
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"shows": map[string]any{"edges": edges}}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
provider := NewAllAnimeProvider()
|
||||
provider.httpClient = server.Client()
|
||||
provider.baseURL = server.URL
|
||||
|
||||
got, err := provider.SeasonalShows(context.Background(), "summer", 2026)
|
||||
if err != nil {
|
||||
t.Fatalf("SeasonalShows: %v", err)
|
||||
}
|
||||
if len(got) != 1 || got[0].MalID != 10 {
|
||||
t.Fatalf("SeasonalShows = %+v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user