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 {
Sub int
Dub int
Raw int
Sub []string
Dub []string
Raw []string
}
type allAnimeClient struct {
@@ -497,6 +497,7 @@ func (c *allAnimeClient) GetAvailableEpisodes(ctx context.Context, showID string
graphqlQuery := `query($showId: String!) {
show(_id: $showId) {
availableEpisodesDetail
lastEpisodeInfo
}
}`
@@ -522,18 +523,69 @@ func (c *allAnimeClient) GetAvailableEpisodes(ctx context.Context, showID string
var count AvailableEpisodes
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 {
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 {
count.Raw = len(raw)
for _, s := range raw {
if str, ok := s.(string); ok {
count.Raw = append(count.Raw, str)
}
}
}
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 {
return StreamSource{
URL: url,

View File

@@ -118,17 +118,44 @@ func (h *Handler) HandleWatchPage(w http.ResponseWriter, r *http.Request) {
}
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
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,
Episode: fmt.Sprintf("Episode %d", i),
Title: fmt.Sprintf("Episode %d", i),
Images: &jikan.EpisodeImages{},
}
dummy.Images.Jpg.ImageURL = dummy.GetFallbackImage(id)
episodes.Data = append(episodes.Data, dummy)
Title: title,
Images: &jikan.EpisodeImages{
Jpg: struct {
ImageURL string `json:"image_url"`
}{ImageURL: imgURL},
},
})
}
}
}

View File

@@ -143,9 +143,9 @@ func (s *Service) BuildWatchPageData(ctx context.Context, malID int, titleCandid
fallbackEpisodes := make(map[string]int)
if counts, err := s.allAnimeClient.GetAvailableEpisodes(ctx, showID); err == nil {
fallbackEpisodes["sub"] = counts.Sub
fallbackEpisodes["dub"] = counts.Dub
fallbackEpisodes["raw"] = counts.Raw
fallbackEpisodes["sub"] = len(counts.Sub)
fallbackEpisodes["dub"] = len(counts.Dub)
fallbackEpisodes["raw"] = len(counts.Raw)
}
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 {
if items == nil {
return []T{}