Files
gronod 42570915ed
Build and publish / Test and build (darwin) (push) Successful in 1m43s
Build and publish / Test and build (linux) (push) Successful in 2m30s
Build and publish / Test and build (windows) (push) Successful in 3m5s
Build and publish / Build and publish Docker image (push) Successful in 1m46s
Add browse, next-episode, and track-control tools
Implements the three roadmap feature sections:

- Browse: retrieve_item_children, retrieve_season_list, retrieve_episode_list;
  search_for_item gains item_types (e.g. "Series") so shows are findable
  without a selected library. Items now expose item_type, series_name, and
  per-user played state/resume position.
- retrieve_next_episode: next_unplayed (Emby NextUp, resume counts) or latest
  (most recent present episode; virtual placeholders skipped).
- retrieve_now_playing + set_subtitle/set_audio_track: stream listing and
  track switching via SetSubtitleStreamIndex/SetAudioStreamIndex GeneralCommands,
  with a PlayNow restart-at-position fallback for sessions that cannot switch
  mid-play (DLNA/Chromecast bridges).

Conformance manifest grows to 34 call sites with three new documented spec
gaps (StartPositionTicks on PlayRequest, missing /Shows/{Id}/Episodes response
schema, undeclared Fields/EnableUserData on /Users/{UserId}/Items/{Id}).
2026-09-21 00:09:49 +01:00

180 lines
6.7 KiB
Go

package emby
// DTOs mirroring the Emby REST API JSON payloads (PascalCase field names).
// Only the fields used by Emby.MCP are modelled.
// QueryResult is Emby's standard paged list envelope.
type QueryResult[T any] struct {
Items []T `json:"Items"`
TotalRecordCount int `json:"TotalRecordCount"`
StartIndex int `json:"StartIndex"`
}
// authRequest is the body of POST /Users/AuthenticateByName.
type authRequest struct {
Username string `json:"Username"`
Pw string `json:"Pw"`
}
// AuthResult is the response of POST /Users/AuthenticateByName.
type AuthResult struct {
AccessToken string `json:"AccessToken"`
User *UserDto `json:"User"`
SessionInfo any `json:"SessionInfo"`
ServerID string `json:"ServerId"`
}
// UserDto is an Emby user (subset).
type UserDto struct {
ID string `json:"Id"`
Name string `json:"Name"`
}
// UserItemAccessEntry is one row of GET /Users/ItemAccess.
type UserItemAccessEntry struct {
ID string `json:"Id"`
Name string `json:"Name"`
UserItemShareLevel string `json:"UserItemShareLevel"`
}
// LibraryFolder is one item of GET /Library/MediaFolders.
type LibraryFolder struct {
Name string `json:"Name"`
Type string `json:"Type"`
CollectionType string `json:"CollectionType"`
ID string `json:"Id"`
}
// GenreItem is one item of GET /Genres.
type GenreItem struct {
Name string `json:"Name"`
}
// UserItemDataDto is the per-user state on a BaseItemDto (subset).
type UserItemDataDto struct {
Played bool `json:"Played"`
PlayedPercentage *float64 `json:"PlayedPercentage"`
PlaybackPositionTicks int64 `json:"PlaybackPositionTicks"`
IsFavorite bool `json:"IsFavorite"`
PlayCount int `json:"PlayCount"`
}
// MediaStream is one stream inside a MediaSource.
type MediaStream struct {
Index int `json:"Index"`
Type string `json:"Type"`
Language string `json:"Language"`
Codec string `json:"Codec"`
Title string `json:"Title"`
DisplayTitle string `json:"DisplayTitle"`
IsDefault bool `json:"IsDefault"`
IsForced bool `json:"IsForced"`
IsExternal bool `json:"IsExternal"`
IsTextSubtitleStream bool `json:"IsTextSubtitleStream"`
ExtraData string `json:"ExtraData"`
}
// MediaSource is one media source of a BaseItemDto.
type MediaSource struct {
MediaStreams []MediaStream `json:"MediaStreams"`
}
// BaseItemDto is the subset of Emby's BaseItemDto we consume.
type BaseItemDto struct {
ID string `json:"Id"`
Name string `json:"Name"`
Type string `json:"Type"`
CollectionType string `json:"CollectionType"`
Album string `json:"Album"`
AlbumID string `json:"AlbumId"`
AlbumArtist string `json:"AlbumArtist"`
Artists []string `json:"Artists"`
ParentIndexNumber *int `json:"ParentIndexNumber"`
IndexNumber *int `json:"IndexNumber"`
DateCreated string `json:"DateCreated"`
PremiereDate string `json:"PremiereDate"`
ProductionYear *int `json:"ProductionYear"`
Genres []string `json:"Genres"`
Overview string `json:"Overview"`
MediaSources []MediaSource `json:"MediaSources"`
MediaType string `json:"MediaType"`
RunTimeTicks int64 `json:"RunTimeTicks"`
Bitrate *int `json:"Bitrate"`
Path string `json:"Path"`
PlaylistItemID string `json:"PlaylistItemId"`
SeriesName string `json:"SeriesName"`
SeriesID string `json:"SeriesId"`
SeasonID string `json:"SeasonId"`
SeasonName string `json:"SeasonName"`
LocationType string `json:"LocationType"`
UserData *UserItemDataDto `json:"UserData"`
}
// PlayState is the playback state embedded in a session.
type PlayState struct {
PositionTicks int64 `json:"PositionTicks"`
IsPaused bool `json:"IsPaused"`
CanSeek bool `json:"CanSeek"`
AudioStreamIndex *int `json:"AudioStreamIndex"`
SubtitleStreamIndex *int `json:"SubtitleStreamIndex"`
MediaSourceID string `json:"MediaSourceId"`
}
// SessionInfo is an Emby session (subset).
type SessionInfo struct {
Client string `json:"Client"`
ID string `json:"Id"`
DeviceID string `json:"DeviceId"`
DeviceName string `json:"DeviceName"`
UserID string `json:"UserId"`
UserName string `json:"UserName"`
RemoteEndPoint string `json:"RemoteEndPoint"`
PlayableMediaTypes []string `json:"PlayableMediaTypes"`
SupportedCommands []string `json:"SupportedCommands"`
NowPlayingItem *BaseItemDto `json:"NowPlayingItem"`
PlayState *PlayState `json:"PlayState"`
}
// GeneralCommand is the body of POST /Sessions/{Id}/Command.
type GeneralCommand struct {
Name string `json:"Name"`
ControllingUserID string `json:"ControllingUserId,omitempty"`
Arguments map[string]string `json:"Arguments,omitempty"`
}
// playlistCreateResult is the response of POST /Playlists.
type playlistCreateResult struct {
ID string `json:"Id"`
}
// playlistAddResult is the response of POST /Playlists/{id}/Items.
type playlistAddResult struct {
ItemAddedCount int `json:"ItemAddedCount"`
}
// playRequest is the body of POST /Sessions/{id}/Playing.
// StartPositionTicks is sent although the OpenAPI PlayRequest schema omits it
// (spec gap — real Emby accepts it for resume-position restarts).
type playRequest struct {
ItemIDs []string `json:"ItemIds,omitempty"`
PlayCommand string `json:"PlayCommand,omitempty"`
ControllingUserID string `json:"ControllingUserId,omitempty"`
SubtitleStreamIndex *int `json:"SubtitleStreamIndex,omitempty"`
AudioStreamIndex *int `json:"AudioStreamIndex,omitempty"`
StartPositionTicks *int64 `json:"StartPositionTicks,omitempty"`
}
// playstateRequest is the body of POST /Sessions/{id}/Playing/{command}.
type playstateRequest struct {
Command string `json:"Command"`
SeekPositionTicks int64 `json:"SeekPositionTicks,omitempty"`
ControllingUserID string `json:"ControllingUserId,omitempty"`
}
// updateUserItemAccess is the body of POST /Items/Access.
type updateUserItemAccess struct {
ItemIDs []string `json:"ItemIds"`
UserIDs []string `json:"UserIds"`
ItemAccess string `json:"ItemAccess"`
}