Files
MeBox/internal/service/emby_system.go
T
truewhile 541de2165e 优化客户端封面获取逻辑
修改emby客户端获取封面逻辑,修复没有设置封面导致封面为空的问题
2026-08-20 09:20:42 +08:00

198 lines
6.6 KiB
Go

package service
import (
"context"
"strings"
"time"
"github.com/ShukeBta/MediaStationGo/internal/model"
)
// SystemInfo returns the full Emby identity payload.
func (e *EmbyService) SystemInfo() map[string]any {
return map[string]any{
"Id": embyServerID,
"ServerId": embyServerID,
"ServerName": "MediaStationGo",
"Version": embyCompatVersion,
"ServerVersion": embyCompatVersion,
"ProductName": "Emby Server",
"OperatingSystem": "Windows",
"Architecture": "X64",
"LocalAddress": "",
"WanAddress": "",
"HasPendingRestart": false,
"IsShuttingDown": false,
"SupportsLibraryMonitor": true,
"SupportsHttps": false,
"SupportsAutoDiscovery": true,
"HttpServerPortNumber": e.cfg.App.Port,
"HttpsPortNumber": 0,
"PublishedServerUrl": "",
"WebSocketPortNumber": e.cfg.App.Port,
"CompletedInstallations": []any{},
"CanSelfRestart": false,
"CanLaunchWebBrowser": false,
"CanRestart": false,
}
}
// SystemInfoPublic 是不需要认证的精简版(Emby Web 客户端登陆前会拉)。
func (e *EmbyService) SystemInfoPublic() map[string]any {
return map[string]any{
"Id": embyServerID,
"ServerId": embyServerID,
"ServerName": "MediaStationGo",
"Version": embyCompatVersion,
"ServerVersion": embyCompatVersion,
"ProductName": "Emby Server",
"OperatingSystem": "Windows",
"LocalAddress": "",
"WanAddress": "",
"HttpServerPortNumber": e.cfg.App.Port,
"HttpsPortNumber": 0,
"SupportsHttps": false,
"SupportsAutoDiscovery": true,
"StartupWizardCompleted": true,
}
}
// ListUsers returns Emby-shaped users.
func (e *EmbyService) ListUsers(ctx context.Context) ([]map[string]any, error) {
users, err := e.repo.User.List(ctx)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(users))
for _, u := range users {
out = append(out, e.userPayload(&u))
}
return out, nil
}
// FindUser 用 ID 查用户,用于 /Users/Me 与 /Users/{id}。
func (e *EmbyService) FindUser(ctx context.Context, id string) (map[string]any, error) {
u, err := e.repo.User.FindByID(ctx, id)
if err != nil || u == nil {
return nil, err
}
return e.userPayload(u), nil
}
func (e *EmbyService) userPayload(u *model.User) map[string]any {
canDownload := u.Role == "admin"
return map[string]any{
"Id": u.ID,
"Name": u.Username,
"ServerId": embyServerID,
"ServerName": "MediaStationGo",
"HasPassword": true,
"HasConfiguredPassword": true,
"HasConfiguredEasyPassword": false,
"EnableAutoLogin": false,
"LastLoginDate": u.LastLoginAt,
"LastActivityDate": u.UpdatedAt,
"Configuration": map[string]any{
"PlayDefaultAudioTrack": true,
"DisplayCollectionsView": true,
"DisplayMissingEpisodes": false,
"SubtitleMode": "Default",
"EnableNextEpisodeAutoPlay": true,
"AudioLanguagePreference": "",
"SubtitleLanguagePreference": "",
},
"Policy": map[string]any{
"IsAdministrator": u.Role == "admin",
"IsHidden": false,
"IsDisabled": !u.IsActive,
"EnableUserPreferenceAccess": true,
"EnableRemoteAccess": true,
"EnableMediaPlayback": true,
"EnableAudioPlaybackTranscoding": true,
"EnableVideoPlaybackTranscoding": true,
"EnablePlaybackRemuxing": true,
"EnableLiveTvAccess": false,
"EnableContentDownloading": canDownload,
"EnableSyncTranscoding": canDownload,
"EnableMediaConversion": canDownload,
"EnableAllChannels": true,
"EnableAllFolders": true,
"EnableAllDevices": true,
"AuthenticationProviderId": embyLocalAuthenticationProviderID,
"PasswordResetProviderId": embyLocalPasswordResetProviderID,
},
}
}
// Views 返回 Emby 中"虚拟根目录"——每个 library 一个条目。
func (e *EmbyService) Views(ctx context.Context, userID string) (map[string]any, error) {
libs, err := e.repo.Library.List(ctx)
if err != nil {
return nil, err
}
libs = FilterDisplayCloudLibraries(ctx, e.repo, libs)
visibility := e.mediaVisibility(ctx, userID)
items := make([]map[string]any, 0, len(libs))
for _, l := range libs {
if !e.libraryVisibleFromCachedVisibility(l, visibility) {
continue
}
items = append(items, e.libraryAsView(ctx, &l))
}
return map[string]any{"Items": items, "TotalRecordCount": len(items), "StartIndex": 0}, nil
}
func (e *EmbyService) libraryAsView(ctx context.Context, l *model.Library) map[string]any {
collectionType := "movies"
switch l.Type {
case "tv":
collectionType = "tvshows"
case "anime":
collectionType = "tvshows" // Emby 没有专门的 anime CollectionType
case "variety":
collectionType = "tvshows"
case "music":
collectionType = "music"
}
// 库封面:与剧集/电影一样,只有在能解析出封面时才广告 ImageTags.Primary,
// 否则客户端会认为该库无主图而不去请求 /Images/Primary。
imageTags := map[string]string{}
if e.LibraryHasCover(ctx, l.ID) {
imageTags["Primary"] = l.ID
}
return map[string]any{
"Id": l.ID,
"Name": l.Name,
"CollectionType": collectionType,
"ServerId": embyServerID,
"Type": "CollectionFolder",
"IsFolder": true,
"Path": l.Path,
"SortName": strings.ToLower(l.Name),
"DateCreated": l.CreatedAt.UTC().Format(time.RFC3339),
"CanDelete": false,
"CanDownload": false,
"DisplayPreferencesId": l.ID,
"PrimaryImageItemId": l.ID,
"PrimaryImageAspectRatio": 1.7777777777777777,
"RecursiveItemCount": 0,
"ChildCount": 0,
"SpecialFeatureCount": 0,
"EnableMediaSourceDisplay": true,
"PlayAccess": "Full",
"ExternalUrls": []any{},
"ProviderIds": map[string]string{},
"Genres": []string{},
"Tags": []string{},
"ImageTags": imageTags,
"BackdropImageTags": []string{},
"UserData": map[string]any{
"PlaybackPositionTicks": 0,
"PlayCount": 0,
"IsFavorite": false,
"Played": false,
"UnplayedItemCount": 0,
},
}
}