forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
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}