@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.
- package/README.md +71 -245
- package/dist/agent.d.ts +82 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/client.d.ts +165 -111
- package/dist/client.d.ts.map +1 -1
- package/dist/dedup.d.ts +38 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/errors.d.ts +3 -23
- package/dist/errors.d.ts.map +1 -1
- package/dist/generated/types.d.ts +106 -8
- package/dist/generated/types.d.ts.map +1 -1
- package/dist/hash.d.ts +7 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/index.d.ts +17 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6956 -1267
- package/dist/jetstream.d.ts +17 -0
- package/dist/jetstream.d.ts.map +1 -0
- package/package.json +15 -11
- package/src/agent.ts +278 -0
- package/src/client.ts +393 -212
- package/src/dedup.ts +207 -0
- package/src/errors.ts +6 -43
- package/src/generated/types.ts +115 -8
- package/src/hash.ts +22 -0
- package/src/index.ts +17 -86
- package/src/jetstream.ts +122 -0
- package/src/http.ts +0 -195
- package/src/namespaces/_helpers.ts +0 -27
- package/src/namespaces/actor.ts +0 -93
- package/src/namespaces/album.ts +0 -34
- package/src/namespaces/apikey.ts +0 -50
- package/src/namespaces/artist.ts +0 -68
- package/src/namespaces/charts.ts +0 -38
- package/src/namespaces/dropbox.ts +0 -53
- package/src/namespaces/feed.ts +0 -112
- package/src/namespaces/googledrive.ts +0 -41
- package/src/namespaces/graph.ts +0 -62
- package/src/namespaces/like.ts +0 -46
- package/src/namespaces/mirror.ts +0 -27
- package/src/namespaces/player.ts +0 -125
- package/src/namespaces/playlist.ts +0 -95
- package/src/namespaces/scrobble.ts +0 -41
- package/src/namespaces/shout.ts +0 -99
- package/src/namespaces/song.ts +0 -60
- package/src/namespaces/spotify.ts +0 -56
- package/src/namespaces/stats.ts +0 -27
- package/src/paginate.ts +0 -90
- package/src/pipe.ts +0 -146
- package/src/realtime.ts +0 -408
- package/src/types.ts +0 -41
package/src/client.ts
CHANGED
|
@@ -1,252 +1,433 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
/**
|
|
250
|
-
|
|
251
|
-
|
|
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
|
}
|