This commit is contained in:
truewhile
2026-09-06 19:02:53 +08:00
parent 292ae22dcd
commit 465e98a792
4 changed files with 205 additions and 17 deletions
+7 -1
View File
@@ -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
+1
View File
@@ -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 {
+70 -16
View File
@@ -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())
+127
View File
@@ -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)
}
}