A decentralized music tracking and discovery platform built on AT Protocol 馃幍
at 28fa4c0042d8061cee71dda28c007981f203dd71 308 lines 10 kB view raw
1import { z } from "zod"; 2 3export namespace WebScrobbler { 4 /* -------------------------------- Connector -------------------------------- */ 5 6 export const ConnectorSchema = z.object({ 7 id: z.string(), 8 js: z.string(), 9 label: z.string(), 10 matches: z.array(z.string()), 11 }); 12 13 /* ----------------------- IsRegrexEditedByUser ----------------------- */ 14 15 export const IsRegrexEditedByUserSchema = z.object({ 16 album: z.boolean(), 17 albumArtist: z.boolean(), 18 artist: z.boolean(), 19 track: z.boolean(), 20 }); 21 22 /* ---------------------------------- Flags ---------------------------------- */ 23 24 export const FlagsSchema = z.object({ 25 finishedProcessing: z.boolean(), 26 hasBlockedTag: z.boolean(), 27 isAlbumFetched: z.boolean(), 28 isCorrectedByUser: z.boolean(), 29 isLovedInService: z.boolean().optional(), 30 isMarkedAsPlaying: z.boolean(), 31 isRegexEditedByUser: IsRegrexEditedByUserSchema, 32 isReplaying: z.boolean(), 33 isScrobbled: z.boolean(), 34 isSkipped: z.boolean(), 35 isValid: z.boolean(), 36 }); 37 38 /* -------------------------------- Metadata -------------------------------- */ 39 40 export const MetadataSchema = z.object({ 41 albumUrl: z.string().url().optional(), 42 artistUrl: z.string().url().optional(), 43 label: z.string(), 44 startTimestamp: z.number().int().nonnegative(), 45 trackUrl: z.string().url().optional(), 46 userPlayCount: z.number().int().nonnegative().optional(), 47 userloved: z.boolean().optional(), 48 }); 49 50 /* -------------------------------- NoRegex -------------------------------- */ 51 52 export const NoRegexSchema = z.object({ 53 album: z.string().optional(), 54 albumArtist: z.string().optional(), 55 artist: z.string(), 56 duration: z.number().int().nonnegative().optional(), 57 track: z.string(), 58 }); 59 60 /* ---------------------------------- Parsed --------------------------------- */ 61 62 export const ParsedSchema = z.object({ 63 album: z.string().optional(), 64 albumArtist: z.string().optional(), 65 artist: z.string(), 66 currentTime: z.number().int().nonnegative().optional(), 67 duration: z.number().int().nonnegative().optional(), 68 isPlaying: z.boolean(), 69 isPodcast: z.boolean(), 70 originUrl: z.string().url().optional(), 71 scrobblingDisallowedReason: z.string().optional(), 72 track: z.string(), 73 trackArt: z.string().url().optional(), 74 uniqueID: z.string().optional(), 75 }); 76 77 /* ----------------------------------- Song ---------------------------------- */ 78 79 export const SongSchema = z.object({ 80 connector: ConnectorSchema, 81 controllerTabId: z.number().int().nonnegative(), 82 flags: FlagsSchema, 83 metadata: MetadataSchema, 84 noRegex: NoRegexSchema, 85 parsed: ParsedSchema, 86 }); 87 88 /* -------------------------------- Processed -------------------------------- */ 89 90 export const ProcessedSchema = z.object({ 91 album: z.string(), 92 albumArtist: z.string().optional(), 93 artist: z.string(), 94 duration: z.number().int().nonnegative(), 95 track: z.string(), 96 }); 97 98 /* --------------------------------- Scrobble -------------------------------- */ 99 100 export const ScrobbleSchema = z.object({ 101 song: SongSchema, 102 }); 103 104 /* ------------------------------ ScrobbleRequest ----------------------------- */ 105 106 export const ScrobbleRequestSchema = z.object({ 107 data: ScrobbleSchema, 108 eventName: z.string(), 109 time: z.number().int().nonnegative(), 110 }); 111 112 export type Song = z.infer<typeof SongSchema>; 113 export type Processed = z.infer<typeof ProcessedSchema>; 114 export type Scrobble = z.infer<typeof ScrobbleSchema>; 115 export type ScrobbleRequest = z.infer<typeof ScrobbleRequestSchema>; 116 export type ScrobbleRequestData = z.infer< 117 typeof ScrobbleRequestSchema 118 >["data"]; 119} 120 121export namespace Lastfm { 122 /* -------------------------------- Legacy API Schemas -------------------------------- */ 123 124 export const LegacyNowPlayingRequestSchema = z.object({ 125 s: z.string(), // session ID 126 a: z.string(), // artist 127 t: z.string(), // track 128 b: z.string().optional(), // album 129 l: z.string().optional(), // length in seconds 130 n: z.string().optional(), // track number 131 m: z.string().optional(), // MusicBrainz ID 132 }); 133 134 export const LegacySubmissionRequestSchema = z.object({ 135 s: z.string(), // session ID 136 "a[0]": z.string(), // artist 137 "t[0]": z.string(), // track 138 "i[0]": z.string(), // timestamp 139 "o[0]": z.string().optional(), // source (P/R/E/L/U/B) 140 "r[0]": z.string().optional(), // rating (L/B/S) 141 "l[0]": z.string().optional(), // length in seconds 142 "b[0]": z.string().optional(), // album 143 "n[0]": z.string().optional(), // track number 144 "m[0]": z.string().optional(), // MusicBrainz ID 145 }); 146 147 /* -------------------------------- Auth Request -------------------------------- */ 148 149 export const AuthRequestSchema = z.object({ 150 method: z.string(), 151 api_key: z.string(), 152 api_sig: z.string(), 153 format: z.string().optional(), 154 }); 155 156 /* -------------------------------- Auth GetSession Request -------------------------------- */ 157 158 export const AuthGetSessionRequestSchema = z.object({ 159 method: z.literal("auth.getSession"), 160 api_key: z.string(), 161 token: z.string(), 162 api_sig: z.string(), 163 format: z.string().optional(), 164 }); 165 166 /* -------------------------------- Auth GetSession Response -------------------------------- */ 167 168 export const AuthGetSessionResponseSchema = z.object({ 169 session: z.object({ 170 name: z.string(), 171 key: z.string(), 172 subscriber: z.number(), 173 }), 174 }); 175 176 /* -------------------------------- Track Scrobble Request -------------------------------- */ 177 178 export const TrackScrobbleRequestSchema = z.object({ 179 method: z.literal("track.scrobble"), 180 api_key: z.string(), 181 api_sig: z.string(), 182 sk: z.string(), 183 "track[0]": z.string(), 184 "artist[0]": z.string(), 185 "timestamp[0]": z.string(), 186 "album[0]": z.string().optional(), 187 "albumArtist[0]": z.string().optional(), 188 "duration[0]": z.string().optional(), 189 format: z.string().optional(), 190 }); 191 192 /* -------------------------------- Track Update Now Playing Request -------------------------------- */ 193 194 export const TrackUpdateNowPlayingRequestSchema = z.object({ 195 method: z.literal("track.updateNowPlaying"), 196 api_key: z.string(), 197 api_sig: z.string(), 198 sk: z.string(), 199 track: z.string(), 200 artist: z.string(), 201 album: z.string().optional(), 202 albumArtist: z.string().optional(), 203 duration: z.string().optional(), 204 format: z.string().optional(), 205 }); 206 207 /* -------------------------------- Scrobble Response -------------------------------- */ 208 209 export const ScrobbleResponseSchema = z.object({ 210 scrobbles: z.object({ 211 "@attr": z.object({ 212 accepted: z.number(), 213 ignored: z.number(), 214 }), 215 scrobble: z 216 .object({ 217 artist: z.object({ "#text": z.string() }), 218 track: z.object({ "#text": z.string() }), 219 album: z.object({ "#text": z.string() }).optional(), 220 albumArtist: z.object({ "#text": z.string() }).optional(), 221 timestamp: z.string(), 222 ignoredMessage: z 223 .object({ code: z.string(), "#text": z.string() }) 224 .optional(), 225 }) 226 .optional(), 227 }), 228 }); 229 230 /* -------------------------------- Error Response -------------------------------- */ 231 232 export const ErrorResponseSchema = z.object({ 233 error: z.number(), 234 message: z.string(), 235 }); 236 237 export type LegacyNowPlayingRequest = z.infer< 238 typeof LegacyNowPlayingRequestSchema 239 >; 240 export type LegacySubmissionRequest = z.infer< 241 typeof LegacySubmissionRequestSchema 242 >; 243 export type AuthRequest = z.infer<typeof AuthRequestSchema>; 244 export type AuthGetSessionRequest = z.infer< 245 typeof AuthGetSessionRequestSchema 246 >; 247 export type AuthGetSessionResponse = z.infer< 248 typeof AuthGetSessionResponseSchema 249 >; 250 export type TrackScrobbleRequest = z.infer<typeof TrackScrobbleRequestSchema>; 251 export type TrackUpdateNowPlayingRequest = z.infer< 252 typeof TrackUpdateNowPlayingRequestSchema 253 >; 254 export type ScrobbleResponse = z.infer<typeof ScrobbleResponseSchema>; 255 export type ErrorResponse = z.infer<typeof ErrorResponseSchema>; 256} 257 258export namespace Listenbrainz { 259 /* -------------------------------- TrackMetadata -------------------------------- */ 260 261 export const TrackMetadataSchema = z.object({ 262 artist_name: z.string(), 263 track_name: z.string(), 264 release_name: z.string().optional(), 265 additional_info: z.record(z.any()).optional(), 266 }); 267 268 /* -------------------------------- Payload -------------------------------- */ 269 270 export const PayloadSchema = z.object({ 271 listened_at: z.number().int().nonnegative().optional(), 272 track_metadata: TrackMetadataSchema, 273 }); 274 275 /* -------------------------------- SubmitListensRequest -------------------------------- */ 276 277 export const SubmitListensRequestSchema = z.object({ 278 listen_type: z.enum(["single", "playing_now", "import"]), 279 payload: z.array(PayloadSchema), 280 }); 281 282 /* -------------------------------- SubmitListensResponse -------------------------------- */ 283 284 export const SubmitListensResponseSchema = z.object({ 285 status: z.string(), 286 code: z.number().int(), 287 }); 288 289 /* -------------------------------- ValidateTokenResponse -------------------------------- */ 290 291 export const ValidateTokenResponseSchema = z.object({ 292 code: z.number().int(), 293 message: z.string(), 294 valid: z.boolean(), 295 user_name: z.string().optional(), 296 }); 297 298 export type TrackMetadata = z.infer<typeof TrackMetadataSchema>; 299 300 export type Payload = z.infer<typeof PayloadSchema>; 301 export type SubmitListensRequest = z.infer<typeof SubmitListensRequestSchema>; 302 export type SubmitListensResponse = z.infer< 303 typeof SubmitListensResponseSchema 304 >; 305 export type ValidateTokenResponse = z.infer< 306 typeof ValidateTokenResponseSchema 307 >; 308}