@rocksky/sdk 0.3.0 → 0.5.0

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.
Files changed (51) hide show
  1. package/README.md +71 -245
  2. package/dist/agent.d.ts +82 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/client.d.ts +165 -111
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/dedup.d.ts +38 -0
  7. package/dist/dedup.d.ts.map +1 -0
  8. package/dist/errors.d.ts +3 -23
  9. package/dist/errors.d.ts.map +1 -1
  10. package/dist/generated/types.d.ts +106 -8
  11. package/dist/generated/types.d.ts.map +1 -1
  12. package/dist/hash.d.ts +7 -0
  13. package/dist/hash.d.ts.map +1 -0
  14. package/dist/index.d.ts +17 -17
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6956 -1267
  17. package/dist/jetstream.d.ts +17 -0
  18. package/dist/jetstream.d.ts.map +1 -0
  19. package/package.json +15 -11
  20. package/src/agent.ts +278 -0
  21. package/src/client.ts +393 -212
  22. package/src/dedup.ts +207 -0
  23. package/src/errors.ts +6 -43
  24. package/src/generated/types.ts +115 -8
  25. package/src/hash.ts +22 -0
  26. package/src/index.ts +17 -86
  27. package/src/jetstream.ts +122 -0
  28. package/src/http.ts +0 -195
  29. package/src/namespaces/_helpers.ts +0 -27
  30. package/src/namespaces/actor.ts +0 -93
  31. package/src/namespaces/album.ts +0 -34
  32. package/src/namespaces/apikey.ts +0 -50
  33. package/src/namespaces/artist.ts +0 -68
  34. package/src/namespaces/charts.ts +0 -38
  35. package/src/namespaces/dropbox.ts +0 -53
  36. package/src/namespaces/feed.ts +0 -112
  37. package/src/namespaces/googledrive.ts +0 -41
  38. package/src/namespaces/graph.ts +0 -62
  39. package/src/namespaces/like.ts +0 -46
  40. package/src/namespaces/mirror.ts +0 -27
  41. package/src/namespaces/player.ts +0 -125
  42. package/src/namespaces/playlist.ts +0 -95
  43. package/src/namespaces/scrobble.ts +0 -41
  44. package/src/namespaces/shout.ts +0 -99
  45. package/src/namespaces/song.ts +0 -60
  46. package/src/namespaces/spotify.ts +0 -56
  47. package/src/namespaces/stats.ts +0 -27
  48. package/src/paginate.ts +0 -90
  49. package/src/pipe.ts +0 -146
  50. package/src/realtime.ts +0 -408
  51. package/src/types.ts +0 -41
package/src/client.ts CHANGED
@@ -1,252 +1,433 @@
1
- import { buildConfig, type HttpClientConfig, xrpcCall } from "./http.js";
2
- import { makeCall } from "./namespaces/_helpers.js";
3
- import {
4
- type PaginateArgs,
5
- paginate as paginateFn,
6
- } from "./paginate.js";
7
- import {
8
- RealtimeClient,
9
- type RealtimeOptions,
10
- createRealtimeClient,
11
- } from "./realtime.js";
12
- import { ActorNamespace } from "./namespaces/actor.js";
13
- import { AlbumNamespace } from "./namespaces/album.js";
14
- import { ApikeyNamespace } from "./namespaces/apikey.js";
15
- import { ArtistNamespace } from "./namespaces/artist.js";
16
- import { ChartsNamespace } from "./namespaces/charts.js";
17
- import { DropboxNamespace } from "./namespaces/dropbox.js";
18
- import { FeedNamespace } from "./namespaces/feed.js";
19
- import { GoogleDriveNamespace } from "./namespaces/googledrive.js";
20
- import { GraphNamespace } from "./namespaces/graph.js";
21
- import { LikeNamespace } from "./namespaces/like.js";
22
- import { MirrorNamespace } from "./namespaces/mirror.js";
23
- import { PlayerNamespace } from "./namespaces/player.js";
24
- import { PlaylistNamespace } from "./namespaces/playlist.js";
25
- import { ScrobbleNamespace } from "./namespaces/scrobble.js";
26
- import { ShoutNamespace } from "./namespaces/shout.js";
27
- import { SongNamespace } from "./namespaces/song.js";
28
- import { SpotifyNamespace } from "./namespaces/spotify.js";
29
- import { StatsNamespace } from "./namespaces/stats.js";
30
- import type { Endpoints } from "./generated/types.js";
1
+ import { Client, simpleFetchHandler } from "@atcute/client";
2
+
3
+ import { RockskyError } from "./errors.js";
31
4
  import type {
32
- AuthProvider,
33
- ClientOptions,
34
- FetchLike,
35
- RequestOptions,
36
- } from "./types.js";
37
-
38
- type XrpcOpts = {
39
- params?: Record<string, unknown>;
40
- body?: unknown;
41
- requireAuth?: boolean;
42
- } & RequestOptions;
5
+ ActorProfileViewBasic,
6
+ ActorProfileViewDetailed,
7
+ AlbumViewBasic,
8
+ ArtistViewBasic,
9
+ FeedSearchResultsView,
10
+ GetActorAlbumsOutput,
11
+ GetActorArtistsOutput,
12
+ GetActorScrobblesOutput,
13
+ ScrobbleViewBasic,
14
+ SongViewBasic,
15
+ StatsGlobalStatsView,
16
+ } from "./generated/types.js";
17
+
18
+ /**
19
+ * A typed date window for the `top*` charts. Build one with the {@link Interval}
20
+ * factories; `range` bounds are RFC-3339 datetimes.
21
+ */
22
+ export interface DateInterval {
23
+ startDate?: string;
24
+ endDate?: string;
25
+ }
43
26
 
27
+ function since(days = 0, months = 0, years = 0): DateInterval {
28
+ const now = new Date();
29
+ const start = new Date(now);
30
+ start.setUTCFullYear(start.getUTCFullYear() - years);
31
+ start.setUTCMonth(start.getUTCMonth() - months);
32
+ start.setUTCDate(start.getUTCDate() - days);
33
+ return { startDate: start.toISOString(), endDate: now.toISOString() };
34
+ }
35
+
36
+ /** Factories for {@link DateInterval} windows used by the `top*Interval` charts. */
37
+ export const Interval = {
38
+ /** No bounds — the all-time chart. */
39
+ allTime: (): DateInterval => ({}),
40
+ /** The last `n` days ending now. */
41
+ lastDays: (n: number): DateInterval => since(n),
42
+ /** The last `n` weeks ending now. */
43
+ lastWeeks: (n: number): DateInterval => since(7 * n),
44
+ /** The last `n` months ending now. */
45
+ lastMonths: (n: number): DateInterval => since(0, n),
46
+ /** The last `n` years ending now. */
47
+ lastYears: (n: number): DateInterval => since(0, 0, n),
48
+ /** An explicit closed `[start, end]` window. */
49
+ range: (start: Date, end: Date): DateInterval => ({
50
+ startDate: start.toISOString(),
51
+ endDate: end.toISOString(),
52
+ }),
53
+ };
54
+
55
+ /** The default public Rocksky AppView base URL. */
56
+ export const DEFAULT_APPVIEW = "https://api.rocksky.app";
57
+
58
+ /** Unauthenticated read client over the public Rocksky AppView XRPC. */
44
59
  export class RockskyClient {
45
- readonly config: HttpClientConfig;
46
-
47
- readonly actor: ActorNamespace;
48
- readonly album: AlbumNamespace;
49
- readonly apikey: ApikeyNamespace;
50
- readonly artist: ArtistNamespace;
51
- readonly charts: ChartsNamespace;
52
- readonly dropbox: DropboxNamespace;
53
- readonly feed: FeedNamespace;
54
- readonly googledrive: GoogleDriveNamespace;
55
- readonly graph: GraphNamespace;
56
- readonly like: LikeNamespace;
57
- readonly mirror: MirrorNamespace;
58
- readonly player: PlayerNamespace;
59
- readonly playlist: PlaylistNamespace;
60
- readonly scrobble: ScrobbleNamespace;
61
- readonly shout: ShoutNamespace;
62
- readonly song: SongNamespace;
63
- readonly spotify: SpotifyNamespace;
64
- readonly stats: StatsNamespace;
65
-
66
- constructor(options: ClientOptions = {}) {
67
- this.config = buildConfig(options);
68
- const call = makeCall(this.config);
69
-
70
- this.actor = new ActorNamespace(call);
71
- this.album = new AlbumNamespace(call);
72
- this.apikey = new ApikeyNamespace(call);
73
- this.artist = new ArtistNamespace(call);
74
- this.charts = new ChartsNamespace(call);
75
- this.dropbox = new DropboxNamespace(call);
76
- this.feed = new FeedNamespace(call);
77
- this.googledrive = new GoogleDriveNamespace(call);
78
- this.graph = new GraphNamespace(call);
79
- this.like = new LikeNamespace(call);
80
- this.mirror = new MirrorNamespace(call);
81
- this.player = new PlayerNamespace(call);
82
- this.playlist = new PlaylistNamespace(call);
83
- this.scrobble = new ScrobbleNamespace(call);
84
- this.shout = new ShoutNamespace(call);
85
- this.song = new SongNamespace(call);
86
- this.spotify = new SpotifyNamespace(call);
87
- this.stats = new StatsNamespace(call);
88
- }
89
-
90
- /** Build a one-off authenticated copy without mutating this client. */
91
- withAuth(auth: AuthProvider): RockskyClient {
92
- return new RockskyClient({
93
- ...this.optionsSnapshot(),
94
- auth,
60
+ private rpc: Client;
61
+
62
+ /**
63
+ * Build a read client against an AppView base URL (defaults to
64
+ * {@link DEFAULT_APPVIEW}). Pass `token` to send it as
65
+ * `Authorization: Bearer <token>` on every read — needed only for auth-gated
66
+ * queries.
67
+ */
68
+ constructor(appview: string = DEFAULT_APPVIEW, token?: string) {
69
+ let handler = simpleFetchHandler({ service: appview });
70
+ if (token) {
71
+ const inner = handler;
72
+ handler = ((pathname: string, init?: RequestInit) =>
73
+ inner(pathname, {
74
+ ...init,
75
+ headers: { ...(init?.headers as Record<string, string>), authorization: `Bearer ${token}` },
76
+ })) as typeof handler;
77
+ }
78
+ this.rpc = new Client({ handler });
79
+ }
80
+
81
+ private async query<T>(nsid: string, params: Record<string, unknown>): Promise<T> {
82
+ const clean: Record<string, unknown> = {};
83
+ for (const [k, v] of Object.entries(params)) {
84
+ if (v !== undefined && v !== "") clean[k] = v;
85
+ }
86
+ const res = await this.rpc.get(nsid as never, { params: clean } as never);
87
+ if (!res.ok) throw new RockskyError(res.data);
88
+ return res.data as T;
89
+ }
90
+
91
+ /** An actor's detailed profile. `actor` is a handle or DID. */
92
+ profile(actor: string): Promise<ActorProfileViewDetailed> {
93
+ return this.query("app.rocksky.actor.getProfile", { did: actor });
94
+ }
95
+
96
+ /** An actor's scrobbles, newest first. */
97
+ async scrobbles(actor: string, limit = 50, offset = 0): Promise<ScrobbleViewBasic[]> {
98
+ const out = await this.query<GetActorScrobblesOutput>("app.rocksky.actor.getActorScrobbles", {
99
+ did: actor,
100
+ limit,
101
+ offset,
95
102
  });
103
+ return out.scrobbles ?? [];
104
+ }
105
+
106
+ /** Call any AppView read query by nsid; returns the raw JSON response. Every
107
+ * method here is sugar over this — use it for queries without a wrapper. */
108
+ get(nsid: string, params: Record<string, unknown> = {}): Promise<unknown> {
109
+ return this.query(nsid, params);
96
110
  }
97
111
 
98
- /** Build a copy with an overridden base URL. */
99
- withBaseUrl(baseUrl: string): RockskyClient {
100
- return new RockskyClient({
101
- ...this.optionsSnapshot(),
102
- baseUrl,
112
+ /** An actor's most-played songs. */
113
+ async songs(actor: string, limit = 50, offset = 0): Promise<SongViewBasic[]> {
114
+ const out = await this.query<{ tracks?: SongViewBasic[] }>("app.rocksky.actor.getActorSongs", {
115
+ did: actor,
116
+ limit,
117
+ offset,
103
118
  });
119
+ return out.tracks ?? [];
104
120
  }
105
121
 
106
- /**
107
- * Open a realtime WebSocket connection to /ws.
108
- *
109
- * const rt = client.realtime({ token, clientName: "my-app" });
110
- * rt.on("message", m => console.log(m));
111
- * await rt.connect();
112
- *
113
- * Defaults `baseUrl` to this client's base URL. Override anything by
114
- * passing the option, or use `RealtimeClient.builder()` for full control.
115
- */
116
- realtime(
117
- options: Omit<RealtimeOptions, "baseUrl"> &
118
- Partial<Pick<RealtimeOptions, "baseUrl">>,
119
- ): RealtimeClient {
120
- return createRealtimeClient({
121
- baseUrl: this.config.baseUrl,
122
- ...options,
122
+ /** An actor's loved (liked) songs. */
123
+ async lovedSongs(actor: string, limit = 50, offset = 0): Promise<SongViewBasic[]> {
124
+ const out = await this.query<{ tracks?: SongViewBasic[] }>(
125
+ "app.rocksky.actor.getActorLovedSongs",
126
+ { did: actor, limit, offset },
127
+ );
128
+ return out.tracks ?? [];
129
+ }
130
+
131
+ /** An actor's most-played albums. */
132
+ async albums(actor: string, limit = 50, offset = 0): Promise<AlbumViewBasic[]> {
133
+ const out = await this.query<GetActorAlbumsOutput>("app.rocksky.actor.getActorAlbums", {
134
+ did: actor,
135
+ limit,
136
+ offset,
123
137
  });
138
+ return out.albums ?? [];
124
139
  }
125
140
 
126
- /**
127
- * Page through any limit/offset or cursor-based endpoint as an async iterable.
128
- *
129
- * for await (const s of client.paginate({
130
- * fetch: ({ limit, offset }) =>
131
- * client.actor.getActorScrobbles({ did, limit, offset }),
132
- * pageSize: 50,
133
- * })) { ... }
134
- */
135
- paginate<T>(args: PaginateArgs<T>) {
136
- return paginateFn(args);
141
+ /** An actor's most-played artists. */
142
+ async artists(actor: string, limit = 50, offset = 0): Promise<ArtistViewBasic[]> {
143
+ const out = await this.query<GetActorArtistsOutput>("app.rocksky.actor.getActorArtists", {
144
+ did: actor,
145
+ limit,
146
+ offset,
147
+ });
148
+ return out.artists ?? [];
137
149
  }
138
150
 
139
- /**
140
- * Direct escape hatch call any XRPC endpoint by NSID.
141
- *
142
- * Known NSIDs (string literals) are typed via the generated `Endpoints`
143
- * map; arbitrary strings fall back to `unknown` (override with `<T>`).
144
- */
145
- xrpc<K extends keyof Endpoints>(
146
- nsid: K,
147
- method?: "GET" | "POST",
148
- opts?: XrpcOpts,
149
- ): Promise<Endpoints[K]>;
150
- xrpc<T = unknown>(
151
- nsid: string,
152
- method?: "GET" | "POST",
153
- opts?: XrpcOpts,
154
- ): Promise<T>;
155
- xrpc(
156
- nsid: string,
157
- method: "GET" | "POST" = "GET",
158
- opts: XrpcOpts = {},
159
- ): Promise<unknown> {
160
- return xrpcCall(this.config, nsid, method, opts);
161
- }
162
-
163
- static builder(): RockskyClientBuilder {
164
- return new RockskyClientBuilder();
165
- }
166
-
167
- private optionsSnapshot(): ClientOptions {
168
- return {
169
- baseUrl: this.config.baseUrl,
170
- auth: this.config.auth,
171
- fetch: this.config.fetch,
172
- headers: this.config.headers,
173
- timeoutMs: this.config.timeoutMs,
174
- retries: this.config.retries,
175
- retryDelayMs: this.config.retryDelayMs,
176
- };
151
+ /** The platform-wide top tracks chart (all-time). */
152
+ topTracks(limit = 50, offset = 0): Promise<SongViewBasic[]> {
153
+ return this.topTracksInterval(limit, offset, Interval.allTime());
177
154
  }
178
- }
179
155
 
180
- /**
181
- * Fluent builder.
182
- *
183
- * const client = RockskyClient.builder()
184
- * .baseUrl("https://api.rocksky.app")
185
- * .auth(() => loadToken())
186
- * .timeout(10_000)
187
- * .retries(3)
188
- * .userAgent("my-app/1.0")
189
- * .build();
190
- */
191
- export class RockskyClientBuilder {
192
- private readonly opts: ClientOptions = {};
156
+ /** The platform-wide top artists chart (all-time). */
157
+ topArtists(limit = 50, offset = 0): Promise<ArtistViewBasic[]> {
158
+ return this.topArtistsInterval(limit, offset, Interval.allTime());
159
+ }
193
160
 
194
- baseUrl(url: string): this {
195
- this.opts.baseUrl = url;
196
- return this;
161
+ /** The top tracks chart over a typed {@link DateInterval}. */
162
+ async topTracksInterval(limit: number, offset: number, interval: DateInterval): Promise<SongViewBasic[]> {
163
+ const out = await this.query<{ tracks?: SongViewBasic[] }>("app.rocksky.charts.getTopTracks", {
164
+ limit,
165
+ offset,
166
+ ...interval,
167
+ });
168
+ return out.tracks ?? [];
197
169
  }
198
170
 
199
- auth(auth: AuthProvider): this {
200
- this.opts.auth = auth;
201
- return this;
171
+ /** The top artists chart over a typed {@link DateInterval}. */
172
+ async topArtistsInterval(limit: number, offset: number, interval: DateInterval): Promise<ArtistViewBasic[]> {
173
+ const out = await this.query<{ artists?: ArtistViewBasic[] }>("app.rocksky.charts.getTopArtists", {
174
+ limit,
175
+ offset,
176
+ ...interval,
177
+ });
178
+ return out.artists ?? [];
202
179
  }
203
180
 
204
- bearer(token: string): this {
205
- this.opts.auth = token;
206
- return this;
181
+ /** The album catalog, optionally filtered by `genre`. */
182
+ async catalogAlbums(limit = 50, offset = 0, genre?: string): Promise<AlbumViewBasic[]> {
183
+ const out = await this.query<{ albums?: AlbumViewBasic[] }>("app.rocksky.album.getAlbums", {
184
+ limit,
185
+ offset,
186
+ genre,
187
+ });
188
+ return out.albums ?? [];
207
189
  }
208
190
 
209
- fetch(impl: FetchLike): this {
210
- this.opts.fetch = impl;
211
- return this;
191
+ /** The artist catalog, optionally filtered by `genre`. */
192
+ async catalogArtists(limit = 50, offset = 0, genre?: string): Promise<ArtistViewBasic[]> {
193
+ const out = await this.query<{ artists?: ArtistViewBasic[] }>("app.rocksky.artist.getArtists", {
194
+ limit,
195
+ offset,
196
+ genre,
197
+ });
198
+ return out.artists ?? [];
212
199
  }
213
200
 
214
- header(key: string, value: string): this {
215
- this.opts.headers = { ...this.opts.headers, [key]: value };
216
- return this;
201
+ /** The song catalog, optionally filtered by `genre`. */
202
+ async catalogSongs(limit = 50, offset = 0, genre?: string): Promise<SongViewBasic[]> {
203
+ const out = await this.query<{ tracks?: SongViewBasic[] }>("app.rocksky.song.getSongs", {
204
+ limit,
205
+ offset,
206
+ genre,
207
+ });
208
+ return out.tracks ?? [];
217
209
  }
218
210
 
219
- headers(headers: Record<string, string>): this {
220
- this.opts.headers = { ...this.opts.headers, ...headers };
221
- return this;
211
+ /** An album's tracklist by album at:// URI. */
212
+ async albumTracks(uri: string): Promise<SongViewBasic[]> {
213
+ const out = await this.query<{ tracks?: SongViewBasic[] }>("app.rocksky.album.getAlbumTracks", { uri });
214
+ return out.tracks ?? [];
222
215
  }
223
216
 
224
- userAgent(ua: string): this {
225
- this.opts.userAgent = ua;
226
- return this;
217
+ /** An artist's albums by artist at:// URI. */
218
+ async artistAlbums(uri: string): Promise<AlbumViewBasic[]> {
219
+ const out = await this.query<{ albums?: AlbumViewBasic[] }>("app.rocksky.artist.getArtistAlbums", { uri });
220
+ return out.albums ?? [];
227
221
  }
228
222
 
229
- timeout(ms: number): this {
230
- this.opts.timeoutMs = ms;
231
- return this;
223
+ /** An artist's top tracks by artist at:// URI. */
224
+ async artistTracks(uri: string, limit = 50, offset = 0): Promise<SongViewBasic[]> {
225
+ const out = await this.query<{ tracks?: SongViewBasic[] }>("app.rocksky.artist.getArtistTracks", {
226
+ uri,
227
+ limit,
228
+ offset,
229
+ });
230
+ return out.tracks ?? [];
232
231
  }
233
232
 
234
- retries(n: number): this {
235
- this.opts.retries = n;
236
- return this;
233
+ /** A social/global scrobbles feed. Pass `did` to scope to an actor and
234
+ * `following = true` for their follow graph. */
235
+ async scrobbleFeed(did?: string, following = false, limit = 50, offset = 0): Promise<ScrobbleViewBasic[]> {
236
+ const out = await this.query<{ scrobbles?: ScrobbleViewBasic[] }>("app.rocksky.scrobble.getScrobbles", {
237
+ did,
238
+ following,
239
+ limit,
240
+ offset,
241
+ });
242
+ return out.scrobbles ?? [];
237
243
  }
238
244
 
239
- retryDelay(ms: number): this {
240
- this.opts.retryDelayMs = ms;
241
- return this;
245
+ /** A single scrobble by its at:// URI. */
246
+ scrobble(uri: string): Promise<ScrobbleViewBasic> {
247
+ return this.query("app.rocksky.scrobble.getScrobble", { uri });
242
248
  }
243
249
 
244
- build(): RockskyClient {
245
- return new RockskyClient(this.opts);
250
+ /** The accounts `actor` follows. */
251
+ async follows(actor: string, limit = 50, cursor?: string): Promise<ActorProfileViewBasic[]> {
252
+ const out = await this.query<{ follows?: ActorProfileViewBasic[] }>("app.rocksky.graph.getFollows", {
253
+ actor,
254
+ limit,
255
+ cursor,
256
+ });
257
+ return out.follows ?? [];
246
258
  }
247
- }
248
259
 
249
- /** Convenience factory equivalent to `new RockskyClient(options)`. */
250
- export function createClient(options: ClientOptions = {}): RockskyClient {
251
- return new RockskyClient(options);
260
+ /** The accounts that follow `actor`. */
261
+ async followers(actor: string, limit = 50, cursor?: string): Promise<ActorProfileViewBasic[]> {
262
+ const out = await this.query<{ followers?: ActorProfileViewBasic[] }>("app.rocksky.graph.getFollowers", {
263
+ actor,
264
+ limit,
265
+ cursor,
266
+ });
267
+ return out.followers ?? [];
268
+ }
269
+
270
+ /** Followers of `actor` that the viewer also follows. */
271
+ async knownFollowers(actor: string, limit = 50, cursor?: string): Promise<ActorProfileViewBasic[]> {
272
+ const out = await this.query<{ followers?: ActorProfileViewBasic[] }>(
273
+ "app.rocksky.graph.getKnownFollowers",
274
+ { actor, limit, cursor },
275
+ );
276
+ return out.followers ?? [];
277
+ }
278
+
279
+ /** Full-text search across songs, albums, artists, playlists, actors. */
280
+ search(query: string): Promise<FeedSearchResultsView> {
281
+ return this.query("app.rocksky.feed.search", { query });
282
+ }
283
+
284
+ /** Platform-wide totals. */
285
+ globalStats(): Promise<StatsGlobalStatsView> {
286
+ return this.query("app.rocksky.stats.getGlobalStats", {});
287
+ }
288
+
289
+ // ---- raw-JSON long tail: bespoke shapes returned as `unknown` ----------
290
+
291
+ /** A feed by its at:// URI (paginate via `cursor`). */
292
+ feed(feed: string, limit = 50, cursor?: string): Promise<unknown> {
293
+ return this.query("app.rocksky.feed.getFeed", { feed, limit, cursor });
294
+ }
295
+ /** A single album with its tracklist. */
296
+ album(uri: string): Promise<unknown> {
297
+ return this.query("app.rocksky.album.getAlbum", { uri });
298
+ }
299
+ /** A single artist with detail. */
300
+ artist(uri: string): Promise<unknown> {
301
+ return this.query("app.rocksky.artist.getArtist", { uri });
302
+ }
303
+ /** Resolve full canonical metadata for a bare title + artist
304
+ * (`app.rocksky.song.matchSong`); optionally anchor with `mbId` / `isrc`. */
305
+ matchSong(title: string, artist: string, mbId?: string, isrc?: string): Promise<unknown> {
306
+ return this.query("app.rocksky.song.matchSong", { title, artist, mbId, isrc });
307
+ }
308
+ /** A single song by at:// `uri` (or by `mbid` / `isrc` / `spotifyId`). */
309
+ song(opts: { uri?: string; mbid?: string; isrc?: string; spotifyId?: string }): Promise<unknown> {
310
+ return this.query("app.rocksky.song.getSong", opts);
311
+ }
312
+ /** An actor's playlists. */
313
+ actorPlaylists(actor: string, limit = 50, offset = 0): Promise<unknown> {
314
+ return this.query("app.rocksky.actor.getActorPlaylists", { did: actor, limit, offset });
315
+ }
316
+ /** Actors with similar taste to `actor`. */
317
+ neighbours(actor: string): Promise<unknown> {
318
+ return this.query("app.rocksky.actor.getActorNeighbours", { did: actor });
319
+ }
320
+ /** Music compatibility between the viewer and `actor` (auth). */
321
+ compatibility(actor: string): Promise<unknown> {
322
+ return this.query("app.rocksky.actor.getActorCompatibility", { did: actor });
323
+ }
324
+ /** An artist's all-time listeners. */
325
+ artistListeners(uri: string, limit = 50, offset = 0): Promise<unknown> {
326
+ return this.query("app.rocksky.artist.getArtistListeners", { uri, limit, offset });
327
+ }
328
+ /** An artist's recent listeners. */
329
+ artistRecentListeners(uri: string, limit = 50, offset = 0): Promise<unknown> {
330
+ return this.query("app.rocksky.artist.getArtistRecentListeners", { uri, limit, offset });
331
+ }
332
+ /** A song's recent listeners. */
333
+ songRecentListeners(uri: string, limit = 50, offset = 0): Promise<unknown> {
334
+ return this.query("app.rocksky.song.getSongRecentListeners", { uri, limit, offset });
335
+ }
336
+ /** A scrobble time-series chart. Scope with any of `did` / `artisturi` /
337
+ * `albumuri` / `songuri` / `genre` and bound with `from` / `to`. */
338
+ scrobblesChart(opts: {
339
+ did?: string;
340
+ artisturi?: string;
341
+ albumuri?: string;
342
+ songuri?: string;
343
+ genre?: string;
344
+ from?: string;
345
+ to?: string;
346
+ }): Promise<unknown> {
347
+ return this.query("app.rocksky.charts.getScrobblesChart", opts);
348
+ }
349
+ /** List the available feed generators. */
350
+ feedGenerators(size?: number): Promise<unknown> {
351
+ return this.query("app.rocksky.feed.getFeedGenerators", { size });
352
+ }
353
+ /** A single feed generator's record. */
354
+ feedGenerator(feed: string): Promise<unknown> {
355
+ return this.query("app.rocksky.feed.getFeedGenerator", { feed });
356
+ }
357
+ /** The stories row. */
358
+ stories(size?: number, feed?: string, following?: boolean): Promise<unknown> {
359
+ return this.query("app.rocksky.feed.getStories", { size, feed, following });
360
+ }
361
+ /** Track recommendations for `actor`. */
362
+ recommendations(actor: string, limit?: number): Promise<unknown> {
363
+ return this.query("app.rocksky.feed.getRecommendations", { did: actor, limit });
364
+ }
365
+ /** Artist recommendations for `actor`. */
366
+ artistRecommendations(actor: string, limit?: number): Promise<unknown> {
367
+ return this.query("app.rocksky.feed.getArtistRecommendations", { did: actor, limit });
368
+ }
369
+ /** Album recommendations for `actor`. */
370
+ albumRecommendations(actor: string, limit?: number): Promise<unknown> {
371
+ return this.query("app.rocksky.feed.getAlbumRecommendations", { did: actor, limit });
372
+ }
373
+ /** An actor's aggregate stats. */
374
+ stats(actor: string): Promise<unknown> {
375
+ return this.query("app.rocksky.stats.getStats", { did: actor });
376
+ }
377
+ /** An actor's year-in-review. */
378
+ wrapped(actor: string, year?: number): Promise<unknown> {
379
+ return this.query("app.rocksky.stats.getWrapped", { did: actor, year });
380
+ }
381
+ /** The viewer's configured scrobble mirror sources (auth). */
382
+ mirrorSources(): Promise<unknown> {
383
+ return this.query("app.rocksky.mirror.getMirrorSources", {});
384
+ }
385
+ /** What `actor` is playing now. */
386
+ currentlyPlaying(playerId?: string, actor?: string): Promise<unknown> {
387
+ return this.query("app.rocksky.player.getCurrentlyPlaying", { playerId, actor });
388
+ }
389
+ /** A player's playback queue. */
390
+ playbackQueue(playerId: string): Promise<unknown> {
391
+ return this.query("app.rocksky.player.getPlaybackQueue", { playerId });
392
+ }
393
+ /** What `actor` is playing now on Spotify. */
394
+ spotifyCurrentlyPlaying(actor: string): Promise<unknown> {
395
+ return this.query("app.rocksky.spotify.getCurrentlyPlaying", { actor });
396
+ }
397
+ /** The playlist catalog. */
398
+ playlists(limit = 50, offset = 0): Promise<unknown> {
399
+ return this.query("app.rocksky.playlist.getPlaylists", { limit, offset });
400
+ }
401
+ /** A single playlist with its items. */
402
+ playlist(uri: string): Promise<unknown> {
403
+ return this.query("app.rocksky.playlist.getPlaylist", { uri });
404
+ }
405
+ /** Shouts on an album. */
406
+ albumShouts(uri: string, limit = 50, offset = 0): Promise<unknown> {
407
+ return this.query("app.rocksky.shout.getAlbumShouts", { uri, limit, offset });
408
+ }
409
+ /** Shouts on an artist. */
410
+ artistShouts(uri: string, limit = 50, offset = 0): Promise<unknown> {
411
+ return this.query("app.rocksky.shout.getArtistShouts", { uri, limit, offset });
412
+ }
413
+ /** Shouts on a profile. */
414
+ profileShouts(actor: string, limit = 50, offset = 0): Promise<unknown> {
415
+ return this.query("app.rocksky.shout.getProfileShouts", { did: actor, limit, offset });
416
+ }
417
+ /** Shouts on a track. */
418
+ trackShouts(uri: string): Promise<unknown> {
419
+ return this.query("app.rocksky.shout.getTrackShouts", { uri });
420
+ }
421
+ /** Replies to a shout. */
422
+ shoutReplies(uri: string, limit = 50, offset = 0): Promise<unknown> {
423
+ return this.query("app.rocksky.shout.getShoutReplies", { uri, limit, offset });
424
+ }
425
+ /** An actor's Rockbox EQ / audio settings. */
426
+ audioSettings(actor: string): Promise<unknown> {
427
+ return this.query("app.rocksky.rockbox.getAudioSettings", { did: actor });
428
+ }
429
+ /** The viewer's API keys (auth). */
430
+ apikeys(limit = 50, offset = 0): Promise<unknown> {
431
+ return this.query("app.rocksky.apikey.getApikeys", { limit, offset });
432
+ }
252
433
  }