diff --git a/src/backend/common/vendor/webscrobbler/interfaces.ts b/src/backend/common/vendor/webscrobbler/interfaces.ts index 91791206..d94d7609 100644 --- a/src/backend/common/vendor/webscrobbler/interfaces.ts +++ b/src/backend/common/vendor/webscrobbler/interfaces.ts @@ -1,95 +1,80 @@ import * as z from 'zod'; -export type WebScrobblerHookEvent = 'scrobble' | 'paused' | 'resumedplaying' | 'nowplaying' | string; +//export type WebScrobblerHookEvent = 'scrobble' | 'paused' | 'resumedplaying' | 'nowplaying' | string; const webScrobblerHookEventSchema = z.union([z.enum(['scrobble','paused','resumedplaying','nowplaying']), z.string()]); +export type WebScrobblerHookEvent = z.infer; -interface ProcessedSongData { - artist?: string | null; - album?: string | null; - albumArtist?: string | null; - track?: string | null; - duration?: number | null; -} +const processedSongDataSchema = z.object({ + artist: z.string().nullish(), + album: z.string().nullish(), + albumArtist: z.string().nullish(), + track: z.string().nullish(), + duration: z.number().nullish(), +}); +export type ProcessedSongData = z.output; -interface ParsedSongData extends ProcessedSongData { - trackArt?: string | null; - uniqueID?: string | null; - originUrl?: string | null; - isPodcast?: boolean | null; - isPlaying?: boolean | null; - currentTime?: number | null; - isScrobblingAllowed?: boolean | null; -} +const parsedSongDataSchema = processedSongDataSchema.extend({ + trackArt: z.string().nullish(), + uniqueID: z.string().nullish(), + originUrl: z.string().nullish(), + isPodcast: z.boolean().nullish(), + isPlaying: z.boolean().nullish(), + currentTime: z.number().nullish(), + isScrobblingAllowed: z.boolean().nullish(), +}); +export type ParsedSongData = z.output; -export type Flags = - | { - isScrobbled: boolean; - isCorrectedByUser: boolean; - isRegexEditedByUser: { - track: boolean; - artist: boolean; - album: boolean; - albumArtist: boolean; - }; - isAlbumFetched: boolean; - isValid: boolean; - isMarkedAsPlaying: boolean; - isSkipped: boolean; - isReplaying: boolean; -} - | Record; +// Record means "no keys allowed" -> empty object with no extra props +export const flagsSchema = z.looseObject({ + isScrobbled: z.boolean(), + isCorrectedByUser: z.boolean(), + isRegexEditedByUser: z.object({ + track: z.boolean(), + artist: z.boolean(), + album: z.boolean(), + albumArtist: z.boolean(), + }), + isAlbumFetched: z.boolean(), + isValid: z.boolean(), + isMarkedAsPlaying: z.boolean(), + isSkipped: z.boolean(), + isReplaying: z.boolean(), +}); +export type Flags = z.infer; -export type Metadata = - | { - label: string; - startTimestamp: number; +export const metadataSchema = z.looseObject({ + label: z.string(), + startTimestamp: z.number(), + albumMbId: z.string().optional(), + albumUrl: z.string().optional(), + artistUrl: z.string().optional(), + notificationId: z.string().optional(), + trackArtUrl: z.string().optional(), + trackUrl: z.string().optional(), + userPlayCount: z.number().optional(), + userloved: z.boolean().optional(), +}); +export type Metadata = z.infer; - albumMbId?: string; - albumUrl?: string; - artistUrl?: string; - notificationId?: string; - trackArtUrl?: string; - trackUrl?: string; - userPlayCount?: number; - userloved?: boolean; -} - | Record; +export const connectorSchema = z.object({ + id: z.string(), + js: z.string(), + label: z.string(), +}); +export type Connector = z.infer; -export interface Connector { - id: string - js: string - label: string -} -export interface WebScrobblerSong { - controllerTabId: string | number; - connector: Connector - parsed: ParsedSongData; - processed: ProcessedSongData; - noRegex: ProcessedSongData; - flags: Flags; - metadata: Metadata; - connectorLabel: string; -} export const webScrobblerSongSchema = z.object({ controllerTabId: z.union([z.string(),z.number()]), - connector: z.looseObject({}), - parsed: z.looseObject({}), - processed: z.looseObject({}), - noRegex: z.looseObject({}), - flags: z.looseObject({}), - metadata: z.looseObject({}), - connectorLabel: z.looseObject({}) -}) + connector: connectorSchema, + parsed: parsedSongDataSchema, + processed: processedSongDataSchema, + noRegex: processedSongDataSchema, + flags: flagsSchema, + metadata: metadataSchema, + connectorLabel: z.string() +}); +export type WebScrobblerSong = z.infer; -export interface WebScrobblerPayload { - eventName: WebScrobblerHookEvent - time?: number - data: { - song: WebScrobblerSong - songs?: WebScrobblerSong[] - currentlyPlaying?: boolean - } -} export const webScrobblePayloadSchema = z.object({ eventName: webScrobblerHookEventSchema, time: z.number().optional(), @@ -98,4 +83,5 @@ export const webScrobblePayloadSchema = z.object({ songs: z.array(webScrobblerSongSchema).optional(), currentlyPlaying: z.boolean().optional() }) -}) +}); +export type WebScrobblerPayload = z.infer; \ No newline at end of file diff --git a/src/backend/sources/WebScrobblerSource.ts b/src/backend/sources/WebScrobblerSource.ts index 78d46811..7cad7c3f 100644 --- a/src/backend/sources/WebScrobblerSource.ts +++ b/src/backend/sources/WebScrobblerSource.ts @@ -81,7 +81,7 @@ export class WebScrobblerSource extends MemorySource { } } - static playStateFromRequest(obj: WebScrobblerPayload,): PlayerStateData { + static playStateFromRequest(obj: WebScrobblerPayload): PlayerStateData { const { eventName, time = dayjs().unix(),