feat: use real metadata for fallback episodes

This commit is contained in:
2026-05-02 17:21:56 +02:00
committed by Mikkel Elvers
parent dd301384c5
commit 70dbc1ba85
3 changed files with 103 additions and 16 deletions

View File

@@ -108,9 +108,9 @@ type searchResult struct {
} }
type AvailableEpisodes struct { type AvailableEpisodes struct {
Sub int Sub []string
Dub int Dub []string
Raw int Raw []string
} }
type allAnimeClient struct { type allAnimeClient struct {
@@ -497,6 +497,7 @@ func (c *allAnimeClient) GetAvailableEpisodes(ctx context.Context, showID string
graphqlQuery := `query($showId: String!) { graphqlQuery := `query($showId: String!) {
show(_id: $showId) { show(_id: $showId) {
availableEpisodesDetail availableEpisodesDetail
lastEpisodeInfo
} }
}` }`
@@ -522,18 +523,69 @@ func (c *allAnimeClient) GetAvailableEpisodes(ctx context.Context, showID string
var count AvailableEpisodes var count AvailableEpisodes
if sub, ok := detail["sub"].([]any); ok { if sub, ok := detail["sub"].([]any); ok {
count.Sub = len(sub) for _, s := range sub {
if str, ok := s.(string); ok {
count.Sub = append(count.Sub, str)
}
}
} }
if dub, ok := detail["dub"].([]any); ok { if dub, ok := detail["dub"].([]any); ok {
count.Dub = len(dub) for _, s := range dub {
if str, ok := s.(string); ok {
count.Dub = append(count.Dub, str)
}
}
} }
if raw, ok := detail["raw"].([]any); ok { if raw, ok := detail["raw"].([]any); ok {
count.Raw = len(raw) for _, s := range raw {
if str, ok := s.(string); ok {
count.Raw = append(count.Raw, str)
}
}
} }
return count, nil return count, nil
} }
func (c *allAnimeClient) GetEpisodeMetadata(ctx context.Context, showID string, episode string) (map[string]any, error) {
// First try to get it from the standard episode query which often contains metadata
// but we'll use a specific query that includes images and titles if available
graphqlQuery := `query($showId: String!, $episodeString: String!, $translationType: VaildTranslationTypeEnumType!) {
episode(showId: $showId, episodeString: $episodeString, translationType: $translationType) {
notes
episodeInfo {
notes
thumbnails
vidinfors {
vidPath
}
}
}
}`
// We'll try SUB by default for metadata
result, err := c.graphqlRequest(ctx, graphqlQuery, map[string]any{
"showId": showID,
"episodeString": episode,
"translationType": "sub",
})
if err != nil {
return nil, err
}
data, ok := result["data"].(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid response")
}
ep, ok := data["episode"].(map[string]any)
if !ok {
return nil, fmt.Errorf("episode not found")
}
return ep, nil
}
func buildStreamSource(url, sourceType, provider string) StreamSource { func buildStreamSource(url, sourceType, provider string) StreamSource {
return StreamSource{ return StreamSource{
URL: url, URL: url,

View File

@@ -118,17 +118,44 @@ func (h *Handler) HandleWatchPage(w http.ResponseWriter, r *http.Request) {
} }
if maxCount > len(episodes.Data) { if maxCount > len(episodes.Data) {
// Add dummy episodes for the ones Jikan is missing // Fetch metadata for the missing episodes
start := len(episodes.Data) + 1 start := len(episodes.Data) + 1
for i := start; i <= maxCount; i++ { for i := start; i <= maxCount; i++ {
dummy := jikan.Episode{ epStr := strconv.Itoa(i)
meta, err := h.svc.GetEpisodeMetadata(r.Context(), id, epStr)
title := fmt.Sprintf("Episode %d", i)
imgURL := ""
if err == nil && meta != nil {
if info, ok := meta["episodeInfo"].(map[string]any); ok {
if thumbs, ok := info["thumbnails"].([]any); ok && len(thumbs) > 0 {
if firstThumb, ok := thumbs[0].(string); ok {
imgURL = firstThumb
}
}
}
if notes, ok := meta["notes"].(string); ok && notes != "" {
title = notes
}
}
if imgURL == "" {
// Last resort fallback
tmpEp := jikan.Episode{MalID: i}
imgURL = tmpEp.GetFallbackImage(id)
}
episodes.Data = append(episodes.Data, jikan.Episode{
MalID: i, MalID: i,
Episode: fmt.Sprintf("Episode %d", i), Episode: fmt.Sprintf("Episode %d", i),
Title: fmt.Sprintf("Episode %d", i), Title: title,
Images: &jikan.EpisodeImages{}, Images: &jikan.EpisodeImages{
} Jpg: struct {
dummy.Images.Jpg.ImageURL = dummy.GetFallbackImage(id) ImageURL string `json:"image_url"`
episodes.Data = append(episodes.Data, dummy) }{ImageURL: imgURL},
},
})
} }
} }
} }

View File

@@ -143,9 +143,9 @@ func (s *Service) BuildWatchPageData(ctx context.Context, malID int, titleCandid
fallbackEpisodes := make(map[string]int) fallbackEpisodes := make(map[string]int)
if counts, err := s.allAnimeClient.GetAvailableEpisodes(ctx, showID); err == nil { if counts, err := s.allAnimeClient.GetAvailableEpisodes(ctx, showID); err == nil {
fallbackEpisodes["sub"] = counts.Sub fallbackEpisodes["sub"] = len(counts.Sub)
fallbackEpisodes["dub"] = counts.Dub fallbackEpisodes["dub"] = len(counts.Dub)
fallbackEpisodes["raw"] = counts.Raw fallbackEpisodes["raw"] = len(counts.Raw)
} }
watchTitle := strings.TrimSpace(resolvedTitle) watchTitle := strings.TrimSpace(resolvedTitle)
@@ -360,6 +360,14 @@ func clonePlaybackBaseData(data playbackBaseData) playbackBaseData {
} }
} }
func (s *Service) GetEpisodeMetadata(ctx context.Context, malID int, episode string) (map[string]any, error) {
showID, _, err := s.resolveShowCached(ctx, malID, nil)
if err != nil {
return nil, err
}
return s.allAnimeClient.GetEpisodeMetadata(ctx, showID, episode)
}
func cloneSlice[T any](items []T) []T { func cloneSlice[T any](items []T) []T {
if items == nil { if items == nil {
return []T{} return []T{}