@rocksky/sdk 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +49 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/filter.d.ts +19 -0
- package/dist/filter.d.ts.map +1 -1
- package/dist/generated/types.d.ts +157 -27
- package/dist/generated/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -11
- package/dist/library.d.ts +101 -11
- package/dist/library.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent.ts +1 -1
- package/src/client.test.ts +83 -0
- package/src/client.ts +125 -15
- package/src/filter.ts +20 -0
- package/src/generated/types.ts +172 -31
- package/src/index.ts +9 -1
- package/src/library.ts +115 -19
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { RockskyClient } from "./client.js";
|
|
4
|
+
|
|
5
|
+
const TOKEN = "test-token";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Swap in a fetch that records the request instead of making one. The client
|
|
9
|
+
* builds its fetch handler in the constructor, so install this first.
|
|
10
|
+
*/
|
|
11
|
+
function captureFetch(): {
|
|
12
|
+
calls: Request[];
|
|
13
|
+
only: () => Request;
|
|
14
|
+
restore: () => void;
|
|
15
|
+
} {
|
|
16
|
+
const calls: Request[] = [];
|
|
17
|
+
const real = globalThis.fetch;
|
|
18
|
+
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
19
|
+
calls.push(new Request(input as RequestInfo, init));
|
|
20
|
+
return new Response(JSON.stringify({ status: "ok" }), {
|
|
21
|
+
status: 200,
|
|
22
|
+
headers: { "content-type": "application/json" },
|
|
23
|
+
});
|
|
24
|
+
}) as typeof globalThis.fetch;
|
|
25
|
+
return {
|
|
26
|
+
calls,
|
|
27
|
+
/** The single request that was made; fails loudly if there wasn't exactly one. */
|
|
28
|
+
only: () => {
|
|
29
|
+
expect(calls).toHaveLength(1);
|
|
30
|
+
return calls[0] as Request;
|
|
31
|
+
},
|
|
32
|
+
restore: () => {
|
|
33
|
+
globalThis.fetch = real;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let restore: (() => void) | null = null;
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
restore?.();
|
|
41
|
+
restore = null;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("authenticated request headers", () => {
|
|
45
|
+
test("a JSON procedure keeps its content-type alongside the bearer token", async () => {
|
|
46
|
+
// Regression: the auth wrapper used to merge headers with an object spread.
|
|
47
|
+
// atcute passes a Headers instance, which has no own enumerable properties,
|
|
48
|
+
// so the spread produced `{}` and dropped the `application/json` content-type
|
|
49
|
+
// atcute sets for a JSON body. The AppView then saw text/plain and replied
|
|
50
|
+
// `InvalidRequest: Wrong request encoding (Content-Type): text/plain`.
|
|
51
|
+
const cap = captureFetch();
|
|
52
|
+
restore = cap.restore;
|
|
53
|
+
|
|
54
|
+
const client = new RockskyClient("https://appview.test", TOKEN);
|
|
55
|
+
await client.library().createPlaylist("Late night drive");
|
|
56
|
+
|
|
57
|
+
const req = cap.only();
|
|
58
|
+
expect(req.method).toBe("POST");
|
|
59
|
+
expect(req.headers.get("content-type")).toBe("application/json");
|
|
60
|
+
expect(req.headers.get("authorization")).toBe(`Bearer ${TOKEN}`);
|
|
61
|
+
expect(await req.json()).toEqual({ name: "Late night drive" });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("a query still carries the bearer token", async () => {
|
|
65
|
+
const cap = captureFetch();
|
|
66
|
+
restore = cap.restore;
|
|
67
|
+
|
|
68
|
+
const client = new RockskyClient("https://appview.test", TOKEN);
|
|
69
|
+
await client.library().getPlaylists();
|
|
70
|
+
|
|
71
|
+
expect(cap.only().headers.get("authorization")).toBe(`Bearer ${TOKEN}`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("no token means no authorization header", async () => {
|
|
75
|
+
const cap = captureFetch();
|
|
76
|
+
restore = cap.restore;
|
|
77
|
+
|
|
78
|
+
const client = new RockskyClient("https://appview.test");
|
|
79
|
+
await client.get("app.rocksky.actor.getProfile", { did: "did:plc:test" });
|
|
80
|
+
|
|
81
|
+
expect(cap.only().headers.get("authorization")).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
});
|
package/src/client.ts
CHANGED
|
@@ -10,6 +10,8 @@ import type {
|
|
|
10
10
|
AlbumViewDetailed,
|
|
11
11
|
ArtistViewBasic,
|
|
12
12
|
ArtistViewDetailed,
|
|
13
|
+
ChartsDecadeViewBasic,
|
|
14
|
+
ChartsScrobblerViewBasic,
|
|
13
15
|
ChartsView,
|
|
14
16
|
CreateScrobbleInput,
|
|
15
17
|
FollowAccountOutput,
|
|
@@ -53,6 +55,9 @@ import type {
|
|
|
53
55
|
StatsGlobalStatsView,
|
|
54
56
|
StatsView,
|
|
55
57
|
StatsWrappedView,
|
|
58
|
+
PlaylistCreatePlaylistOutput,
|
|
59
|
+
PlaylistUpdatePlaylistOutput,
|
|
60
|
+
AddSongsOutput,
|
|
56
61
|
UnfollowAccountOutput,
|
|
57
62
|
UpdateSeenOutput,
|
|
58
63
|
} from "./generated/types.js";
|
|
@@ -113,11 +118,16 @@ export class RockskyClient {
|
|
|
113
118
|
let handler = simpleFetchHandler({ service: appview });
|
|
114
119
|
if (token) {
|
|
115
120
|
const inner = handler;
|
|
116
|
-
handler = ((pathname: string, init?: RequestInit) =>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
handler = ((pathname: string, init?: RequestInit) => {
|
|
122
|
+
// `new Headers(init.headers)`, not a spread: atcute hands us a Headers
|
|
123
|
+
// instance, and a Headers has no own enumerable properties — spreading
|
|
124
|
+
// it yields `{}` and silently drops everything already set. That threw
|
|
125
|
+
// away the `content-type: application/json` atcute adds for a JSON
|
|
126
|
+
// body, so the AppView saw text/plain and rejected the request.
|
|
127
|
+
const headers = new Headers(init?.headers);
|
|
128
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
129
|
+
return inner(pathname, { ...init, headers });
|
|
130
|
+
}) as typeof handler;
|
|
121
131
|
}
|
|
122
132
|
this.rpc = new Client({ handler });
|
|
123
133
|
}
|
|
@@ -234,26 +244,67 @@ export class RockskyClient {
|
|
|
234
244
|
return this.topArtistsInterval(limit, offset, Interval.allTime());
|
|
235
245
|
}
|
|
236
246
|
|
|
237
|
-
/** The top tracks chart over a typed {@link DateInterval}.
|
|
238
|
-
|
|
247
|
+
/** The top tracks chart over a typed {@link DateInterval}. Pass `did` to scope
|
|
248
|
+
* it to one actor instead of the platform-wide ranking. */
|
|
249
|
+
async topTracksInterval(
|
|
250
|
+
limit: number,
|
|
251
|
+
offset: number,
|
|
252
|
+
interval: DateInterval,
|
|
253
|
+
did?: string,
|
|
254
|
+
): Promise<SongViewBasic[]> {
|
|
239
255
|
const out = await this.query<{ tracks?: SongViewBasic[] }>("app.rocksky.charts.getTopTracks", {
|
|
240
256
|
limit,
|
|
241
257
|
offset,
|
|
258
|
+
did,
|
|
242
259
|
...interval,
|
|
243
260
|
});
|
|
244
261
|
return out.tracks ?? [];
|
|
245
262
|
}
|
|
246
263
|
|
|
247
|
-
/** The top artists chart over a typed {@link DateInterval}.
|
|
248
|
-
|
|
264
|
+
/** The top artists chart over a typed {@link DateInterval}. Pass `did` to scope
|
|
265
|
+
* it to one actor instead of the platform-wide ranking. */
|
|
266
|
+
async topArtistsInterval(
|
|
267
|
+
limit: number,
|
|
268
|
+
offset: number,
|
|
269
|
+
interval: DateInterval,
|
|
270
|
+
did?: string,
|
|
271
|
+
): Promise<ArtistViewBasic[]> {
|
|
249
272
|
const out = await this.query<{ artists?: ArtistViewBasic[] }>("app.rocksky.charts.getTopArtists", {
|
|
250
273
|
limit,
|
|
251
274
|
offset,
|
|
275
|
+
did,
|
|
252
276
|
...interval,
|
|
253
277
|
});
|
|
254
278
|
return out.artists ?? [];
|
|
255
279
|
}
|
|
256
280
|
|
|
281
|
+
/** Scrobbles grouped by the release decade of the music, over a typed
|
|
282
|
+
* {@link DateInterval}. Pass `did` to scope it to one actor. */
|
|
283
|
+
async decades(
|
|
284
|
+
interval: DateInterval = {},
|
|
285
|
+
did?: string,
|
|
286
|
+
): Promise<ChartsDecadeViewBasic[]> {
|
|
287
|
+
const out = await this.query<{ decades?: ChartsDecadeViewBasic[] }>(
|
|
288
|
+
"app.rocksky.charts.getDecades",
|
|
289
|
+
{ did, ...interval },
|
|
290
|
+
);
|
|
291
|
+
return out.decades ?? [];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** The listeners who scrobbled the most, over a typed {@link DateInterval}.
|
|
295
|
+
* Pass `Interval.allTime()` for the all-time leaderboard. */
|
|
296
|
+
async topScrobblers(
|
|
297
|
+
limit = 20,
|
|
298
|
+
offset = 0,
|
|
299
|
+
interval: DateInterval = {},
|
|
300
|
+
): Promise<ChartsScrobblerViewBasic[]> {
|
|
301
|
+
const out = await this.query<{ scrobblers?: ChartsScrobblerViewBasic[] }>(
|
|
302
|
+
"app.rocksky.charts.getTopScrobblers",
|
|
303
|
+
{ limit, offset, ...interval },
|
|
304
|
+
);
|
|
305
|
+
return out.scrobblers ?? [];
|
|
306
|
+
}
|
|
307
|
+
|
|
257
308
|
/** The album catalog, optionally filtered by `genre` and/or an RSQL
|
|
258
309
|
* {@link Filter} expression (see {@link AlbumFields}). */
|
|
259
310
|
async catalogAlbums(
|
|
@@ -406,9 +457,11 @@ export class RockskyClient {
|
|
|
406
457
|
return this.query("app.rocksky.artist.getArtist", { uri });
|
|
407
458
|
}
|
|
408
459
|
/** Resolve full canonical metadata for a bare title + artist
|
|
409
|
-
* (`app.rocksky.song.matchSong`); optionally anchor with `mbId` / `isrc`.
|
|
410
|
-
|
|
411
|
-
|
|
460
|
+
* (`app.rocksky.song.matchSong`); optionally anchor with `mbId` / `isrc`.
|
|
461
|
+
* `album` steers the match toward that release (case-insensitive) so a
|
|
462
|
+
* remaster/live/single edition doesn't shadow the intended album. */
|
|
463
|
+
matchSong(title: string, artist: string, mbId?: string, isrc?: string, album?: string): Promise<SongViewDetailed> {
|
|
464
|
+
return this.query("app.rocksky.song.matchSong", { title, artist, mbId, isrc, album });
|
|
412
465
|
}
|
|
413
466
|
/** A single song by at:// `uri` (or by `mbid` / `isrc` / `spotifyId`). */
|
|
414
467
|
song(opts: {
|
|
@@ -508,14 +561,71 @@ export class RockskyClient {
|
|
|
508
561
|
spotifyCurrentlyPlaying(actor: string): Promise<PlayerCurrentlyPlayingViewDetailed> {
|
|
509
562
|
return this.query("app.rocksky.spotify.getCurrentlyPlaying", { actor });
|
|
510
563
|
}
|
|
511
|
-
/** The playlist catalog
|
|
512
|
-
|
|
513
|
-
|
|
564
|
+
/** The playlist catalog, optionally filtered by an RSQL {@link Filter}
|
|
565
|
+
* expression (see {@link PlaylistFields}). */
|
|
566
|
+
playlists(
|
|
567
|
+
limit = 50,
|
|
568
|
+
offset = 0,
|
|
569
|
+
filter?: string | Filter,
|
|
570
|
+
): Promise<PlaylistGetPlaylistsOutput> {
|
|
571
|
+
return this.query("app.rocksky.playlist.getPlaylists", {
|
|
572
|
+
limit,
|
|
573
|
+
offset,
|
|
574
|
+
filter: filter?.toString(),
|
|
575
|
+
});
|
|
514
576
|
}
|
|
515
577
|
/** A single playlist with its items. */
|
|
516
578
|
playlist(uri: string): Promise<PlaylistViewDetailed> {
|
|
517
579
|
return this.query("app.rocksky.playlist.getPlaylist", { uri });
|
|
518
580
|
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Create a playlist (`app.rocksky.playlist.createPlaylist`). Auth required.
|
|
584
|
+
* Publishes an app.rocksky.playlist record to the caller's repo; the AppView
|
|
585
|
+
* only lists it once the commit has been ingested.
|
|
586
|
+
*/
|
|
587
|
+
createPlaylist(input: {
|
|
588
|
+
name: string;
|
|
589
|
+
description?: string;
|
|
590
|
+
pictureUrl?: string;
|
|
591
|
+
}): Promise<PlaylistCreatePlaylistOutput> {
|
|
592
|
+
return this.post("app.rocksky.playlist.createPlaylist", { params: input });
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** Rename or re-describe a playlist (`app.rocksky.playlist.updatePlaylist`). Owner only. */
|
|
596
|
+
updatePlaylist(input: {
|
|
597
|
+
uri: string;
|
|
598
|
+
name?: string;
|
|
599
|
+
description?: string;
|
|
600
|
+
pictureUrl?: string;
|
|
601
|
+
}): Promise<PlaylistUpdatePlaylistOutput> {
|
|
602
|
+
return this.post("app.rocksky.playlist.updatePlaylist", { params: input });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Add songs to a playlist (`app.rocksky.playlist.addSongs`). Owner only.
|
|
607
|
+
* `songs` are app.rocksky.song AT-URIs; returns the created entry URIs.
|
|
608
|
+
*/
|
|
609
|
+
addSongs(uri: string, songs: string[]): Promise<AddSongsOutput> {
|
|
610
|
+
return this.post("app.rocksky.playlist.addSongs", {
|
|
611
|
+
params: { uri, songs },
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** Delete a playlist and the caller's own entries (`app.rocksky.playlist.removePlaylist`). Owner only. */
|
|
616
|
+
removePlaylist(uri: string): Promise<void> {
|
|
617
|
+
return this.post("app.rocksky.playlist.removePlaylist", { params: { uri } });
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Remove a song from a playlist (`app.rocksky.playlist.removeTrack`). An
|
|
622
|
+
* entry can only be retracted by the repo that published it.
|
|
623
|
+
*/
|
|
624
|
+
removeTrack(uri: string, songUri: string): Promise<void> {
|
|
625
|
+
return this.post("app.rocksky.playlist.removeTrack", {
|
|
626
|
+
params: { uri, songUri },
|
|
627
|
+
});
|
|
628
|
+
}
|
|
519
629
|
/** Shouts on an album. */
|
|
520
630
|
albumShouts(uri: string, limit = 50, offset = 0): Promise<GetAlbumShoutsOutput> {
|
|
521
631
|
return this.query("app.rocksky.shout.getAlbumShouts", { uri, limit, offset });
|
package/src/filter.ts
CHANGED
|
@@ -168,6 +168,26 @@ export const ArtistFields = {
|
|
|
168
168
|
createdAt: "createdAt",
|
|
169
169
|
} as const;
|
|
170
170
|
|
|
171
|
+
/** Filterable fields of app.rocksky.playlist.getPlaylists (`track.*` selectors match the playlist's contents). */
|
|
172
|
+
export const PlaylistFields = {
|
|
173
|
+
name: "name",
|
|
174
|
+
title: "title",
|
|
175
|
+
description: "description",
|
|
176
|
+
uri: "uri",
|
|
177
|
+
spotifyLink: "spotifyLink",
|
|
178
|
+
tidalLink: "tidalLink",
|
|
179
|
+
appleMusicLink: "appleMusicLink",
|
|
180
|
+
createdAt: "createdAt",
|
|
181
|
+
updatedAt: "updatedAt",
|
|
182
|
+
curatorDid: "curatorDid",
|
|
183
|
+
curatorHandle: "curatorHandle",
|
|
184
|
+
curatorName: "curatorName",
|
|
185
|
+
trackTitle: "track.title",
|
|
186
|
+
trackArtist: "track.artist",
|
|
187
|
+
trackAlbum: "track.album",
|
|
188
|
+
trackAlbumArtist: "track.albumArtist",
|
|
189
|
+
} as const;
|
|
190
|
+
|
|
171
191
|
/** Filterable fields of app.rocksky.scrobble.getScrobbles (dotted selectors reach the joined track/user/artist). */
|
|
172
192
|
export const ScrobbleFields = {
|
|
173
193
|
uri: "uri",
|
package/src/generated/types.ts
CHANGED
|
@@ -126,6 +126,18 @@ export interface AddItemsToQueueParams {
|
|
|
126
126
|
shuffle?: boolean;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
export interface AddSongsOutput {
|
|
130
|
+
/** AT-URIs of the created app.rocksky.playlist.song records */
|
|
131
|
+
uris: AtUri[];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface AddSongsParams {
|
|
135
|
+
/** The URI of the playlist to add the songs to */
|
|
136
|
+
uri: AtUri;
|
|
137
|
+
/** AT-URIs of the app.rocksky.song records to add */
|
|
138
|
+
songs: AtUri[];
|
|
139
|
+
}
|
|
140
|
+
|
|
129
141
|
export interface AlbumGetAlbumParams {
|
|
130
142
|
/** The URI of the album to retrieve. */
|
|
131
143
|
uri: AtUri;
|
|
@@ -355,6 +367,34 @@ export interface ArtistViewDetailed {
|
|
|
355
367
|
tags?: string[];
|
|
356
368
|
}
|
|
357
369
|
|
|
370
|
+
export interface ChartsDecadeViewBasic {
|
|
371
|
+
/** The first year of the decade, e.g. 1990. */
|
|
372
|
+
decade?: number;
|
|
373
|
+
/** The number of scrobbles of music released in this decade. */
|
|
374
|
+
scrobbles?: number;
|
|
375
|
+
/** The number of distinct albums scrobbled from this decade. */
|
|
376
|
+
uniqueAlbums?: number;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export interface ChartsScrobblerViewBasic {
|
|
380
|
+
/** The unique identifier of the actor. */
|
|
381
|
+
id?: string;
|
|
382
|
+
/** The DID of the actor. */
|
|
383
|
+
did?: string;
|
|
384
|
+
/** The handle of the actor. */
|
|
385
|
+
handle?: string;
|
|
386
|
+
/** The display name of the actor. */
|
|
387
|
+
displayName?: string;
|
|
388
|
+
/** The URL of the actor's avatar image. */
|
|
389
|
+
avatar?: Uri;
|
|
390
|
+
/** The number of scrobbles in the requested window. */
|
|
391
|
+
scrobbles?: number;
|
|
392
|
+
/** The number of distinct artists scrobbled in the window. */
|
|
393
|
+
uniqueArtists?: number;
|
|
394
|
+
/** The number of distinct tracks scrobbled in the window. */
|
|
395
|
+
uniqueTracks?: number;
|
|
396
|
+
}
|
|
397
|
+
|
|
358
398
|
export interface ChartsScrobbleViewBasic {
|
|
359
399
|
/** The date of the scrobble. */
|
|
360
400
|
date?: DateTime;
|
|
@@ -755,10 +795,12 @@ export interface GetActorPlaylistsOutput {
|
|
|
755
795
|
export interface GetActorPlaylistsParams {
|
|
756
796
|
/** The DID or handle of the actor */
|
|
757
797
|
did: AtIdentifier;
|
|
758
|
-
/** The maximum number of
|
|
798
|
+
/** The maximum number of playlists to return */
|
|
759
799
|
limit?: number;
|
|
760
800
|
/** The offset for pagination */
|
|
761
801
|
offset?: number;
|
|
802
|
+
/** RSQL filter expression, e.g. `name=="Road trip*";track.artist=="Daft Punk"`. Supports ==, !=, <, <=, >, >=, =in=, =out=, and `;`/`and`, `,`/`or` combinators, `*` wildcards in string values. Filterable fields: name, title, description, uri, spotifyLink, tidalLink, appleMusicLink, createdAt, updatedAt. The `track.title`, `track.artist`, `track.album` and `track.albumArtist` selectors match the playlist's contents, returning playlists that contain a matching track; several `track.*` terms joined with `;` must all be satisfied by the same track. */
|
|
803
|
+
filter?: string;
|
|
762
804
|
}
|
|
763
805
|
|
|
764
806
|
export interface GetActorScrobblesOutput {
|
|
@@ -966,6 +1008,19 @@ export interface GetCoverArtUrlParams {
|
|
|
966
1008
|
size?: number;
|
|
967
1009
|
}
|
|
968
1010
|
|
|
1011
|
+
export interface GetDecadesOutput {
|
|
1012
|
+
decades?: ChartsDecadeViewBasic[];
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
export interface GetDecadesParams {
|
|
1016
|
+
/** The DID or handle of the actor to scope the chart to */
|
|
1017
|
+
did?: AtIdentifier;
|
|
1018
|
+
/** The start date to count scrobbles from (ISO 8601 format) */
|
|
1019
|
+
startDate?: DateTime;
|
|
1020
|
+
/** The end date to count scrobbles to (ISO 8601 format) */
|
|
1021
|
+
endDate?: DateTime;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
969
1024
|
export interface GetDownloadUrlOutput {
|
|
970
1025
|
/** The resolved media or cover-art URL. */
|
|
971
1026
|
url: Uri;
|
|
@@ -1368,6 +1423,8 @@ export interface GetTopArtistsOutput {
|
|
|
1368
1423
|
}
|
|
1369
1424
|
|
|
1370
1425
|
export interface GetTopArtistsParams {
|
|
1426
|
+
/** The DID or handle of the actor to scope the chart to */
|
|
1427
|
+
did?: AtIdentifier;
|
|
1371
1428
|
/** The maximum number of artists to return */
|
|
1372
1429
|
limit?: number;
|
|
1373
1430
|
/** The offset for pagination */
|
|
@@ -1378,6 +1435,21 @@ export interface GetTopArtistsParams {
|
|
|
1378
1435
|
endDate?: DateTime;
|
|
1379
1436
|
}
|
|
1380
1437
|
|
|
1438
|
+
export interface GetTopScrobblersOutput {
|
|
1439
|
+
scrobblers?: ChartsScrobblerViewBasic[];
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
export interface GetTopScrobblersParams {
|
|
1443
|
+
/** The maximum number of scrobblers to return */
|
|
1444
|
+
limit?: number;
|
|
1445
|
+
/** The offset for pagination */
|
|
1446
|
+
offset?: number;
|
|
1447
|
+
/** The start date to count scrobbles from (ISO 8601 format) */
|
|
1448
|
+
startDate?: DateTime;
|
|
1449
|
+
/** The end date to count scrobbles to (ISO 8601 format) */
|
|
1450
|
+
endDate?: DateTime;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1381
1453
|
export interface GetTopSongsOutput {
|
|
1382
1454
|
|
|
1383
1455
|
}
|
|
@@ -1394,6 +1466,8 @@ export interface GetTopTracksOutput {
|
|
|
1394
1466
|
}
|
|
1395
1467
|
|
|
1396
1468
|
export interface GetTopTracksParams {
|
|
1469
|
+
/** The DID or handle of the actor to scope the chart to */
|
|
1470
|
+
did?: AtIdentifier;
|
|
1397
1471
|
/** The maximum number of tracks to return */
|
|
1398
1472
|
limit?: number;
|
|
1399
1473
|
/** The offset for pagination */
|
|
@@ -1565,6 +1639,23 @@ export interface LibrarySearchParams {
|
|
|
1565
1639
|
songOffset?: number;
|
|
1566
1640
|
}
|
|
1567
1641
|
|
|
1642
|
+
export interface LibraryUpdatePlaylistInput {
|
|
1643
|
+
/** The playlist id to update. */
|
|
1644
|
+
playlistId: string;
|
|
1645
|
+
/** New playlist name. */
|
|
1646
|
+
name?: string;
|
|
1647
|
+
/** New playlist comment. */
|
|
1648
|
+
comment?: string;
|
|
1649
|
+
/** A song id to add to the playlist. */
|
|
1650
|
+
songIdToAdd?: string;
|
|
1651
|
+
/** A track index to remove from the playlist. */
|
|
1652
|
+
songIndexToRemove?: number;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
export interface LibraryUpdatePlaylistOutput {
|
|
1656
|
+
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1568
1659
|
export interface LikeRecord {
|
|
1569
1660
|
/** The date when the like was created. */
|
|
1570
1661
|
createdAt: DateTime;
|
|
@@ -1599,6 +1690,8 @@ export interface MatchSongParams {
|
|
|
1599
1690
|
title: string;
|
|
1600
1691
|
/** The artist of the song to retrieve */
|
|
1601
1692
|
artist: string;
|
|
1693
|
+
/** Optional album title — candidates whose album matches it (case-insensitive) are preferred, so remaster/live/single editions don't shadow the intended release */
|
|
1694
|
+
album?: string;
|
|
1602
1695
|
/** Optional MusicBrainz recording ID to anchor the match */
|
|
1603
1696
|
mbId?: string;
|
|
1604
1697
|
/** Optional International Standard Recording Code (ISRC) to anchor the match */
|
|
@@ -1610,6 +1703,8 @@ export interface MirrorSourceView {
|
|
|
1610
1703
|
provider: string;
|
|
1611
1704
|
/** Whether scrobbles from this source are being mirrored into Rocksky. */
|
|
1612
1705
|
enabled: boolean;
|
|
1706
|
+
/** Whether Rocksky scrobbles are mirrored out to this source. Enabled unless the user turned it off. teal.fm only. */
|
|
1707
|
+
pushEnabled?: boolean;
|
|
1613
1708
|
/** Username on the external service (Last.fm / ListenBrainz). Null for Teal.fm. */
|
|
1614
1709
|
externalUsername?: string;
|
|
1615
1710
|
/** True when an API key is stored. Last.fm/ListenBrainz only; always false for Teal.fm. */
|
|
@@ -1723,16 +1818,27 @@ export interface PlayFileParams {
|
|
|
1723
1818
|
fileId: string;
|
|
1724
1819
|
}
|
|
1725
1820
|
|
|
1821
|
+
export interface PlaylistCreatePlaylistOutput {
|
|
1822
|
+
/** The AT-URI of the created app.rocksky.playlist record. */
|
|
1823
|
+
uri: AtUri;
|
|
1824
|
+
/** The CID of the created app.rocksky.playlist record. */
|
|
1825
|
+
cid: string;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1726
1828
|
export interface PlaylistCreatePlaylistParams {
|
|
1727
1829
|
/** The name of the playlist */
|
|
1728
1830
|
name: string;
|
|
1729
1831
|
/** A brief description of the playlist */
|
|
1730
1832
|
description?: string;
|
|
1833
|
+
/** The URL of the cover image for the playlist */
|
|
1834
|
+
pictureUrl?: Uri;
|
|
1731
1835
|
}
|
|
1732
1836
|
|
|
1733
1837
|
export interface PlaylistGetPlaylistParams {
|
|
1734
1838
|
/** The URI of the playlist to retrieve. */
|
|
1735
1839
|
uri: AtUri;
|
|
1840
|
+
/** RSQL filter expression applied to the playlist's tracks, e.g. `artist=="Daft Punk";duration=gt=200000`. Supports ==, !=, <, <=, >, >=, =in=, =out=, and `;`/`and`, `,`/`or` combinators, `*` wildcards in string values. Filterable fields: title, artist, album, albumArtist, genre, composer, label, duration, trackNumber, discNumber, mbId, isrc, sha256, uri, albumUri, artistUri, addedAt */
|
|
1841
|
+
filter?: string;
|
|
1736
1842
|
}
|
|
1737
1843
|
|
|
1738
1844
|
export interface PlaylistGetPlaylistsOutput {
|
|
@@ -1744,15 +1850,8 @@ export interface PlaylistGetPlaylistsParams {
|
|
|
1744
1850
|
limit?: number;
|
|
1745
1851
|
/** The offset for pagination, used to skip a number of playlists. */
|
|
1746
1852
|
offset?: number;
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
export interface PlaylistItemRecord {
|
|
1750
|
-
subject: StrongRef;
|
|
1751
|
-
/** The date the playlist was created. */
|
|
1752
|
-
createdAt: DateTime;
|
|
1753
|
-
track: SongViewBasic;
|
|
1754
|
-
/** The order of the item in the playlist. */
|
|
1755
|
-
order: number;
|
|
1853
|
+
/** RSQL filter expression, e.g. `name=="Road trip*";track.artist=="Daft Punk"`. Supports ==, !=, <, <=, >, >=, =in=, =out=, and `;`/`and`, `,`/`or` combinators, `*` wildcards in string values. Filterable fields: name, title, description, uri, spotifyLink, tidalLink, appleMusicLink, createdAt, updatedAt, curatorDid, curatorHandle, curatorName. The `track.title`, `track.artist`, `track.album` and `track.albumArtist` selectors match the playlist's contents, returning playlists that contain a matching track; several `track.*` terms joined with `;` must all be satisfied by the same track. */
|
|
1854
|
+
filter?: string;
|
|
1756
1855
|
}
|
|
1757
1856
|
|
|
1758
1857
|
export interface PlaylistRecord {
|
|
@@ -1776,6 +1875,45 @@ export interface PlaylistRecord {
|
|
|
1776
1875
|
appleMusicLink?: string;
|
|
1777
1876
|
}
|
|
1778
1877
|
|
|
1878
|
+
export interface PlaylistSongRecord {
|
|
1879
|
+
/** Strong reference (AT-URI + CID) to the parent app.rocksky.playlist record. */
|
|
1880
|
+
playlist: StrongRef;
|
|
1881
|
+
/** Strong reference (AT-URI + CID) to the app.rocksky.song record this entry points at. */
|
|
1882
|
+
song: StrongRef;
|
|
1883
|
+
/** The title of the song. */
|
|
1884
|
+
title: string;
|
|
1885
|
+
/** The artist of the song. */
|
|
1886
|
+
artist: string;
|
|
1887
|
+
/** The album the song belongs to. */
|
|
1888
|
+
album: string;
|
|
1889
|
+
/** The album artist of the song. */
|
|
1890
|
+
albumArtist: string;
|
|
1891
|
+
/** The duration of the song in milliseconds. */
|
|
1892
|
+
duration: number;
|
|
1893
|
+
/** The URL of the album art of the song. */
|
|
1894
|
+
albumArtUrl?: Uri;
|
|
1895
|
+
/** The date and time the song was added to the playlist. */
|
|
1896
|
+
addedAt: DateTime;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
export interface PlaylistUpdatePlaylistOutput {
|
|
1900
|
+
/** The AT-URI of the updated app.rocksky.playlist record. */
|
|
1901
|
+
uri: AtUri;
|
|
1902
|
+
/** The CID of the updated app.rocksky.playlist record. */
|
|
1903
|
+
cid: string;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
export interface PlaylistUpdatePlaylistParams {
|
|
1907
|
+
/** The URI of the playlist to update */
|
|
1908
|
+
uri: AtUri;
|
|
1909
|
+
/** The new name of the playlist */
|
|
1910
|
+
name?: string;
|
|
1911
|
+
/** The new description of the playlist */
|
|
1912
|
+
description?: string;
|
|
1913
|
+
/** The new cover image URL for the playlist */
|
|
1914
|
+
pictureUrl?: Uri;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1779
1917
|
/** Basic view of a playlist, including its metadata */
|
|
1780
1918
|
export interface PlaylistViewBasic {
|
|
1781
1919
|
/** The unique identifier of the playlist. */
|
|
@@ -1800,6 +1938,8 @@ export interface PlaylistViewBasic {
|
|
|
1800
1938
|
createdAt?: DateTime;
|
|
1801
1939
|
/** The number of tracks in the playlist. */
|
|
1802
1940
|
trackCount?: number;
|
|
1941
|
+
/** Album-art URLs of up to four of the playlist's tracks, for rendering a cover mosaic when the playlist has no picture of its own. */
|
|
1942
|
+
trackArts?: Uri[];
|
|
1803
1943
|
}
|
|
1804
1944
|
|
|
1805
1945
|
/** Detailed view of a playlist, including its tracks and metadata */
|
|
@@ -1858,6 +1998,8 @@ export interface PutMirrorSourceInput {
|
|
|
1858
1998
|
provider: string;
|
|
1859
1999
|
/** Enable or disable mirroring for this provider. */
|
|
1860
2000
|
enabled?: boolean;
|
|
2001
|
+
/** Enable or disable mirroring Rocksky scrobbles out to this provider. teal.fm only. */
|
|
2002
|
+
pushEnabled?: boolean;
|
|
1861
2003
|
/** External username (Last.fm / ListenBrainz). Required when enabling those providers. Ignored for Teal.fm. */
|
|
1862
2004
|
externalUsername?: string;
|
|
1863
2005
|
/** API key / token to be encrypted at rest. Omit to leave the existing key unchanged. Pass an empty string to clear it. */
|
|
@@ -1929,8 +2071,10 @@ export interface RemoveShoutParams {
|
|
|
1929
2071
|
export interface RemoveTrackParams {
|
|
1930
2072
|
/** The URI of the playlist to remove the track from */
|
|
1931
2073
|
uri: AtUri;
|
|
1932
|
-
/** The
|
|
1933
|
-
|
|
2074
|
+
/** The URI of the app.rocksky.song record to remove. Removes every copy of it; pass `index` instead to remove one. */
|
|
2075
|
+
songUri?: AtUri;
|
|
2076
|
+
/** 0-based position of the entry to remove, in the order getPlaylist returns. */
|
|
2077
|
+
index?: number;
|
|
1934
2078
|
}
|
|
1935
2079
|
|
|
1936
2080
|
export interface ReplyShoutInput {
|
|
@@ -2166,6 +2310,8 @@ export interface ScrobbleViewDetailed {
|
|
|
2166
2310
|
/** The SHA256 hash of the scrobble data. */
|
|
2167
2311
|
sha256?: string;
|
|
2168
2312
|
liked?: boolean;
|
|
2313
|
+
/** The URI of the track (song) this scrobble is of. */
|
|
2314
|
+
trackUri?: string;
|
|
2169
2315
|
likesCount?: number;
|
|
2170
2316
|
/** The number of listeners */
|
|
2171
2317
|
listeners?: number;
|
|
@@ -2405,6 +2551,10 @@ export interface SongViewBasic {
|
|
|
2405
2551
|
discNumber?: number;
|
|
2406
2552
|
/** The number of times the song has been played. */
|
|
2407
2553
|
playCount?: number;
|
|
2554
|
+
/** The number of users who have loved this song. */
|
|
2555
|
+
likesCount?: number;
|
|
2556
|
+
/** Whether the authenticated user has loved this song. False when unauthenticated. */
|
|
2557
|
+
liked?: boolean;
|
|
2408
2558
|
/** The number of unique listeners who have played the song. */
|
|
2409
2559
|
uniqueListeners?: number;
|
|
2410
2560
|
/** The URI of the album the song belongs to. */
|
|
@@ -2445,6 +2595,10 @@ export interface SongViewDetailed {
|
|
|
2445
2595
|
discNumber?: number;
|
|
2446
2596
|
/** The number of times the song has been played. */
|
|
2447
2597
|
playCount?: number;
|
|
2598
|
+
/** The number of users who have loved this song. */
|
|
2599
|
+
likesCount?: number;
|
|
2600
|
+
/** Whether the authenticated user has loved this song. False when unauthenticated. */
|
|
2601
|
+
liked?: boolean;
|
|
2448
2602
|
/** The number of unique listeners who have played the song. */
|
|
2449
2603
|
uniqueListeners?: number;
|
|
2450
2604
|
/** The URI of the album the song belongs to. */
|
|
@@ -2714,23 +2868,6 @@ export interface UpdateNowPlayingOutput {
|
|
|
2714
2868
|
|
|
2715
2869
|
}
|
|
2716
2870
|
|
|
2717
|
-
export interface UpdatePlaylistInput {
|
|
2718
|
-
/** The playlist id to update. */
|
|
2719
|
-
playlistId: string;
|
|
2720
|
-
/** New playlist name. */
|
|
2721
|
-
name?: string;
|
|
2722
|
-
/** New playlist comment. */
|
|
2723
|
-
comment?: string;
|
|
2724
|
-
/** A song id to add to the playlist. */
|
|
2725
|
-
songIdToAdd?: string;
|
|
2726
|
-
/** A track index to remove from the playlist. */
|
|
2727
|
-
songIndexToRemove?: number;
|
|
2728
|
-
}
|
|
2729
|
-
|
|
2730
|
-
export interface UpdatePlaylistOutput {
|
|
2731
|
-
|
|
2732
|
-
}
|
|
2733
|
-
|
|
2734
2871
|
export interface UpdateSeenInput {
|
|
2735
2872
|
/** The ids of the notifications to mark as viewed. Omit to mark all. */
|
|
2736
2873
|
ids?: string[];
|
|
@@ -2770,8 +2907,10 @@ export interface Endpoints {
|
|
|
2770
2907
|
"app.rocksky.artist.getArtistRecentListeners": GetArtistRecentListenersOutput;
|
|
2771
2908
|
"app.rocksky.artist.getArtists": ArtistGetArtistsOutput;
|
|
2772
2909
|
"app.rocksky.artist.getArtistTracks": GetArtistTracksOutput;
|
|
2910
|
+
"app.rocksky.charts.getDecades": GetDecadesOutput;
|
|
2773
2911
|
"app.rocksky.charts.getScrobblesChart": ChartsView;
|
|
2774
2912
|
"app.rocksky.charts.getTopArtists": GetTopArtistsOutput;
|
|
2913
|
+
"app.rocksky.charts.getTopScrobblers": GetTopScrobblersOutput;
|
|
2775
2914
|
"app.rocksky.charts.getTopTracks": GetTopTracksOutput;
|
|
2776
2915
|
"app.rocksky.dropbox.downloadFile": void;
|
|
2777
2916
|
"app.rocksky.dropbox.getFiles": DropboxFileListView;
|
|
@@ -2835,7 +2974,7 @@ export interface Endpoints {
|
|
|
2835
2974
|
"app.rocksky.library.startScan": StartScanOutput;
|
|
2836
2975
|
"app.rocksky.library.unstar": UnstarOutput;
|
|
2837
2976
|
"app.rocksky.library.updateNowPlaying": UpdateNowPlayingOutput;
|
|
2838
|
-
"app.rocksky.library.updatePlaylist":
|
|
2977
|
+
"app.rocksky.library.updatePlaylist": LibraryUpdatePlaylistOutput;
|
|
2839
2978
|
"app.rocksky.like.dislikeShout": ShoutView;
|
|
2840
2979
|
"app.rocksky.like.dislikeSong": SongViewDetailed;
|
|
2841
2980
|
"app.rocksky.like.likeShout": ShoutView;
|
|
@@ -2856,7 +2995,8 @@ export interface Endpoints {
|
|
|
2856
2995
|
"app.rocksky.player.playFile": void;
|
|
2857
2996
|
"app.rocksky.player.previous": void;
|
|
2858
2997
|
"app.rocksky.player.seek": void;
|
|
2859
|
-
"app.rocksky.playlist.
|
|
2998
|
+
"app.rocksky.playlist.addSongs": AddSongsOutput;
|
|
2999
|
+
"app.rocksky.playlist.createPlaylist": PlaylistCreatePlaylistOutput;
|
|
2860
3000
|
"app.rocksky.playlist.getPlaylist": PlaylistViewDetailed;
|
|
2861
3001
|
"app.rocksky.playlist.getPlaylists": PlaylistGetPlaylistsOutput;
|
|
2862
3002
|
"app.rocksky.playlist.insertDirectory": void;
|
|
@@ -2864,6 +3004,7 @@ export interface Endpoints {
|
|
|
2864
3004
|
"app.rocksky.playlist.removePlaylist": void;
|
|
2865
3005
|
"app.rocksky.playlist.removeTrack": void;
|
|
2866
3006
|
"app.rocksky.playlist.startPlaylist": void;
|
|
3007
|
+
"app.rocksky.playlist.updatePlaylist": PlaylistUpdatePlaylistOutput;
|
|
2867
3008
|
"app.rocksky.rockbox.getAudioSettings": RockboxSettingsView;
|
|
2868
3009
|
"app.rocksky.rockbox.putAudioSettings": RockboxSettingsView;
|
|
2869
3010
|
"app.rocksky.scrobble.createScrobble": ScrobbleViewBasic;
|