import {z} from 'zod' export type GetApiDevicesArgs = {supportsSync?: (boolean | null) | undefined, userId?: (string | null) | undefined}; export type GetApiDevicesOptions = {baseUrl?: string | undefined, CustomAuthentication?: string | undefined, fetch?: typeof fetch | undefined}; export const generalCommandType = z.enum(["MoveUp", "MoveDown", "MoveLeft", "MoveRight", "PageUp", "PageDown", "PreviousLetter", "NextLetter", "ToggleOsd", "ToggleContextMenu", "Select", "Back", "TakeScreenshot", "SendKey", "SendString", "GoHome", "GoToSettings", "VolumeUp", "VolumeDown", "Mute", "Unmute", "ToggleMute", "SetVolume", "SetAudioStreamIndex", "SetSubtitleStreamIndex", "ToggleFullscreen", "DisplayContent", "GoToSearch", "DisplayMessage", "SetRepeatMode", "ChannelUp", "ChannelDown", "Guide", "ToggleStats", "PlayMediaSource", "PlayTrailers", "SetShuffleQueue", "PlayState", "PlayNext", "ToggleOsdMenu", "Play"]); export const headerMatchType = z.enum(["Equals", "Regex", "Substring"]); export const httpHeaderInfo = z.object({Name: z.string().nullable().optional(), Value: z.string().nullable().optional(), Match: headerMatchType.optional()}); export const deviceIdentification = z.object({FriendlyName: z.string().nullable().optional(), ModelNumber: z.string().nullable().optional(), SerialNumber: z.string().nullable().optional(), ModelName: z.string().nullable().optional(), ModelDescription: z.string().nullable().optional(), ModelUrl: z.string().nullable().optional(), Manufacturer: z.string().nullable().optional(), ManufacturerUrl: z.string().nullable().optional(), Headers: z.array(httpHeaderInfo).nullable().optional()}); export const xmlAttribute = z.object({Name: z.string().nullable().optional(), Value: z.string().nullable().optional()}); export const dlnaProfileType = z.enum(["Audio", "Video", "Photo"]); export const directPlayProfile = z.object({Container: z.string().nullable().optional(), AudioCodec: z.string().nullable().optional(), VideoCodec: z.string().nullable().optional(), Type: dlnaProfileType.optional()}); export const transcodeSeekInfo = z.enum(["Auto", "Bytes"]); export const encodingContext = z.enum(["Streaming", "Static"]); export const transcodingProfile = z.object({Container: z.string().nullable().optional(), Type: dlnaProfileType.optional(), VideoCodec: z.string().nullable().optional(), AudioCodec: z.string().nullable().optional(), Protocol: z.string().nullable().optional(), EstimateContentLength: z.boolean().optional(), EnableMpegtsM2TsMode: z.boolean().optional(), TranscodeSeekInfo: transcodeSeekInfo.optional(), CopyTimestamps: z.boolean().optional(), Context: encodingContext.optional(), EnableSubtitlesInManifest: z.boolean().optional(), MaxAudioChannels: z.string().nullable().optional(), MinSegments: z.number().int().optional(), SegmentLength: z.number().int().optional(), BreakOnNonKeyFrames: z.boolean().optional()}); export const profileConditionType = z.enum(["Equals", "NotEquals", "LessThanEqual", "GreaterThanEqual", "EqualsAny"]); export const profileConditionValue = z.enum(["AudioChannels", "AudioBitrate", "AudioProfile", "Width", "Height", "Has64BitOffsets", "PacketLength", "VideoBitDepth", "VideoBitrate", "VideoFramerate", "VideoLevel", "VideoProfile", "VideoTimestamp", "IsAnamorphic", "RefFrames", "NumAudioStreams", "NumVideoStreams", "IsSecondaryAudio", "VideoCodecTag", "IsAvc", "IsInterlaced", "AudioSampleRate", "AudioBitDepth"]); export const profileCondition = z.object({Condition: profileConditionType.optional(), Property: profileConditionValue.optional(), Value: z.string().nullable().optional(), IsRequired: z.boolean().optional()}); export const containerProfile = z.object({Type: dlnaProfileType.optional(), Conditions: z.array(profileCondition).nullable().optional(), Container: z.string().nullable().optional()}); export const codecType = z.enum(["Video", "VideoAudio", "Audio"]); export const codecProfile = z.object({Type: codecType.optional(), Conditions: z.array(profileCondition).nullable().optional(), ApplyConditions: z.array(profileCondition).nullable().optional(), Codec: z.string().nullable().optional(), Container: z.string().nullable().optional()}); export const responseProfile = z.object({Container: z.string().nullable().optional(), AudioCodec: z.string().nullable().optional(), VideoCodec: z.string().nullable().optional(), Type: dlnaProfileType.optional(), OrgPn: z.string().nullable().optional(), MimeType: z.string().nullable().optional(), Conditions: z.array(profileCondition).nullable().optional()}); export const subtitleDeliveryMethod = z.enum(["Encode", "Embed", "External", "Hls"]); export const subtitleProfile = z.object({Format: z.string().nullable().optional(), Method: subtitleDeliveryMethod.optional(), DidlMode: z.string().nullable().optional(), Language: z.string().nullable().optional(), Container: z.string().nullable().optional()}); export const deviceProfile = z.object({Name: z.string().nullable().optional(), Id: z.string().nullable().optional(), Identification: deviceIdentification.optional(), FriendlyName: z.string().nullable().optional(), Manufacturer: z.string().nullable().optional(), ManufacturerUrl: z.string().nullable().optional(), ModelName: z.string().nullable().optional(), ModelDescription: z.string().nullable().optional(), ModelNumber: z.string().nullable().optional(), ModelUrl: z.string().nullable().optional(), SerialNumber: z.string().nullable().optional(), EnableAlbumArtInDidl: z.boolean().optional(), EnableSingleAlbumArtLimit: z.boolean().optional(), EnableSingleSubtitleLimit: z.boolean().optional(), SupportedMediaTypes: z.string().nullable().optional(), UserId: z.string().nullable().optional(), AlbumArtPn: z.string().nullable().optional(), MaxAlbumArtWidth: z.number().int().optional(), MaxAlbumArtHeight: z.number().int().optional(), MaxIconWidth: z.number().int().nullable().optional(), MaxIconHeight: z.number().int().nullable().optional(), MaxStreamingBitrate: z.number().int().nullable().optional(), MaxStaticBitrate: z.number().int().nullable().optional(), MusicStreamingTranscodingBitrate: z.number().int().nullable().optional(), MaxStaticMusicBitrate: z.number().int().nullable().optional(), SonyAggregationFlags: z.string().nullable().optional(), ProtocolInfo: z.string().nullable().optional(), TimelineOffsetSeconds: z.number().int().optional(), RequiresPlainVideoItems: z.boolean().optional(), RequiresPlainFolders: z.boolean().optional(), EnableMSMediaReceiverRegistrar: z.boolean().optional(), IgnoreTranscodeByteRangeRequests: z.boolean().optional(), XmlRootAttributes: z.array(xmlAttribute).nullable().optional(), DirectPlayProfiles: z.array(directPlayProfile).nullable().optional(), TranscodingProfiles: z.array(transcodingProfile).nullable().optional(), ContainerProfiles: z.array(containerProfile).nullable().optional(), CodecProfiles: z.array(codecProfile).nullable().optional(), ResponseProfiles: z.array(responseProfile).nullable().optional(), SubtitleProfiles: z.array(subtitleProfile).nullable().optional()}); export const clientCapabilities = z.object({PlayableMediaTypes: z.array(z.string()).nullable().optional(), SupportedCommands: z.array(generalCommandType).nullable().optional(), SupportsMediaControl: z.boolean().optional(), SupportsContentUploading: z.boolean().optional(), MessageCallbackUrl: z.string().nullable().optional(), SupportsPersistentIdentifier: z.boolean().optional(), SupportsSync: z.boolean().optional(), DeviceProfile: deviceProfile.optional(), AppStoreUrl: z.string().nullable().optional(), IconUrl: z.string().nullable().optional()}); export const deviceInfo = z.object({Name: z.string().nullable().optional(), Id: z.string().nullable().optional(), LastUserName: z.string().nullable().optional(), AppName: z.string().nullable().optional(), AppVersion: z.string().nullable().optional(), LastUserId: z.string().optional(), DateLastActivity: z.string().optional(), Capabilities: clientCapabilities.optional(), IconUrl: z.string().nullable().optional()}); export const deviceInfoQueryResult = z.object({Items: z.array(deviceInfo).nullable().optional(), TotalRecordCount: z.number().int().optional(), StartIndex: z.number().int().optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Get Devices. * * GET /Devices */ export const getApiDevices = async (args: GetApiDevicesArgs, options?: GetApiDevicesOptions) => { const {baseUrl = "http://localhost", fetch: fetchFn = fetch, CustomAuthentication} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.supportsSync != null) query.append("supportsSync", String(args.supportsSync)) if (args.userId != null) query.append("userId", String(args.userId)) if (CustomAuthentication !== undefined) headers.set("X-Emby-Authorization", CustomAuthentication) const url = `${baseUrl.replace(/\/+$/, '')}/Devices${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(deviceInfoQueryResult, await res.json()) };