From 465e98a792a5539a9f2ff340c476220cbce0fa85 Mon Sep 17 00:00:00 2001 From: truewhile <62226914+truewhile@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:02:53 +0800 Subject: [PATCH] bug --- internal/handler/media.go | 8 +- internal/service/emby_remote.go | 1 + internal/service/emby_remote_web.go | 86 ++++++++++++--- internal/service/emby_remote_web_test.go | 127 +++++++++++++++++++++++ 4 files changed, 205 insertions(+), 17 deletions(-) diff --git a/internal/handler/media.go b/internal/handler/media.go index 99e88ef..99c52f2 100644 --- a/internal/handler/media.go +++ b/internal/handler/media.go @@ -170,7 +170,13 @@ func listLibrariesHandler(svc *service.Container) gin.HandlerFunc { if acct == nil { return } - tmpMount := &model.EmbyMount{Base: model.Base{ID: v.MountID}} + tmpMount := &model.EmbyMount{ + Base: model.Base{ID: v.MountID}, + AccountID: v.AccountID, + RemoteViewID: v.RemoteID, + CollectionType: v.CollectionType, + Name: v.Library.Name, + } itemTypes := remoteLibraryItemTypes(v.CollectionType) if _, total, err := svc.EmbyRemote.RemoteLibraryMedia(ctx, tmpMount, acct, v.RemoteID, itemTypes, 0, 1); err == nil { remotePayloads[i].Total = total diff --git a/internal/service/emby_remote.go b/internal/service/emby_remote.go index f55c177..44d1678 100644 --- a/internal/service/emby_remote.go +++ b/internal/service/emby_remote.go @@ -820,6 +820,7 @@ func (r *EmbyRemoteService) RemoteLatest(ctx context.Context, mount *model.EmbyM if parentID != "" { q.Set("ParentId", parentID) } + q.Set("Fields", "Overview,Genres,ProviderIds,Path,SeriesPrimaryImage,DateCreated,DateLastMediaAdded,PremiereDate,ProductionYear,CommunityRating,CriticRating") path := "/Users/" + url.PathEscape(r.remoteUserID(cfg)) + "/Items/Latest" var out []map[string]any if err := r.doGet(ctx, acct, cfg, path, q, &out); err != nil { diff --git a/internal/service/emby_remote_web.go b/internal/service/emby_remote_web.go index 6dd7a09..346e5d7 100644 --- a/internal/service/emby_remote_web.go +++ b/internal/service/emby_remote_web.go @@ -180,21 +180,31 @@ func (r *EmbyRemoteService) MapRemoteItemToMedia(ctx context.Context, mount *mod if _, rid, ok := DecodeEmbyRemoteID(seriesID); ok { seriesID = rid } - rating := remoteItemFloat(item, "CommunityRating") - if rating == 0 { - rating = remoteItemFloat(item, "CriticRating") - } - media := model.Media{ - Base: model.Base{ID: EncodeEmbyRemoteID(encodeScope, remoteID)}, - Title: remoteItemString(item, "Name"), - OriginalName: remoteItemString(item, "OriginalTitle"), - Overview: remoteItemString(item, "Overview"), - Year: remoteItemInt(item, "ProductionYear"), - Rating: float32(rating), - Path: remoteItemString(item, "Path"), - Genres: remoteItemGenres(item), - ScrapeStatus: "done", - } + rating := remoteItemFloat(item, "CommunityRating") + if rating == 0 { + rating = remoteItemFloat(item, "CriticRating") + } + year := remoteItemInt(item, "ProductionYear") + if year == 0 { + year = remoteItemInt(item, "Year") + } + if year == 0 { + year = remoteItemInt(item, "SeriesProductionYear") + } + if year == 0 { + year = remoteItemInt(item, "SeriesYear") + } + media := model.Media{ + Base: model.Base{ID: EncodeEmbyRemoteID(encodeScope, remoteID)}, + Title: remoteItemString(item, "Name"), + OriginalName: remoteItemString(item, "OriginalTitle"), + Overview: remoteItemString(item, "Overview"), + Year: year, + Rating: float32(rating), + Path: remoteItemString(item, "Path"), + Genres: remoteItemGenres(item), + ScrapeStatus: "done", + } if date, ok := parseEmbyRemoteDate(remoteItemString(item, "DateCreated")); ok { media.CreatedAt = date media.UpdatedAt = date @@ -561,6 +571,46 @@ func (r *EmbyRemoteService) RemoteLatestCards(ctx context.Context, mount *model. if r.cache != nil && r.cache.GetJSON(ctx, cacheKey, &cached) { return cached, nil } + + // 剧集类媒体库:直接拉取最新入库/更新的 Series 剧集本身(按上次添加集日期倒序)。 + // 避免 Emby /Items/Latest 默认返回无年份/无系列海报的单集(Episode)。 + if mount != nil && (mount.CollectionType == "tvshows" || mount.CollectionType == "tv") { + q := url.Values{} + q.Set("ParentId", remoteViewID) + q.Set("IncludeItemTypes", "Series") + q.Set("Recursive", "false") + q.Set("SortBy", "DateLastContentAdded") + q.Set("SortOrder", "Descending") + q.Set("Limit", strconv.Itoa(limit)) + q.Set("Fields", "Overview,Genres,ProviderIds,Path,RecursiveItemCount,SeriesPrimaryImage,DateCreated,DateLastMediaAdded,PremiereDate,ProductionYear,CommunityRating,CriticRating") + var body struct { + Items []map[string]any `json:"Items"` + } + if err := r.doGet(ctx, acct, cfg, "/Users/"+url.PathEscape(r.remoteUserID(cfg))+"/Items", q, &body); err == nil && len(body.Items) > 0 { + cards := make([]SeriesCard, 0, len(body.Items)) + for _, it := range body.Items { + RewriteEmbyRemoteIDs(it, mount.ID) + m := r.MapRemoteItemToMedia(ctx, mount, acct, cfg, it) + count := remoteItemInt(it, "RecursiveItemCount") + if count == 0 { + count = remoteItemInt(it, "ChildCount") + } + if count == 0 { + count = 1 + } + var lastAdded *time.Time + if date, ok := parseEmbyRemoteDate(remoteItemString(it, "DateLastMediaAdded")); ok { + lastAdded = &date + } + cards = append(cards, SeriesCard{Key: m.ID, Rep: m, LinkMedia: m, Count: count, LastAddedAt: lastAdded}) + } + if r.cache != nil { + r.cache.SetJSON(ctx, cacheKey, cards, r.remoteMediaCacheTTL()) + } + return cards, nil + } + } + items, err := r.RemoteLatest(ctx, mount, acct, remoteViewID, limit) if err != nil { return nil, err @@ -576,7 +626,11 @@ func (r *EmbyRemoteService) RemoteLatestCards(ctx context.Context, mount *model. t := m.CreatedAt lastAdded = &t } - cards = append(cards, SeriesCard{Key: m.ID, Rep: m, LinkMedia: m, Count: 0, LastAddedAt: lastAdded}) + count := remoteItemInt(it, "RecursiveItemCount") + if count == 0 { + count = remoteItemInt(it, "ChildCount") + } + cards = append(cards, SeriesCard{Key: m.ID, Rep: m, LinkMedia: m, Count: count, LastAddedAt: lastAdded}) } if r.cache != nil { r.cache.SetJSON(ctx, cacheKey, cards, r.remoteMediaCacheTTL()) diff --git a/internal/service/emby_remote_web_test.go b/internal/service/emby_remote_web_test.go index f6f5d88..ec3886e 100644 --- a/internal/service/emby_remote_web_test.go +++ b/internal/service/emby_remote_web_test.go @@ -400,3 +400,130 @@ func TestRemoteSearchMedia(t *testing.T) { t.Fatalf("expected 0 items due to HiddenLibraryIDs, got %d", len(hiddenFiltered)) } } + +func TestRemoteLatestCardsTvShowsYearAndPoster(t *testing.T) { + var requestedFields string + var requestedIncludeItemTypes string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestedFields = r.URL.Query().Get("Fields") + requestedIncludeItemTypes = r.URL.Query().Get("IncludeItemTypes") + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "TotalRecordCount": 1, + "Items": []map[string]any{ + { + "Id": "series-100", + "Name": "炒翻天", + "Type": "Series", + "ProductionYear": 2024, + "ImageTags": map[string]any{ + "Primary": "tag123", + }, + "RecursiveItemCount": 12, + }, + }, + }) + })) + defer server.Close() + + db := newServiceTestDB(t, &model.StrmAccount{}, &model.EmbyMount{}) + repos := repository.New(db) + svc := NewEmbyRemoteService(&config.Config{}, zap.NewNop(), repos, NewCryptoService("", zap.NewNop())) + + rawConfig, _ := json.Marshal(map[string]string{ + "url": server.URL, + "token": "fake-token", + }) + acct := &model.StrmAccount{ + Base: model.Base{ID: "acct-tv"}, + Name: "tv-emby", + Provider: model.StrmProviderEmbyRemote, + Config: string(rawConfig), + Enabled: true, + } + _ = repos.StrmAccount.Create(t.Context(), acct) + mount := &model.EmbyMount{ + Base: model.Base{ID: "mount-tv"}, + AccountID: acct.ID, + RemoteViewID: "view-tv", + RemoteViewName: "新番连载", + CollectionType: "tvshows", + Enabled: true, + } + _ = repos.EmbyMount.Create(t.Context(), mount) + + cards, err := svc.RemoteLatestCards(t.Context(), mount, acct, "view-tv", 10) + if err != nil { + t.Fatalf("RemoteLatestCards failed: %v", err) + } + if len(cards) != 1 { + t.Fatalf("expected 1 card, got %d", len(cards)) + } + if cards[0].Rep.Year != 2024 { + t.Fatalf("expected Year 2024, got %d", cards[0].Rep.Year) + } + if cards[0].Count != 12 { + t.Fatalf("expected Count 12, got %d", cards[0].Count) + } + if cards[0].Rep.PosterURL == "" { + t.Fatalf("expected PosterURL not empty") + } + if requestedIncludeItemTypes != "Series" { + t.Fatalf("expected IncludeItemTypes=Series, got %q", requestedIncludeItemTypes) + } + if !strings.Contains(requestedFields, "ProductionYear") { + t.Fatalf("expected Fields to contain ProductionYear, got %q", requestedFields) + } +} + +func TestRemoteLatestFields(t *testing.T) { + var requestedFields string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestedFields = r.URL.Query().Get("Fields") + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode([]map[string]any{ + { + "Id": "movie-100", + "Name": "测试电影", + "Type": "Movie", + "ProductionYear": 2023, + }, + }) + })) + defer server.Close() + + db := newServiceTestDB(t, &model.StrmAccount{}, &model.EmbyMount{}) + repos := repository.New(db) + svc := NewEmbyRemoteService(&config.Config{}, zap.NewNop(), repos, NewCryptoService("", zap.NewNop())) + + rawConfig, _ := json.Marshal(map[string]string{ + "url": server.URL, + "token": "fake-token", + }) + acct := &model.StrmAccount{ + Base: model.Base{ID: "acct-movie"}, + Name: "movie-emby", + Provider: model.StrmProviderEmbyRemote, + Config: string(rawConfig), + Enabled: true, + } + _ = repos.StrmAccount.Create(t.Context(), acct) + mount := &model.EmbyMount{ + Base: model.Base{ID: "mount-movie"}, + AccountID: acct.ID, + RemoteViewID: "view-movie", + CollectionType: "movies", + Enabled: true, + } + + items, err := svc.RemoteLatest(t.Context(), mount, acct, "view-movie", 10) + if err != nil { + t.Fatalf("RemoteLatest failed: %v", err) + } + if len(items) != 1 { + t.Fatalf("expected 1 item, got %d", len(items)) + } + if !strings.Contains(requestedFields, "ProductionYear") { + t.Fatalf("expected Fields to contain ProductionYear, got %q", requestedFields) + } +}