+34
apps/api/lexicons/song/matchSong.json
+34
apps/api/lexicons/song/matchSong.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.rocksky.song.matchSong",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "Get a song by its uri",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"title",
12
+
"artist"
13
+
],
14
+
"properties": {
15
+
"title": {
16
+
"type": "string",
17
+
"description": "The title of the song to retrieve"
18
+
},
19
+
"artist": {
20
+
"type": "string",
21
+
"description": "The artist of the song to retrieve"
22
+
}
23
+
}
24
+
},
25
+
"output": {
26
+
"encoding": "application/json",
27
+
"schema": {
28
+
"type": "ref",
29
+
"ref": "app.rocksky.song.defs#songViewDetailed"
30
+
}
31
+
}
32
+
}
33
+
}
34
+
}
+1
apps/api/package.json
+1
apps/api/package.json
+28
apps/api/pkl/defs/song/matchSong.pkl
+28
apps/api/pkl/defs/song/matchSong.pkl
···
1
+
amends "../../schema/lexicon.pkl"
2
+
3
+
lexicon = 1
4
+
id = "app.rocksky.song.matchSong"
5
+
defs = new Mapping<String, Query> {
6
+
["main"] {
7
+
type = "query"
8
+
description = "Get a song by its uri"
9
+
parameters = new Params {
10
+
required = List("title", "artist")
11
+
properties {
12
+
["title"] = new StringType {
13
+
description = "The title of the song to retrieve"
14
+
}
15
+
["artist"] = new StringType {
16
+
description = "The artist of the song to retrieve"
17
+
}
18
+
}
19
+
}
20
+
output {
21
+
encoding = "application/json"
22
+
schema = new Ref {
23
+
type = "ref"
24
+
ref = "app.rocksky.song.defs#songViewDetailed"
25
+
}
26
+
}
27
+
}
28
+
}
+3
-2
apps/api/scripts/pkl.ts
+3
-2
apps/api/scripts/pkl.ts
···
2
2
import { readdirSync, statSync } from "fs";
3
3
import { join } from "path";
4
4
import { $ } from "zx";
5
+
import { consola } from "consola";
5
6
6
7
function getPklFilesRecursive(dir: string): string[] {
7
8
const entries = readdirSync(dir);
···
28
29
29
30
await Promise.all(
30
31
files.map(async (fullPath) => {
31
-
console.log(`pkl eval ${chalk.cyan(fullPath)}`);
32
+
consola.info(`pkl eval ${chalk.cyan(fullPath)}`);
32
33
await $`pkl eval -f json ${fullPath} > ${fullPath.replace(/\.pkl$/, ".json").replace(/pkl[\\\/]defs/g, "lexicons")}`;
33
-
})
34
+
}),
34
35
);
+3
-2
apps/api/src/bsky/app.ts
+3
-2
apps/api/src/bsky/app.ts
···
1
1
import { AtpAgent } from "@atproto/api";
2
+
import { consola } from "consola";
2
3
import type { BlobRef } from "@atproto/lexicon";
3
4
import { isValidHandle } from "@atproto/syntax";
4
5
import { ctx } from "context";
···
134
135
);
135
136
ctx.kv.set(did, token);
136
137
} catch (err) {
137
-
console.error({ err }, "oauth callback failed");
138
+
consola.error({ err }, "oauth callback failed");
138
139
return c.redirect(`${env.FRONTEND_URL}?error=1`);
139
140
}
140
141
···
205
206
.execute();
206
207
} catch (e) {
207
208
if (!e.message.includes("invalid record: column [did]: is not unique")) {
208
-
console.error(e.message);
209
+
consola.error(e.message);
209
210
} else {
210
211
await ctx.db
211
212
.update(users)
+2
-1
apps/api/src/context.ts
+2
-1
apps/api/src/context.ts
···
1
1
import { createClient } from "auth/client";
2
2
import axios from "axios";
3
+
import { consola } from "consola";
3
4
import { createDb, migrateToLatest } from "db";
4
5
import drizzle from "drizzle";
5
6
import authVerifier from "lib/authVerifier";
···
35
36
redis: await redis
36
37
.createClient({ url: env.REDIS_URL })
37
38
.on("error", (err) => {
38
-
console.error("Uncaught Redis Client Error", err);
39
+
consola.error("Uncaught Redis Client Error", err);
39
40
process.exit(1);
40
41
})
41
42
.connect(),
+6
-5
apps/api/src/db.ts
+6
-5
apps/api/src/db.ts
···
9
9
SqliteDialect,
10
10
} from "kysely";
11
11
import { createAgent } from "lib/agent";
12
+
import { consola } from "consola";
12
13
13
14
// Types
14
15
···
113
114
export const updateExpiresAt = async (db: Database) => {
114
115
// get all sessions that have expiresAt is null
115
116
const sessions = await db.selectFrom("auth_session").selectAll().execute();
116
-
console.log("Found", sessions.length, "sessions to update");
117
+
consola.info("Found", sessions.length, "sessions to update");
117
118
for (const session of sessions) {
118
119
const data = JSON.parse(session.session) as {
119
120
tokenSet: { expires_at?: string | null };
120
121
};
121
-
console.log(session.key, data.tokenSet.expires_at);
122
+
consola.info(session.key, data.tokenSet.expires_at);
122
123
await db
123
124
.updateTable("auth_session")
124
125
.set({ expiresAt: data.tokenSet.expires_at })
···
126
127
.execute();
127
128
}
128
129
129
-
console.log(`Updated ${chalk.greenBright(sessions.length)} sessions`);
130
+
consola.info(`Updated ${chalk.greenBright(sessions.length)} sessions`);
130
131
};
131
132
132
133
export const refreshSessionsAboutToExpire = async (
···
144
145
.execute();
145
146
146
147
for (const session of sessions) {
147
-
console.log(
148
+
consola.info(
148
149
"Session about to expire:",
149
150
chalk.cyan(session.key),
150
151
session.expiresAt,
···
155
156
await new Promise((r) => setTimeout(r, 200));
156
157
}
157
158
158
-
console.log(
159
+
consola.info(
159
160
`Found ${chalk.yellowBright(sessions.length)} sessions to refresh`,
160
161
);
161
162
};
+2
-1
apps/api/src/dropbox/app.ts
+2
-1
apps/api/src/dropbox/app.ts
···
1
1
import axios from "axios";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Hono } from "hono";
···
159
160
if (
160
161
!e.message.includes("invalid record: column [user_id]: is not unique")
161
162
) {
162
-
console.error(e.message);
163
+
consola.error(e.message);
163
164
} else {
164
165
throw e;
165
166
}
+3
-2
apps/api/src/googledrive/app.ts
+3
-2
apps/api/src/googledrive/app.ts
···
1
1
import axios from "axios";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import fs from "fs";
···
185
186
});
186
187
} catch (e) {
187
188
if (!e.message.includes("duplicate key value violates unique constraint")) {
188
-
console.error(e.message);
189
+
consola.error(e.message);
189
190
} else {
190
191
throw e;
191
192
}
···
263
264
return c.json(data);
264
265
} catch (error) {
265
266
if (axios.isAxiosError(error)) {
266
-
console.error("Axios error:", error.response?.data || error.message);
267
+
consola.error("Axios error:", error.response?.data || error.message);
267
268
268
269
const credentials = JSON.parse(
269
270
fs.readFileSync("credentials.json").toString("utf-8"),
+2
-1
apps/api/src/index.ts
+2
-1
apps/api/src/index.ts
···
1
1
import { serve } from "@hono/node-server";
2
2
import { createNodeWebSocket } from "@hono/node-ws";
3
3
import { trace } from "@opentelemetry/api";
4
+
import { consola } from "consola";
4
5
import { ctx } from "context";
5
6
import { and, desc, eq, isNotNull, or } from "drizzle-orm";
6
7
import { Hono } from "hono";
···
466
467
await saveTrack(ctx, track, agent);
467
468
} catch (e) {
468
469
if (!e.message.includes("duplicate key value violates unique constraint")) {
469
-
console.error("[tracks]", e.message);
470
+
consola.error("[tracks]", e.message);
470
471
}
471
472
}
472
473
+12
apps/api/src/lexicon/index.ts
+12
apps/api/src/lexicon/index.ts
···
93
93
import type * as AppRockskySongCreateSong from "./types/app/rocksky/song/createSong";
94
94
import type * as AppRockskySongGetSong from "./types/app/rocksky/song/getSong";
95
95
import type * as AppRockskySongGetSongs from "./types/app/rocksky/song/getSongs";
96
+
import type * as AppRockskySongMatchSong from "./types/app/rocksky/song/matchSong";
96
97
import type * as AppRockskySpotifyGetCurrentlyPlaying from "./types/app/rocksky/spotify/getCurrentlyPlaying";
97
98
import type * as AppRockskySpotifyNext from "./types/app/rocksky/spotify/next";
98
99
import type * as AppRockskySpotifyPause from "./types/app/rocksky/spotify/pause";
···
1261
1262
>,
1262
1263
) {
1263
1264
const nsid = "app.rocksky.song.getSongs"; // @ts-ignore
1265
+
return this._server.xrpc.method(nsid, cfg);
1266
+
}
1267
+
1268
+
matchSong<AV extends AuthVerifier>(
1269
+
cfg: ConfigOf<
1270
+
AV,
1271
+
AppRockskySongMatchSong.Handler<ExtractAuth<AV>>,
1272
+
AppRockskySongMatchSong.HandlerReqCtx<ExtractAuth<AV>>
1273
+
>,
1274
+
) {
1275
+
const nsid = "app.rocksky.song.matchSong"; // @ts-ignore
1264
1276
return this._server.xrpc.method(nsid, cfg);
1265
1277
}
1266
1278
}
+32
apps/api/src/lexicon/lexicons.ts
+32
apps/api/src/lexicon/lexicons.ts
···
5548
5548
},
5549
5549
},
5550
5550
},
5551
+
AppRockskySongMatchSong: {
5552
+
lexicon: 1,
5553
+
id: "app.rocksky.song.matchSong",
5554
+
defs: {
5555
+
main: {
5556
+
type: "query",
5557
+
description: "Get a song by its uri",
5558
+
parameters: {
5559
+
type: "params",
5560
+
required: ["title", "artist"],
5561
+
properties: {
5562
+
title: {
5563
+
type: "string",
5564
+
description: "The title of the song to retrieve",
5565
+
},
5566
+
artist: {
5567
+
type: "string",
5568
+
description: "The artist of the song to retrieve",
5569
+
},
5570
+
},
5571
+
},
5572
+
output: {
5573
+
encoding: "application/json",
5574
+
schema: {
5575
+
type: "ref",
5576
+
ref: "lex:app.rocksky.song.defs#songViewDetailed",
5577
+
},
5578
+
},
5579
+
},
5580
+
},
5581
+
},
5551
5582
AppRockskySong: {
5552
5583
lexicon: 1,
5553
5584
id: "app.rocksky.song",
···
6032
6063
AppRockskySongDefs: "app.rocksky.song.defs",
6033
6064
AppRockskySongGetSong: "app.rocksky.song.getSong",
6034
6065
AppRockskySongGetSongs: "app.rocksky.song.getSongs",
6066
+
AppRockskySongMatchSong: "app.rocksky.song.matchSong",
6035
6067
AppRockskySong: "app.rocksky.song",
6036
6068
AppRockskySpotifyDefs: "app.rocksky.spotify.defs",
6037
6069
AppRockskySpotifyGetCurrentlyPlaying:
+45
apps/api/src/lexicon/types/app/rocksky/song/matchSong.ts
+45
apps/api/src/lexicon/types/app/rocksky/song/matchSong.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import type express from "express";
5
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
6
+
import { lexicons } from "../../../../lexicons";
7
+
import { isObj, hasProp } from "../../../../util";
8
+
import { CID } from "multiformats/cid";
9
+
import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server";
10
+
import type * as AppRockskySongDefs from "./defs";
11
+
12
+
export interface QueryParams {
13
+
/** The title of the song to retrieve */
14
+
title: string;
15
+
/** The artist of the song to retrieve */
16
+
artist: string;
17
+
}
18
+
19
+
export type InputSchema = undefined;
20
+
export type OutputSchema = AppRockskySongDefs.SongViewDetailed;
21
+
export type HandlerInput = undefined;
22
+
23
+
export interface HandlerSuccess {
24
+
encoding: "application/json";
25
+
body: OutputSchema;
26
+
headers?: { [key: string]: string };
27
+
}
28
+
29
+
export interface HandlerError {
30
+
status: number;
31
+
message?: string;
32
+
}
33
+
34
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
35
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
36
+
auth: HA;
37
+
params: QueryParams;
38
+
input: HandlerInput;
39
+
req: express.Request;
40
+
res: express.Response;
41
+
resetRouteRateLimits: () => Promise<void>;
42
+
};
43
+
export type Handler<HA extends HandlerAuth = never> = (
44
+
ctx: HandlerReqCtx<HA>,
45
+
) => Promise<HandlerOutput> | HandlerOutput;
+7
-6
apps/api/src/lib/agent.ts
+7
-6
apps/api/src/lib/agent.ts
···
1
1
import { Agent, AtpAgent } from "@atproto/api";
2
2
import type { NodeOAuthClient } from "@atproto/oauth-client-node";
3
+
import { consola } from "consola";
3
4
import extractPdsFromDid from "./extractPdsFromDid";
4
5
import { ctx } from "context";
5
6
···
29
30
try {
30
31
await atpAgent.resumeSession(JSON.parse(result.session));
31
32
} catch (e) {
32
-
console.log("Error resuming session");
33
-
console.log(did);
34
-
console.log(e);
33
+
consola.info("Error resuming session");
34
+
consola.info(did);
35
+
consola.info(e);
35
36
await ctx.sqliteDb
36
37
.deleteFrom("auth_session")
37
38
.where("key", "=", `atp:${did}`)
···
47
48
retry += 1;
48
49
}
49
50
} catch (e) {
50
-
console.log("Error creating agent");
51
-
console.log(did);
52
-
console.log(e);
51
+
consola.info("Error creating agent");
52
+
consola.info(did);
53
+
consola.info(e);
53
54
await new Promise((r) => setTimeout(r, 1000));
54
55
retry += 1;
55
56
}
+4
-3
apps/api/src/lovedtracks/lovedtracks.service.ts
+4
-3
apps/api/src/lovedtracks/lovedtracks.service.ts
···
1
1
import { AtpAgent, type Agent } from "@atproto/api";
2
2
import { TID } from "@atproto/common";
3
+
import { consola } from "consola";
3
4
import type { Context } from "context";
4
5
import { and, desc, eq, type SQLWrapper } from "drizzle-orm";
5
6
import * as LikeLexicon from "lexicon/types/app/rocksky/like";
···
279
280
};
280
281
281
282
if (!LikeLexicon.validateRecord(record).success) {
282
-
console.log(LikeLexicon.validateRecord(record));
283
+
consola.info(LikeLexicon.validateRecord(record));
283
284
throw new Error("Invalid record");
284
285
}
285
286
···
292
293
validate: false,
293
294
});
294
295
const uri = res.data.uri;
295
-
console.log(`Like record created at: ${uri}`);
296
+
consola.info(`Like record created at: ${uri}`);
296
297
297
298
[created] = await ctx.db
298
299
.update(lovedTracks)
···
300
301
.where(eq(lovedTracks.id, created.id))
301
302
.returning();
302
303
} catch (e) {
303
-
console.error(`Error creating like record: ${e.message}`);
304
+
consola.error(`Error creating like record: ${e.message}`);
304
305
}
305
306
}
306
307
+30
-29
apps/api/src/nowplaying/nowplaying.service.ts
+30
-29
apps/api/src/nowplaying/nowplaying.service.ts
···
1
1
import type { Agent } from "@atproto/api";
2
2
import { TID } from "@atproto/common";
3
3
import chalk from "chalk";
4
+
import { consola } from "consola";
4
5
import type { Context } from "context";
5
6
import dayjs from "dayjs";
6
7
import { and, eq, gte, lte, or } from "drizzle-orm";
···
38
39
};
39
40
40
41
if (!Artist.validateRecord(record).success) {
41
-
console.log(Artist.validateRecord(record));
42
-
console.log(JSON.stringify(record, null, 2));
42
+
consola.info(Artist.validateRecord(record));
43
+
consola.info(JSON.stringify(record, null, 2));
43
44
throw new Error("Invalid record");
44
45
}
45
46
···
52
53
validate: false,
53
54
});
54
55
const uri = res.data.uri;
55
-
console.log(`Artist record created at ${uri}`);
56
+
consola.info(`Artist record created at ${uri}`);
56
57
return uri;
57
58
} catch (e) {
58
-
console.error("Error creating artist record", e);
59
+
consola.error("Error creating artist record", e);
59
60
return null;
60
61
}
61
62
}
···
79
80
};
80
81
81
82
if (!Album.validateRecord(record).success) {
82
-
console.log(Album.validateRecord(record));
83
-
console.log(JSON.stringify(record, null, 2));
83
+
consola.info(Album.validateRecord(record));
84
+
consola.info(JSON.stringify(record, null, 2));
84
85
throw new Error("Invalid record");
85
86
}
86
87
···
93
94
validate: false,
94
95
});
95
96
const uri = res.data.uri;
96
-
console.log(`Album record created at ${uri}`);
97
+
consola.info(`Album record created at ${uri}`);
97
98
return uri;
98
99
} catch (e) {
99
-
console.error("Error creating album record", e);
100
+
consola.error("Error creating album record", e);
100
101
return null;
101
102
}
102
103
}
···
134
135
};
135
136
136
137
if (!Song.validateRecord(record).success) {
137
-
console.log(Song.validateRecord(record));
138
-
console.log(chalk.cyan(JSON.stringify(record, null, 2)));
138
+
consola.info(Song.validateRecord(record));
139
+
consola.info(chalk.cyan(JSON.stringify(record, null, 2)));
139
140
throw new Error("Invalid record");
140
141
}
141
142
···
148
149
validate: false,
149
150
});
150
151
const uri = res.data.uri;
151
-
console.log(`Song record created at ${uri}`);
152
+
consola.info(`Song record created at ${uri}`);
152
153
return uri;
153
154
} catch (e) {
154
-
console.error("Error creating song record", e);
155
+
consola.error("Error creating song record", e);
155
156
return null;
156
157
}
157
158
}
···
192
193
};
193
194
194
195
if (!Scrobble.validateRecord(record).success) {
195
-
console.log(Scrobble.validateRecord(record));
196
-
console.log(JSON.stringify(record, null, 2));
196
+
consola.info(Scrobble.validateRecord(record));
197
+
consola.info(JSON.stringify(record, null, 2));
197
198
throw new Error("Invalid record");
198
199
}
199
200
···
206
207
validate: false,
207
208
});
208
209
const uri = res.data.uri;
209
-
console.log(`Scrobble record created at ${uri}`);
210
+
consola.info(`Scrobble record created at ${uri}`);
210
211
return uri;
211
212
} catch (e) {
212
-
console.error("Error creating scrobble record", e);
213
+
consola.error("Error creating scrobble record", e);
213
214
return null;
214
215
}
215
216
}
···
531
532
.then((rows) => rows[0]);
532
533
533
534
if (existingScrobble) {
534
-
console.log(
535
+
consola.info(
535
536
`Scrobble already exists for ${chalk.cyan(track.title)} at ${chalk.cyan(
536
537
scrobbleTime.format("YYYY-MM-DD HH:mm:ss"),
537
538
)}`,
···
614
615
615
616
let mbTrack;
616
617
try {
617
-
let { data } = await ctx.musicbrainz.post<MusicbrainzTrack>("/hydrate", {
618
+
const { data } = await ctx.musicbrainz.post<MusicbrainzTrack>("/hydrate", {
618
619
artist: track.artist
619
620
.replaceAll(";", ",")
620
621
.split(",")
···
641
642
name: artist.name,
642
643
}));
643
644
} catch (error) {
644
-
console.error("Error fetching MusicBrainz data");
645
+
consola.error("Error fetching MusicBrainz data");
645
646
}
646
647
647
648
if (!existingTrack?.uri || !userTrack?.userTrack.uri?.includes(userDid)) {
···
664
665
665
666
let tries = 0;
666
667
while (!existingTrack && tries < 30) {
667
-
console.log(`Song not found, trying again: ${chalk.magenta(tries + 1)}`);
668
+
consola.info(`Song not found, trying again: ${chalk.magenta(tries + 1)}`);
668
669
existingTrack = await ctx.db
669
670
.select()
670
671
.from(tracks)
···
685
686
}
686
687
687
688
if (tries === 30 && !existingTrack) {
688
-
console.log(`Song not found after ${chalk.magenta("30 tries")}`);
689
+
consola.info(`Song not found after ${chalk.magenta("30 tries")}`);
689
690
}
690
691
691
692
if (existingTrack) {
692
-
console.log(
693
+
consola.info(
693
694
`Song found: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries`,
694
695
);
695
696
}
···
768
769
.then((rows) => rows[0]);
769
770
770
771
while (!existingTrack?.artistUri && !existingTrack?.albumUri && tries < 30) {
771
-
console.log(
772
+
consola.info(
772
773
`Artist uri not ready, trying again: ${chalk.magenta(tries + 1)}`,
773
774
);
774
775
existingTrack = await ctx.db
···
847
848
}
848
849
849
850
if (tries === 30 && !existingTrack?.artistUri) {
850
-
console.log(`Artist uri not ready after ${chalk.magenta("30 tries")}`);
851
+
consola.info(`Artist uri not ready after ${chalk.magenta("30 tries")}`);
851
852
}
852
853
853
854
if (existingTrack?.artistUri) {
854
-
console.log(
855
+
consola.info(
855
856
`Artist uri ready: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries`,
856
857
);
857
858
}
···
925
926
scrobble.track.artistUri &&
926
927
scrobble.track.albumUri
927
928
) {
928
-
console.log("Scrobble found after ", chalk.magenta(tries + 1), " tries");
929
+
consola.info("Scrobble found after ", chalk.magenta(tries + 1), " tries");
929
930
await publishScrobble(ctx, scrobble.scrobble.id);
930
-
console.log("Scrobble published");
931
+
consola.info("Scrobble published");
931
932
break;
932
933
}
933
934
tries += 1;
934
-
console.log("Scrobble not found, trying again: ", chalk.magenta(tries));
935
+
consola.info("Scrobble not found, trying again: ", chalk.magenta(tries));
935
936
await new Promise((resolve) => setTimeout(resolve, 1000));
936
937
}
937
938
938
939
if (tries === 30 && !scrobble) {
939
-
console.log(`Scrobble not found after ${chalk.magenta("30 tries")}`);
940
+
consola.info(`Scrobble not found after ${chalk.magenta("30 tries")}`);
940
941
}
941
942
942
943
ctx.nc.publish("rocksky.user.scrobble.sync", Buffer.from(userDid));
+10
-9
apps/api/src/scripts/avatar.ts
+10
-9
apps/api/src/scripts/avatar.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { eq, or } from "drizzle-orm";
4
5
import _ from "lodash";
···
15
16
16
17
const serviceEndpoint = _.get(plc, "service.0.serviceEndpoint");
17
18
if (!serviceEndpoint) {
18
-
console.log(`Service endpoint not found for ${user.did}`);
19
+
consola.info(`Service endpoint not found for ${user.did}`);
19
20
return;
20
21
}
21
22
···
33
34
.where(eq(users.did, user.did))
34
35
.execute();
35
36
} else {
36
-
console.log(`Skipping avatar update for ${user.did}`);
37
+
consola.info(`Skipping avatar update for ${user.did}`);
37
38
}
38
39
39
40
const [u] = await ctx.db
···
54
55
xata_version: u.xataVersion,
55
56
};
56
57
57
-
console.log(userPayload);
58
+
consola.info(userPayload);
58
59
ctx.nc.publish("rocksky.user", Buffer.from(JSON.stringify(userPayload)));
59
60
}
60
61
···
67
68
.limit(1)
68
69
.execute();
69
70
if (!user) {
70
-
console.log(`User ${did} not found`);
71
+
consola.info(`User ${did} not found`);
71
72
continue;
72
73
}
73
74
···
77
78
let offset = 0;
78
79
let processedCount = 0;
79
80
80
-
console.log("Processing all users...");
81
+
consola.info("Processing all users...");
81
82
82
83
while (true) {
83
84
const batch = await ctx.db
···
91
92
break; // No more users to process
92
93
}
93
94
94
-
console.log(
95
+
consola.info(
95
96
`Processing batch ${Math.floor(offset / BATCH_SIZE) + 1}, users ${offset + 1}-${offset + batch.length}`,
96
97
);
97
98
···
100
101
await processUser(user);
101
102
processedCount++;
102
103
} catch (error) {
103
-
console.error(`Error processing user ${user.did}:`, error);
104
+
consola.error(`Error processing user ${user.did}:`, error);
104
105
}
105
106
}
106
107
···
110
111
await new Promise((resolve) => setTimeout(resolve, 100));
111
112
}
112
113
113
-
console.log(`Processed ${chalk.greenBright(processedCount)} users total`);
114
+
consola.info(`Processed ${chalk.greenBright(processedCount)} users total`);
114
115
}
115
116
116
117
// Ensure all messages are flushed before exiting
117
118
await ctx.nc.flush();
118
119
119
-
console.log("Done");
120
+
consola.info("Done");
120
121
121
122
process.exit(0);
+8
-7
apps/api/src/scripts/dedup.ts
+8
-7
apps/api/src/scripts/dedup.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { createAgent } from "lib/agent";
···
7
8
const args = process.argv.slice(2);
8
9
9
10
if (args.length === 0) {
10
-
console.error("Please provide user author identifier (handle or DID).");
11
-
console.log(`Usage: ${chalk.cyan("npm run feed -- <handle|did>")}`);
11
+
consola.error("Please provide user author identifier (handle or DID).");
12
+
consola.info(`Usage: ${chalk.cyan("npm run feed -- <handle|did>")}`);
12
13
process.exit(1);
13
14
}
14
15
···
36
37
.where(eq(tables.scrobbles.uri, record.uri))
37
38
.limit(1);
38
39
if (result.length === 0) {
39
-
console.log(`${i} Deleting record:`);
40
-
console.log(record);
40
+
consola.info(`${i} Deleting record:`);
41
+
consola.info(record);
41
42
const rkey = record.uri.split("/").pop();
42
43
await agent.com.atproto.repo.deleteRecord({
43
44
repo: agent.assertDid,
···
46
47
});
47
48
await new Promise((resolve) => setTimeout(resolve, 1000)); // rate limit
48
49
} else {
49
-
console.log(chalk.greenBright(`${i} Keeping record:`));
50
-
console.log(record);
50
+
consola.info(chalk.greenBright(`${i} Keeping record:`));
51
+
consola.info(record);
51
52
}
52
53
i += 1;
53
54
}
54
55
cursor = records.data.cursor;
55
56
} while (cursor);
56
57
57
-
console.log(chalk.greenBright("Deduplication complete."));
58
+
consola.info(chalk.greenBright("Deduplication complete."));
58
59
59
60
process.exit(0);
+3
-2
apps/api/src/scripts/exp.ts
+3
-2
apps/api/src/scripts/exp.ts
···
1
+
import { consola } from "consola";
1
2
import { ctx, db } from "context";
2
3
import { refreshSessionsAboutToExpire, updateExpiresAt } from "db";
3
4
import { env } from "lib/env";
4
5
import cron from "node-cron";
5
6
6
-
console.log("DB Path:", env.DB_PATH);
7
+
consola.info("DB Path:", env.DB_PATH);
7
8
8
9
await updateExpiresAt(db);
9
10
···
11
12
12
13
// run every 1 minute
13
14
cron.schedule("* * * * *", async () => {
14
-
console.log("Running session refresh job...");
15
+
consola.info("Running session refresh job...");
15
16
await refreshSessionsAboutToExpire(db, ctx);
16
17
});
+16
-15
apps/api/src/scripts/feed.ts
+16
-15
apps/api/src/scripts/feed.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import type * as FeedGenerator from "lexicon/types/app/rocksky/feed/generator";
4
5
import { createAgent } from "lib/agent";
···
7
8
const args = process.argv.slice(2);
8
9
9
10
if (args.length === 0) {
10
-
console.error("Please provide user author identifier (handle or DID).");
11
+
consola.error("Please provide user author identifier (handle or DID).");
11
12
console.log(`Usage: ${chalk.cyan("npm run feed -- <handle|did>")}`);
12
13
process.exit(1);
13
14
}
···
19
20
});
20
21
21
22
if (name.value.length < 3 || name.value.length > 240) {
22
-
console.error("Feed name must be between 3 and 240 characters.");
23
+
consola.error("Feed name must be between 3 and 240 characters.");
23
24
process.exit(1);
24
25
}
25
26
···
30
31
});
31
32
32
33
if (description.value.length > 3000) {
33
-
console.error("Description is too long. Maximum length is 3000 characters.");
34
+
consola.error("Description is too long. Maximum length is 3000 characters.");
34
35
process.exit(1);
35
36
}
36
37
···
41
42
});
42
43
43
44
if (!/^did:web:[a-zA-Z0-9_.-]{3,30}$/.test(did.value)) {
44
-
console.error(
45
+
consola.error(
45
46
"Invalid DID format. It should start with 'did:web:' followed by 3 to 30 alphanumeric characters, underscores, hyphens, or periods.",
46
47
);
47
48
process.exit(1);
···
54
55
});
55
56
56
57
if (!/^[a-zA-Z0-9_-]{3,30}$/.test(rkey.value)) {
57
-
console.error(
58
+
consola.error(
58
59
"Invalid record key. Only alphanumeric characters, underscores, and hyphens are allowed. Length must be between 3 and 30 characters.",
59
60
);
60
61
process.exit(1);
61
62
}
62
63
63
-
console.log("Creating feed with the following details:");
64
-
65
-
console.log("Feed name:", name.value);
66
-
console.log("Description:", description.value);
67
-
console.log("DID:", did.value);
68
-
console.log("Record key (rkey):", rkey.value);
64
+
consola.info("Creating feed with the following details:");
65
+
consola.info("---");
66
+
consola.info("Feed name:", name.value);
67
+
consola.info("Description:", description.value);
68
+
consola.info("DID:", did.value);
69
+
consola.info("Record key (rkey):", rkey.value);
69
70
70
71
const confirm = await prompts({
71
72
type: "confirm",
···
75
76
});
76
77
77
78
if (!confirm.value) {
78
-
console.log("Feed creation cancelled.");
79
+
consola.info("Feed creation cancelled.");
79
80
process.exit(0);
80
81
}
81
82
···
87
88
88
89
const agent = await createAgent(ctx.oauthClient, userDid);
89
90
90
-
console.log(
91
+
consola.info(
91
92
`Writing ${chalk.greenBright("app.rocksky.feed.generator")} record...`,
92
93
);
93
94
···
106
107
rkey: rkey.value,
107
108
});
108
109
109
-
console.log(chalk.greenBright("Feed created successfully!"));
110
-
console.log(`Record created at: ${chalk.cyan(res.data.uri)}`);
110
+
consola.info(chalk.greenBright("Feed created successfully!"));
111
+
consola.info(`Record created at: ${chalk.cyan(res.data.uri)}`);
111
112
112
113
process.exit(0);
+4
-3
apps/api/src/scripts/genres.ts
+4
-3
apps/api/src/scripts/genres.ts
···
1
+
import { consola } from "consola";
1
2
import { ctx } from "context";
2
3
import { eq, isNull } from "drizzle-orm";
3
4
import { decrypt } from "lib/crypto";
···
78
79
.then(async (data) => _.get(data, "artists.items.0"));
79
80
80
81
if (result) {
81
-
console.log(JSON.stringify(result, null, 2), "\n");
82
+
consola.info(JSON.stringify(result, null, 2), "\n");
82
83
if (result.genres && result.genres.length > 0) {
83
84
await ctx.db
84
85
.update(tables.artists)
···
97
98
}
98
99
break; // exit the retry loop on success
99
100
} catch (error) {
100
-
console.error("Error fetching genres for artist:", artist.name, error);
101
+
consola.error("Error fetching genres for artist:", artist.name, error);
101
102
// wait for a while before retrying
102
103
await new Promise((resolve) => setTimeout(resolve, 1000));
103
104
}
···
130
131
await getGenresAndPicture(artists);
131
132
}
132
133
133
-
console.log(`Artists without genres: ${count}`);
134
+
consola.info(`Artists without genres: ${count}`);
134
135
135
136
process.exit(0);
+4
-3
apps/api/src/scripts/likes.ts
+4
-3
apps/api/src/scripts/likes.ts
···
1
+
import chalk from "chalk";
2
+
import { consola } from "consola";
1
3
import { ctx } from "context";
2
4
import lovedTracks from "../schema/loved-tracks";
3
-
import chalk from "chalk";
4
5
5
6
const likes = await ctx.db.select().from(lovedTracks).execute();
6
7
···
14
15
xata_updatedat: like.createdAt.toISOString(),
15
16
xata_version: 0,
16
17
});
17
-
console.log("Publishing like:", chalk.cyanBright(like.uri));
18
+
consola.info("Publishing like:", chalk.cyanBright(like.uri));
18
19
ctx.nc.publish("rocksky.like", Buffer.from(message));
19
20
}
20
21
21
22
await ctx.nc.flush();
22
23
23
-
console.log("Done");
24
+
consola.info("Done");
24
25
25
26
process.exit(0);
+8
-7
apps/api/src/scripts/meili.ts
+8
-7
apps/api/src/scripts/meili.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { count } from "drizzle-orm";
4
5
import tables from "schema";
5
6
6
7
async function main() {
7
-
console.log(chalk.cyan("Starting Meilisearch sync..."));
8
+
consola.info(chalk.cyan("Starting Meilisearch sync..."));
8
9
9
10
try {
10
11
await Promise.all([
···
13
14
createTracks(),
14
15
createUsers(),
15
16
]);
16
-
console.log(chalk.green("Meilisearch sync completed successfully."));
17
+
consola.info(chalk.green("Meilisearch sync completed successfully."));
17
18
} catch (error) {
18
-
console.error(chalk.red("Error during Meilisearch sync:"), error);
19
+
consola.error(chalk.red("Error during Meilisearch sync:"), error);
19
20
}
20
21
}
21
22
···
31
32
.then(([row]) => row.value);
32
33
for (let i = 0; i < total; i += size) {
33
34
const skip = i;
34
-
console.log(
35
+
consola.info(
35
36
`Processing ${chalk.magentaBright("albums")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`,
36
37
);
37
38
const results = await ctx.db
···
55
56
.then(([row]) => row.value);
56
57
for (let i = 0; i < total; i += size) {
57
58
const skip = i;
58
-
console.log(
59
+
consola.info(
59
60
`Processing ${chalk.magentaBright("artists")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`,
60
61
);
61
62
const results = await ctx.db
···
79
80
.then(([row]) => row.value);
80
81
for (let i = 0; i < total; i += size) {
81
82
const skip = i;
82
-
console.log(
83
+
consola.info(
83
84
`Processing ${chalk.magentaBright("tracks")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`,
84
85
);
85
86
const results = await ctx.db
···
104
105
105
106
for (let i = 0; i < total; i += size) {
106
107
const skip = i;
107
-
console.log(
108
+
consola.info(
108
109
`Processing ${chalk.magentaBright("users")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`,
109
110
);
110
111
const results = await ctx.db
+5
-4
apps/api/src/scripts/seed-feed.ts
+5
-4
apps/api/src/scripts/seed-feed.ts
···
1
1
import type { Agent } from "@atproto/api";
2
2
import chalk from "chalk";
3
+
import { consola } from "consola";
3
4
import { ctx } from "context";
5
+
import { eq } from "drizzle-orm";
4
6
import { createAgent } from "lib/agent";
5
7
import * as FeedGenerator from "lexicon/types/app/rocksky/feed/generator";
6
8
import tables from "schema";
7
9
import type { InsertFeed } from "schema/feeds";
8
-
import { eq } from "drizzle-orm";
9
10
10
11
const args = process.argv.slice(2);
11
12
12
13
if (args.length === 0) {
13
-
console.error("Please provide user author identifier (handle or DID).");
14
-
console.log(`Usage: ${chalk.cyan("npm run seed:feed -- <handle|did>")}`);
14
+
consola.error("Please provide user author identifier (handle or DID).");
15
+
consola.info(`Usage: ${chalk.cyan("npm run seed:feed -- <handle|did>")}`);
15
16
process.exit(1);
16
17
}
17
18
···
57
58
} satisfies InsertFeed)
58
59
.onConflictDoNothing()
59
60
.execute();
60
-
console.log(
61
+
consola.info(
61
62
`Feed ${chalk.cyanBright(feed.value.displayName)} seeded successfully.`,
62
63
);
63
64
}
+3
-2
apps/api/src/scripts/spotify.ts
+3
-2
apps/api/src/scripts/spotify.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { encrypt } from "lib/crypto";
4
5
import { env } from "lib/env";
···
9
10
const clientSecret = args[1];
10
11
11
12
if (!clientId || !clientSecret) {
12
-
console.error(
13
+
consola.error(
13
14
"Please provide Spotify Client ID and Client Secret as command line arguments",
14
15
);
15
-
console.log(
16
+
consola.info(
16
17
chalk.greenBright("Usage: ts-node spotify.ts <client_id> <client_secret>"),
17
18
);
18
19
process.exit(1);
+14
-11
apps/api/src/scripts/sync-library.ts
+14
-11
apps/api/src/scripts/sync-library.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { and, count, eq } from "drizzle-orm";
4
5
import tables from "schema";
···
11
12
.execute()
12
13
.then(([row]) => row.value);
13
14
14
-
console.log(`Total tracks to process: ${chalk.magentaBright(total)}`);
15
+
consola.info(`Total tracks to process: ${chalk.magentaBright(total)}`);
15
16
16
17
for (let i = 0; i < total; i += size) {
17
18
const skip = i;
18
-
console.log(
19
+
consola.info(
19
20
`Processing ${chalk.magentaBright("tracks")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`,
20
21
);
21
22
const results = await ctx.db
···
27
28
28
29
for (const track of results) {
29
30
if (!track.artistUri || !track.albumUri) {
30
-
console.log(
31
-
`Skipping track ${chalk.cyan(track.title)} due to missing artist or album URI`,
31
+
consola.info(
32
+
`Deleting album-track relationship for track: ${chalk.redBright(track.uri)}`,
32
33
);
33
-
console.log("artistUri", track.artistUri);
34
-
console.log("albumUri", track.albumUri);
34
+
consola.info("artistUri", track.artistUri);
35
+
consola.info("albumUri", track.albumUri);
35
36
continue;
36
37
}
37
38
···
57
58
.then((rows) => rows.length > 0);
58
59
59
60
if (!found) {
60
-
console.log(`Creating artist-album relationship for track: ${track.uri}`);
61
+
consola.info(
62
+
`Creating artist-album relationship for track: ${track.uri}`,
63
+
);
61
64
const [artist, album] = await Promise.all([
62
65
ctx.db
63
66
.select()
···
76
79
]);
77
80
78
81
if (!artist || !album) {
79
-
console.error(
80
-
`Artist or album not found for track: ${track.uri}. Skipping...`,
82
+
consola.error(
83
+
`Artist-album relationship already exists for track: ${chalk.redBright(track.uri)}`,
81
84
);
82
-
console.log("artist", artist);
83
-
console.log("album", album);
85
+
consola.info("artist", artist);
86
+
consola.info("album", album);
84
87
continue;
85
88
}
86
89
+13
-12
apps/api/src/scripts/sync.ts
+13
-12
apps/api/src/scripts/sync.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { desc, eq, or } from "drizzle-orm";
4
5
import { createHash } from "node:crypto";
···
11
12
12
13
const args = process.argv.slice(2);
13
14
14
-
async function updateUris(did: string) {
15
+
export async function updateUris(did: string) {
15
16
// Get scrobbles with track and user data
16
17
const records = await ctx.db
17
18
.select({
···
38
39
.then((rows) => rows[0]);
39
40
40
41
if (existingTrack && !existingTrack.albumUri) {
41
-
console.log(`Updating album uri for ${chalk.cyan(track.id)} ...`);
42
+
consola.info(`Updating album uri for ${chalk.cyan(track.id)} ...`);
42
43
43
44
const albumHash = createHash("sha256")
44
45
.update(`${track.album} - ${track.albumArtist}`.toLowerCase())
···
60
61
}
61
62
62
63
if (existingTrack && !existingTrack.artistUri) {
63
-
console.log(`Updating artist uri for ${chalk.cyan(track.id)} ...`);
64
+
consola.info(`Updating artist uri for ${chalk.cyan(track.id)} ...`);
64
65
65
66
const artistHash = createHash("sha256")
66
67
.update(track.albumArtist.toLowerCase())
···
93
94
.then((rows) => rows[0]);
94
95
95
96
if (existingTrack && album && !album.artistUri) {
96
-
console.log(`Updating artist uri for ${chalk.cyan(album.id)} ...`);
97
+
consola.info(`Updating artist uri for ${chalk.cyan(album.id)} ...`);
97
98
98
99
const artistHash = createHash("sha256")
99
100
.update(track.albumArtist.toLowerCase())
···
117
118
}
118
119
119
120
if (args.includes("--background")) {
120
-
console.log("Wait for new scrobbles to sync ...");
121
+
consola.info("Wait for new scrobbles to sync ...");
121
122
const sub = ctx.nc.subscribe("rocksky.user.scrobble.sync");
122
123
for await (const m of sub) {
123
124
const did = new TextDecoder().decode(m.data);
124
125
// wait for 15 seconds to ensure the scrobble is fully created
125
126
await new Promise((resolve) => setTimeout(resolve, 15000));
126
-
console.log(`Syncing scrobbles ${chalk.magenta(did)} ...`);
127
+
consola.info(`Syncing scrobbles ${chalk.magenta(did)} ...`);
127
128
await updateUris(did);
128
129
129
130
const records = await ctx.db
···
137
138
.limit(5);
138
139
139
140
for (const { scrobble } of records) {
140
-
console.log(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`);
141
+
consola.info(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`);
141
142
try {
142
143
await publishScrobble(ctx, scrobble.id);
143
144
} catch (err) {
144
-
console.error(
145
+
consola.error(
145
146
`Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`,
146
147
err,
147
148
);
···
152
153
}
153
154
154
155
for (const arg of args) {
155
-
console.log(`Syncing scrobbles ${chalk.magenta(arg)} ...`);
156
+
consola.info(`Syncing scrobbles ${chalk.magenta(arg)} ...`);
156
157
await updateUris(arg);
157
158
158
159
const records = await ctx.db
···
166
167
.limit(process.env.SYNC_SIZE ? parseInt(process.env.SYNC_SIZE, 10) : 20);
167
168
168
169
for (const { scrobble } of records) {
169
-
console.log(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`);
170
+
consola.info(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`);
170
171
try {
171
172
await publishScrobble(ctx, scrobble.id);
172
173
} catch (err) {
173
-
console.error(`Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`, err);
174
+
consola.error(`Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`, err);
174
175
}
175
176
}
176
-
console.log(`Synced ${chalk.greenBright(records.length)} scrobbles`);
177
+
consola.info(`Synced ${chalk.greenBright(records.length)} scrobbles`);
177
178
}
178
179
179
180
process.exit(0);
+2
-1
apps/api/src/server.ts
+2
-1
apps/api/src/server.ts
···
1
+
import { consola } from "consola";
1
2
import { ctx } from "context";
2
3
import cors from "cors";
3
4
import type { Request, Response } from "express";
···
30
31
app.use(proxyMiddleware);
31
32
32
33
app.listen(process.env.ROCKSKY_XPRC_PORT || 3004, () => {
33
-
console.log(
34
+
consola.info(
34
35
`Rocksky XRPC API is running on port ${process.env.ROCKSKY_XRPC_PORT || 3004}`,
35
36
);
36
37
});
+11
-10
apps/api/src/shouts/shouts.service.ts
+11
-10
apps/api/src/shouts/shouts.service.ts
···
1
1
import { type Agent, AtpAgent } from "@atproto/api";
2
+
import { consola } from "consola";
2
3
import { TID } from "@atproto/common";
3
4
import type { Context } from "context";
4
5
import { and, eq } from "drizzle-orm";
···
98
99
cid: subjectRecord.data.cid,
99
100
});
100
101
if (!subjectRef.success) {
101
-
console.log(subjectRef);
102
+
consola.info(subjectRef);
102
103
throw new Error("Invalid ref");
103
104
}
104
105
···
111
112
};
112
113
113
114
if (!ShoutLexicon.validateRecord(record).success) {
114
-
console.log(ShoutLexicon.validateRecord(record));
115
+
consola.info(ShoutLexicon.validateRecord(record));
115
116
throw new Error("[shout] invalid record");
116
117
}
117
118
···
125
126
});
126
127
const uri = res.data.uri;
127
128
128
-
console.log(`Shout record created at: ${uri}`);
129
+
consola.info(`Shout record created at: ${uri}`);
129
130
130
131
const createdShout = await ctx.db
131
132
.insert(shouts)
···
148
149
});
149
150
}
150
151
} catch (e) {
151
-
console.error(`Error creating shout record: ${e.message}`);
152
+
consola.error(`Error creating shout record: ${e.message}`);
152
153
}
153
154
}
154
155
···
269
270
};
270
271
271
272
if (!ShoutLexicon.validateRecord(record).success) {
272
-
console.log(ShoutLexicon.validateRecord(record));
273
+
consola.info(ShoutLexicon.validateRecord(record));
273
274
throw new Error("Invalid record");
274
275
}
275
276
···
283
284
});
284
285
const uri = res.data.uri;
285
286
286
-
console.log(`Reply record created at: ${uri}`);
287
+
consola.info(`Reply record created at: ${uri}`);
287
288
288
289
const createdShout = await ctx.db
289
290
.insert(shouts)
···
314
315
});
315
316
}
316
317
} catch (e) {
317
-
console.error(`Error creating reply record: ${e.message}`);
318
+
consola.error(`Error creating reply record: ${e.message}`);
318
319
}
319
320
}
320
321
···
370
371
};
371
372
372
373
if (!LikeLexicon.validateRecord(record).success) {
373
-
console.log(LikeLexicon.validateRecord(record));
374
+
consola.info(LikeLexicon.validateRecord(record));
374
375
throw new Error("Invalid record");
375
376
}
376
377
···
383
384
validate: false,
384
385
});
385
386
const uri = res.data.uri;
386
-
console.log(`Like record created at: ${uri}`);
387
+
consola.info(`Like record created at: ${uri}`);
387
388
388
389
const shout = await ctx.db
389
390
.select()
···
402
403
uri,
403
404
});
404
405
} catch (e) {
405
-
console.error(`Error creating like record: ${e.message}`);
406
+
consola.error(`Error creating like record: ${e.message}`);
406
407
}
407
408
}
408
409
+2
-1
apps/api/src/spotify/app.ts
+2
-1
apps/api/src/spotify/app.ts
···
1
+
import { consola } from "consola";
1
2
import { ctx } from "context";
2
3
import { and, eq, or, sql } from "drizzle-orm";
3
4
import { Hono } from "hono";
···
249
250
});
250
251
} catch (e) {
251
252
if (!e.message.includes("duplicate key value violates unique constraint")) {
252
-
console.error(e.message);
253
+
consola.error(e.message);
253
254
} else {
254
255
throw e;
255
256
}
+2
apps/api/src/subscribers/index.ts
+2
apps/api/src/subscribers/index.ts
···
2
2
import { onNewPlaylist } from "./playlist";
3
3
import { onNewTrack } from "./track";
4
4
import { onNewUser } from "./user";
5
+
import { onNewScrobble } from "./scrobble";
5
6
6
7
export default function subscribe(ctx: Context) {
7
8
onNewPlaylist(ctx);
8
9
onNewTrack(ctx);
9
10
onNewUser(ctx);
11
+
onNewScrobble(ctx);
10
12
}
+6
-5
apps/api/src/subscribers/playlist.ts
+6
-5
apps/api/src/subscribers/playlist.ts
···
1
1
import { TID } from "@atproto/common";
2
+
import { consola } from "consola";
2
3
import chalk from "chalk";
3
4
import type { Context } from "context";
4
5
import { eq } from "drizzle-orm";
···
16
17
id: string;
17
18
did: string;
18
19
} = JSON.parse(sc.decode(m.data));
19
-
console.log(
20
+
consola.info(
20
21
`New playlist: ${chalk.cyan(payload.did)} - ${chalk.greenBright(payload.id)}`,
21
22
);
22
23
await putPlaylistRecord(ctx, payload);
···
31
32
const agent = await createAgent(ctx.oauthClient, payload.did);
32
33
33
34
if (!agent) {
34
-
console.error(
35
+
consola.error(
35
36
`Failed to create agent, skipping playlist: ${chalk.cyan(payload.id)} for ${chalk.greenBright(payload.did)}`,
36
37
);
37
38
return;
···
69
70
};
70
71
71
72
if (!Playlist.validateRecord(record)) {
72
-
console.error(`Invalid record: ${chalk.redBright(JSON.stringify(record))}`);
73
+
consola.error(`Invalid record: ${chalk.redBright(JSON.stringify(record))}`);
73
74
return;
74
75
}
75
76
···
82
83
validate: false,
83
84
});
84
85
const uri = res.data.uri;
85
-
console.log(`Playlist record created: ${chalk.greenBright(uri)}`);
86
+
consola.info(`Playlist record created: ${chalk.greenBright(uri)}`);
86
87
await ctx.db
87
88
.update(tables.playlists)
88
89
.set({ uri })
89
90
.where(eq(tables.playlists.id, payload.id))
90
91
.execute();
91
92
} catch (e) {
92
-
console.error(`Failed to put record: ${chalk.redBright(e.message)}`);
93
+
consola.error(`Failed to put record: ${chalk.redBright(e.message)}`);
93
94
}
94
95
95
96
const [updatedPlaylist] = await ctx.db
+161
apps/api/src/subscribers/scrobble.ts
+161
apps/api/src/subscribers/scrobble.ts
···
1
+
import { consola } from "consola";
2
+
import type { Context } from "context";
3
+
import { eq, or, desc } from "drizzle-orm";
4
+
import _ from "lodash";
5
+
import { StringCodec } from "nats";
6
+
import { createHash } from "node:crypto";
7
+
import tables from "schema";
8
+
import chalk from "chalk";
9
+
import { publishScrobble } from "nowplaying/nowplaying.service";
10
+
11
+
export function onNewScrobble(ctx: Context) {
12
+
const sc = StringCodec();
13
+
const sub = ctx.nc.subscribe("rocksky.scrobble.new");
14
+
(async () => {
15
+
for await (const m of sub) {
16
+
const scrobbleId = sc.decode(m.data);
17
+
const result = await ctx.db
18
+
.select()
19
+
.from(tables.scrobbles)
20
+
.where(eq(tables.scrobbles.id, scrobbleId))
21
+
.leftJoin(tables.users, eq(tables.scrobbles.userId, tables.users.id))
22
+
.execute()
23
+
.then((rows) => rows[0]);
24
+
25
+
if (!result) {
26
+
consola.info(`Scrobble with ID ${scrobbleId} not found, skipping`);
27
+
continue;
28
+
}
29
+
30
+
await updateUris(ctx, result.users.did);
31
+
await refreshScrobbles(ctx, result.users.did);
32
+
}
33
+
})();
34
+
}
35
+
36
+
async function updateUris(ctx: Context, did: string) {
37
+
// Get scrobbles with track and user data
38
+
const records = await ctx.db
39
+
.select({
40
+
track: tables.tracks,
41
+
user: tables.users,
42
+
})
43
+
.from(tables.scrobbles)
44
+
.innerJoin(tables.tracks, eq(tables.scrobbles.trackId, tables.tracks.id))
45
+
.innerJoin(tables.users, eq(tables.scrobbles.userId, tables.users.id))
46
+
.where(or(eq(tables.users.did, did), eq(tables.users.handle, did)))
47
+
.orderBy(desc(tables.scrobbles.createdAt))
48
+
.limit(5);
49
+
50
+
for (const { track } of records) {
51
+
const trackHash = createHash("sha256")
52
+
.update(`${track.title} - ${track.artist} - ${track.album}`.toLowerCase())
53
+
.digest("hex");
54
+
55
+
const existingTrack = await ctx.db
56
+
.select()
57
+
.from(tables.tracks)
58
+
.where(eq(tables.tracks.sha256, trackHash))
59
+
.limit(1)
60
+
.then((rows) => rows[0]);
61
+
62
+
if (existingTrack && !existingTrack.albumUri) {
63
+
consola.info(`Updating album uri for ${chalk.cyan(track.id)} ...`);
64
+
65
+
const albumHash = createHash("sha256")
66
+
.update(`${track.album} - ${track.albumArtist}`.toLowerCase())
67
+
.digest("hex");
68
+
69
+
const album = await ctx.db
70
+
.select()
71
+
.from(tables.albums)
72
+
.where(eq(tables.albums.sha256, albumHash))
73
+
.limit(1)
74
+
.then((rows) => rows[0]);
75
+
76
+
if (album) {
77
+
await ctx.db
78
+
.update(tables.tracks)
79
+
.set({ albumUri: album.uri })
80
+
.where(eq(tables.tracks.id, existingTrack.id));
81
+
}
82
+
}
83
+
84
+
if (existingTrack && !existingTrack.artistUri) {
85
+
consola.info(`Updating artist uri for ${chalk.cyan(track.id)} ...`);
86
+
87
+
const artistHash = createHash("sha256")
88
+
.update(track.albumArtist.toLowerCase())
89
+
.digest("hex");
90
+
91
+
const artist = await ctx.db
92
+
.select()
93
+
.from(tables.artists)
94
+
.where(eq(tables.artists.sha256, artistHash))
95
+
.limit(1)
96
+
.then((rows) => rows[0]);
97
+
98
+
if (artist) {
99
+
await ctx.db
100
+
.update(tables.tracks)
101
+
.set({ artistUri: artist.uri })
102
+
.where(eq(tables.tracks.id, existingTrack.id));
103
+
}
104
+
}
105
+
106
+
const albumHash = createHash("sha256")
107
+
.update(`${track.album} - ${track.albumArtist}`.toLowerCase())
108
+
.digest("hex");
109
+
110
+
const album = await ctx.db
111
+
.select()
112
+
.from(tables.albums)
113
+
.where(eq(tables.albums.sha256, albumHash))
114
+
.limit(1)
115
+
.then((rows) => rows[0]);
116
+
117
+
if (existingTrack && album && !album.artistUri) {
118
+
consola.info(`Updating artist uri for ${chalk.cyan(album.id)} ...`);
119
+
120
+
const artistHash = createHash("sha256")
121
+
.update(track.albumArtist.toLowerCase())
122
+
.digest("hex");
123
+
124
+
const artist = await ctx.db
125
+
.select()
126
+
.from(tables.artists)
127
+
.where(eq(tables.artists.sha256, artistHash))
128
+
.limit(1)
129
+
.then((rows) => rows[0]);
130
+
131
+
if (artist) {
132
+
await ctx.db
133
+
.update(tables.albums)
134
+
.set({ artistUri: artist.uri })
135
+
.where(eq(tables.albums.id, album.id));
136
+
}
137
+
}
138
+
}
139
+
}
140
+
141
+
async function refreshScrobbles(ctx: Context, did: string) {
142
+
const records = await ctx.db
143
+
.select({
144
+
scrobble: tables.scrobbles,
145
+
})
146
+
.from(tables.scrobbles)
147
+
.innerJoin(tables.users, eq(tables.scrobbles.userId, tables.users.id))
148
+
.where(or(eq(tables.users.did, did), eq(tables.users.handle, did)))
149
+
.orderBy(desc(tables.scrobbles.createdAt))
150
+
.limit(5);
151
+
152
+
for (const { scrobble } of records) {
153
+
consola.info(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`);
154
+
try {
155
+
await publishScrobble(ctx, scrobble.id);
156
+
} catch (err) {
157
+
consola.error(`Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`, err);
158
+
}
159
+
}
160
+
consola.info(`Synced ${chalk.greenBright(records.length)} scrobbles`);
161
+
}
+2
-1
apps/api/src/subscribers/track.ts
+2
-1
apps/api/src/subscribers/track.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import _ from "lodash";
···
36
37
.execute(),
37
38
]);
38
39
39
-
console.log(`New track: ${chalk.cyan(_.get(tracks, "0.title"))}`);
40
+
consola.info(`New track: ${chalk.cyan(_.get(tracks, "0.title"))}`);
40
41
41
42
await Promise.all([
42
43
ctx.meilisearch.post(`indexes/albums/documents?primaryKey=id`, albums),
+2
-1
apps/api/src/subscribers/user.ts
+2
-1
apps/api/src/subscribers/user.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import _ from "lodash";
···
19
20
.where(eq(tables.users.id, payload.xata_id))
20
21
.execute();
21
22
22
-
console.log(`New user: ${chalk.cyan(_.get(results, "0.handle"))}`);
23
+
consola.info(`New user: ${chalk.cyan(_.get(results, "0.handle"))}`);
23
24
24
25
await ctx.meilisearch.post(
25
26
`/indexes/users/documents?primaryKey=id`,
+10
-7
apps/api/src/tealfm/index.ts
+10
-7
apps/api/src/tealfm/index.ts
···
1
1
import type { Agent } from "@atproto/api";
2
2
import { TID } from "@atproto/common";
3
3
import chalk from "chalk";
4
+
import { consola } from "consola";
4
5
import type * as Status from "lexicon/types/fm/teal/alpha/actor/status";
5
6
import type { PlayView } from "lexicon/types/fm/teal/alpha/feed/defs";
6
7
import * as Play from "lexicon/types/fm/teal/alpha/feed/play";
···
24
25
duration: number,
25
26
) {
26
27
if (env.DISABLED_TEALFM.includes(agent.assertDid)) {
27
-
console.log(`teal.fm is disabled for ${chalk.cyanBright(agent.assertDid)}`);
28
+
consola.info(
29
+
`teal.fm is disabled for ${chalk.cyanBright(agent.assertDid)}`,
30
+
);
28
31
return;
29
32
}
30
33
···
47
50
);
48
51
});
49
52
if (alreadyPlayed) {
50
-
console.log(
53
+
consola.info(
51
54
`Track ${chalk.cyan(track.name)} by ${chalk.cyan(
52
55
track.artist.map((a) => a.name).join(", "),
53
56
)} already played recently. Skipping...`,
···
72
75
};
73
76
74
77
if (!Play.validateRecord(record).success) {
75
-
console.log(Play.validateRecord(record));
76
-
console.log(chalk.cyan(JSON.stringify(record, null, 2)));
78
+
consola.info(Play.validateRecord(record));
79
+
consola.info(chalk.cyan(JSON.stringify(record, null, 2)));
77
80
throw new Error("Invalid record");
78
81
}
79
82
···
85
88
validate: false,
86
89
});
87
90
const uri = res.data.uri;
88
-
console.log(`tealfm Play record created at ${uri}`);
91
+
consola.info(`tealfm Play record created at ${uri}`);
89
92
90
93
await publishStatus(agent, track, duration);
91
94
} catch (error) {
92
-
console.error("Error publishing teal.fm record:", error);
95
+
consola.error("Error publishing teal.fm record:", error);
93
96
}
94
97
}
95
98
···
127
130
record,
128
131
swapRecord,
129
132
});
130
-
console.log(`tealfm Status record published at ${res.data.uri}`);
133
+
consola.info(`tealfm Status record published at ${res.data.uri}`);
131
134
}
132
135
133
136
async function getStatusSwapRecord(agent: Agent): Promise<string | undefined> {
+15
-14
apps/api/src/tracks/tracks.service.ts
+15
-14
apps/api/src/tracks/tracks.service.ts
···
1
1
import type { Agent } from "@atproto/api";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { and, eq } from "drizzle-orm";
4
5
import { deepSnakeCaseKeys } from "lib";
···
130
131
.then((results) => results[0]);
131
132
132
133
if (!track_id || !album_id || !artist_id) {
133
-
console.log(
134
+
consola.info(
134
135
"Track not yet saved (uri not saved), retrying...",
135
136
tries + 1,
136
137
);
···
218
219
track_id.albumUri &&
219
220
track_id.artistUri
220
221
) {
221
-
console.log("Track saved successfully after", tries + 1, "tries");
222
+
consola.info("Track saved successfully after", tries + 1, "tries");
222
223
223
224
const message = JSON.stringify(
224
225
deepSnakeCaseKeys({
···
275
276
}
276
277
277
278
tries += 1;
278
-
console.log("Track not yet saved, retrying...", tries + 1);
279
+
consola.info("Track not yet saved, retrying...", tries + 1);
279
280
if (tries === 15) {
280
-
console.log(">>>");
281
-
console.log(album_track);
282
-
console.log(artist_track);
283
-
console.log(artist_album);
284
-
console.log(artist_id);
285
-
console.log(album_id);
286
-
console.log(track_id);
287
-
console.log(track_id.albumUri);
288
-
console.log(track_id.artistUri);
289
-
console.log("<<<");
281
+
consola.info(">>>");
282
+
consola.info(album_track);
283
+
consola.info(artist_track);
284
+
consola.info(artist_album);
285
+
consola.info(artist_id);
286
+
consola.info(album_id);
287
+
consola.info(track_id);
288
+
consola.info(track_id.albumUri);
289
+
consola.info(track_id.artistUri);
290
+
consola.info("<<<");
290
291
}
291
292
await new Promise((resolve) => setTimeout(resolve, 1000));
292
293
}
293
294
294
295
if (tries === 15) {
295
-
console.log("Failed to save track after 15 tries");
296
+
consola.info("Failed to save track after 15 tries");
296
297
}
297
298
}
+12
-11
apps/api/src/websocket/handler.ts
+12
-11
apps/api/src/websocket/handler.ts
···
1
1
import chalk from "chalk";
2
+
import { consola } from "consola";
2
3
import { ctx } from "context";
3
4
import { and, eq } from "drizzle-orm";
4
5
import type { Context } from "hono";
···
174
175
const { did } = jwt.verify(token, env.JWT_SECRET, {
175
176
ignoreExpiration: true,
176
177
});
177
-
console.log(
178
+
consola.info(
178
179
`Control message: ${chalk.greenBright(type)}, ${chalk.greenBright(target)}, ${chalk.greenBright(action)}, ${chalk.greenBright(args)}, ${chalk.greenBright("***")}`,
179
180
);
180
181
// Handle control message
···
183
184
const targetDevice = devices[deviceId];
184
185
if (targetDevice) {
185
186
targetDevice.send(JSON.stringify({ type, action, args }));
186
-
console.log(
187
+
consola.info(
187
188
`Control message sent to device: ${chalk.greenBright(deviceId)}, ${chalk.greenBright(target)}`,
188
189
);
189
190
return;
190
191
}
191
-
console.error(`Device not found: ${target}`);
192
+
consola.error(`Device not found: ${target}`);
192
193
return;
193
194
}
194
195
userDevices[did]?.forEach((id) => {
195
196
const targetDevice = devices[id];
196
197
if (targetDevice) {
197
198
targetDevice.send(JSON.stringify({ type, action, args }));
198
-
console.log(
199
+
consola.info(
199
200
`Control message sent to all devices: ${chalk.greenBright(id)}, ${chalk.greenBright(target)}`,
200
201
);
201
202
}
202
203
});
203
204
204
-
console.error(`Device ID not found for target: ${target}`);
205
+
consola.error(`Device ID not found for target: ${target}`);
205
206
return;
206
207
}
207
208
208
209
if (registerMessage.success) {
209
210
const { type, clientName, token } = registerMessage.data;
210
-
console.log(
211
+
consola.info(
211
212
`Register message: ${chalk.greenBright(type)}, ${chalk.greenBright(clientName)}, ${chalk.greenBright("****")}`,
212
213
);
213
214
// Handle register Message
···
220
221
devices[deviceId] = ws;
221
222
deviceNames[deviceId] = clientName;
222
223
userDevices[did] = [...(userDevices[did] || []), deviceId];
223
-
console.log(
224
+
consola.info(
224
225
`Device registered: ${chalk.greenBright(deviceId)}, ${chalk.greenBright(clientName)}`,
225
226
);
226
227
···
244
245
return;
245
246
}
246
247
} catch (e) {
247
-
console.error("Error parsing message:", e);
248
+
consola.error("Error parsing message:", e);
248
249
}
249
250
},
250
251
onClose: (_, ws) => {
251
-
console.log("Connection closed");
252
+
consola.info("Connection closed");
252
253
// remove device from devices
253
254
const deviceId = ws.deviceId;
254
255
const did = ws.did;
255
256
if (deviceId && devices[deviceId]) {
256
257
delete devices[deviceId];
257
-
console.log(`Device removed: ${chalk.redBright(deviceId)}`);
258
+
consola.info(`Device removed: ${chalk.redBright(deviceId)}`);
258
259
}
259
260
if (did && userDevices[did]) {
260
261
userDevices[did] = userDevices[did].filter((id) => id !== deviceId);
···
265
266
if (deviceId && deviceNames[deviceId]) {
266
267
const clientName = deviceNames[deviceId];
267
268
delete deviceNames[deviceId];
268
-
console.log(
269
+
consola.info(
269
270
`Device name removed: ${chalk.redBright(deviceId)}, ${chalk.redBright(clientName)}`,
270
271
);
271
272
}
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorAlbums.ts
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorAlbums.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorAlbums";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("120 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({ artists: [] });
19
20
}),
20
21
);
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorArtists.ts
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorArtists.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorArtists";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("120 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({ artists: [] });
19
20
}),
20
21
);
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorCompatibility.ts
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorCompatibility.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorCompatibility";
···
17
18
Effect.retry({ times: 3 }),
18
19
Effect.timeout("120 seconds"),
19
20
Effect.catchAll((err) => {
20
-
console.error(err);
21
+
consola.error(err);
21
22
return Effect.succeed({ comptibility: null });
22
23
}),
23
24
);
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorLovedSongs.ts
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorLovedSongs.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { and, desc, eq, isNotNull, not, or } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
16
17
Effect.retry({ times: 3 }),
17
18
Effect.timeout("120 seconds"),
18
19
Effect.catchAll((err) => {
19
-
console.error(err);
20
+
consola.error(err);
20
21
return Effect.succeed({ tracks: [] });
21
22
}),
22
23
);
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorNeighbours.ts
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorNeighbours.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorNeighbours";
···
16
17
Effect.retry({ times: 3 }),
17
18
Effect.timeout("120 seconds"),
18
19
Effect.catchAll((err) => {
19
-
console.error(err);
20
+
consola.error(err);
20
21
return Effect.succeed({ neighbours: [] });
21
22
}),
22
23
);
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorPlaylists.ts
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorPlaylists.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { eq, or, sql } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
16
17
Effect.retry({ times: 3 }),
17
18
Effect.timeout("120 seconds"),
18
19
Effect.catchAll((err) => {
19
-
console.error(err);
20
+
consola.error(err);
20
21
return Effect.succeed({ playlists: [] });
21
22
}),
22
23
);
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorScrobbles.ts
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorScrobbles.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorScrobbles";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("120 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({ scrobbles: [] });
19
20
}),
20
21
);
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorSongs.ts
+2
-1
apps/api/src/xrpc/app/rocksky/actor/getActorSongs.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorSongs";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("120 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({ tracks: [] });
19
20
}),
20
21
);
+3
-2
apps/api/src/xrpc/app/rocksky/actor/getProfile.ts
+3
-2
apps/api/src/xrpc/app/rocksky/actor/getProfile.ts
···
1
1
import { type Agent, AtpAgent } from "@atproto/api";
2
+
import { consola } from "consola";
2
3
import type { OutputSchema } from "@atproto/api/dist/client/types/com/atproto/repo/getRecord";
3
4
import type { HandlerAuth } from "@atproto/xrpc-server";
4
5
import type { Context } from "context";
···
31
32
Effect.retry({ times: 3 }),
32
33
Effect.timeout("120 seconds"),
33
34
Effect.catchAll((err) => {
34
-
console.error(err);
35
+
consola.error(err);
35
36
return Effect.succeed({});
36
37
}),
37
38
);
···
139
140
}: WithAgent): Effect.Effect<WithUser, Error> => {
140
141
return Effect.tryPromise({
141
142
try: async () => {
142
-
console.log(">> did", did);
143
+
consola.info(">> did", did);
143
144
return ctx.db
144
145
.select()
145
146
.from(tables.users)
+3
-2
apps/api/src/xrpc/app/rocksky/album/getAlbum.ts
+3
-2
apps/api/src/xrpc/app/rocksky/album/getAlbum.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { asc, count, eq, or } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
19
20
Effect.retry({ times: 3 }),
20
21
Effect.timeout("120 seconds"),
21
22
Effect.catchAll((err) => {
22
-
console.error(err);
23
+
consola.error(err);
23
24
return Effect.succeed({});
24
25
}),
25
26
);
···
91
92
]);
92
93
},
93
94
catch: (error) => {
94
-
console.log("Error retrieving album:", error);
95
+
consola.info("Error retrieving album:", error);
95
96
return new Error(`Failed to retrieve album: ${error}`);
96
97
},
97
98
});
+2
-1
apps/api/src/xrpc/app/rocksky/album/getAlbumTracks.ts
+2
-1
apps/api/src/xrpc/app/rocksky/album/getAlbumTracks.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { asc, eq } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
17
18
Effect.retry({ times: 3 }),
18
19
Effect.timeout("120 seconds"),
19
20
Effect.catchAll((err) => {
20
-
console.error(err);
21
+
consola.error(err);
21
22
return Effect.succeed({});
22
23
}),
23
24
);
+2
-1
apps/api/src/xrpc/app/rocksky/album/getAlbums.ts
+2
-1
apps/api/src/xrpc/app/rocksky/album/getAlbums.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { AlbumViewBasic } from "lexicon/types/app/rocksky/album/defs";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("120 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({ albums: [] });
19
20
}),
20
21
);
+2
-1
apps/api/src/xrpc/app/rocksky/apikey/createApikey.ts
+2
-1
apps/api/src/xrpc/app/rocksky/apikey/createApikey.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/apikey/getApikeys.ts
+2
-1
apps/api/src/xrpc/app/rocksky/apikey/getApikeys.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
12
13
Effect.retry({ times: 3 }),
13
14
Effect.timeout("10 seconds"),
14
15
Effect.catchAll((err) => {
15
-
console.error(err);
16
+
consola.error(err);
16
17
return Effect.succeed({});
17
18
}),
18
19
);
+2
-1
apps/api/src/xrpc/app/rocksky/apikey/removeApikey.ts
+2
-1
apps/api/src/xrpc/app/rocksky/apikey/removeApikey.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/apikey/updateApikey.ts
+2
-1
apps/api/src/xrpc/app/rocksky/apikey/updateApikey.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { eq } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("10 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({});
19
20
}),
20
21
);
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtist.ts
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtist.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { count, eq, or } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
16
17
Effect.retry({ times: 3 }),
17
18
Effect.timeout("10 seconds"),
18
19
Effect.catchAll((err) => {
19
-
console.error(err);
20
+
consola.error(err);
20
21
return Effect.succeed({});
21
22
}),
22
23
);
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtistAlbums.ts
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtistAlbums.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { AlbumViewBasic } from "lexicon/types/app/rocksky/album/defs";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("10 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({ albums: [] });
19
20
}),
20
21
);
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtistListeners.ts
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtistListeners.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { ListenerViewBasic } from "lexicon/types/app/rocksky/artist/defs";
···
13
14
Effect.retry({ times: 3 }),
14
15
Effect.timeout("10 seconds"),
15
16
Effect.catchAll((err) => {
16
-
console.error(err);
17
+
consola.error(err);
17
18
return Effect.succeed({ listeners: [] });
18
19
}),
19
20
);
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtistTracks.ts
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtistTracks.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { QueryParams } from "lexicon/types/app/rocksky/artist/getArtistTracks";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("10 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({ tracks: [] });
19
20
}),
20
21
);
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtists.ts
+2
-1
apps/api/src/xrpc/app/rocksky/artist/getArtists.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { ArtistViewBasic } from "lexicon/types/app/rocksky/artist/defs";
···
17
18
Effect.retry({ times: 3 }),
18
19
Effect.timeout("10 seconds"),
19
20
Effect.catchAll((err) => {
20
-
console.error(err);
21
+
consola.error(err);
21
22
return Effect.succeed({ artists: [] });
22
23
}),
23
24
);
+2
-1
apps/api/src/xrpc/app/rocksky/charts/getScrobblesChart.ts
+2
-1
apps/api/src/xrpc/app/rocksky/charts/getScrobblesChart.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { eq } from "drizzle-orm";
3
4
import { Effect, Match, pipe, Cache, Duration } from "effect";
4
5
import type { Server } from "lexicon";
···
25
26
getScrobblesCache,
26
27
Effect.flatMap((cache) => cache.get(params)),
27
28
Effect.catchAll((err) => {
28
-
console.error(err);
29
+
consola.error(err);
29
30
return Effect.succeed({ scrobbles: [] });
30
31
}),
31
32
);
+3
-2
apps/api/src/xrpc/app/rocksky/dropbox/getFiles.ts
+3
-2
apps/api/src/xrpc/app/rocksky/dropbox/getFiles.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { and, asc, eq, or } from "drizzle-orm";
4
5
import { alias } from "drizzle-orm/pg-core";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({ files: [], directories: [] });
23
24
}),
24
25
);
···
105
106
]);
106
107
},
107
108
catch: (error) => {
108
-
console.error("Failed to retrieve files:", error);
109
+
consola.error("Failed to retrieve files:", error);
109
110
return new Error(`Failed to retrieve files: ${error}`);
110
111
},
111
112
});
+2
-1
apps/api/src/xrpc/app/rocksky/feed/getFeed.ts
+2
-1
apps/api/src/xrpc/app/rocksky/feed/getFeed.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { desc, eq, inArray } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
23
24
Effect.retry({ times: 3 }),
24
25
Effect.timeout("10 seconds"),
25
26
Effect.catchAll((err) => {
26
-
console.error("Error retrieving scrobbles:", err);
27
+
consola.error("Error retrieving scrobbles:", err);
27
28
return Effect.succeed({ scrobbles: [] });
28
29
}),
29
30
);
+8
-7
apps/api/src/xrpc/app/rocksky/feed/getNowPlayings.ts
+8
-7
apps/api/src/xrpc/app/rocksky/feed/getNowPlayings.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { desc, eq, sql } from "drizzle-orm";
3
4
import { Cache, Duration, Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
21
22
Effect.map((data) => ({ data })),
22
23
Effect.flatMap(presentation),
23
24
Effect.retry({ times: 3 }),
24
-
Effect.timeout("120 seconds")
25
+
Effect.timeout("120 seconds"),
25
26
),
26
27
});
27
28
···
30
31
nowPlayingCache,
31
32
Effect.flatMap((cache) => cache.get(params)),
32
33
Effect.catchAll((err) => {
33
-
console.error(err);
34
+
consola.error(err);
34
35
return Effect.succeed({});
35
-
})
36
+
}),
36
37
);
37
38
server.app.rocksky.feed.getNowPlayings({
38
39
handler: async ({ params }) => {
···
78
79
.leftJoin(tracks, eq(scrobbles.trackId, tracks.id))
79
80
.leftJoin(users, eq(scrobbles.userId, users.id))
80
81
.where(
81
-
sql`scrobbles.timestamp = (
82
-
SELECT MAX(inner_s.timestamp)
82
+
sql`scrobbles.xata_id IN (
83
+
SELECT DISTINCT ON (inner_s.user_id) inner_s.xata_id
83
84
FROM scrobbles inner_s
84
-
WHERE inner_s.user_id = ${users.id}
85
-
)`
85
+
ORDER BY inner_s.user_id, inner_s.timestamp DESC, inner_s.xata_id DESC
86
+
)`,
86
87
)
87
88
.orderBy(desc(scrobbles.timestamp))
88
89
.limit(params.size || 20)
+3
-2
apps/api/src/xrpc/app/rocksky/googledrive/getFiles.ts
+3
-2
apps/api/src/xrpc/app/rocksky/googledrive/getFiles.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { and, asc, eq, or } from "drizzle-orm";
3
4
import { alias } from "drizzle-orm/pg-core";
4
5
import { Effect, pipe } from "effect";
···
17
18
Effect.retry({ times: 3 }),
18
19
Effect.timeout("10 seconds"),
19
20
Effect.catchAll((err) => {
20
-
console.error(err);
21
+
consola.error(err);
21
22
return Effect.succeed({ files: [], directories: [] });
22
23
}),
23
24
);
···
113
114
]);
114
115
},
115
116
catch: (error) => {
116
-
console.error("Failed to retrieve files:", error);
117
+
consola.error("Failed to retrieve files:", error);
117
118
return new Error(`Failed to retrieve albums: ${error}`);
118
119
},
119
120
});
+4
-3
apps/api/src/xrpc/app/rocksky/graph/followAccount.ts
+4
-3
apps/api/src/xrpc/app/rocksky/graph/followAccount.ts
···
1
1
import { TID } from "@atproto/common";
2
+
import { consola } from "consola";
2
3
import type { HandlerAuth } from "@atproto/xrpc-server";
3
4
import type { Context } from "context";
4
5
import { and, eq, desc } from "drizzle-orm";
···
20
21
Effect.retry({ times: 3 }),
21
22
Effect.timeout("120 seconds"),
22
23
Effect.catchAll((err) => {
23
-
console.error(err);
24
+
consola.error(err);
24
25
return Effect.succeed({
25
26
subject: {} satisfies ProfileViewBasic,
26
27
followers: [],
···
75
76
};
76
77
77
78
if (!FollowLexicon.validateRecord(record).success) {
78
-
console.log(FollowLexicon.validateRecord(record));
79
+
consola.info(FollowLexicon.validateRecord(record));
79
80
throw new Error("Invalid record");
80
81
}
81
82
···
87
88
validate: false,
88
89
});
89
90
const uri = res.data.uri;
90
-
console.log(`Follow record created at: ${uri}`);
91
+
consola.info(`Follow record created at: ${uri}`);
91
92
92
93
await ctx.db
93
94
.insert(tables.follows)
+2
-1
apps/api/src/xrpc/app/rocksky/graph/getFollowers.ts
+2
-1
apps/api/src/xrpc/app/rocksky/graph/getFollowers.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { eq, desc, and, lt, inArray, count } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
16
17
Effect.retry({ times: 3 }),
17
18
Effect.timeout("120 seconds"),
18
19
Effect.catchAll((err) => {
19
-
console.error(err);
20
+
consola.error(err);
20
21
return Effect.succeed({
21
22
subject: {} satisfies ProfileViewBasic,
22
23
followers: [] as ProfileViewBasic[],
+2
-1
apps/api/src/xrpc/app/rocksky/graph/getFollows.ts
+2
-1
apps/api/src/xrpc/app/rocksky/graph/getFollows.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { eq, desc, and, lt, inArray, count } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("120 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({
23
24
subject: undefined,
24
25
follows: [],
+2
-1
apps/api/src/xrpc/app/rocksky/graph/getKnownFollowers.ts
+2
-1
apps/api/src/xrpc/app/rocksky/graph/getKnownFollowers.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { and, eq, sql, desc, lt } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
17
18
Effect.retry({ times: 3 }),
18
19
Effect.timeout("120 seconds"),
19
20
Effect.catchAll((err) => {
20
-
console.error("getKnownFollowers error:", err);
21
+
consola.error("getKnownFollowers error:", err);
21
22
return Effect.succeed({
22
23
subject: {} satisfies ProfileViewBasic,
23
24
followers: [] as ProfileViewBasic[],
+2
-1
apps/api/src/xrpc/app/rocksky/graph/unfollowAccount.ts
+2
-1
apps/api/src/xrpc/app/rocksky/graph/unfollowAccount.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { and, eq, desc } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("120 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({
23
24
subject: {} satisfies ProfileViewBasic,
24
25
followers: [],
+3
-2
apps/api/src/xrpc/app/rocksky/player/addItemsToQueue.ts
+3
-2
apps/api/src/xrpc/app/rocksky/player/addItemsToQueue.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { inArray } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
20
21
Effect.retry({ times: 3 }),
21
22
Effect.timeout("10 seconds"),
22
23
Effect.catchAll((err) => {
23
-
console.error(err);
24
+
consola.error(err);
24
25
return Effect.succeed({});
25
26
}),
26
27
);
···
79
80
});
80
81
},
81
82
catch: (err) => {
82
-
console.error(err);
83
+
consola.error(err);
83
84
return {};
84
85
},
85
86
});
+2
-1
apps/api/src/xrpc/app/rocksky/player/getCurrentlyPlaying.ts
+2
-1
apps/api/src/xrpc/app/rocksky/player/getCurrentlyPlaying.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+3
-2
apps/api/src/xrpc/app/rocksky/player/getPlaybackQueue.ts
+3
-2
apps/api/src/xrpc/app/rocksky/player/getPlaybackQueue.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({});
23
24
}),
24
25
);
···
49
50
// Logic to retrieve the playback queue would go here
50
51
},
51
52
catch: (err) => {
52
-
console.error(err);
53
+
consola.error(err);
53
54
return {};
54
55
},
55
56
});
+2
-1
apps/api/src/xrpc/app/rocksky/player/next.ts
+2
-1
apps/api/src/xrpc/app/rocksky/player/next.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/player/pause.ts
+2
-1
apps/api/src/xrpc/app/rocksky/player/pause.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/player/play.ts
+2
-1
apps/api/src/xrpc/app/rocksky/player/play.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+3
-2
apps/api/src/xrpc/app/rocksky/player/playDirectory.ts
+3
-2
apps/api/src/xrpc/app/rocksky/player/playDirectory.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({});
23
24
}),
24
25
);
···
47
48
});
48
49
},
49
50
catch: (err) => {
50
-
console.error(err);
51
+
consola.error(err);
51
52
return {};
52
53
},
53
54
});
+3
-2
apps/api/src/xrpc/app/rocksky/player/playFile.ts
+3
-2
apps/api/src/xrpc/app/rocksky/player/playFile.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({});
23
24
}),
24
25
);
···
47
48
});
48
49
},
49
50
catch: (err) => {
50
-
console.error(err);
51
+
consola.error(err);
51
52
return {};
52
53
},
53
54
});
+2
-1
apps/api/src/xrpc/app/rocksky/player/previous.ts
+2
-1
apps/api/src/xrpc/app/rocksky/player/previous.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/player/seek.ts
+2
-1
apps/api/src/xrpc/app/rocksky/player/seek.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/createPlaylist.ts
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/createPlaylist.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({});
23
24
}),
24
25
);
···
44
45
await ctx.db.select().from(tables.playlists).execute();
45
46
},
46
47
catch: (err) => {
47
-
console.error(err);
48
+
consola.error(err);
48
49
return {};
49
50
},
50
51
});
+2
-1
apps/api/src/xrpc/app/rocksky/playlist/getPlaylist.ts
+2
-1
apps/api/src/xrpc/app/rocksky/playlist/getPlaylist.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { asc, eq, sql } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
19
20
Effect.retry({ times: 3 }),
20
21
Effect.timeout("10 seconds"),
21
22
Effect.catchAll((err) => {
22
-
console.error(err);
23
+
consola.error(err);
23
24
return Effect.succeed({});
24
25
}),
25
26
);
+2
-1
apps/api/src/xrpc/app/rocksky/playlist/getPlaylists.ts
+2
-1
apps/api/src/xrpc/app/rocksky/playlist/getPlaylists.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { desc, eq, sql } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({ playlists: [] });
23
24
}),
24
25
);
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/insertDirectory.ts
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/insertDirectory.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({});
23
24
}),
24
25
);
···
46
47
});
47
48
},
48
49
catch: (err) => {
49
-
console.error(err);
50
+
consola.error(err);
50
51
return {};
51
52
},
52
53
});
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/insertFiles.ts
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/insertFiles.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({});
23
24
}),
24
25
);
···
47
48
});
48
49
},
49
50
catch: (err) => {
50
-
console.error(err);
51
+
consola.error(err);
51
52
return {};
52
53
},
53
54
});
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/removePlaylist.ts
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/removePlaylist.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({});
23
24
}),
24
25
);
···
45
46
// Logic to remove the playlist would go here
46
47
},
47
48
catch: (err) => {
48
-
console.error(err);
49
+
consola.error(err);
49
50
return {};
50
51
},
51
52
});
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/startPlaylist.ts
+3
-2
apps/api/src/xrpc/app/rocksky/playlist/startPlaylist.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({});
23
24
}),
24
25
);
···
47
48
});
48
49
},
49
50
catch: (err) => {
50
-
console.error(err);
51
+
consola.error(err);
51
52
return {};
52
53
},
53
54
});
+3
-2
apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts
+3
-2
apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts
···
1
1
import type { Agent } from "@atproto/api";
2
+
import { consola } from "consola";
2
3
import { TID } from "@atproto/common";
3
4
import type { HandlerAuth } from "@atproto/xrpc-server";
4
5
import chalk from "chalk";
···
45
46
Effect.retry({ times: 3 }),
46
47
Effect.timeout("600 seconds"),
47
48
Effect.catchAll((err) => {
48
-
console.error(err);
49
+
consola.error(err);
49
50
return Effect.succeed({});
50
51
}),
51
52
);
···
153
154
),
154
155
),
155
156
Effect.catchAll((error) => {
156
-
console.error(`Error creating ${collection} record`, error);
157
+
consola.error(`Error creating ${collection} record`, error);
157
158
return Effect.succeed(null);
158
159
}),
159
160
);
+2
-1
apps/api/src/xrpc/app/rocksky/scrobble/getScrobble.ts
+2
-1
apps/api/src/xrpc/app/rocksky/scrobble/getScrobble.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { count, countDistinct, eq } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
20
21
Effect.retry({ times: 3 }),
21
22
Effect.timeout("10 seconds"),
22
23
Effect.catchAll((err) => {
23
-
console.error("Error retrieving scrobble:", err);
24
+
consola.error("Error retrieving scrobble:", err);
24
25
return Effect.succeed({});
25
26
}),
26
27
);
+2
-1
apps/api/src/xrpc/app/rocksky/scrobble/getScrobbles.ts
+2
-1
apps/api/src/xrpc/app/rocksky/scrobble/getScrobbles.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { desc, eq, inArray } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
19
20
Effect.retry({ times: 3 }),
20
21
Effect.timeout("10 seconds"),
21
22
Effect.catchAll((err) => {
22
-
console.error("Error retrieving scrobbles:", err);
23
+
consola.error("Error retrieving scrobbles:", err);
23
24
return Effect.succeed({ scrobbles: [] });
24
25
}),
25
26
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/createShout.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/createShout.ts
···
1
1
import type { Agent } from "@atproto/api";
2
+
import { consola } from "consola";
2
3
import type { HandlerAuth } from "@atproto/xrpc-server";
3
4
import type { Context } from "context";
4
5
import { eq } from "drizzle-orm";
···
21
22
Effect.retry({ times: 3 }),
22
23
Effect.timeout("10 seconds"),
23
24
Effect.catchAll((err) => {
24
-
console.error(err);
25
+
consola.error(err);
25
26
return Effect.succeed({});
26
27
}),
27
28
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getAlbumShouts.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getAlbumShouts.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { count, desc, eq } from "drizzle-orm";
4
5
import { sql } from "drizzle-orm/sql";
···
19
20
Effect.retry({ times: 3 }),
20
21
Effect.timeout("10 seconds"),
21
22
Effect.catchAll((err) => {
22
-
console.error(err);
23
+
consola.error(err);
23
24
return Effect.succeed({ shouts: [] });
24
25
}),
25
26
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getArtistShouts.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getArtistShouts.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { count, desc, eq, sql } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
18
19
Effect.retry({ times: 3 }),
19
20
Effect.timeout("10 seconds"),
20
21
Effect.catchAll((err) => {
21
-
console.error(err);
22
+
consola.error(err);
22
23
return Effect.succeed({ shouts: [] });
23
24
}),
24
25
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getProfileShouts.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getProfileShouts.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { aliasedTable, count, desc, eq, or, sql } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({ shouts: [] });
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getShoutReplies.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getShoutReplies.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { asc, eq } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
17
18
Effect.retry({ times: 3 }),
18
19
Effect.timeout("10 seconds"),
19
20
Effect.catchAll((err) => {
20
-
console.error(err);
21
+
consola.error(err);
21
22
return Effect.succeed({ shouts: [] });
22
23
}),
23
24
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getTrackShouts.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/getTrackShouts.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { count, desc, eq, sql } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({ shouts: [] });
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/removeShout.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/removeShout.ts
···
1
1
import type { Agent } from "@atproto/api";
2
+
import { consola } from "consola";
2
3
import type { HandlerAuth } from "@atproto/xrpc-server";
3
4
import type { Context } from "context";
4
5
import { Effect, pipe } from "effect";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({});
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/replyShout.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/replyShout.ts
···
1
1
import type { Agent } from "@atproto/api";
2
2
import type { HandlerAuth } from "@atproto/xrpc-server";
3
+
import { consola } from "consola";
3
4
import type { Context } from "context";
4
5
import { Effect, pipe } from "effect";
5
6
import type { Server } from "lexicon";
···
15
16
Effect.retry({ times: 3 }),
16
17
Effect.timeout("10 seconds"),
17
18
Effect.catchAll((err) => {
18
-
console.error(err);
19
+
consola.error(err);
19
20
return Effect.succeed({ albums: [] });
20
21
}),
21
22
);
+2
-1
apps/api/src/xrpc/app/rocksky/shout/reportShout.ts
+2
-1
apps/api/src/xrpc/app/rocksky/shout/reportShout.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
12
13
Effect.retry({ times: 3 }),
13
14
Effect.timeout("10 seconds"),
14
15
Effect.catchAll((err) => {
15
-
console.error(err);
16
+
consola.error(err);
16
17
return Effect.succeed({});
17
18
}),
18
19
);
+3
-2
apps/api/src/xrpc/app/rocksky/song/createSong.ts
+3
-2
apps/api/src/xrpc/app/rocksky/song/createSong.ts
···
1
1
import type { Agent } from "@atproto/api";
2
+
import { consola } from "consola";
2
3
import { TID } from "@atproto/common";
3
4
import type { HandlerAuth } from "@atproto/xrpc-server";
4
5
import chalk from "chalk";
···
41
42
Effect.retry({ times: 3 }),
42
43
Effect.timeout("120 seconds"),
43
44
Effect.catchAll((err) => {
44
-
console.error(err);
45
+
consola.error(err);
45
46
return Effect.succeed({});
46
47
}),
47
48
);
···
213
214
),
214
215
),
215
216
Effect.catchAll((error) => {
216
-
console.error(`Error creating ${collection} record`, error);
217
+
consola.error(`Error creating ${collection} record`, error);
217
218
return Effect.fail(error);
218
219
}),
219
220
);
+2
-1
apps/api/src/xrpc/app/rocksky/song/getSong.ts
+2
-1
apps/api/src/xrpc/app/rocksky/song/getSong.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { count, eq, or } from "drizzle-orm";
3
4
import { Effect, pipe } from "effect";
4
5
import type { Server } from "lexicon";
···
16
17
Effect.retry({ times: 3 }),
17
18
Effect.timeout("10 seconds"),
18
19
Effect.catchAll((err) => {
19
-
console.error(err);
20
+
consola.error(err);
20
21
return Effect.succeed({});
21
22
}),
22
23
);
+2
-1
apps/api/src/xrpc/app/rocksky/song/getSongs.ts
+2
-1
apps/api/src/xrpc/app/rocksky/song/getSongs.ts
···
1
1
import type { Context } from "context";
2
+
import { consola } from "consola";
2
3
import { Effect, pipe } from "effect";
3
4
import type { Server } from "lexicon";
4
5
import type { SongViewBasic } from "lexicon/types/app/rocksky/song/defs";
···
14
15
Effect.retry({ times: 3 }),
15
16
Effect.timeout("10 seconds"),
16
17
Effect.catchAll((err) => {
17
-
console.error(err);
18
+
consola.error(err);
18
19
return Effect.succeed({ songs: [] });
19
20
}),
20
21
);
+350
apps/api/src/xrpc/app/rocksky/song/matchSong.ts
+350
apps/api/src/xrpc/app/rocksky/song/matchSong.ts
···
1
+
import type { Context } from "context";
2
+
import { consola } from "consola";
3
+
import { and, count, eq, or, sql } from "drizzle-orm";
4
+
import { Effect, pipe } from "effect";
5
+
import type { Server } from "lexicon";
6
+
import type { SongViewDetailed } from "lexicon/types/app/rocksky/song/defs";
7
+
import type { QueryParams } from "lexicon/types/app/rocksky/song/matchSong";
8
+
import { decrypt } from "lib/crypto";
9
+
import { env } from "lib/env";
10
+
import tables from "schema";
11
+
import type { SelectTrack } from "schema/tracks";
12
+
import type {
13
+
Album,
14
+
Artist,
15
+
MusicBrainzArtist,
16
+
SearchResponse,
17
+
Track,
18
+
} from "./types";
19
+
import type { MusicbrainzTrack } from "types/track";
20
+
21
+
export default function (server: Server, ctx: Context) {
22
+
const matchSong = (params: QueryParams) =>
23
+
pipe(
24
+
{ params, ctx },
25
+
retrieve,
26
+
Effect.flatMap(presentation),
27
+
Effect.retry({ times: 3 }),
28
+
Effect.timeout("10 seconds"),
29
+
Effect.catchAll((err) => {
30
+
consola.error(err);
31
+
return Effect.succeed({});
32
+
}),
33
+
);
34
+
server.app.rocksky.song.matchSong({
35
+
handler: async ({ params }) => {
36
+
const result = await Effect.runPromise(matchSong(params));
37
+
return {
38
+
encoding: "application/json",
39
+
body: result,
40
+
};
41
+
},
42
+
});
43
+
}
44
+
45
+
const retrieve = ({ params, ctx }: { params: QueryParams; ctx: Context }) => {
46
+
return Effect.tryPromise({
47
+
try: async () => {
48
+
const record = await ctx.db
49
+
.select()
50
+
.from(tables.tracks)
51
+
.leftJoin(
52
+
tables.albumTracks,
53
+
eq(tables.albumTracks.trackId, tables.tracks.id),
54
+
)
55
+
.leftJoin(
56
+
tables.albums,
57
+
eq(tables.albumTracks.albumId, tables.albums.id),
58
+
)
59
+
.leftJoin(
60
+
tables.artistAlbums,
61
+
eq(tables.artistAlbums.albumId, tables.albums.id),
62
+
)
63
+
.leftJoin(
64
+
tables.artists,
65
+
eq(tables.artistAlbums.artistId, tables.artists.id),
66
+
)
67
+
.where(
68
+
or(
69
+
and(
70
+
sql`LOWER(${tables.tracks.title}) = LOWER(${params.title})`,
71
+
sql`LOWER(${tables.tracks.artist}) = LOWER(${params.artist})`,
72
+
),
73
+
and(
74
+
sql`LOWER(${tables.tracks.title}) = LOWER(${params.title})`,
75
+
sql`LOWER(${tables.tracks.albumArtist}) = LOWER(${params.artist})`,
76
+
),
77
+
),
78
+
)
79
+
.execute()
80
+
.then(([row]) => row);
81
+
82
+
let track = record?.tracks;
83
+
84
+
let releaseDate = null,
85
+
year = null,
86
+
artistPicture = null,
87
+
genres = null;
88
+
89
+
if (!record) {
90
+
const spotifyTrack = await searchOnSpotify(
91
+
ctx,
92
+
params.title,
93
+
params.artist,
94
+
);
95
+
if (spotifyTrack) {
96
+
track = {
97
+
id: "",
98
+
title: spotifyTrack.name,
99
+
artist: spotifyTrack.artists
100
+
.map((artist) => artist.name)
101
+
.join(", "),
102
+
albumArtist: spotifyTrack.album.artists[0]?.name,
103
+
albumArt: spotifyTrack.album.images[0]?.url || null,
104
+
album: spotifyTrack.album.name,
105
+
trackNumber: spotifyTrack.track_number,
106
+
duration: spotifyTrack.duration_ms,
107
+
mbId: null,
108
+
genre: null,
109
+
youtubeLink: null,
110
+
spotifyLink: spotifyTrack.external_urls.spotify,
111
+
appleMusicLink: null,
112
+
tidalLink: null,
113
+
sha256: null,
114
+
discNumber: spotifyTrack.disc_number,
115
+
lyrics: null,
116
+
composer: null,
117
+
label: spotifyTrack.album.label || null,
118
+
copyrightMessage: spotifyTrack.album.copyrights?.[0]?.text || null,
119
+
uri: null,
120
+
albumUri: null,
121
+
artistUri: null,
122
+
createdAt: new Date(),
123
+
updatedAt: new Date(),
124
+
xataVersion: 0,
125
+
};
126
+
127
+
if (spotifyTrack.album.release_date_precision == "day") {
128
+
releaseDate = spotifyTrack.album.release_date;
129
+
year = parseInt(spotifyTrack.album.release_date.split("-")[0]);
130
+
}
131
+
132
+
if (spotifyTrack.album.release_date_precision == "year") {
133
+
releaseDate = `${spotifyTrack.album.release_date}-01-01`;
134
+
year = parseInt(spotifyTrack.album.release_date);
135
+
}
136
+
137
+
artistPicture = spotifyTrack.artists[0]?.images?.[0]?.url || null;
138
+
genres = spotifyTrack.artists[0]?.genres || null;
139
+
}
140
+
} else {
141
+
artistPicture = record.artists.picture;
142
+
genres = record.artists.genres;
143
+
releaseDate = record.albums.releaseDate;
144
+
year = record.albums.year;
145
+
}
146
+
147
+
const mbTrack = await searchOnMusicBrainz(ctx, track);
148
+
track.mbId = mbTrack.mbId;
149
+
150
+
return Promise.all([
151
+
Promise.resolve(track),
152
+
ctx.db
153
+
.select({
154
+
count: count(),
155
+
})
156
+
.from(tables.userTracks)
157
+
.where(eq(tables.userTracks.trackId, track?.id))
158
+
.execute()
159
+
.then((rows) => rows[0]?.count || 0),
160
+
ctx.db
161
+
.select({ count: count() })
162
+
.from(tables.scrobbles)
163
+
.where(eq(tables.scrobbles.trackId, track?.id))
164
+
.execute()
165
+
.then((rows) => rows[0]?.count || 0),
166
+
Promise.resolve(releaseDate),
167
+
Promise.resolve(year),
168
+
Promise.resolve(artistPicture),
169
+
Promise.resolve(genres),
170
+
Promise.resolve(mbTrack.artists),
171
+
]);
172
+
},
173
+
catch: (error) => new Error(`Failed to retrieve artist: ${error}`),
174
+
});
175
+
};
176
+
177
+
const presentation = ([
178
+
track,
179
+
uniqueListeners,
180
+
playCount,
181
+
releaseDate,
182
+
year,
183
+
artistPicture,
184
+
genres,
185
+
mbArtists,
186
+
]: [
187
+
SelectTrack,
188
+
number,
189
+
number,
190
+
string | null,
191
+
number | null,
192
+
string | null,
193
+
string[] | null,
194
+
MusicBrainzArtist[] | null,
195
+
]): Effect.Effect<SongViewDetailed, never> => {
196
+
return Effect.sync(() => ({
197
+
...track,
198
+
releaseDate,
199
+
year,
200
+
artistPicture,
201
+
genres,
202
+
mbArtists,
203
+
playCount,
204
+
uniqueListeners,
205
+
createdAt: track.createdAt.toISOString(),
206
+
updatedAt: track.updatedAt.toISOString(),
207
+
}));
208
+
};
209
+
210
+
const searchOnSpotify = async (
211
+
ctx: Context,
212
+
title: string,
213
+
artist: string,
214
+
): Promise<Track | undefined> => {
215
+
const spotifyTokens = await ctx.db
216
+
.select()
217
+
.from(tables.spotifyTokens)
218
+
.leftJoin(
219
+
tables.spotifyApps,
220
+
eq(tables.spotifyApps.spotifyAppId, tables.spotifyTokens.spotifyAppId),
221
+
)
222
+
.leftJoin(
223
+
tables.spotifyAccounts,
224
+
eq(tables.spotifyAccounts.userId, tables.spotifyTokens.userId),
225
+
)
226
+
.where(eq(tables.spotifyAccounts.isBetaUser, true))
227
+
.limit(500)
228
+
.execute();
229
+
230
+
if (!spotifyTokens || spotifyTokens.length === 0) {
231
+
consola.warn("No Spotify tokens available for beta users");
232
+
return undefined;
233
+
}
234
+
235
+
const { spotify_tokens, spotify_apps } =
236
+
spotifyTokens[Math.floor(Math.random() * spotifyTokens.length)];
237
+
238
+
if (!spotify_tokens || !spotify_apps) {
239
+
consola.warn("Invalid Spotify token or app data");
240
+
return undefined;
241
+
}
242
+
243
+
const refreshToken = decrypt(
244
+
spotify_tokens.refreshToken,
245
+
env.SPOTIFY_ENCRYPTION_KEY,
246
+
);
247
+
248
+
// get new access token
249
+
const newAccessToken = await fetch("https://accounts.spotify.com/api/token", {
250
+
method: "POST",
251
+
headers: {
252
+
"Content-Type": "application/x-www-form-urlencoded",
253
+
},
254
+
body: new URLSearchParams({
255
+
grant_type: "refresh_token",
256
+
refresh_token: refreshToken,
257
+
client_id: spotify_apps.spotifyAppId,
258
+
client_secret: decrypt(
259
+
spotify_apps.spotifySecret,
260
+
env.SPOTIFY_ENCRYPTION_KEY,
261
+
),
262
+
}),
263
+
});
264
+
265
+
const { access_token } = (await newAccessToken.json()) as {
266
+
access_token: string;
267
+
};
268
+
269
+
const q = `q=track:"${encodeURIComponent(title)}"%20artist:"${encodeURIComponent(artist)}"&type=track`;
270
+
const response = await fetch(`https://api.spotify.com/v1/search?${q}`, {
271
+
method: "GET",
272
+
headers: {
273
+
Authorization: `Bearer ${access_token}`,
274
+
},
275
+
}).then((res) => res.json<SearchResponse>());
276
+
277
+
const track = response.tracks?.items?.[0];
278
+
279
+
if (track) {
280
+
const album = await fetch(
281
+
`https://api.spotify.com/v1/albums/${track.album.id}`,
282
+
{
283
+
method: "GET",
284
+
headers: {
285
+
Authorization: `Bearer ${access_token}`,
286
+
},
287
+
},
288
+
).then((res) => res.json<Album>());
289
+
290
+
track.album = album;
291
+
292
+
const artist = await fetch(
293
+
`https://api.spotify.com/v1/artists/${track.artists[0].id}`,
294
+
{
295
+
method: "GET",
296
+
headers: {
297
+
Authorization: `Bearer ${access_token}`,
298
+
},
299
+
},
300
+
).then((res) => res.json<Artist>());
301
+
302
+
track.artists[0] = artist;
303
+
}
304
+
305
+
return track;
306
+
};
307
+
308
+
const searchOnMusicBrainz = async (ctx: Context, track: SelectTrack) => {
309
+
let mbTrack;
310
+
try {
311
+
const { data } = await ctx.musicbrainz.post<MusicbrainzTrack>("/hydrate", {
312
+
artist: track.artist
313
+
.replaceAll(";", ",")
314
+
.split(",")
315
+
.map((a) => ({ name: a.trim() })),
316
+
name: track.title,
317
+
album: track.album,
318
+
});
319
+
mbTrack = data;
320
+
321
+
if (!mbTrack?.trackMBID) {
322
+
const response = await ctx.musicbrainz.post<MusicbrainzTrack>(
323
+
"/hydrate",
324
+
{
325
+
artist: track.artist.split(",").map((a) => ({ name: a.trim() })),
326
+
name: track.title,
327
+
},
328
+
);
329
+
mbTrack = response.data;
330
+
}
331
+
332
+
const mbId = mbTrack?.trackMBID;
333
+
const artists: MusicBrainzArtist[] = mbTrack?.artist?.map((artist) => ({
334
+
mbid: artist.mbid,
335
+
name: artist.name,
336
+
}));
337
+
338
+
return {
339
+
mbId,
340
+
artists,
341
+
};
342
+
} catch (error) {
343
+
consola.error("Error fetching MusicBrainz data");
344
+
}
345
+
346
+
return {
347
+
mbId: null,
348
+
artists: null,
349
+
};
350
+
};
+95
apps/api/src/xrpc/app/rocksky/song/types.ts
+95
apps/api/src/xrpc/app/rocksky/song/types.ts
···
1
+
export interface SearchResponse {
2
+
tracks: Tracks;
3
+
}
4
+
5
+
export interface Tracks {
6
+
href: string;
7
+
limit: number;
8
+
next: string | null;
9
+
offset: number;
10
+
previous: string | null;
11
+
total: number;
12
+
items: Track[];
13
+
}
14
+
15
+
export interface Track {
16
+
album: Album;
17
+
artists: Artist[];
18
+
available_markets: string[];
19
+
disc_number: number;
20
+
duration_ms: number;
21
+
explicit: boolean;
22
+
external_ids: ExternalIds;
23
+
external_urls: ExternalUrls;
24
+
href: string;
25
+
id: string;
26
+
is_local: boolean;
27
+
is_playable?: boolean;
28
+
name: string;
29
+
popularity: number;
30
+
preview_url: string | null;
31
+
track_number: number;
32
+
type: string;
33
+
uri: string;
34
+
}
35
+
36
+
export interface Album {
37
+
album_type: string;
38
+
artists: Artist[];
39
+
available_markets: string[];
40
+
external_urls: ExternalUrls;
41
+
href: string;
42
+
id: string;
43
+
images: Image[];
44
+
name: string;
45
+
release_date: string;
46
+
release_date_precision: string;
47
+
total_tracks: number;
48
+
type: string;
49
+
uri: string;
50
+
label?: string;
51
+
genres?: string[];
52
+
copyrights?: Copyright[];
53
+
}
54
+
55
+
export interface Copyright {
56
+
text: string;
57
+
type: string;
58
+
}
59
+
60
+
export interface Artist {
61
+
external_urls: ExternalUrls;
62
+
href: string;
63
+
id: string;
64
+
name: string;
65
+
type: string;
66
+
uri: string;
67
+
images?: Image[];
68
+
genres?: string[];
69
+
}
70
+
71
+
export interface ExternalUrls {
72
+
spotify: string;
73
+
}
74
+
75
+
export interface ExternalIds {
76
+
isrc: string;
77
+
}
78
+
79
+
export interface Image {
80
+
height: number;
81
+
width: number;
82
+
url: string;
83
+
}
84
+
85
+
export interface AccessToken {
86
+
access_token: string;
87
+
token_type: string;
88
+
scope: string;
89
+
expires_in: number;
90
+
}
91
+
92
+
export interface MusicBrainzArtist {
93
+
mbid: string;
94
+
name: string;
95
+
}
+2
-1
apps/api/src/xrpc/app/rocksky/spotify/getCurrentlyPlaying.ts
+2
-1
apps/api/src/xrpc/app/rocksky/spotify/getCurrentlyPlaying.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { and, eq, or } from "drizzle-orm";
4
5
import { Effect, Match, pipe } from "effect";
···
21
22
Effect.retry({ times: 3 }),
22
23
Effect.timeout("10 seconds"),
23
24
Effect.catchAll((err) => {
24
-
console.error(err);
25
+
consola.error(err);
25
26
return Effect.succeed({});
26
27
}),
27
28
);
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/next.ts
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/next.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
21
22
Effect.retry({ times: 3 }),
22
23
Effect.timeout("10 seconds"),
23
24
Effect.catchAll((err) => {
24
-
console.error(err);
25
+
consola.error(err);
25
26
return Effect.succeed({});
26
27
}),
27
28
);
···
143
144
144
145
const presentation = (result): Effect.Effect<{}, never> => {
145
146
// Logic to format the result for presentation
146
-
console.log("Next action result:", result);
147
+
consola.info("Next action result:", result);
147
148
return Effect.sync(() => ({}));
148
149
};
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/pause.ts
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/pause.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
21
22
Effect.retry({ times: 3 }),
22
23
Effect.timeout("10 seconds"),
23
24
Effect.catchAll((err) => {
24
-
console.error(err);
25
+
consola.error(err);
25
26
return Effect.succeed({});
26
27
}),
27
28
);
···
143
144
144
145
const presentation = (result): Effect.Effect<{}, never> => {
145
146
// Logic to format the result for presentation
146
-
console.log("Pause action result:", result);
147
+
consola.info("Pause action result:", result);
147
148
return Effect.sync(() => ({}));
148
149
};
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/play.ts
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/play.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
21
22
Effect.retry({ times: 3 }),
22
23
Effect.timeout("10 seconds"),
23
24
Effect.catchAll((err) => {
24
-
console.error(err);
25
+
consola.error(err);
25
26
return Effect.succeed({});
26
27
}),
27
28
);
···
142
143
};
143
144
144
145
const presentation = (result) => {
145
-
console.log("Play action result:", result);
146
+
consola.info("Play action result:", result);
146
147
return Effect.sync(() => ({}));
147
148
};
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/previous.ts
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/previous.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
21
22
Effect.retry({ times: 3 }),
22
23
Effect.timeout("10 seconds"),
23
24
Effect.catchAll((err) => {
24
-
console.error(err);
25
+
consola.error(err);
25
26
return Effect.succeed({});
26
27
}),
27
28
);
···
143
144
144
145
const presentation = (result) => {
145
146
// Logic to format the result for presentation
146
-
console.log("Previous action result:", result);
147
+
consola.info("Previous action result:", result);
147
148
return Effect.sync(() => ({}));
148
149
};
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/seek.ts
+3
-2
apps/api/src/xrpc/app/rocksky/spotify/seek.ts
···
1
1
import type { HandlerAuth } from "@atproto/xrpc-server";
2
+
import { consola } from "consola";
2
3
import type { Context } from "context";
3
4
import { eq } from "drizzle-orm";
4
5
import { Effect, pipe } from "effect";
···
21
22
Effect.retry({ times: 3 }),
22
23
Effect.timeout("10 seconds"),
23
24
Effect.catchAll((err) => {
24
-
console.error(err);
25
+
consola.error(err);
25
26
return Effect.succeed({});
26
27
}),
27
28
);
···
161
162
162
163
const presentation = (result) => {
163
164
// Logic to format the result for presentation
164
-
console.log("Seek action result:", result);
165
+
consola.info("Seek action result:", result);
165
166
return Effect.sync(() => ({}));
166
167
};
+2
apps/api/src/xrpc/index.ts
+2
apps/api/src/xrpc/index.ts
···
82
82
import unfollowAccount from "./app/rocksky/graph/unfollowAccount";
83
83
import getActorNeighbours from "./app/rocksky/actor/getActorNeighbours";
84
84
import getActorCompatibility from "./app/rocksky/actor/getActorCompatibility";
85
+
import matchSong from "./app/rocksky/song/matchSong";
85
86
86
87
export default function (server: Server, ctx: Context) {
87
88
// app.rocksky
···
167
168
unfollowAccount(server, ctx);
168
169
getActorNeighbours(server, ctx);
169
170
getActorCompatibility(server, ctx);
171
+
matchSong(server, ctx);
170
172
171
173
return server;
172
174
}
+220
apps/cli/drizzle/0000_parallel_paper_doll.sql
+220
apps/cli/drizzle/0000_parallel_paper_doll.sql
···
1
+
CREATE TABLE `album_tracks` (
2
+
`id` text PRIMARY KEY NOT NULL,
3
+
`album_id` text NOT NULL,
4
+
`track_id` text NOT NULL,
5
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
6
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
7
+
FOREIGN KEY (`album_id`) REFERENCES `albums`(`id`) ON UPDATE no action ON DELETE no action,
8
+
FOREIGN KEY (`track_id`) REFERENCES `tracks`(`id`) ON UPDATE no action ON DELETE no action
9
+
);
10
+
--> statement-breakpoint
11
+
CREATE UNIQUE INDEX `album_tracks_unique_index` ON `album_tracks` (`album_id`,`track_id`);--> statement-breakpoint
12
+
CREATE TABLE `albums` (
13
+
`id` text PRIMARY KEY NOT NULL,
14
+
`title` text NOT NULL,
15
+
`artist` text NOT NULL,
16
+
`release_date` text,
17
+
`year` integer,
18
+
`album_art` text,
19
+
`uri` text,
20
+
`cid` text NOT NULL,
21
+
`artist_uri` text,
22
+
`apple_music_link` text,
23
+
`spotify_link` text,
24
+
`tidal_link` text,
25
+
`youtube_link` text,
26
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
27
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
28
+
);
29
+
--> statement-breakpoint
30
+
CREATE UNIQUE INDEX `albums_uri_unique` ON `albums` (`uri`);--> statement-breakpoint
31
+
CREATE UNIQUE INDEX `albums_cid_unique` ON `albums` (`cid`);--> statement-breakpoint
32
+
CREATE UNIQUE INDEX `albums_apple_music_link_unique` ON `albums` (`apple_music_link`);--> statement-breakpoint
33
+
CREATE UNIQUE INDEX `albums_spotify_link_unique` ON `albums` (`spotify_link`);--> statement-breakpoint
34
+
CREATE UNIQUE INDEX `albums_tidal_link_unique` ON `albums` (`tidal_link`);--> statement-breakpoint
35
+
CREATE UNIQUE INDEX `albums_youtube_link_unique` ON `albums` (`youtube_link`);--> statement-breakpoint
36
+
CREATE TABLE `artist_albums` (
37
+
`id` text PRIMARY KEY NOT NULL,
38
+
`artist_id` text NOT NULL,
39
+
`album_id` text NOT NULL,
40
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
41
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
42
+
FOREIGN KEY (`artist_id`) REFERENCES `artists`(`id`) ON UPDATE no action ON DELETE no action,
43
+
FOREIGN KEY (`album_id`) REFERENCES `albums`(`id`) ON UPDATE no action ON DELETE no action
44
+
);
45
+
--> statement-breakpoint
46
+
CREATE UNIQUE INDEX `artist_albums_unique_index` ON `artist_albums` (`artist_id`,`album_id`);--> statement-breakpoint
47
+
CREATE TABLE `artist_genres ` (
48
+
`id` text PRIMARY KEY NOT NULL,
49
+
`artist_id` text NOT NULL,
50
+
`genre_id` text NOT NULL
51
+
);
52
+
--> statement-breakpoint
53
+
CREATE UNIQUE INDEX `artist_genre_unique_index` ON `artist_genres ` (`artist_id`,`genre_id`);--> statement-breakpoint
54
+
CREATE TABLE `artist_tracks` (
55
+
`id` text PRIMARY KEY NOT NULL,
56
+
`artist_id` text NOT NULL,
57
+
`track_id` text NOT NULL,
58
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
59
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
60
+
FOREIGN KEY (`artist_id`) REFERENCES `artists`(`id`) ON UPDATE no action ON DELETE no action,
61
+
FOREIGN KEY (`track_id`) REFERENCES `tracks`(`id`) ON UPDATE no action ON DELETE no action
62
+
);
63
+
--> statement-breakpoint
64
+
CREATE UNIQUE INDEX `artist_tracks_unique_index` ON `artist_tracks` (`artist_id`,`track_id`);--> statement-breakpoint
65
+
CREATE TABLE `artists` (
66
+
`id` text PRIMARY KEY NOT NULL,
67
+
`name` text NOT NULL,
68
+
`biography` text,
69
+
`born` integer,
70
+
`born_in` text,
71
+
`died` integer,
72
+
`picture` text,
73
+
`uri` text,
74
+
`cid` text NOT NULL,
75
+
`apple_music_link` text,
76
+
`spotify_link` text,
77
+
`tidal_link` text,
78
+
`youtube_link` text,
79
+
`genres` text,
80
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
81
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
82
+
);
83
+
--> statement-breakpoint
84
+
CREATE UNIQUE INDEX `artists_uri_unique` ON `artists` (`uri`);--> statement-breakpoint
85
+
CREATE UNIQUE INDEX `artists_cid_unique` ON `artists` (`cid`);--> statement-breakpoint
86
+
CREATE TABLE `auth_sessions` (
87
+
`key` text PRIMARY KEY NOT NULL,
88
+
`session` text NOT NULL,
89
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
90
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
91
+
);
92
+
--> statement-breakpoint
93
+
CREATE TABLE `genres` (
94
+
`id` text PRIMARY KEY NOT NULL,
95
+
`name` text NOT NULL,
96
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
97
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
98
+
);
99
+
--> statement-breakpoint
100
+
CREATE UNIQUE INDEX `genres_name_unique` ON `genres` (`name`);--> statement-breakpoint
101
+
CREATE TABLE `loved_tracks` (
102
+
`id` text PRIMARY KEY NOT NULL,
103
+
`user_id` text NOT NULL,
104
+
`track_id` text NOT NULL,
105
+
`uri` text,
106
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
107
+
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
108
+
FOREIGN KEY (`track_id`) REFERENCES `tracks`(`id`) ON UPDATE no action ON DELETE no action
109
+
);
110
+
--> statement-breakpoint
111
+
CREATE UNIQUE INDEX `loved_tracks_uri_unique` ON `loved_tracks` (`uri`);--> statement-breakpoint
112
+
CREATE UNIQUE INDEX `loved_tracks_unique_index` ON `loved_tracks` (`user_id`,`track_id`);--> statement-breakpoint
113
+
CREATE TABLE `scrobbles` (
114
+
`xata_id` text PRIMARY KEY NOT NULL,
115
+
`user_id` text,
116
+
`track_id` text,
117
+
`album_id` text,
118
+
`artist_id` text,
119
+
`uri` text,
120
+
`cid` text,
121
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
122
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
123
+
`timestamp` integer DEFAULT (unixepoch()) NOT NULL,
124
+
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
125
+
FOREIGN KEY (`track_id`) REFERENCES `tracks`(`id`) ON UPDATE no action ON DELETE no action,
126
+
FOREIGN KEY (`album_id`) REFERENCES `albums`(`id`) ON UPDATE no action ON DELETE no action,
127
+
FOREIGN KEY (`artist_id`) REFERENCES `artists`(`id`) ON UPDATE no action ON DELETE no action
128
+
);
129
+
--> statement-breakpoint
130
+
CREATE UNIQUE INDEX `scrobbles_uri_unique` ON `scrobbles` (`uri`);--> statement-breakpoint
131
+
CREATE UNIQUE INDEX `scrobbles_cid_unique` ON `scrobbles` (`cid`);--> statement-breakpoint
132
+
CREATE TABLE `tracks` (
133
+
`id` text PRIMARY KEY NOT NULL,
134
+
`title` text NOT NULL,
135
+
`artist` text NOT NULL,
136
+
`album_artist` text NOT NULL,
137
+
`album_art` text,
138
+
`album` text NOT NULL,
139
+
`track_number` integer,
140
+
`duration` integer NOT NULL,
141
+
`mb_id` text,
142
+
`youtube_link` text,
143
+
`spotify_link` text,
144
+
`apple_music_link` text,
145
+
`tidal_link` text,
146
+
`disc_number` integer,
147
+
`lyrics` text,
148
+
`composer` text,
149
+
`genre` text,
150
+
`label` text,
151
+
`copyright_message` text,
152
+
`uri` text,
153
+
`cid` text NOT NULL,
154
+
`album_uri` text,
155
+
`artist_uri` text,
156
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
157
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
158
+
);
159
+
--> statement-breakpoint
160
+
CREATE UNIQUE INDEX `tracks_mb_id_unique` ON `tracks` (`mb_id`);--> statement-breakpoint
161
+
CREATE UNIQUE INDEX `tracks_youtube_link_unique` ON `tracks` (`youtube_link`);--> statement-breakpoint
162
+
CREATE UNIQUE INDEX `tracks_spotify_link_unique` ON `tracks` (`spotify_link`);--> statement-breakpoint
163
+
CREATE UNIQUE INDEX `tracks_apple_music_link_unique` ON `tracks` (`apple_music_link`);--> statement-breakpoint
164
+
CREATE UNIQUE INDEX `tracks_tidal_link_unique` ON `tracks` (`tidal_link`);--> statement-breakpoint
165
+
CREATE UNIQUE INDEX `tracks_uri_unique` ON `tracks` (`uri`);--> statement-breakpoint
166
+
CREATE UNIQUE INDEX `tracks_cid_unique` ON `tracks` (`cid`);--> statement-breakpoint
167
+
CREATE TABLE `user_albums` (
168
+
`id` text PRIMARY KEY NOT NULL,
169
+
`user_id` text NOT NULL,
170
+
`album_id` text NOT NULL,
171
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
172
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
173
+
`scrobbles` integer,
174
+
`uri` text NOT NULL,
175
+
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
176
+
FOREIGN KEY (`album_id`) REFERENCES `albums`(`id`) ON UPDATE no action ON DELETE no action
177
+
);
178
+
--> statement-breakpoint
179
+
CREATE UNIQUE INDEX `user_albums_uri_unique` ON `user_albums` (`uri`);--> statement-breakpoint
180
+
CREATE UNIQUE INDEX `user_albums_unique_index` ON `user_albums` (`user_id`,`album_id`);--> statement-breakpoint
181
+
CREATE TABLE `user_artists` (
182
+
`id` text PRIMARY KEY NOT NULL,
183
+
`user_id` text NOT NULL,
184
+
`artist_id` text NOT NULL,
185
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
186
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
187
+
`scrobbles` integer,
188
+
`uri` text NOT NULL,
189
+
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
190
+
FOREIGN KEY (`artist_id`) REFERENCES `artists`(`id`) ON UPDATE no action ON DELETE no action
191
+
);
192
+
--> statement-breakpoint
193
+
CREATE UNIQUE INDEX `user_artists_uri_unique` ON `user_artists` (`uri`);--> statement-breakpoint
194
+
CREATE UNIQUE INDEX `user_artists_unique_index` ON `user_artists` (`user_id`,`artist_id`);--> statement-breakpoint
195
+
CREATE TABLE `user_tracks` (
196
+
`id` text PRIMARY KEY NOT NULL,
197
+
`user_id` text NOT NULL,
198
+
`track_id` text NOT NULL,
199
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
200
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
201
+
`scrobbles` integer,
202
+
`uri` text NOT NULL,
203
+
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
204
+
FOREIGN KEY (`track_id`) REFERENCES `tracks`(`id`) ON UPDATE no action ON DELETE no action
205
+
);
206
+
--> statement-breakpoint
207
+
CREATE UNIQUE INDEX `user_tracks_uri_unique` ON `user_tracks` (`uri`);--> statement-breakpoint
208
+
CREATE UNIQUE INDEX `user_tracks_unique_index` ON `user_tracks` (`user_id`,`track_id`);--> statement-breakpoint
209
+
CREATE TABLE `users` (
210
+
`id` text PRIMARY KEY NOT NULL,
211
+
`did` text NOT NULL,
212
+
`display_name` text,
213
+
`handle` text NOT NULL,
214
+
`avatar` text NOT NULL,
215
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
216
+
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
217
+
);
218
+
--> statement-breakpoint
219
+
CREATE UNIQUE INDEX `users_did_unique` ON `users` (`did`);--> statement-breakpoint
220
+
CREATE UNIQUE INDEX `users_handle_unique` ON `users` (`handle`);
+1559
apps/cli/drizzle/meta/0000_snapshot.json
+1559
apps/cli/drizzle/meta/0000_snapshot.json
···
1
+
{
2
+
"version": "6",
3
+
"dialect": "sqlite",
4
+
"id": "926f883e-c7c5-4c01-b88b-c8f7f649559a",
5
+
"prevId": "00000000-0000-0000-0000-000000000000",
6
+
"tables": {
7
+
"album_tracks": {
8
+
"name": "album_tracks",
9
+
"columns": {
10
+
"id": {
11
+
"name": "id",
12
+
"type": "text",
13
+
"primaryKey": true,
14
+
"notNull": true,
15
+
"autoincrement": false
16
+
},
17
+
"album_id": {
18
+
"name": "album_id",
19
+
"type": "text",
20
+
"primaryKey": false,
21
+
"notNull": true,
22
+
"autoincrement": false
23
+
},
24
+
"track_id": {
25
+
"name": "track_id",
26
+
"type": "text",
27
+
"primaryKey": false,
28
+
"notNull": true,
29
+
"autoincrement": false
30
+
},
31
+
"created_at": {
32
+
"name": "created_at",
33
+
"type": "integer",
34
+
"primaryKey": false,
35
+
"notNull": true,
36
+
"autoincrement": false,
37
+
"default": "(unixepoch())"
38
+
},
39
+
"updated_at": {
40
+
"name": "updated_at",
41
+
"type": "integer",
42
+
"primaryKey": false,
43
+
"notNull": true,
44
+
"autoincrement": false,
45
+
"default": "(unixepoch())"
46
+
}
47
+
},
48
+
"indexes": {
49
+
"album_tracks_unique_index": {
50
+
"name": "album_tracks_unique_index",
51
+
"columns": [
52
+
"album_id",
53
+
"track_id"
54
+
],
55
+
"isUnique": true
56
+
}
57
+
},
58
+
"foreignKeys": {
59
+
"album_tracks_album_id_albums_id_fk": {
60
+
"name": "album_tracks_album_id_albums_id_fk",
61
+
"tableFrom": "album_tracks",
62
+
"tableTo": "albums",
63
+
"columnsFrom": [
64
+
"album_id"
65
+
],
66
+
"columnsTo": [
67
+
"id"
68
+
],
69
+
"onDelete": "no action",
70
+
"onUpdate": "no action"
71
+
},
72
+
"album_tracks_track_id_tracks_id_fk": {
73
+
"name": "album_tracks_track_id_tracks_id_fk",
74
+
"tableFrom": "album_tracks",
75
+
"tableTo": "tracks",
76
+
"columnsFrom": [
77
+
"track_id"
78
+
],
79
+
"columnsTo": [
80
+
"id"
81
+
],
82
+
"onDelete": "no action",
83
+
"onUpdate": "no action"
84
+
}
85
+
},
86
+
"compositePrimaryKeys": {},
87
+
"uniqueConstraints": {},
88
+
"checkConstraints": {}
89
+
},
90
+
"albums": {
91
+
"name": "albums",
92
+
"columns": {
93
+
"id": {
94
+
"name": "id",
95
+
"type": "text",
96
+
"primaryKey": true,
97
+
"notNull": true,
98
+
"autoincrement": false
99
+
},
100
+
"title": {
101
+
"name": "title",
102
+
"type": "text",
103
+
"primaryKey": false,
104
+
"notNull": true,
105
+
"autoincrement": false
106
+
},
107
+
"artist": {
108
+
"name": "artist",
109
+
"type": "text",
110
+
"primaryKey": false,
111
+
"notNull": true,
112
+
"autoincrement": false
113
+
},
114
+
"release_date": {
115
+
"name": "release_date",
116
+
"type": "text",
117
+
"primaryKey": false,
118
+
"notNull": false,
119
+
"autoincrement": false
120
+
},
121
+
"year": {
122
+
"name": "year",
123
+
"type": "integer",
124
+
"primaryKey": false,
125
+
"notNull": false,
126
+
"autoincrement": false
127
+
},
128
+
"album_art": {
129
+
"name": "album_art",
130
+
"type": "text",
131
+
"primaryKey": false,
132
+
"notNull": false,
133
+
"autoincrement": false
134
+
},
135
+
"uri": {
136
+
"name": "uri",
137
+
"type": "text",
138
+
"primaryKey": false,
139
+
"notNull": false,
140
+
"autoincrement": false
141
+
},
142
+
"cid": {
143
+
"name": "cid",
144
+
"type": "text",
145
+
"primaryKey": false,
146
+
"notNull": true,
147
+
"autoincrement": false
148
+
},
149
+
"artist_uri": {
150
+
"name": "artist_uri",
151
+
"type": "text",
152
+
"primaryKey": false,
153
+
"notNull": false,
154
+
"autoincrement": false
155
+
},
156
+
"apple_music_link": {
157
+
"name": "apple_music_link",
158
+
"type": "text",
159
+
"primaryKey": false,
160
+
"notNull": false,
161
+
"autoincrement": false
162
+
},
163
+
"spotify_link": {
164
+
"name": "spotify_link",
165
+
"type": "text",
166
+
"primaryKey": false,
167
+
"notNull": false,
168
+
"autoincrement": false
169
+
},
170
+
"tidal_link": {
171
+
"name": "tidal_link",
172
+
"type": "text",
173
+
"primaryKey": false,
174
+
"notNull": false,
175
+
"autoincrement": false
176
+
},
177
+
"youtube_link": {
178
+
"name": "youtube_link",
179
+
"type": "text",
180
+
"primaryKey": false,
181
+
"notNull": false,
182
+
"autoincrement": false
183
+
},
184
+
"created_at": {
185
+
"name": "created_at",
186
+
"type": "integer",
187
+
"primaryKey": false,
188
+
"notNull": true,
189
+
"autoincrement": false,
190
+
"default": "(unixepoch())"
191
+
},
192
+
"updated_at": {
193
+
"name": "updated_at",
194
+
"type": "integer",
195
+
"primaryKey": false,
196
+
"notNull": true,
197
+
"autoincrement": false,
198
+
"default": "(unixepoch())"
199
+
}
200
+
},
201
+
"indexes": {
202
+
"albums_uri_unique": {
203
+
"name": "albums_uri_unique",
204
+
"columns": [
205
+
"uri"
206
+
],
207
+
"isUnique": true
208
+
},
209
+
"albums_cid_unique": {
210
+
"name": "albums_cid_unique",
211
+
"columns": [
212
+
"cid"
213
+
],
214
+
"isUnique": true
215
+
},
216
+
"albums_apple_music_link_unique": {
217
+
"name": "albums_apple_music_link_unique",
218
+
"columns": [
219
+
"apple_music_link"
220
+
],
221
+
"isUnique": true
222
+
},
223
+
"albums_spotify_link_unique": {
224
+
"name": "albums_spotify_link_unique",
225
+
"columns": [
226
+
"spotify_link"
227
+
],
228
+
"isUnique": true
229
+
},
230
+
"albums_tidal_link_unique": {
231
+
"name": "albums_tidal_link_unique",
232
+
"columns": [
233
+
"tidal_link"
234
+
],
235
+
"isUnique": true
236
+
},
237
+
"albums_youtube_link_unique": {
238
+
"name": "albums_youtube_link_unique",
239
+
"columns": [
240
+
"youtube_link"
241
+
],
242
+
"isUnique": true
243
+
}
244
+
},
245
+
"foreignKeys": {},
246
+
"compositePrimaryKeys": {},
247
+
"uniqueConstraints": {},
248
+
"checkConstraints": {}
249
+
},
250
+
"artist_albums": {
251
+
"name": "artist_albums",
252
+
"columns": {
253
+
"id": {
254
+
"name": "id",
255
+
"type": "text",
256
+
"primaryKey": true,
257
+
"notNull": true,
258
+
"autoincrement": false
259
+
},
260
+
"artist_id": {
261
+
"name": "artist_id",
262
+
"type": "text",
263
+
"primaryKey": false,
264
+
"notNull": true,
265
+
"autoincrement": false
266
+
},
267
+
"album_id": {
268
+
"name": "album_id",
269
+
"type": "text",
270
+
"primaryKey": false,
271
+
"notNull": true,
272
+
"autoincrement": false
273
+
},
274
+
"created_at": {
275
+
"name": "created_at",
276
+
"type": "integer",
277
+
"primaryKey": false,
278
+
"notNull": true,
279
+
"autoincrement": false,
280
+
"default": "(unixepoch())"
281
+
},
282
+
"updated_at": {
283
+
"name": "updated_at",
284
+
"type": "integer",
285
+
"primaryKey": false,
286
+
"notNull": true,
287
+
"autoincrement": false,
288
+
"default": "(unixepoch())"
289
+
}
290
+
},
291
+
"indexes": {
292
+
"artist_albums_unique_index": {
293
+
"name": "artist_albums_unique_index",
294
+
"columns": [
295
+
"artist_id",
296
+
"album_id"
297
+
],
298
+
"isUnique": true
299
+
}
300
+
},
301
+
"foreignKeys": {
302
+
"artist_albums_artist_id_artists_id_fk": {
303
+
"name": "artist_albums_artist_id_artists_id_fk",
304
+
"tableFrom": "artist_albums",
305
+
"tableTo": "artists",
306
+
"columnsFrom": [
307
+
"artist_id"
308
+
],
309
+
"columnsTo": [
310
+
"id"
311
+
],
312
+
"onDelete": "no action",
313
+
"onUpdate": "no action"
314
+
},
315
+
"artist_albums_album_id_albums_id_fk": {
316
+
"name": "artist_albums_album_id_albums_id_fk",
317
+
"tableFrom": "artist_albums",
318
+
"tableTo": "albums",
319
+
"columnsFrom": [
320
+
"album_id"
321
+
],
322
+
"columnsTo": [
323
+
"id"
324
+
],
325
+
"onDelete": "no action",
326
+
"onUpdate": "no action"
327
+
}
328
+
},
329
+
"compositePrimaryKeys": {},
330
+
"uniqueConstraints": {},
331
+
"checkConstraints": {}
332
+
},
333
+
"artist_genres ": {
334
+
"name": "artist_genres ",
335
+
"columns": {
336
+
"id": {
337
+
"name": "id",
338
+
"type": "text",
339
+
"primaryKey": true,
340
+
"notNull": true,
341
+
"autoincrement": false
342
+
},
343
+
"artist_id": {
344
+
"name": "artist_id",
345
+
"type": "text",
346
+
"primaryKey": false,
347
+
"notNull": true,
348
+
"autoincrement": false
349
+
},
350
+
"genre_id": {
351
+
"name": "genre_id",
352
+
"type": "text",
353
+
"primaryKey": false,
354
+
"notNull": true,
355
+
"autoincrement": false
356
+
}
357
+
},
358
+
"indexes": {
359
+
"artist_genre_unique_index": {
360
+
"name": "artist_genre_unique_index",
361
+
"columns": [
362
+
"artist_id",
363
+
"genre_id"
364
+
],
365
+
"isUnique": true
366
+
}
367
+
},
368
+
"foreignKeys": {},
369
+
"compositePrimaryKeys": {},
370
+
"uniqueConstraints": {},
371
+
"checkConstraints": {}
372
+
},
373
+
"artist_tracks": {
374
+
"name": "artist_tracks",
375
+
"columns": {
376
+
"id": {
377
+
"name": "id",
378
+
"type": "text",
379
+
"primaryKey": true,
380
+
"notNull": true,
381
+
"autoincrement": false
382
+
},
383
+
"artist_id": {
384
+
"name": "artist_id",
385
+
"type": "text",
386
+
"primaryKey": false,
387
+
"notNull": true,
388
+
"autoincrement": false
389
+
},
390
+
"track_id": {
391
+
"name": "track_id",
392
+
"type": "text",
393
+
"primaryKey": false,
394
+
"notNull": true,
395
+
"autoincrement": false
396
+
},
397
+
"created_at": {
398
+
"name": "created_at",
399
+
"type": "integer",
400
+
"primaryKey": false,
401
+
"notNull": true,
402
+
"autoincrement": false,
403
+
"default": "(unixepoch())"
404
+
},
405
+
"updated_at": {
406
+
"name": "updated_at",
407
+
"type": "integer",
408
+
"primaryKey": false,
409
+
"notNull": true,
410
+
"autoincrement": false,
411
+
"default": "(unixepoch())"
412
+
}
413
+
},
414
+
"indexes": {
415
+
"artist_tracks_unique_index": {
416
+
"name": "artist_tracks_unique_index",
417
+
"columns": [
418
+
"artist_id",
419
+
"track_id"
420
+
],
421
+
"isUnique": true
422
+
}
423
+
},
424
+
"foreignKeys": {
425
+
"artist_tracks_artist_id_artists_id_fk": {
426
+
"name": "artist_tracks_artist_id_artists_id_fk",
427
+
"tableFrom": "artist_tracks",
428
+
"tableTo": "artists",
429
+
"columnsFrom": [
430
+
"artist_id"
431
+
],
432
+
"columnsTo": [
433
+
"id"
434
+
],
435
+
"onDelete": "no action",
436
+
"onUpdate": "no action"
437
+
},
438
+
"artist_tracks_track_id_tracks_id_fk": {
439
+
"name": "artist_tracks_track_id_tracks_id_fk",
440
+
"tableFrom": "artist_tracks",
441
+
"tableTo": "tracks",
442
+
"columnsFrom": [
443
+
"track_id"
444
+
],
445
+
"columnsTo": [
446
+
"id"
447
+
],
448
+
"onDelete": "no action",
449
+
"onUpdate": "no action"
450
+
}
451
+
},
452
+
"compositePrimaryKeys": {},
453
+
"uniqueConstraints": {},
454
+
"checkConstraints": {}
455
+
},
456
+
"artists": {
457
+
"name": "artists",
458
+
"columns": {
459
+
"id": {
460
+
"name": "id",
461
+
"type": "text",
462
+
"primaryKey": true,
463
+
"notNull": true,
464
+
"autoincrement": false
465
+
},
466
+
"name": {
467
+
"name": "name",
468
+
"type": "text",
469
+
"primaryKey": false,
470
+
"notNull": true,
471
+
"autoincrement": false
472
+
},
473
+
"biography": {
474
+
"name": "biography",
475
+
"type": "text",
476
+
"primaryKey": false,
477
+
"notNull": false,
478
+
"autoincrement": false
479
+
},
480
+
"born": {
481
+
"name": "born",
482
+
"type": "integer",
483
+
"primaryKey": false,
484
+
"notNull": false,
485
+
"autoincrement": false
486
+
},
487
+
"born_in": {
488
+
"name": "born_in",
489
+
"type": "text",
490
+
"primaryKey": false,
491
+
"notNull": false,
492
+
"autoincrement": false
493
+
},
494
+
"died": {
495
+
"name": "died",
496
+
"type": "integer",
497
+
"primaryKey": false,
498
+
"notNull": false,
499
+
"autoincrement": false
500
+
},
501
+
"picture": {
502
+
"name": "picture",
503
+
"type": "text",
504
+
"primaryKey": false,
505
+
"notNull": false,
506
+
"autoincrement": false
507
+
},
508
+
"uri": {
509
+
"name": "uri",
510
+
"type": "text",
511
+
"primaryKey": false,
512
+
"notNull": false,
513
+
"autoincrement": false
514
+
},
515
+
"cid": {
516
+
"name": "cid",
517
+
"type": "text",
518
+
"primaryKey": false,
519
+
"notNull": true,
520
+
"autoincrement": false
521
+
},
522
+
"apple_music_link": {
523
+
"name": "apple_music_link",
524
+
"type": "text",
525
+
"primaryKey": false,
526
+
"notNull": false,
527
+
"autoincrement": false
528
+
},
529
+
"spotify_link": {
530
+
"name": "spotify_link",
531
+
"type": "text",
532
+
"primaryKey": false,
533
+
"notNull": false,
534
+
"autoincrement": false
535
+
},
536
+
"tidal_link": {
537
+
"name": "tidal_link",
538
+
"type": "text",
539
+
"primaryKey": false,
540
+
"notNull": false,
541
+
"autoincrement": false
542
+
},
543
+
"youtube_link": {
544
+
"name": "youtube_link",
545
+
"type": "text",
546
+
"primaryKey": false,
547
+
"notNull": false,
548
+
"autoincrement": false
549
+
},
550
+
"genres": {
551
+
"name": "genres",
552
+
"type": "text",
553
+
"primaryKey": false,
554
+
"notNull": false,
555
+
"autoincrement": false
556
+
},
557
+
"created_at": {
558
+
"name": "created_at",
559
+
"type": "integer",
560
+
"primaryKey": false,
561
+
"notNull": true,
562
+
"autoincrement": false,
563
+
"default": "(unixepoch())"
564
+
},
565
+
"updated_at": {
566
+
"name": "updated_at",
567
+
"type": "integer",
568
+
"primaryKey": false,
569
+
"notNull": true,
570
+
"autoincrement": false,
571
+
"default": "(unixepoch())"
572
+
}
573
+
},
574
+
"indexes": {
575
+
"artists_uri_unique": {
576
+
"name": "artists_uri_unique",
577
+
"columns": [
578
+
"uri"
579
+
],
580
+
"isUnique": true
581
+
},
582
+
"artists_cid_unique": {
583
+
"name": "artists_cid_unique",
584
+
"columns": [
585
+
"cid"
586
+
],
587
+
"isUnique": true
588
+
}
589
+
},
590
+
"foreignKeys": {},
591
+
"compositePrimaryKeys": {},
592
+
"uniqueConstraints": {},
593
+
"checkConstraints": {}
594
+
},
595
+
"auth_sessions": {
596
+
"name": "auth_sessions",
597
+
"columns": {
598
+
"key": {
599
+
"name": "key",
600
+
"type": "text",
601
+
"primaryKey": true,
602
+
"notNull": true,
603
+
"autoincrement": false
604
+
},
605
+
"session": {
606
+
"name": "session",
607
+
"type": "text",
608
+
"primaryKey": false,
609
+
"notNull": true,
610
+
"autoincrement": false
611
+
},
612
+
"created_at": {
613
+
"name": "created_at",
614
+
"type": "integer",
615
+
"primaryKey": false,
616
+
"notNull": true,
617
+
"autoincrement": false,
618
+
"default": "(unixepoch())"
619
+
},
620
+
"updated_at": {
621
+
"name": "updated_at",
622
+
"type": "integer",
623
+
"primaryKey": false,
624
+
"notNull": true,
625
+
"autoincrement": false,
626
+
"default": "(unixepoch())"
627
+
}
628
+
},
629
+
"indexes": {},
630
+
"foreignKeys": {},
631
+
"compositePrimaryKeys": {},
632
+
"uniqueConstraints": {},
633
+
"checkConstraints": {}
634
+
},
635
+
"genres": {
636
+
"name": "genres",
637
+
"columns": {
638
+
"id": {
639
+
"name": "id",
640
+
"type": "text",
641
+
"primaryKey": true,
642
+
"notNull": true,
643
+
"autoincrement": false
644
+
},
645
+
"name": {
646
+
"name": "name",
647
+
"type": "text",
648
+
"primaryKey": false,
649
+
"notNull": true,
650
+
"autoincrement": false
651
+
},
652
+
"created_at": {
653
+
"name": "created_at",
654
+
"type": "integer",
655
+
"primaryKey": false,
656
+
"notNull": true,
657
+
"autoincrement": false,
658
+
"default": "(unixepoch())"
659
+
},
660
+
"updated_at": {
661
+
"name": "updated_at",
662
+
"type": "integer",
663
+
"primaryKey": false,
664
+
"notNull": true,
665
+
"autoincrement": false,
666
+
"default": "(unixepoch())"
667
+
}
668
+
},
669
+
"indexes": {
670
+
"genres_name_unique": {
671
+
"name": "genres_name_unique",
672
+
"columns": [
673
+
"name"
674
+
],
675
+
"isUnique": true
676
+
}
677
+
},
678
+
"foreignKeys": {},
679
+
"compositePrimaryKeys": {},
680
+
"uniqueConstraints": {},
681
+
"checkConstraints": {}
682
+
},
683
+
"loved_tracks": {
684
+
"name": "loved_tracks",
685
+
"columns": {
686
+
"id": {
687
+
"name": "id",
688
+
"type": "text",
689
+
"primaryKey": true,
690
+
"notNull": true,
691
+
"autoincrement": false
692
+
},
693
+
"user_id": {
694
+
"name": "user_id",
695
+
"type": "text",
696
+
"primaryKey": false,
697
+
"notNull": true,
698
+
"autoincrement": false
699
+
},
700
+
"track_id": {
701
+
"name": "track_id",
702
+
"type": "text",
703
+
"primaryKey": false,
704
+
"notNull": true,
705
+
"autoincrement": false
706
+
},
707
+
"uri": {
708
+
"name": "uri",
709
+
"type": "text",
710
+
"primaryKey": false,
711
+
"notNull": false,
712
+
"autoincrement": false
713
+
},
714
+
"created_at": {
715
+
"name": "created_at",
716
+
"type": "integer",
717
+
"primaryKey": false,
718
+
"notNull": true,
719
+
"autoincrement": false,
720
+
"default": "(unixepoch())"
721
+
}
722
+
},
723
+
"indexes": {
724
+
"loved_tracks_uri_unique": {
725
+
"name": "loved_tracks_uri_unique",
726
+
"columns": [
727
+
"uri"
728
+
],
729
+
"isUnique": true
730
+
},
731
+
"loved_tracks_unique_index": {
732
+
"name": "loved_tracks_unique_index",
733
+
"columns": [
734
+
"user_id",
735
+
"track_id"
736
+
],
737
+
"isUnique": true
738
+
}
739
+
},
740
+
"foreignKeys": {
741
+
"loved_tracks_user_id_users_id_fk": {
742
+
"name": "loved_tracks_user_id_users_id_fk",
743
+
"tableFrom": "loved_tracks",
744
+
"tableTo": "users",
745
+
"columnsFrom": [
746
+
"user_id"
747
+
],
748
+
"columnsTo": [
749
+
"id"
750
+
],
751
+
"onDelete": "no action",
752
+
"onUpdate": "no action"
753
+
},
754
+
"loved_tracks_track_id_tracks_id_fk": {
755
+
"name": "loved_tracks_track_id_tracks_id_fk",
756
+
"tableFrom": "loved_tracks",
757
+
"tableTo": "tracks",
758
+
"columnsFrom": [
759
+
"track_id"
760
+
],
761
+
"columnsTo": [
762
+
"id"
763
+
],
764
+
"onDelete": "no action",
765
+
"onUpdate": "no action"
766
+
}
767
+
},
768
+
"compositePrimaryKeys": {},
769
+
"uniqueConstraints": {},
770
+
"checkConstraints": {}
771
+
},
772
+
"scrobbles": {
773
+
"name": "scrobbles",
774
+
"columns": {
775
+
"xata_id": {
776
+
"name": "xata_id",
777
+
"type": "text",
778
+
"primaryKey": true,
779
+
"notNull": true,
780
+
"autoincrement": false
781
+
},
782
+
"user_id": {
783
+
"name": "user_id",
784
+
"type": "text",
785
+
"primaryKey": false,
786
+
"notNull": false,
787
+
"autoincrement": false
788
+
},
789
+
"track_id": {
790
+
"name": "track_id",
791
+
"type": "text",
792
+
"primaryKey": false,
793
+
"notNull": false,
794
+
"autoincrement": false
795
+
},
796
+
"album_id": {
797
+
"name": "album_id",
798
+
"type": "text",
799
+
"primaryKey": false,
800
+
"notNull": false,
801
+
"autoincrement": false
802
+
},
803
+
"artist_id": {
804
+
"name": "artist_id",
805
+
"type": "text",
806
+
"primaryKey": false,
807
+
"notNull": false,
808
+
"autoincrement": false
809
+
},
810
+
"uri": {
811
+
"name": "uri",
812
+
"type": "text",
813
+
"primaryKey": false,
814
+
"notNull": false,
815
+
"autoincrement": false
816
+
},
817
+
"cid": {
818
+
"name": "cid",
819
+
"type": "text",
820
+
"primaryKey": false,
821
+
"notNull": false,
822
+
"autoincrement": false
823
+
},
824
+
"created_at": {
825
+
"name": "created_at",
826
+
"type": "integer",
827
+
"primaryKey": false,
828
+
"notNull": true,
829
+
"autoincrement": false,
830
+
"default": "(unixepoch())"
831
+
},
832
+
"updated_at": {
833
+
"name": "updated_at",
834
+
"type": "integer",
835
+
"primaryKey": false,
836
+
"notNull": true,
837
+
"autoincrement": false,
838
+
"default": "(unixepoch())"
839
+
},
840
+
"timestamp": {
841
+
"name": "timestamp",
842
+
"type": "integer",
843
+
"primaryKey": false,
844
+
"notNull": true,
845
+
"autoincrement": false,
846
+
"default": "(unixepoch())"
847
+
}
848
+
},
849
+
"indexes": {
850
+
"scrobbles_uri_unique": {
851
+
"name": "scrobbles_uri_unique",
852
+
"columns": [
853
+
"uri"
854
+
],
855
+
"isUnique": true
856
+
},
857
+
"scrobbles_cid_unique": {
858
+
"name": "scrobbles_cid_unique",
859
+
"columns": [
860
+
"cid"
861
+
],
862
+
"isUnique": true
863
+
}
864
+
},
865
+
"foreignKeys": {
866
+
"scrobbles_user_id_users_id_fk": {
867
+
"name": "scrobbles_user_id_users_id_fk",
868
+
"tableFrom": "scrobbles",
869
+
"tableTo": "users",
870
+
"columnsFrom": [
871
+
"user_id"
872
+
],
873
+
"columnsTo": [
874
+
"id"
875
+
],
876
+
"onDelete": "no action",
877
+
"onUpdate": "no action"
878
+
},
879
+
"scrobbles_track_id_tracks_id_fk": {
880
+
"name": "scrobbles_track_id_tracks_id_fk",
881
+
"tableFrom": "scrobbles",
882
+
"tableTo": "tracks",
883
+
"columnsFrom": [
884
+
"track_id"
885
+
],
886
+
"columnsTo": [
887
+
"id"
888
+
],
889
+
"onDelete": "no action",
890
+
"onUpdate": "no action"
891
+
},
892
+
"scrobbles_album_id_albums_id_fk": {
893
+
"name": "scrobbles_album_id_albums_id_fk",
894
+
"tableFrom": "scrobbles",
895
+
"tableTo": "albums",
896
+
"columnsFrom": [
897
+
"album_id"
898
+
],
899
+
"columnsTo": [
900
+
"id"
901
+
],
902
+
"onDelete": "no action",
903
+
"onUpdate": "no action"
904
+
},
905
+
"scrobbles_artist_id_artists_id_fk": {
906
+
"name": "scrobbles_artist_id_artists_id_fk",
907
+
"tableFrom": "scrobbles",
908
+
"tableTo": "artists",
909
+
"columnsFrom": [
910
+
"artist_id"
911
+
],
912
+
"columnsTo": [
913
+
"id"
914
+
],
915
+
"onDelete": "no action",
916
+
"onUpdate": "no action"
917
+
}
918
+
},
919
+
"compositePrimaryKeys": {},
920
+
"uniqueConstraints": {},
921
+
"checkConstraints": {}
922
+
},
923
+
"tracks": {
924
+
"name": "tracks",
925
+
"columns": {
926
+
"id": {
927
+
"name": "id",
928
+
"type": "text",
929
+
"primaryKey": true,
930
+
"notNull": true,
931
+
"autoincrement": false
932
+
},
933
+
"title": {
934
+
"name": "title",
935
+
"type": "text",
936
+
"primaryKey": false,
937
+
"notNull": true,
938
+
"autoincrement": false
939
+
},
940
+
"artist": {
941
+
"name": "artist",
942
+
"type": "text",
943
+
"primaryKey": false,
944
+
"notNull": true,
945
+
"autoincrement": false
946
+
},
947
+
"album_artist": {
948
+
"name": "album_artist",
949
+
"type": "text",
950
+
"primaryKey": false,
951
+
"notNull": true,
952
+
"autoincrement": false
953
+
},
954
+
"album_art": {
955
+
"name": "album_art",
956
+
"type": "text",
957
+
"primaryKey": false,
958
+
"notNull": false,
959
+
"autoincrement": false
960
+
},
961
+
"album": {
962
+
"name": "album",
963
+
"type": "text",
964
+
"primaryKey": false,
965
+
"notNull": true,
966
+
"autoincrement": false
967
+
},
968
+
"track_number": {
969
+
"name": "track_number",
970
+
"type": "integer",
971
+
"primaryKey": false,
972
+
"notNull": false,
973
+
"autoincrement": false
974
+
},
975
+
"duration": {
976
+
"name": "duration",
977
+
"type": "integer",
978
+
"primaryKey": false,
979
+
"notNull": true,
980
+
"autoincrement": false
981
+
},
982
+
"mb_id": {
983
+
"name": "mb_id",
984
+
"type": "text",
985
+
"primaryKey": false,
986
+
"notNull": false,
987
+
"autoincrement": false
988
+
},
989
+
"youtube_link": {
990
+
"name": "youtube_link",
991
+
"type": "text",
992
+
"primaryKey": false,
993
+
"notNull": false,
994
+
"autoincrement": false
995
+
},
996
+
"spotify_link": {
997
+
"name": "spotify_link",
998
+
"type": "text",
999
+
"primaryKey": false,
1000
+
"notNull": false,
1001
+
"autoincrement": false
1002
+
},
1003
+
"apple_music_link": {
1004
+
"name": "apple_music_link",
1005
+
"type": "text",
1006
+
"primaryKey": false,
1007
+
"notNull": false,
1008
+
"autoincrement": false
1009
+
},
1010
+
"tidal_link": {
1011
+
"name": "tidal_link",
1012
+
"type": "text",
1013
+
"primaryKey": false,
1014
+
"notNull": false,
1015
+
"autoincrement": false
1016
+
},
1017
+
"disc_number": {
1018
+
"name": "disc_number",
1019
+
"type": "integer",
1020
+
"primaryKey": false,
1021
+
"notNull": false,
1022
+
"autoincrement": false
1023
+
},
1024
+
"lyrics": {
1025
+
"name": "lyrics",
1026
+
"type": "text",
1027
+
"primaryKey": false,
1028
+
"notNull": false,
1029
+
"autoincrement": false
1030
+
},
1031
+
"composer": {
1032
+
"name": "composer",
1033
+
"type": "text",
1034
+
"primaryKey": false,
1035
+
"notNull": false,
1036
+
"autoincrement": false
1037
+
},
1038
+
"genre": {
1039
+
"name": "genre",
1040
+
"type": "text",
1041
+
"primaryKey": false,
1042
+
"notNull": false,
1043
+
"autoincrement": false
1044
+
},
1045
+
"label": {
1046
+
"name": "label",
1047
+
"type": "text",
1048
+
"primaryKey": false,
1049
+
"notNull": false,
1050
+
"autoincrement": false
1051
+
},
1052
+
"copyright_message": {
1053
+
"name": "copyright_message",
1054
+
"type": "text",
1055
+
"primaryKey": false,
1056
+
"notNull": false,
1057
+
"autoincrement": false
1058
+
},
1059
+
"uri": {
1060
+
"name": "uri",
1061
+
"type": "text",
1062
+
"primaryKey": false,
1063
+
"notNull": false,
1064
+
"autoincrement": false
1065
+
},
1066
+
"cid": {
1067
+
"name": "cid",
1068
+
"type": "text",
1069
+
"primaryKey": false,
1070
+
"notNull": true,
1071
+
"autoincrement": false
1072
+
},
1073
+
"album_uri": {
1074
+
"name": "album_uri",
1075
+
"type": "text",
1076
+
"primaryKey": false,
1077
+
"notNull": false,
1078
+
"autoincrement": false
1079
+
},
1080
+
"artist_uri": {
1081
+
"name": "artist_uri",
1082
+
"type": "text",
1083
+
"primaryKey": false,
1084
+
"notNull": false,
1085
+
"autoincrement": false
1086
+
},
1087
+
"created_at": {
1088
+
"name": "created_at",
1089
+
"type": "integer",
1090
+
"primaryKey": false,
1091
+
"notNull": true,
1092
+
"autoincrement": false,
1093
+
"default": "(unixepoch())"
1094
+
},
1095
+
"updated_at": {
1096
+
"name": "updated_at",
1097
+
"type": "integer",
1098
+
"primaryKey": false,
1099
+
"notNull": true,
1100
+
"autoincrement": false,
1101
+
"default": "(unixepoch())"
1102
+
}
1103
+
},
1104
+
"indexes": {
1105
+
"tracks_mb_id_unique": {
1106
+
"name": "tracks_mb_id_unique",
1107
+
"columns": [
1108
+
"mb_id"
1109
+
],
1110
+
"isUnique": true
1111
+
},
1112
+
"tracks_youtube_link_unique": {
1113
+
"name": "tracks_youtube_link_unique",
1114
+
"columns": [
1115
+
"youtube_link"
1116
+
],
1117
+
"isUnique": true
1118
+
},
1119
+
"tracks_spotify_link_unique": {
1120
+
"name": "tracks_spotify_link_unique",
1121
+
"columns": [
1122
+
"spotify_link"
1123
+
],
1124
+
"isUnique": true
1125
+
},
1126
+
"tracks_apple_music_link_unique": {
1127
+
"name": "tracks_apple_music_link_unique",
1128
+
"columns": [
1129
+
"apple_music_link"
1130
+
],
1131
+
"isUnique": true
1132
+
},
1133
+
"tracks_tidal_link_unique": {
1134
+
"name": "tracks_tidal_link_unique",
1135
+
"columns": [
1136
+
"tidal_link"
1137
+
],
1138
+
"isUnique": true
1139
+
},
1140
+
"tracks_uri_unique": {
1141
+
"name": "tracks_uri_unique",
1142
+
"columns": [
1143
+
"uri"
1144
+
],
1145
+
"isUnique": true
1146
+
},
1147
+
"tracks_cid_unique": {
1148
+
"name": "tracks_cid_unique",
1149
+
"columns": [
1150
+
"cid"
1151
+
],
1152
+
"isUnique": true
1153
+
}
1154
+
},
1155
+
"foreignKeys": {},
1156
+
"compositePrimaryKeys": {},
1157
+
"uniqueConstraints": {},
1158
+
"checkConstraints": {}
1159
+
},
1160
+
"user_albums": {
1161
+
"name": "user_albums",
1162
+
"columns": {
1163
+
"id": {
1164
+
"name": "id",
1165
+
"type": "text",
1166
+
"primaryKey": true,
1167
+
"notNull": true,
1168
+
"autoincrement": false
1169
+
},
1170
+
"user_id": {
1171
+
"name": "user_id",
1172
+
"type": "text",
1173
+
"primaryKey": false,
1174
+
"notNull": true,
1175
+
"autoincrement": false
1176
+
},
1177
+
"album_id": {
1178
+
"name": "album_id",
1179
+
"type": "text",
1180
+
"primaryKey": false,
1181
+
"notNull": true,
1182
+
"autoincrement": false
1183
+
},
1184
+
"created_at": {
1185
+
"name": "created_at",
1186
+
"type": "integer",
1187
+
"primaryKey": false,
1188
+
"notNull": true,
1189
+
"autoincrement": false,
1190
+
"default": "(unixepoch())"
1191
+
},
1192
+
"updated_at": {
1193
+
"name": "updated_at",
1194
+
"type": "integer",
1195
+
"primaryKey": false,
1196
+
"notNull": true,
1197
+
"autoincrement": false,
1198
+
"default": "(unixepoch())"
1199
+
},
1200
+
"scrobbles": {
1201
+
"name": "scrobbles",
1202
+
"type": "integer",
1203
+
"primaryKey": false,
1204
+
"notNull": false,
1205
+
"autoincrement": false
1206
+
},
1207
+
"uri": {
1208
+
"name": "uri",
1209
+
"type": "text",
1210
+
"primaryKey": false,
1211
+
"notNull": true,
1212
+
"autoincrement": false
1213
+
}
1214
+
},
1215
+
"indexes": {
1216
+
"user_albums_uri_unique": {
1217
+
"name": "user_albums_uri_unique",
1218
+
"columns": [
1219
+
"uri"
1220
+
],
1221
+
"isUnique": true
1222
+
},
1223
+
"user_albums_unique_index": {
1224
+
"name": "user_albums_unique_index",
1225
+
"columns": [
1226
+
"user_id",
1227
+
"album_id"
1228
+
],
1229
+
"isUnique": true
1230
+
}
1231
+
},
1232
+
"foreignKeys": {
1233
+
"user_albums_user_id_users_id_fk": {
1234
+
"name": "user_albums_user_id_users_id_fk",
1235
+
"tableFrom": "user_albums",
1236
+
"tableTo": "users",
1237
+
"columnsFrom": [
1238
+
"user_id"
1239
+
],
1240
+
"columnsTo": [
1241
+
"id"
1242
+
],
1243
+
"onDelete": "no action",
1244
+
"onUpdate": "no action"
1245
+
},
1246
+
"user_albums_album_id_albums_id_fk": {
1247
+
"name": "user_albums_album_id_albums_id_fk",
1248
+
"tableFrom": "user_albums",
1249
+
"tableTo": "albums",
1250
+
"columnsFrom": [
1251
+
"album_id"
1252
+
],
1253
+
"columnsTo": [
1254
+
"id"
1255
+
],
1256
+
"onDelete": "no action",
1257
+
"onUpdate": "no action"
1258
+
}
1259
+
},
1260
+
"compositePrimaryKeys": {},
1261
+
"uniqueConstraints": {},
1262
+
"checkConstraints": {}
1263
+
},
1264
+
"user_artists": {
1265
+
"name": "user_artists",
1266
+
"columns": {
1267
+
"id": {
1268
+
"name": "id",
1269
+
"type": "text",
1270
+
"primaryKey": true,
1271
+
"notNull": true,
1272
+
"autoincrement": false
1273
+
},
1274
+
"user_id": {
1275
+
"name": "user_id",
1276
+
"type": "text",
1277
+
"primaryKey": false,
1278
+
"notNull": true,
1279
+
"autoincrement": false
1280
+
},
1281
+
"artist_id": {
1282
+
"name": "artist_id",
1283
+
"type": "text",
1284
+
"primaryKey": false,
1285
+
"notNull": true,
1286
+
"autoincrement": false
1287
+
},
1288
+
"created_at": {
1289
+
"name": "created_at",
1290
+
"type": "integer",
1291
+
"primaryKey": false,
1292
+
"notNull": true,
1293
+
"autoincrement": false,
1294
+
"default": "(unixepoch())"
1295
+
},
1296
+
"updated_at": {
1297
+
"name": "updated_at",
1298
+
"type": "integer",
1299
+
"primaryKey": false,
1300
+
"notNull": true,
1301
+
"autoincrement": false,
1302
+
"default": "(unixepoch())"
1303
+
},
1304
+
"scrobbles": {
1305
+
"name": "scrobbles",
1306
+
"type": "integer",
1307
+
"primaryKey": false,
1308
+
"notNull": false,
1309
+
"autoincrement": false
1310
+
},
1311
+
"uri": {
1312
+
"name": "uri",
1313
+
"type": "text",
1314
+
"primaryKey": false,
1315
+
"notNull": true,
1316
+
"autoincrement": false
1317
+
}
1318
+
},
1319
+
"indexes": {
1320
+
"user_artists_uri_unique": {
1321
+
"name": "user_artists_uri_unique",
1322
+
"columns": [
1323
+
"uri"
1324
+
],
1325
+
"isUnique": true
1326
+
},
1327
+
"user_artists_unique_index": {
1328
+
"name": "user_artists_unique_index",
1329
+
"columns": [
1330
+
"user_id",
1331
+
"artist_id"
1332
+
],
1333
+
"isUnique": true
1334
+
}
1335
+
},
1336
+
"foreignKeys": {
1337
+
"user_artists_user_id_users_id_fk": {
1338
+
"name": "user_artists_user_id_users_id_fk",
1339
+
"tableFrom": "user_artists",
1340
+
"tableTo": "users",
1341
+
"columnsFrom": [
1342
+
"user_id"
1343
+
],
1344
+
"columnsTo": [
1345
+
"id"
1346
+
],
1347
+
"onDelete": "no action",
1348
+
"onUpdate": "no action"
1349
+
},
1350
+
"user_artists_artist_id_artists_id_fk": {
1351
+
"name": "user_artists_artist_id_artists_id_fk",
1352
+
"tableFrom": "user_artists",
1353
+
"tableTo": "artists",
1354
+
"columnsFrom": [
1355
+
"artist_id"
1356
+
],
1357
+
"columnsTo": [
1358
+
"id"
1359
+
],
1360
+
"onDelete": "no action",
1361
+
"onUpdate": "no action"
1362
+
}
1363
+
},
1364
+
"compositePrimaryKeys": {},
1365
+
"uniqueConstraints": {},
1366
+
"checkConstraints": {}
1367
+
},
1368
+
"user_tracks": {
1369
+
"name": "user_tracks",
1370
+
"columns": {
1371
+
"id": {
1372
+
"name": "id",
1373
+
"type": "text",
1374
+
"primaryKey": true,
1375
+
"notNull": true,
1376
+
"autoincrement": false
1377
+
},
1378
+
"user_id": {
1379
+
"name": "user_id",
1380
+
"type": "text",
1381
+
"primaryKey": false,
1382
+
"notNull": true,
1383
+
"autoincrement": false
1384
+
},
1385
+
"track_id": {
1386
+
"name": "track_id",
1387
+
"type": "text",
1388
+
"primaryKey": false,
1389
+
"notNull": true,
1390
+
"autoincrement": false
1391
+
},
1392
+
"created_at": {
1393
+
"name": "created_at",
1394
+
"type": "integer",
1395
+
"primaryKey": false,
1396
+
"notNull": true,
1397
+
"autoincrement": false,
1398
+
"default": "(unixepoch())"
1399
+
},
1400
+
"updated_at": {
1401
+
"name": "updated_at",
1402
+
"type": "integer",
1403
+
"primaryKey": false,
1404
+
"notNull": true,
1405
+
"autoincrement": false,
1406
+
"default": "(unixepoch())"
1407
+
},
1408
+
"scrobbles": {
1409
+
"name": "scrobbles",
1410
+
"type": "integer",
1411
+
"primaryKey": false,
1412
+
"notNull": false,
1413
+
"autoincrement": false
1414
+
},
1415
+
"uri": {
1416
+
"name": "uri",
1417
+
"type": "text",
1418
+
"primaryKey": false,
1419
+
"notNull": true,
1420
+
"autoincrement": false
1421
+
}
1422
+
},
1423
+
"indexes": {
1424
+
"user_tracks_uri_unique": {
1425
+
"name": "user_tracks_uri_unique",
1426
+
"columns": [
1427
+
"uri"
1428
+
],
1429
+
"isUnique": true
1430
+
},
1431
+
"user_tracks_unique_index": {
1432
+
"name": "user_tracks_unique_index",
1433
+
"columns": [
1434
+
"user_id",
1435
+
"track_id"
1436
+
],
1437
+
"isUnique": true
1438
+
}
1439
+
},
1440
+
"foreignKeys": {
1441
+
"user_tracks_user_id_users_id_fk": {
1442
+
"name": "user_tracks_user_id_users_id_fk",
1443
+
"tableFrom": "user_tracks",
1444
+
"tableTo": "users",
1445
+
"columnsFrom": [
1446
+
"user_id"
1447
+
],
1448
+
"columnsTo": [
1449
+
"id"
1450
+
],
1451
+
"onDelete": "no action",
1452
+
"onUpdate": "no action"
1453
+
},
1454
+
"user_tracks_track_id_tracks_id_fk": {
1455
+
"name": "user_tracks_track_id_tracks_id_fk",
1456
+
"tableFrom": "user_tracks",
1457
+
"tableTo": "tracks",
1458
+
"columnsFrom": [
1459
+
"track_id"
1460
+
],
1461
+
"columnsTo": [
1462
+
"id"
1463
+
],
1464
+
"onDelete": "no action",
1465
+
"onUpdate": "no action"
1466
+
}
1467
+
},
1468
+
"compositePrimaryKeys": {},
1469
+
"uniqueConstraints": {},
1470
+
"checkConstraints": {}
1471
+
},
1472
+
"users": {
1473
+
"name": "users",
1474
+
"columns": {
1475
+
"id": {
1476
+
"name": "id",
1477
+
"type": "text",
1478
+
"primaryKey": true,
1479
+
"notNull": true,
1480
+
"autoincrement": false
1481
+
},
1482
+
"did": {
1483
+
"name": "did",
1484
+
"type": "text",
1485
+
"primaryKey": false,
1486
+
"notNull": true,
1487
+
"autoincrement": false
1488
+
},
1489
+
"display_name": {
1490
+
"name": "display_name",
1491
+
"type": "text",
1492
+
"primaryKey": false,
1493
+
"notNull": false,
1494
+
"autoincrement": false
1495
+
},
1496
+
"handle": {
1497
+
"name": "handle",
1498
+
"type": "text",
1499
+
"primaryKey": false,
1500
+
"notNull": true,
1501
+
"autoincrement": false
1502
+
},
1503
+
"avatar": {
1504
+
"name": "avatar",
1505
+
"type": "text",
1506
+
"primaryKey": false,
1507
+
"notNull": true,
1508
+
"autoincrement": false
1509
+
},
1510
+
"created_at": {
1511
+
"name": "created_at",
1512
+
"type": "integer",
1513
+
"primaryKey": false,
1514
+
"notNull": true,
1515
+
"autoincrement": false,
1516
+
"default": "(unixepoch())"
1517
+
},
1518
+
"updated_at": {
1519
+
"name": "updated_at",
1520
+
"type": "integer",
1521
+
"primaryKey": false,
1522
+
"notNull": true,
1523
+
"autoincrement": false,
1524
+
"default": "(unixepoch())"
1525
+
}
1526
+
},
1527
+
"indexes": {
1528
+
"users_did_unique": {
1529
+
"name": "users_did_unique",
1530
+
"columns": [
1531
+
"did"
1532
+
],
1533
+
"isUnique": true
1534
+
},
1535
+
"users_handle_unique": {
1536
+
"name": "users_handle_unique",
1537
+
"columns": [
1538
+
"handle"
1539
+
],
1540
+
"isUnique": true
1541
+
}
1542
+
},
1543
+
"foreignKeys": {},
1544
+
"compositePrimaryKeys": {},
1545
+
"uniqueConstraints": {},
1546
+
"checkConstraints": {}
1547
+
}
1548
+
},
1549
+
"views": {},
1550
+
"enums": {},
1551
+
"_meta": {
1552
+
"schemas": {},
1553
+
"tables": {},
1554
+
"columns": {}
1555
+
},
1556
+
"internal": {
1557
+
"indexes": {}
1558
+
}
1559
+
}
+13
apps/cli/drizzle/meta/_journal.json
+13
apps/cli/drizzle/meta/_journal.json
+18
apps/cli/drizzle.config.ts
+18
apps/cli/drizzle.config.ts
···
1
+
import { defineConfig } from "drizzle-kit";
2
+
import envpaths from "env-paths";
3
+
import fs from "node:fs";
4
+
import chalk from "chalk";
5
+
6
+
fs.mkdirSync(envpaths("rocksky", { suffix: "" }).data, { recursive: true });
7
+
const url = `${envpaths("rocksky", { suffix: "" }).data}/rocksky.sqlite`;
8
+
9
+
console.log(`Database URL: ${chalk.greenBright(url)}`);
10
+
11
+
export default defineConfig({
12
+
dialect: "sqlite",
13
+
schema: "./src/schema",
14
+
out: "./drizzle",
15
+
dbCredentials: {
16
+
url,
17
+
},
18
+
});
+1
apps/cli/lexicons
+1
apps/cli/lexicons
···
1
+
../api/lexicons
+28
-2
apps/cli/package.json
+28
-2
apps/cli/package.json
···
8
8
"rocksky": "./dist/index.js"
9
9
},
10
10
"scripts": {
11
+
"lexgen": "lex gen-server ./src/lexicon ./lexicons/**/* ./lexicons/*",
11
12
"test": "echo \"Error: no test specified\" && exit 1",
12
13
"dev": "tsx ./src/index.ts",
13
-
"build": "pkgroll && chmod +x ./dist/index.js"
14
+
"build": "pkgroll && chmod +x ./dist/index.js && cp -r drizzle ./dist",
15
+
"db:generate": "drizzle-kit generate",
16
+
"db:migrate": "drizzle-kit migrate",
17
+
"db:studio": "drizzle-kit studio"
14
18
},
15
19
"keywords": [
16
20
"audioscrobbler",
···
22
26
"author": "Tsiry Sandratraina <tsiry.sndr@rocksky.app>",
23
27
"license": "Apache-2.0",
24
28
"dependencies": {
29
+
"@atproto/api": "^0.13.31",
30
+
"@atproto/common": "^0.4.6",
31
+
"@atproto/identity": "^0.4.5",
32
+
"@atproto/jwk-jose": "0.1.5",
33
+
"@atproto/lex-cli": "^0.5.6",
34
+
"@atproto/lexicon": "^0.4.5",
35
+
"@atproto/sync": "^0.1.11",
36
+
"@atproto/syntax": "^0.3.1",
37
+
"@logtape/logtape": "^1.3.6",
25
38
"@modelcontextprotocol/sdk": "^1.10.2",
39
+
"@paralleldrive/cuid2": "^3.0.6",
40
+
"@types/better-sqlite3": "^7.6.13",
26
41
"axios": "^1.8.4",
42
+
"better-sqlite3": "^12.4.1",
27
43
"chalk": "^5.4.1",
28
44
"commander": "^13.1.0",
29
45
"cors": "^2.8.5",
30
46
"dayjs": "^1.11.13",
47
+
"dotenv": "^16.4.7",
48
+
"drizzle-kit": "^0.31.1",
49
+
"drizzle-orm": "^0.45.1",
50
+
"effect": "^3.19.14",
51
+
"env-paths": "^3.0.0",
52
+
"envalid": "^8.0.0",
31
53
"express": "^5.1.0",
54
+
"hono": "^4.4.7",
55
+
"kysely": "^0.27.5",
56
+
"lodash": "^4.17.21",
32
57
"md5": "^2.3.0",
33
58
"open": "^10.1.0",
34
59
"table": "^6.9.0",
60
+
"unstorage": "^1.14.4",
35
61
"zod": "^3.24.3"
36
62
},
37
63
"devDependencies": {
···
46
72
"import": "./dist/index.js"
47
73
}
48
74
}
49
-
}
75
+
}
+32
-14
apps/cli/src/client.ts
+32
-14
apps/cli/src/client.ts
···
35
35
Authorization: this.token ? `Bearer ${this.token}` : undefined,
36
36
"Content-Type": "application/json",
37
37
},
38
-
}
38
+
},
39
39
);
40
40
41
41
if (!response.ok) {
42
42
throw new Error(
43
-
`Failed to fetch now playing data: ${response.statusText}`
43
+
`Failed to fetch now playing data: ${response.statusText}`,
44
44
);
45
45
}
46
46
···
56
56
Authorization: this.token ? `Bearer ${this.token}` : undefined,
57
57
"Content-Type": "application/json",
58
58
},
59
-
}
59
+
},
60
60
);
61
61
62
62
if (!response.ok) {
63
63
throw new Error(
64
-
`Failed to fetch now playing data: ${response.statusText}`
64
+
`Failed to fetch now playing data: ${response.statusText}`,
65
65
);
66
66
}
67
67
···
78
78
Authorization: this.token ? `Bearer ${this.token}` : undefined,
79
79
"Content-Type": "application/json",
80
80
},
81
-
}
81
+
},
82
82
);
83
83
if (!response.ok) {
84
84
throw new Error(
85
-
`Failed to fetch scrobbles data: ${response.statusText}`
85
+
`Failed to fetch scrobbles data: ${response.statusText}`,
86
86
);
87
87
}
88
88
return response.json();
···
96
96
Authorization: this.token ? `Bearer ${this.token}` : undefined,
97
97
"Content-Type": "application/json",
98
98
},
99
-
}
99
+
},
100
100
);
101
101
if (!response.ok) {
102
102
throw new Error(`Failed to fetch scrobbles data: ${response.statusText}`);
···
107
107
108
108
async search(query: string, { size }) {
109
109
const response = await fetch(
110
-
`${ROCKSKY_API_URL}/search?q=${query}&size=${size}`,
110
+
`${ROCKSKY_API_URL}/xrpc/app.rocksky.feed.search?query=${query}&size=${size}`,
111
111
{
112
112
method: "GET",
113
113
headers: {
114
114
Authorization: this.token ? `Bearer ${this.token}` : undefined,
115
115
"Content-Type": "application/json",
116
116
},
117
-
}
117
+
},
118
118
);
119
119
120
120
if (!response.ok) {
···
176
176
Authorization: this.token ? `Bearer ${this.token}` : undefined,
177
177
"Content-Type": "application/json",
178
178
},
179
-
}
179
+
},
180
180
);
181
181
if (!response.ok) {
182
182
throw new Error(`Failed to fetch artists data: ${response.statusText}`);
···
207
207
Authorization: this.token ? `Bearer ${this.token}` : undefined,
208
208
"Content-Type": "application/json",
209
209
},
210
-
}
210
+
},
211
211
);
212
212
if (!response.ok) {
213
213
throw new Error(`Failed to fetch albums data: ${response.statusText}`);
···
238
238
Authorization: this.token ? `Bearer ${this.token}` : undefined,
239
239
"Content-Type": "application/json",
240
240
},
241
-
}
241
+
},
242
242
);
243
243
if (!response.ok) {
244
244
throw new Error(`Failed to fetch tracks data: ${response.statusText}`);
···
252
252
await fs.promises.access(tokenPath);
253
253
} catch (err) {
254
254
console.error(
255
-
`You are not logged in. Please run the login command first.`
255
+
`You are not logged in. Please run the login command first.`,
256
256
);
257
257
return;
258
258
}
···
279
279
throw new Error(
280
280
`Failed to scrobble track: ${
281
281
response.statusText
282
-
} ${await response.text()}`
282
+
} ${await response.text()}`,
283
283
);
284
284
}
285
285
···
317
317
318
318
if (!response.ok) {
319
319
throw new Error(`Failed to create API key: ${response.statusText}`);
320
+
}
321
+
322
+
return response.json();
323
+
}
324
+
325
+
async matchSong(title: string, artist: string) {
326
+
const q = new URLSearchParams({
327
+
title,
328
+
artist,
329
+
});
330
+
const response = await fetch(
331
+
`${ROCKSKY_API_URL}/xrpc/app.rocksky.song.matchSong?${q.toString()}`,
332
+
);
333
+
334
+
if (!response.ok) {
335
+
throw new Error(
336
+
`Failed to match song: ${response.statusText} ${await response.text()}`,
337
+
);
320
338
}
321
339
322
340
return response.json();
+14
-61
apps/cli/src/cmd/scrobble.ts
+14
-61
apps/cli/src/cmd/scrobble.ts
···
1
-
import chalk from "chalk";
2
-
import { RockskyClient } from "client";
3
-
import fs from "fs/promises";
4
-
import md5 from "md5";
5
-
import os from "os";
6
-
import path from "path";
1
+
import { matchTrack } from "lib/matchTrack";
2
+
import { publishScrobble } from "scrobble";
7
3
8
-
export async function scrobble(track, artist, { timestamp }) {
9
-
const tokenPath = path.join(os.homedir(), ".rocksky", "token.json");
10
-
try {
11
-
await fs.access(tokenPath);
12
-
} catch (err) {
13
-
console.error(
14
-
`You are not logged in. Please run ${chalk.greenBright(
15
-
"`rocksky login <username>.bsky.social`"
16
-
)} first.`
17
-
);
18
-
return;
19
-
}
4
+
export async function scrobble(
5
+
track: string,
6
+
artist: string,
7
+
{ timestamp, dryRun },
8
+
) {
9
+
const match = await matchTrack(track, artist);
20
10
21
-
const tokenData = await fs.readFile(tokenPath, "utf-8");
22
-
const { token } = JSON.parse(tokenData);
23
-
if (!token) {
24
-
console.error(
25
-
`You are not logged in. Please run ${chalk.greenBright(
26
-
"`rocksky login <username>.bsky.social`"
27
-
)} first.`
28
-
);
29
-
return;
11
+
if (!match) {
12
+
process.exit(1);
30
13
}
31
14
32
-
const client = new RockskyClient(token);
33
-
const apikeys = await client.getApiKeys();
15
+
const success = await publishScrobble(match, timestamp, dryRun);
34
16
35
-
if (!apikeys || apikeys.length === 0 || !apikeys[0].enabled) {
36
-
console.error(
37
-
`You don't have any API keys. Please create one using ${chalk.greenBright(
38
-
"`rocksky create apikey`"
39
-
)} command.`
40
-
);
41
-
return;
17
+
if (!success) {
18
+
process.exit(1);
42
19
}
43
20
44
-
const signature = md5(
45
-
`api_key${
46
-
apikeys[0].apiKey
47
-
}artist[0]${artist}methodtrack.scrobblesk${token}timestamp[0]${
48
-
timestamp || Math.floor(Date.now() / 1000)
49
-
}track[0]${track}${apikeys[0].sharedSecret}`
50
-
);
51
-
52
-
const response = await client.scrobble(
53
-
apikeys[0].apiKey,
54
-
signature,
55
-
track,
56
-
artist,
57
-
timestamp
58
-
);
59
-
60
-
console.log(
61
-
`Scrobbled ${chalk.greenBright(track)} by ${chalk.greenBright(
62
-
artist
63
-
)} at ${chalk.greenBright(
64
-
new Date(
65
-
(timestamp || Math.floor(Date.now() / 1000)) * 1000
66
-
).toLocaleString()
67
-
)}`
68
-
);
21
+
process.exit(0);
69
22
}
+27
-25
apps/cli/src/cmd/search.ts
+27
-25
apps/cli/src/cmd/search.ts
···
1
1
import chalk from "chalk";
2
2
import { RockskyClient } from "client";
3
+
import _ from "lodash";
3
4
4
5
export async function search(
5
6
query: string,
6
-
{ limit = 20, albums = false, artists = false, tracks = false, users = false }
7
+
{
8
+
limit = 20,
9
+
albums = false,
10
+
artists = false,
11
+
tracks = false,
12
+
users = false,
13
+
},
7
14
) {
8
15
const client = new RockskyClient();
9
16
const results = await client.search(query, { size: limit });
10
-
if (results.records.length === 0) {
17
+
if (results.hits.length === 0) {
11
18
console.log(`No results found for ${chalk.magenta(query)}.`);
12
19
return;
13
20
}
14
21
15
-
// merge all results into one array with type and sort by xata_scrore
16
-
let mergedResults = results.records.map((record) => ({
22
+
let mergedResults = results.hits.map((record) => ({
17
23
...record,
18
-
type: record.table,
24
+
type: _.get(record, "_federation.indexUid"),
19
25
}));
20
26
21
27
if (albums) {
22
-
mergedResults = mergedResults.filter((record) => record.table === "albums");
28
+
mergedResults = mergedResults.filter((record) => record.type === "albums");
23
29
}
24
30
25
31
if (artists) {
26
-
mergedResults = mergedResults.filter(
27
-
(record) => record.table === "artists"
28
-
);
32
+
mergedResults = mergedResults.filter((record) => record.type === "artists");
29
33
}
30
34
31
35
if (tracks) {
32
-
mergedResults = mergedResults.filter(({ table }) => table === "tracks");
36
+
mergedResults = mergedResults.filter(({ type }) => type === "tracks");
33
37
}
34
38
35
39
if (users) {
36
-
mergedResults = mergedResults.filter(({ table }) => table === "users");
40
+
mergedResults = mergedResults.filter(({ type }) => type === "users");
37
41
}
38
42
39
-
mergedResults.sort((a, b) => b.xata_score - a.xata_score);
40
-
41
-
for (const { table, record } of mergedResults) {
42
-
if (table === "users") {
43
+
for (const { type, ...record } of mergedResults) {
44
+
if (type === "users") {
43
45
console.log(
44
46
`${chalk.bold.magenta(record.handle)} ${
45
-
record.display_name
46
-
} ${chalk.yellow(`https://rocksky.app/profile/${record.did}`)}`
47
+
record.displayName
48
+
} ${chalk.yellow(`https://rocksky.app/profile/${record.did}`)}`,
47
49
);
48
50
}
49
51
50
-
if (table === "albums") {
52
+
if (type === "albums") {
51
53
const link = record.uri
52
-
? `https://rocksky.app/${record.uri?.split("at://")[1]}`
54
+
? `https://rocksky.app/${record.uri?.split("at://")[1]?.replace("app.rocksky.", "")}`
53
55
: "";
54
56
console.log(
55
57
`${chalk.bold.magenta(record.title)} ${record.artist} ${chalk.yellow(
56
-
link
57
-
)}`
58
+
link,
59
+
)}`,
58
60
);
59
61
}
60
62
61
-
if (table === "tracks") {
63
+
if (type === "tracks") {
62
64
const link = record.uri
63
-
? `https://rocksky.app/${record.uri?.split("at://")[1]}`
65
+
? `https://rocksky.app/${record.uri?.split("at://")[1]?.replace("app.rocksky.", "")}`
64
66
: "";
65
67
console.log(
66
68
`${chalk.bold.magenta(record.title)} ${record.artist} ${chalk.yellow(
67
-
link
68
-
)}`
69
+
link,
70
+
)}`,
69
71
);
70
72
}
71
73
}
+812
apps/cli/src/cmd/sync.ts
+812
apps/cli/src/cmd/sync.ts
···
1
+
import { JetStreamClient, JetStreamEvent } from "jetstream";
2
+
import { logger } from "logger";
3
+
import { ctx } from "context";
4
+
import { Agent } from "@atproto/api";
5
+
import { env } from "lib/env";
6
+
import { createAgent } from "lib/agent";
7
+
import chalk from "chalk";
8
+
import * as Artist from "lexicon/types/app/rocksky/artist";
9
+
import * as Album from "lexicon/types/app/rocksky/album";
10
+
import * as Song from "lexicon/types/app/rocksky/song";
11
+
import * as Scrobble from "lexicon/types/app/rocksky/scrobble";
12
+
import { SelectUser } from "schema/users";
13
+
import schema from "schema";
14
+
import { createId } from "@paralleldrive/cuid2";
15
+
import _ from "lodash";
16
+
import { and, eq, or } from "drizzle-orm";
17
+
import { indexBy } from "ramda";
18
+
import fs from "node:fs";
19
+
import os from "node:os";
20
+
import path from "node:path";
21
+
import { getDidAndHandle } from "lib/getDidAndHandle";
22
+
import { cleanUpJetstreamLockOnExit } from "lib/cleanUpJetstreamLock";
23
+
import { cleanUpSyncLockOnExit } from "lib/cleanUpSyncLock";
24
+
25
+
const PAGE_SIZE = 100;
26
+
27
+
type Artists = { value: Artist.Record; uri: string; cid: string }[];
28
+
type Albums = { value: Album.Record; uri: string; cid: string }[];
29
+
type Songs = { value: Song.Record; uri: string; cid: string }[];
30
+
type Scrobbles = { value: Scrobble.Record; uri: string; cid: string }[];
31
+
32
+
export async function sync() {
33
+
const [did, handle] = await getDidAndHandle();
34
+
const agent: Agent = await createAgent(did, handle);
35
+
36
+
const user = await createUser(agent, did, handle);
37
+
await subscribeToJetstream(user);
38
+
39
+
logger.info` DID: ${did}`;
40
+
logger.info` Handle: ${handle}`;
41
+
42
+
const [artists, albums, songs, scrobbles] = await Promise.all([
43
+
getRockskyUserArtists(agent),
44
+
getRockskyUserAlbums(agent),
45
+
getRockskyUserSongs(agent),
46
+
getRockskyUserScrobbles(agent),
47
+
]);
48
+
49
+
logger.info` Artists: ${artists.length}`;
50
+
logger.info` Albums: ${albums.length}`;
51
+
logger.info` Songs: ${songs.length}`;
52
+
logger.info` Scrobbles: ${scrobbles.length}`;
53
+
54
+
const lockFilePath = path.join(os.tmpdir(), `rocksky-${did}.lock`);
55
+
56
+
if (await fs.promises.stat(lockFilePath).catch(() => false)) {
57
+
logger.error`Lock file already exists, if you want to force sync, delete the lock file ${lockFilePath}`;
58
+
process.exit(1);
59
+
}
60
+
61
+
await fs.promises.writeFile(lockFilePath, "");
62
+
cleanUpSyncLockOnExit(user.did);
63
+
64
+
await createArtists(artists, user);
65
+
await createAlbums(albums, user);
66
+
await createSongs(songs, user);
67
+
await createScrobbles(scrobbles, user);
68
+
69
+
await fs.promises.unlink(lockFilePath);
70
+
}
71
+
72
+
const getEndpoint = () => {
73
+
const endpoint = env.JETSTREAM_SERVER;
74
+
75
+
if (endpoint?.endsWith("/subscribe")) {
76
+
return endpoint;
77
+
}
78
+
79
+
return `${endpoint}/subscribe`;
80
+
};
81
+
82
+
export const createUser = async (
83
+
agent: Agent,
84
+
did: string,
85
+
handle: string,
86
+
): Promise<SelectUser> => {
87
+
const { data: profileRecord } = await agent.com.atproto.repo.getRecord({
88
+
repo: agent.assertDid,
89
+
collection: "app.bsky.actor.profile",
90
+
rkey: "self",
91
+
});
92
+
93
+
const displayName = _.get(profileRecord, "value.displayName") as
94
+
| string
95
+
| undefined;
96
+
const avatar = `https://cdn.bsky.app/img/avatar/plain/${did}/${_.get(profileRecord, "value.avatar.ref", "").toString()}@jpeg`;
97
+
98
+
const [user] = await ctx.db
99
+
.insert(schema.users)
100
+
.values({
101
+
id: createId(),
102
+
did,
103
+
handle,
104
+
displayName,
105
+
avatar,
106
+
})
107
+
.onConflictDoUpdate({
108
+
target: schema.users.did,
109
+
set: {
110
+
handle,
111
+
displayName,
112
+
avatar,
113
+
updatedAt: new Date(),
114
+
},
115
+
})
116
+
.returning()
117
+
.execute();
118
+
119
+
return user;
120
+
};
121
+
122
+
const createArtists = async (artists: Artists, user: SelectUser) => {
123
+
if (artists.length === 0) return;
124
+
125
+
const tags = artists.map((artist) => artist.value.tags || []);
126
+
127
+
// Batch genre inserts to avoid stack overflow
128
+
const uniqueTags = tags
129
+
.flat()
130
+
.filter((tag) => tag)
131
+
.map((tag) => ({
132
+
id: createId(),
133
+
name: tag,
134
+
}));
135
+
136
+
const BATCH_SIZE = 500;
137
+
for (let i = 0; i < uniqueTags.length; i += BATCH_SIZE) {
138
+
const batch = uniqueTags.slice(i, i + BATCH_SIZE);
139
+
await ctx.db
140
+
.insert(schema.genres)
141
+
.values(batch)
142
+
.onConflictDoNothing({
143
+
target: schema.genres.name,
144
+
})
145
+
.execute();
146
+
}
147
+
148
+
const genres = await ctx.db.select().from(schema.genres).execute();
149
+
150
+
const genreMap = indexBy((genre) => genre.name, genres);
151
+
152
+
// Process artists in batches
153
+
let totalArtistsImported = 0;
154
+
155
+
for (let i = 0; i < artists.length; i += BATCH_SIZE) {
156
+
const batch = artists.slice(i, i + BATCH_SIZE);
157
+
158
+
ctx.db.transaction((tx) => {
159
+
const newArtists = tx
160
+
.insert(schema.artists)
161
+
.values(
162
+
batch.map((artist) => ({
163
+
id: createId(),
164
+
name: artist.value.name,
165
+
cid: artist.cid,
166
+
uri: artist.uri,
167
+
biography: artist.value.bio,
168
+
born: artist.value.born ? new Date(artist.value.born) : null,
169
+
bornIn: artist.value.bornIn,
170
+
died: artist.value.died ? new Date(artist.value.died) : null,
171
+
picture: artist.value.pictureUrl,
172
+
genres: artist.value.tags?.join(", "),
173
+
})),
174
+
)
175
+
.onConflictDoNothing({
176
+
target: schema.artists.cid,
177
+
})
178
+
.returning()
179
+
.all();
180
+
181
+
if (newArtists.length === 0) return;
182
+
183
+
const artistGenres = newArtists
184
+
.map(
185
+
(artist) =>
186
+
artist.genres
187
+
?.split(", ")
188
+
.filter((tag) => !!tag && !!genreMap[tag])
189
+
.map((tag) => ({
190
+
id: createId(),
191
+
artistId: artist.id,
192
+
genreId: genreMap[tag].id,
193
+
})) || [],
194
+
)
195
+
.flat();
196
+
197
+
if (artistGenres.length > 0) {
198
+
tx.insert(schema.artistGenres)
199
+
.values(artistGenres)
200
+
.onConflictDoNothing({
201
+
target: [schema.artistGenres.artistId, schema.artistGenres.genreId],
202
+
})
203
+
.returning()
204
+
.run();
205
+
}
206
+
207
+
tx.insert(schema.userArtists)
208
+
.values(
209
+
newArtists.map((artist) => ({
210
+
id: createId(),
211
+
userId: user.id,
212
+
artistId: artist.id,
213
+
uri: artist.uri,
214
+
})),
215
+
)
216
+
.run();
217
+
218
+
totalArtistsImported += newArtists.length;
219
+
});
220
+
}
221
+
222
+
logger.info`👤 ${totalArtistsImported} Artists imported`;
223
+
};
224
+
225
+
const createAlbums = async (albums: Albums, user: SelectUser) => {
226
+
if (albums.length === 0) return;
227
+
228
+
const artists = await Promise.all(
229
+
albums.map(async (album) =>
230
+
ctx.db
231
+
.select()
232
+
.from(schema.artists)
233
+
.where(eq(schema.artists.name, album.value.artist))
234
+
.execute()
235
+
.then(([artist]) => artist),
236
+
),
237
+
);
238
+
239
+
const validAlbumData = albums
240
+
.map((album, index) => ({ album, artist: artists[index] }))
241
+
.filter(({ artist }) => artist);
242
+
243
+
// Process albums in batches
244
+
const BATCH_SIZE = 500;
245
+
let totalAlbumsImported = 0;
246
+
247
+
for (let i = 0; i < validAlbumData.length; i += BATCH_SIZE) {
248
+
const batch = validAlbumData.slice(i, i + BATCH_SIZE);
249
+
250
+
ctx.db.transaction((tx) => {
251
+
const newAlbums = tx
252
+
.insert(schema.albums)
253
+
.values(
254
+
batch.map(({ album, artist }) => ({
255
+
id: createId(),
256
+
cid: album.cid,
257
+
uri: album.uri,
258
+
title: album.value.title,
259
+
artist: album.value.artist,
260
+
releaseDate: album.value.releaseDate,
261
+
year: album.value.year,
262
+
albumArt: album.value.albumArtUrl,
263
+
artistUri: artist.uri,
264
+
appleMusicLink: album.value.appleMusicLink,
265
+
spotifyLink: album.value.spotifyLink,
266
+
tidalLink: album.value.tidalLink,
267
+
youtubeLink: album.value.youtubeLink,
268
+
})),
269
+
)
270
+
.onConflictDoNothing({
271
+
target: schema.albums.cid,
272
+
})
273
+
.returning()
274
+
.all();
275
+
276
+
if (newAlbums.length === 0) return;
277
+
278
+
tx.insert(schema.userAlbums)
279
+
.values(
280
+
newAlbums.map((album) => ({
281
+
id: createId(),
282
+
userId: user.id,
283
+
albumId: album.id,
284
+
uri: album.uri,
285
+
})),
286
+
)
287
+
.run();
288
+
289
+
totalAlbumsImported += newAlbums.length;
290
+
});
291
+
}
292
+
293
+
logger.info`💿 ${totalAlbumsImported} Albums imported`;
294
+
};
295
+
296
+
const createSongs = async (songs: Songs, user: SelectUser) => {
297
+
if (songs.length === 0) return;
298
+
299
+
const albums = await Promise.all(
300
+
songs.map((song) =>
301
+
ctx.db
302
+
.select()
303
+
.from(schema.albums)
304
+
.where(
305
+
and(
306
+
eq(schema.albums.artist, song.value.albumArtist),
307
+
eq(schema.albums.title, song.value.album),
308
+
),
309
+
)
310
+
.execute()
311
+
.then((result) => result[0]),
312
+
),
313
+
);
314
+
315
+
const artists = await Promise.all(
316
+
songs.map((song) =>
317
+
ctx.db
318
+
.select()
319
+
.from(schema.artists)
320
+
.where(eq(schema.artists.name, song.value.albumArtist))
321
+
.execute()
322
+
.then((result) => result[0]),
323
+
),
324
+
);
325
+
326
+
const validSongData = songs
327
+
.map((song, index) => ({
328
+
song,
329
+
artist: artists[index],
330
+
album: albums[index],
331
+
}))
332
+
.filter(({ artist, album }) => artist && album);
333
+
334
+
// Process in batches to avoid stack overflow with large datasets
335
+
const BATCH_SIZE = 500;
336
+
let totalTracksImported = 0;
337
+
338
+
for (let i = 0; i < validSongData.length; i += BATCH_SIZE) {
339
+
const batch = validSongData.slice(i, i + BATCH_SIZE);
340
+
const batchNumber = Math.floor(i / BATCH_SIZE) + 1;
341
+
const totalBatches = Math.ceil(validSongData.length / BATCH_SIZE);
342
+
343
+
logger.info`▶️ Processing tracks batch ${batchNumber}/${totalBatches} (${Math.min(i + BATCH_SIZE, validSongData.length)}/${validSongData.length})`;
344
+
345
+
ctx.db.transaction((tx) => {
346
+
const tracks = tx
347
+
.insert(schema.tracks)
348
+
.values(
349
+
batch.map(({ song, artist, album }) => ({
350
+
id: createId(),
351
+
cid: song.cid,
352
+
uri: song.uri,
353
+
title: song.value.title,
354
+
artist: song.value.artist,
355
+
albumArtist: song.value.albumArtist,
356
+
albumArt: song.value.albumArtUrl,
357
+
album: song.value.album,
358
+
trackNumber: song.value.trackNumber,
359
+
duration: song.value.duration,
360
+
mbId: song.value.mbid,
361
+
youtubeLink: song.value.youtubeLink,
362
+
spotifyLink: song.value.spotifyLink,
363
+
appleMusicLink: song.value.appleMusicLink,
364
+
tidalLink: song.value.tidalLink,
365
+
discNumber: song.value.discNumber,
366
+
lyrics: song.value.lyrics,
367
+
composer: song.value.composer,
368
+
genre: song.value.genre,
369
+
label: song.value.label,
370
+
copyrightMessage: song.value.copyrightMessage,
371
+
albumUri: album.uri,
372
+
artistUri: artist.uri,
373
+
})),
374
+
)
375
+
.onConflictDoNothing()
376
+
.returning()
377
+
.all();
378
+
379
+
if (tracks.length === 0) return;
380
+
381
+
tx.insert(schema.albumTracks)
382
+
.values(
383
+
tracks.map((track, index) => ({
384
+
id: createId(),
385
+
albumId: batch[index].album.id,
386
+
trackId: track.id,
387
+
})),
388
+
)
389
+
.onConflictDoNothing({
390
+
target: [schema.albumTracks.albumId, schema.albumTracks.trackId],
391
+
})
392
+
.run();
393
+
394
+
tx.insert(schema.userTracks)
395
+
.values(
396
+
tracks.map((track) => ({
397
+
id: createId(),
398
+
userId: user.id,
399
+
trackId: track.id,
400
+
uri: track.uri,
401
+
})),
402
+
)
403
+
.onConflictDoNothing({
404
+
target: [schema.userTracks.userId, schema.userTracks.trackId],
405
+
})
406
+
.run();
407
+
408
+
totalTracksImported += tracks.length;
409
+
});
410
+
}
411
+
412
+
logger.info`▶️ ${totalTracksImported} Tracks imported`;
413
+
};
414
+
415
+
const createScrobbles = async (scrobbles: Scrobbles, user: SelectUser) => {
416
+
if (!scrobbles.length) return;
417
+
418
+
logger.info`Loading Scrobble Tracks ...`;
419
+
420
+
const tracks = await Promise.all(
421
+
scrobbles.map((scrobble) =>
422
+
ctx.db
423
+
.select()
424
+
.from(schema.tracks)
425
+
.where(
426
+
and(
427
+
eq(schema.tracks.title, scrobble.value.title),
428
+
eq(schema.tracks.artist, scrobble.value.artist),
429
+
eq(schema.tracks.album, scrobble.value.album),
430
+
eq(schema.tracks.albumArtist, scrobble.value.albumArtist),
431
+
),
432
+
)
433
+
.execute()
434
+
.then(([track]) => track),
435
+
),
436
+
);
437
+
438
+
logger.info`Loading Scrobble Albums ...`;
439
+
440
+
const albums = await Promise.all(
441
+
scrobbles.map((scrobble) =>
442
+
ctx.db
443
+
.select()
444
+
.from(schema.albums)
445
+
.where(
446
+
and(
447
+
eq(schema.albums.title, scrobble.value.album),
448
+
eq(schema.albums.artist, scrobble.value.albumArtist),
449
+
),
450
+
)
451
+
.execute()
452
+
.then(([album]) => album),
453
+
),
454
+
);
455
+
456
+
logger.info`Loading Scrobble Artists ...`;
457
+
458
+
const artists = await Promise.all(
459
+
scrobbles.map((scrobble) =>
460
+
ctx.db
461
+
.select()
462
+
.from(schema.artists)
463
+
.where(
464
+
or(
465
+
and(eq(schema.artists.name, scrobble.value.artist)),
466
+
and(eq(schema.artists.name, scrobble.value.albumArtist)),
467
+
),
468
+
)
469
+
.execute()
470
+
.then(([artist]) => artist),
471
+
),
472
+
);
473
+
474
+
const validScrobbleData = scrobbles
475
+
.map((scrobble, index) => ({
476
+
scrobble,
477
+
track: tracks[index],
478
+
album: albums[index],
479
+
artist: artists[index],
480
+
}))
481
+
.filter(({ track, album, artist }) => track && album && artist);
482
+
483
+
// Process in batches to avoid stack overflow with large datasets
484
+
const BATCH_SIZE = 500;
485
+
let totalScrobblesImported = 0;
486
+
487
+
for (let i = 0; i < validScrobbleData.length; i += BATCH_SIZE) {
488
+
const batch = validScrobbleData.slice(i, i + BATCH_SIZE);
489
+
const batchNumber = Math.floor(i / BATCH_SIZE) + 1;
490
+
const totalBatches = Math.ceil(validScrobbleData.length / BATCH_SIZE);
491
+
492
+
logger.info`🕒 Processing scrobbles batch ${batchNumber}/${totalBatches} (${Math.min(i + BATCH_SIZE, validScrobbleData.length)}/${validScrobbleData.length})`;
493
+
494
+
const result = await ctx.db
495
+
.insert(schema.scrobbles)
496
+
.values(
497
+
batch.map(({ scrobble, track, album, artist }) => ({
498
+
id: createId(),
499
+
userId: user.id,
500
+
trackId: track.id,
501
+
albumId: album.id,
502
+
artistId: artist.id,
503
+
uri: scrobble.uri,
504
+
cid: scrobble.cid,
505
+
timestamp: new Date(scrobble.value.createdAt),
506
+
})),
507
+
)
508
+
.onConflictDoNothing({
509
+
target: schema.scrobbles.cid,
510
+
})
511
+
.returning()
512
+
.execute();
513
+
514
+
totalScrobblesImported += result.length;
515
+
}
516
+
517
+
logger.info`🕒 ${totalScrobblesImported} scrobbles imported`;
518
+
};
519
+
520
+
export const subscribeToJetstream = (user: SelectUser): Promise<void> => {
521
+
const lockFile = path.join(os.tmpdir(), `rocksky-jetstream-${user.did}.lock`);
522
+
if (fs.existsSync(lockFile)) {
523
+
logger.warn`JetStream subscription already in progress for user ${user.did}`;
524
+
logger.warn`Skipping subscription`;
525
+
logger.warn`Lock file exists at ${lockFile}`;
526
+
return Promise.resolve();
527
+
}
528
+
529
+
fs.writeFileSync(lockFile, "");
530
+
531
+
const client = new JetStreamClient({
532
+
wantedCollections: [
533
+
"app.rocksky.scrobble",
534
+
"app.rocksky.artist",
535
+
"app.rocksky.album",
536
+
"app.rocksky.song",
537
+
],
538
+
endpoint: getEndpoint(),
539
+
wantedDids: [user.did],
540
+
541
+
// Reconnection settings
542
+
maxReconnectAttempts: 10,
543
+
reconnectDelay: 1000,
544
+
maxReconnectDelay: 30000,
545
+
backoffMultiplier: 1.5,
546
+
547
+
// Enable debug logging
548
+
debug: true,
549
+
});
550
+
551
+
return new Promise((resolve, reject) => {
552
+
client.on("open", () => {
553
+
logger.info`✅ Connected to JetStream!`;
554
+
cleanUpJetstreamLockOnExit(user.did);
555
+
resolve();
556
+
});
557
+
558
+
client.on("message", async (data) => {
559
+
const event = data as JetStreamEvent;
560
+
561
+
if (event.kind === "commit" && event.commit) {
562
+
const { operation, collection, record, rkey, cid } = event.commit;
563
+
const uri = `at://${event.did}/${collection}/${rkey}`;
564
+
565
+
logger.info`\n📡 New event:`;
566
+
logger.info` Operation: ${operation}`;
567
+
logger.info` Collection: ${collection}`;
568
+
logger.info` DID: ${event.did}`;
569
+
logger.info` Uri: ${uri}`;
570
+
571
+
if (operation === "create" && record) {
572
+
console.log(JSON.stringify(record, null, 2));
573
+
await onNewCollection(record, cid, uri, user);
574
+
}
575
+
576
+
logger.info` Cursor: ${event.time_us}`;
577
+
}
578
+
});
579
+
580
+
client.on("error", (error) => {
581
+
logger.error`❌ Error: ${error}`;
582
+
cleanUpJetstreamLockOnExit(user.did);
583
+
reject(error);
584
+
});
585
+
586
+
client.on("reconnect", (data) => {
587
+
const { attempt } = data as { attempt: number };
588
+
logger.info`🔄 Reconnecting... (attempt ${attempt})`;
589
+
});
590
+
591
+
client.connect();
592
+
});
593
+
};
594
+
595
+
const onNewCollection = async (
596
+
record: any,
597
+
cid: string,
598
+
uri: string,
599
+
user: SelectUser,
600
+
) => {
601
+
switch (record.$type) {
602
+
case "app.rocksky.song":
603
+
await onNewSong(record, cid, uri, user);
604
+
break;
605
+
case "app.rocksky.album":
606
+
await onNewAlbum(record, cid, uri, user);
607
+
break;
608
+
case "app.rocksky.artist":
609
+
await onNewArtist(record, cid, uri, user);
610
+
break;
611
+
case "app.rocksky.scrobble":
612
+
await onNewScrobble(record, cid, uri, user);
613
+
break;
614
+
default:
615
+
logger.warn`Unknown collection type: ${record.$type}`;
616
+
}
617
+
};
618
+
619
+
const onNewSong = async (
620
+
record: Song.Record,
621
+
cid: string,
622
+
uri: string,
623
+
user: SelectUser,
624
+
) => {
625
+
const { title, artist, album } = record;
626
+
logger.info` New song: ${title} by ${artist} from ${album}`;
627
+
};
628
+
629
+
const onNewAlbum = async (
630
+
record: Album.Record,
631
+
cid: string,
632
+
uri: string,
633
+
user: SelectUser,
634
+
) => {
635
+
const { title, artist } = record;
636
+
logger.info` New album: ${title} by ${artist}`;
637
+
await createAlbums(
638
+
[
639
+
{
640
+
cid,
641
+
uri,
642
+
value: record,
643
+
},
644
+
],
645
+
user,
646
+
);
647
+
};
648
+
649
+
const onNewArtist = async (
650
+
record: Artist.Record,
651
+
cid: string,
652
+
uri: string,
653
+
user: SelectUser,
654
+
) => {
655
+
const { name } = record;
656
+
logger.info` New artist: ${name}`;
657
+
await createArtists(
658
+
[
659
+
{
660
+
cid,
661
+
uri,
662
+
value: record,
663
+
},
664
+
],
665
+
user,
666
+
);
667
+
};
668
+
669
+
const onNewScrobble = async (
670
+
record: Scrobble.Record,
671
+
cid: string,
672
+
uri: string,
673
+
user: SelectUser,
674
+
) => {
675
+
const { title, createdAt } = record;
676
+
logger.info` New scrobble: ${title} at ${createdAt}`;
677
+
await createScrobbles(
678
+
[
679
+
{
680
+
cid,
681
+
uri,
682
+
value: record,
683
+
},
684
+
],
685
+
user,
686
+
);
687
+
};
688
+
689
+
const getRockskyUserSongs = async (agent: Agent): Promise<Songs> => {
690
+
let results: {
691
+
value: Song.Record;
692
+
uri: string;
693
+
cid: string;
694
+
}[] = [];
695
+
let cursor: string | undefined;
696
+
do {
697
+
const res = await agent.com.atproto.repo.listRecords({
698
+
repo: agent.assertDid,
699
+
collection: "app.rocksky.song",
700
+
limit: PAGE_SIZE,
701
+
cursor,
702
+
});
703
+
const records = res.data.records as Array<{
704
+
uri: string;
705
+
cid: string;
706
+
value: Song.Record;
707
+
}>;
708
+
results = results.concat(records);
709
+
cursor = res.data.cursor;
710
+
logger.info(
711
+
`${chalk.cyanBright(agent.assertDid)} ${chalk.greenBright(results.length)} songs`,
712
+
);
713
+
} while (cursor);
714
+
715
+
return results;
716
+
};
717
+
718
+
const getRockskyUserAlbums = async (agent: Agent): Promise<Albums> => {
719
+
let results: {
720
+
value: Album.Record;
721
+
uri: string;
722
+
cid: string;
723
+
}[] = [];
724
+
let cursor: string | undefined;
725
+
do {
726
+
const res = await agent.com.atproto.repo.listRecords({
727
+
repo: agent.assertDid,
728
+
collection: "app.rocksky.album",
729
+
limit: PAGE_SIZE,
730
+
cursor,
731
+
});
732
+
733
+
const records = res.data.records as Array<{
734
+
uri: string;
735
+
cid: string;
736
+
value: Album.Record;
737
+
}>;
738
+
739
+
results = results.concat(records);
740
+
741
+
cursor = res.data.cursor;
742
+
logger.info(
743
+
`${chalk.cyanBright(agent.assertDid)} ${chalk.greenBright(results.length)} albums`,
744
+
);
745
+
} while (cursor);
746
+
747
+
return results;
748
+
};
749
+
750
+
const getRockskyUserArtists = async (agent: Agent): Promise<Artists> => {
751
+
let results: {
752
+
value: Artist.Record;
753
+
uri: string;
754
+
cid: string;
755
+
}[] = [];
756
+
let cursor: string | undefined;
757
+
do {
758
+
const res = await agent.com.atproto.repo.listRecords({
759
+
repo: agent.assertDid,
760
+
collection: "app.rocksky.artist",
761
+
limit: PAGE_SIZE,
762
+
cursor,
763
+
});
764
+
765
+
const records = res.data.records as Array<{
766
+
uri: string;
767
+
cid: string;
768
+
value: Artist.Record;
769
+
}>;
770
+
771
+
results = results.concat(records);
772
+
773
+
cursor = res.data.cursor;
774
+
logger.info(
775
+
`${chalk.cyanBright(agent.assertDid)} ${chalk.greenBright(results.length)} artists`,
776
+
);
777
+
} while (cursor);
778
+
779
+
return results;
780
+
};
781
+
782
+
const getRockskyUserScrobbles = async (agent: Agent): Promise<Scrobbles> => {
783
+
let results: {
784
+
value: Scrobble.Record;
785
+
uri: string;
786
+
cid: string;
787
+
}[] = [];
788
+
let cursor: string | undefined;
789
+
do {
790
+
const res = await agent.com.atproto.repo.listRecords({
791
+
repo: agent.assertDid,
792
+
collection: "app.rocksky.scrobble",
793
+
limit: PAGE_SIZE,
794
+
cursor,
795
+
});
796
+
797
+
const records = res.data.records as Array<{
798
+
uri: string;
799
+
cid: string;
800
+
value: Scrobble.Record;
801
+
}>;
802
+
803
+
results = results.concat(records);
804
+
805
+
cursor = res.data.cursor;
806
+
logger.info(
807
+
`${chalk.cyanBright(agent.assertDid)} ${chalk.greenBright(results.length)} scrobbles`,
808
+
);
809
+
} while (cursor);
810
+
811
+
return results;
812
+
};
+36
-7
apps/cli/src/cmd/whoami.ts
+36
-7
apps/cli/src/cmd/whoami.ts
···
1
1
import chalk from "chalk";
2
2
import { RockskyClient } from "client";
3
3
import fs from "fs/promises";
4
+
import { createAgent } from "lib/agent";
5
+
import { env } from "lib/env";
6
+
import { getDidAndHandle } from "lib/getDidAndHandle";
4
7
import os from "os";
5
8
import path from "path";
9
+
import { createUser } from "./sync";
10
+
import { ctx } from "context";
11
+
import schema from "schema";
12
+
import { eq } from "drizzle-orm";
6
13
7
14
export async function whoami() {
15
+
if (env.ROCKSKY_IDENTIFIER && env.ROCKSKY_PASSWORD) {
16
+
const [did, handle] = await getDidAndHandle();
17
+
const agent = await createAgent(did, handle);
18
+
let user = await ctx.db
19
+
.select()
20
+
.from(schema.users)
21
+
.where(eq(schema.users.did, did))
22
+
.execute()
23
+
.then((rows) => rows[0]);
24
+
25
+
if (!user) {
26
+
user = await createUser(agent, did, handle);
27
+
}
28
+
29
+
console.log(`You are logged in as ${user.handle} (${user.displayName}).`);
30
+
console.log(
31
+
`View your profile at: ${chalk.magenta(
32
+
`https://rocksky.app/profile/${user.handle}`,
33
+
)}`,
34
+
);
35
+
return;
36
+
}
8
37
const tokenPath = path.join(os.homedir(), ".rocksky", "token.json");
9
38
try {
10
39
await fs.access(tokenPath);
11
40
} catch (err) {
12
41
console.error(
13
42
`You are not logged in. Please run ${chalk.greenBright(
14
-
"`rocksky login <username>.bsky.social`"
15
-
)} first.`
43
+
"`rocksky login <username>.bsky.social`",
44
+
)} first.`,
16
45
);
17
46
return;
18
47
}
···
22
51
if (!token) {
23
52
console.error(
24
53
`You are not logged in. Please run ${chalk.greenBright(
25
-
"`rocksky login <username>.bsky.social`"
26
-
)} first.`
54
+
"`rocksky login <username>.bsky.social`",
55
+
)} first.`,
27
56
);
28
57
return;
29
58
}
···
34
63
console.log(`You are logged in as ${user.handle} (${user.displayName}).`);
35
64
console.log(
36
65
`View your profile at: ${chalk.magenta(
37
-
`https://rocksky.app/profile/${user.handle}`
38
-
)}`
66
+
`https://rocksky.app/profile/${user.handle}`,
67
+
)}`,
39
68
);
40
69
} catch (err) {
41
70
console.error(
42
-
`Failed to fetch user data. Please check your token and try again.`
71
+
`Failed to fetch user data. Please check your token and try again.`,
43
72
);
44
73
}
45
74
}
+24
apps/cli/src/context.ts
+24
apps/cli/src/context.ts
···
1
+
import drizzle from "./drizzle";
2
+
import sqliteKv from "sqliteKv";
3
+
import { createBidirectionalResolver, createIdResolver } from "lib/idResolver";
4
+
import { createStorage } from "unstorage";
5
+
import envpaths from "env-paths";
6
+
import fs from "node:fs";
7
+
8
+
fs.mkdirSync(envpaths("rocksky", { suffix: "" }).data, { recursive: true });
9
+
const kvPath = `${envpaths("rocksky", { suffix: "" }).data}/rocksky-kv.sqlite`;
10
+
11
+
const kv = createStorage({
12
+
driver: sqliteKv({ location: kvPath, table: "kv" }),
13
+
});
14
+
15
+
const baseIdResolver = createIdResolver(kv);
16
+
17
+
export const ctx = {
18
+
db: drizzle.db,
19
+
resolver: createBidirectionalResolver(baseIdResolver),
20
+
baseIdResolver,
21
+
kv,
22
+
};
23
+
24
+
export type Context = typeof ctx;
+53
apps/cli/src/drizzle.ts
+53
apps/cli/src/drizzle.ts
···
1
+
import { drizzle } from "drizzle-orm/better-sqlite3";
2
+
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
3
+
import Database from "better-sqlite3";
4
+
import envpaths from "env-paths";
5
+
import fs from "node:fs";
6
+
import path from "node:path";
7
+
import { fileURLToPath } from "node:url";
8
+
9
+
const __filename = fileURLToPath(import.meta.url);
10
+
const __dirname = path.dirname(__filename);
11
+
12
+
fs.mkdirSync(envpaths("rocksky", { suffix: "" }).data, { recursive: true });
13
+
const url = `${envpaths("rocksky", { suffix: "" }).data}/rocksky.sqlite`;
14
+
15
+
const sqlite = new Database(url);
16
+
const db = drizzle(sqlite);
17
+
18
+
let initialized = false;
19
+
20
+
/**
21
+
* Initialize the database and run migrations
22
+
* This should be called before any database operations
23
+
*/
24
+
export async function initializeDatabase() {
25
+
if (initialized) {
26
+
return;
27
+
}
28
+
29
+
try {
30
+
// In production (built), migrations are in ../drizzle
31
+
// In development (src), migrations are in ../../drizzle
32
+
let migrationsFolder = path.join(__dirname, "../drizzle");
33
+
34
+
if (!fs.existsSync(migrationsFolder)) {
35
+
// Try development path
36
+
migrationsFolder = path.join(__dirname, "../../drizzle");
37
+
}
38
+
39
+
if (fs.existsSync(migrationsFolder)) {
40
+
migrate(db, { migrationsFolder });
41
+
initialized = true;
42
+
} else {
43
+
// No migrations folder found - this might be the first run
44
+
// or migrations haven't been generated yet
45
+
initialized = true;
46
+
}
47
+
} catch (error) {
48
+
console.error("Failed to run migrations:", error);
49
+
throw error;
50
+
}
51
+
}
52
+
53
+
export default { db, initializeDatabase };
+43
-10
apps/cli/src/index.ts
+43
-10
apps/cli/src/index.ts
···
13
13
import { tracks } from "cmd/tracks";
14
14
import { whoami } from "cmd/whoami";
15
15
import { Command } from "commander";
16
-
import version from "../package.json" assert { type: "json" };
16
+
import { version } from "../package.json" assert { type: "json" };
17
17
import { login } from "./cmd/login";
18
+
import { sync } from "cmd/sync";
19
+
import { initializeDatabase } from "./drizzle";
20
+
21
+
await initializeDatabase();
18
22
19
23
const program = new Command();
20
24
21
25
program
22
26
.name("rocksky")
23
27
.description(
24
-
`Command-line interface for Rocksky (${chalk.underline(
25
-
"https://rocksky.app"
26
-
)}) – scrobble tracks, view stats, and manage your listening history.`
28
+
`
29
+
___ __ __ _______ ____
30
+
/ _ \\___ ____/ /__ ___ / /____ __ / ___/ / / _/
31
+
/ , _/ _ \\/ __/ '_/(_-</ '_/ // / / /__/ /___/ /
32
+
/_/|_|\\___/\\__/_/\\_\\/___/_/\\_\\\\_, / \\___/____/___/
33
+
/___/
34
+
Command-line interface for Rocksky ${chalk.magentaBright(
35
+
"https://rocksky.app",
36
+
)} – scrobble tracks, view stats, and manage your listening history.`,
27
37
)
28
-
.version(version.version);
38
+
.version(version);
39
+
40
+
program.configureHelp({
41
+
styleTitle: (str) => chalk.bold.cyan(str),
42
+
styleCommandText: (str) => chalk.yellow(str),
43
+
styleDescriptionText: (str) => chalk.white(str),
44
+
styleOptionText: (str) => chalk.green(str),
45
+
styleArgumentText: (str) => chalk.magenta(str),
46
+
styleSubcommandText: (str) => chalk.blue(str),
47
+
});
48
+
49
+
program.addHelpText(
50
+
"after",
51
+
`
52
+
${chalk.bold("\nLearn more about Rocksky:")} https://docs.rocksky.app
53
+
${chalk.bold("Join our Discord community:")} ${chalk.blueBright("https://discord.gg/EVcBy2fVa3")}
54
+
`,
55
+
);
29
56
30
57
program
31
58
.command("login")
32
-
.argument("<handle>", "your BlueSky handle (e.g., <username>.bsky.social)")
33
-
.description("login with your BlueSky account and get a session token.")
59
+
.argument("<handle>", "your AT Proto handle (e.g., <username>.bsky.social)")
60
+
.description("login with your AT Proto account and get a session token.")
34
61
.action(login);
35
62
36
63
program
···
42
69
.command("nowplaying")
43
70
.argument(
44
71
"[did]",
45
-
"the DID or handle of the user to get the now playing track for."
72
+
"the DID or handle of the user to get the now playing track for.",
46
73
)
47
74
.description("get the currently playing track.")
48
75
.action(nowplaying);
···
63
90
.option("-l, --limit <number>", "number of results to limit")
64
91
.argument(
65
92
"<query>",
66
-
"the search query, e.g., artist, album, title or account"
93
+
"the search query, e.g., artist, album, title or account",
67
94
)
68
95
.description("search for tracks, albums, or accounts.")
69
96
.action(search);
···
101
128
.argument("<track>", "the title of the track")
102
129
.argument("<artist>", "the artist of the track")
103
130
.option("-t, --timestamp <timestamp>", "the timestamp of the scrobble")
131
+
.option("-d, --dry-run", "simulate the scrobble without actually sending it")
104
132
.description("scrobble a track to your profile.")
105
133
.action(scrobble);
106
134
···
115
143
116
144
program
117
145
.command("mcp")
118
-
.description("Starts an MCP server to use with Claude or other LLMs.")
146
+
.description("starts an MCP server to use with Claude or other LLMs.")
119
147
.action(mcp);
148
+
149
+
program
150
+
.command("sync")
151
+
.description("sync your local Rocksky data from AT Protocol.")
152
+
.action(sync);
120
153
121
154
program.parse(process.argv);
+285
apps/cli/src/jetstream.ts
+285
apps/cli/src/jetstream.ts
···
1
+
export interface JetStreamEvent {
2
+
did: string;
3
+
time_us: number;
4
+
kind: "commit" | "identity" | "account";
5
+
commit?: {
6
+
rev: string;
7
+
operation: "create" | "update" | "delete";
8
+
collection: string;
9
+
rkey: string;
10
+
record?: Record<string, unknown>;
11
+
cid?: string;
12
+
};
13
+
identity?: {
14
+
did: string;
15
+
handle?: string;
16
+
seq?: number;
17
+
time?: string;
18
+
};
19
+
account?: {
20
+
active: boolean;
21
+
did: string;
22
+
seq: number;
23
+
time: string;
24
+
};
25
+
}
26
+
27
+
export interface JetStreamClientOptions {
28
+
endpoint?: string;
29
+
wantedCollections?: string[];
30
+
wantedDids?: string[];
31
+
maxReconnectAttempts?: number;
32
+
reconnectDelay?: number;
33
+
maxReconnectDelay?: number;
34
+
backoffMultiplier?: number;
35
+
debug?: boolean;
36
+
}
37
+
38
+
export type JetStreamEventType =
39
+
| "open"
40
+
| "message"
41
+
| "error"
42
+
| "close"
43
+
| "reconnect";
44
+
45
+
export class JetStreamClient {
46
+
private ws: WebSocket | null = null;
47
+
private options: Required<JetStreamClientOptions>;
48
+
private reconnectAttempts = 0;
49
+
private reconnectTimer: number | null = null;
50
+
private isManualClose = false;
51
+
private eventHandlers: Map<
52
+
JetStreamEventType,
53
+
Set<(data?: unknown) => void>
54
+
> = new Map();
55
+
private cursor: number | null = null;
56
+
57
+
constructor(options: JetStreamClientOptions = {}) {
58
+
this.options = {
59
+
endpoint:
60
+
options.endpoint || "wss://jetstream1.us-east.bsky.network/subscribe",
61
+
wantedCollections: options.wantedCollections || [],
62
+
wantedDids: options.wantedDids || [],
63
+
maxReconnectAttempts: options.maxReconnectAttempts ?? Infinity,
64
+
reconnectDelay: options.reconnectDelay ?? 1000,
65
+
maxReconnectDelay: options.maxReconnectDelay ?? 30000,
66
+
backoffMultiplier: options.backoffMultiplier ?? 1.5,
67
+
debug: options.debug ?? false,
68
+
};
69
+
70
+
// Initialize event handler sets
71
+
["open", "message", "error", "close", "reconnect"].forEach((event) => {
72
+
this.eventHandlers.set(event as JetStreamEventType, new Set());
73
+
});
74
+
}
75
+
76
+
/**
77
+
* Register an event handler
78
+
*/
79
+
on(event: JetStreamEventType, handler: (data?: unknown) => void): this {
80
+
this.eventHandlers.get(event)?.add(handler);
81
+
return this;
82
+
}
83
+
84
+
/**
85
+
* Remove an event handler
86
+
*/
87
+
off(event: JetStreamEventType, handler: (data?: unknown) => void): this {
88
+
this.eventHandlers.get(event)?.delete(handler);
89
+
return this;
90
+
}
91
+
92
+
/**
93
+
* Emit an event to all registered handlers
94
+
*/
95
+
private emit(event: JetStreamEventType, data?: unknown): void {
96
+
this.eventHandlers.get(event)?.forEach((handler) => {
97
+
try {
98
+
handler(data);
99
+
} catch (error) {
100
+
this.log("error", `Handler error for ${event}:`, error);
101
+
}
102
+
});
103
+
}
104
+
105
+
/**
106
+
* Build the WebSocket URL with query parameters
107
+
*/
108
+
private buildUrl(): string {
109
+
const url = new URL(this.options.endpoint);
110
+
111
+
if (this.options.wantedCollections.length > 0) {
112
+
this.options.wantedCollections.forEach((collection) => {
113
+
url.searchParams.append("wantedCollections", collection);
114
+
});
115
+
}
116
+
117
+
if (this.options.wantedDids.length > 0) {
118
+
this.options.wantedDids.forEach((did) => {
119
+
url.searchParams.append("wantedDids", did);
120
+
});
121
+
}
122
+
123
+
if (this.cursor !== null) {
124
+
url.searchParams.set("cursor", this.cursor.toString());
125
+
}
126
+
127
+
return url.toString();
128
+
}
129
+
130
+
/**
131
+
* Connect to the JetStream WebSocket
132
+
*/
133
+
connect(): void {
134
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
135
+
this.log("warn", "Already connected");
136
+
return;
137
+
}
138
+
139
+
this.isManualClose = false;
140
+
const url = this.buildUrl();
141
+
this.log("info", `Connecting to ${url}`);
142
+
143
+
try {
144
+
this.ws = new WebSocket(url);
145
+
146
+
this.ws.onopen = () => {
147
+
this.log("info", "Connected successfully");
148
+
this.reconnectAttempts = 0;
149
+
this.emit("open");
150
+
};
151
+
152
+
this.ws.onmessage = (event) => {
153
+
try {
154
+
const data = JSON.parse(event.data) as JetStreamEvent;
155
+
156
+
// Update cursor for resumption
157
+
if (data.time_us) {
158
+
this.cursor = data.time_us;
159
+
}
160
+
161
+
this.emit("message", data);
162
+
} catch (error) {
163
+
this.log("error", "Failed to parse message:", error);
164
+
this.emit("error", { type: "parse_error", error });
165
+
}
166
+
};
167
+
168
+
this.ws.onerror = (event) => {
169
+
this.log("error", "WebSocket error:", event);
170
+
this.emit("error", event);
171
+
};
172
+
173
+
this.ws.onclose = (event) => {
174
+
this.log("info", `Connection closed: ${event.code} ${event.reason}`);
175
+
this.emit("close", event);
176
+
177
+
if (!this.isManualClose) {
178
+
this.scheduleReconnect();
179
+
}
180
+
};
181
+
} catch (error) {
182
+
this.log("error", "Failed to create WebSocket:", error);
183
+
this.emit("error", { type: "connection_error", error });
184
+
this.scheduleReconnect();
185
+
}
186
+
}
187
+
188
+
/**
189
+
* Schedule a reconnection attempt with exponential backoff
190
+
*/
191
+
private scheduleReconnect(): void {
192
+
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
193
+
this.log("error", "Max reconnection attempts reached");
194
+
return;
195
+
}
196
+
197
+
const delay = Math.min(
198
+
this.options.reconnectDelay *
199
+
Math.pow(this.options.backoffMultiplier, this.reconnectAttempts),
200
+
this.options.maxReconnectDelay,
201
+
);
202
+
203
+
this.reconnectAttempts++;
204
+
this.log(
205
+
"info",
206
+
`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`,
207
+
);
208
+
209
+
this.reconnectTimer = setTimeout(() => {
210
+
this.emit("reconnect", { attempt: this.reconnectAttempts });
211
+
this.connect();
212
+
}, delay) as unknown as number;
213
+
}
214
+
215
+
/**
216
+
* Manually disconnect from the WebSocket
217
+
*/
218
+
disconnect(): void {
219
+
this.isManualClose = true;
220
+
221
+
if (this.reconnectTimer !== null) {
222
+
clearTimeout(this.reconnectTimer);
223
+
this.reconnectTimer = null;
224
+
}
225
+
226
+
if (this.ws) {
227
+
this.ws.close();
228
+
this.ws = null;
229
+
}
230
+
231
+
this.log("info", "Disconnected");
232
+
}
233
+
234
+
/**
235
+
* Update subscription filters (requires reconnection)
236
+
*/
237
+
updateFilters(options: {
238
+
wantedCollections?: string[];
239
+
wantedDids?: string[];
240
+
}): void {
241
+
if (options.wantedCollections) {
242
+
this.options.wantedCollections = options.wantedCollections;
243
+
}
244
+
if (options.wantedDids) {
245
+
this.options.wantedDids = options.wantedDids;
246
+
}
247
+
248
+
// Reconnect with new filters
249
+
if (this.ws) {
250
+
this.disconnect();
251
+
this.connect();
252
+
}
253
+
}
254
+
255
+
/**
256
+
* Get current connection state
257
+
*/
258
+
get readyState(): number {
259
+
return this.ws?.readyState ?? WebSocket.CLOSED;
260
+
}
261
+
262
+
/**
263
+
* Check if currently connected
264
+
*/
265
+
get isConnected(): boolean {
266
+
return this.ws?.readyState === WebSocket.OPEN;
267
+
}
268
+
269
+
/**
270
+
* Get current cursor position
271
+
*/
272
+
get currentCursor(): number | null {
273
+
return this.cursor;
274
+
}
275
+
276
+
/**
277
+
* Logging utility
278
+
*/
279
+
private log(level: "info" | "warn" | "error", ...args: unknown[]): void {
280
+
if (this.options.debug || level === "error") {
281
+
const prefix = `[JetStream ${level.toUpperCase()}]`;
282
+
console[level](prefix, ...args);
283
+
}
284
+
}
285
+
}
+1321
apps/cli/src/lexicon/index.ts
+1321
apps/cli/src/lexicon/index.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import {
5
+
createServer as createXrpcServer,
6
+
Server as XrpcServer,
7
+
Options as XrpcOptions,
8
+
AuthVerifier,
9
+
StreamAuthVerifier,
10
+
} from '@atproto/xrpc-server'
11
+
import { schemas } from './lexicons'
12
+
import * as AppRockskyActorGetActorAlbums from './types/app/rocksky/actor/getActorAlbums'
13
+
import * as AppRockskyActorGetActorArtists from './types/app/rocksky/actor/getActorArtists'
14
+
import * as AppRockskyActorGetActorCompatibility from './types/app/rocksky/actor/getActorCompatibility'
15
+
import * as AppRockskyActorGetActorLovedSongs from './types/app/rocksky/actor/getActorLovedSongs'
16
+
import * as AppRockskyActorGetActorNeighbours from './types/app/rocksky/actor/getActorNeighbours'
17
+
import * as AppRockskyActorGetActorPlaylists from './types/app/rocksky/actor/getActorPlaylists'
18
+
import * as AppRockskyActorGetActorScrobbles from './types/app/rocksky/actor/getActorScrobbles'
19
+
import * as AppRockskyActorGetActorSongs from './types/app/rocksky/actor/getActorSongs'
20
+
import * as AppRockskyActorGetProfile from './types/app/rocksky/actor/getProfile'
21
+
import * as AppRockskyAlbumGetAlbum from './types/app/rocksky/album/getAlbum'
22
+
import * as AppRockskyAlbumGetAlbums from './types/app/rocksky/album/getAlbums'
23
+
import * as AppRockskyAlbumGetAlbumTracks from './types/app/rocksky/album/getAlbumTracks'
24
+
import * as AppRockskyApikeyCreateApikey from './types/app/rocksky/apikey/createApikey'
25
+
import * as AppRockskyApikeyGetApikeys from './types/app/rocksky/apikey/getApikeys'
26
+
import * as AppRockskyApikeyRemoveApikey from './types/app/rocksky/apikey/removeApikey'
27
+
import * as AppRockskyApikeyUpdateApikey from './types/app/rocksky/apikey/updateApikey'
28
+
import * as AppRockskyArtistGetArtist from './types/app/rocksky/artist/getArtist'
29
+
import * as AppRockskyArtistGetArtistAlbums from './types/app/rocksky/artist/getArtistAlbums'
30
+
import * as AppRockskyArtistGetArtistListeners from './types/app/rocksky/artist/getArtistListeners'
31
+
import * as AppRockskyArtistGetArtists from './types/app/rocksky/artist/getArtists'
32
+
import * as AppRockskyArtistGetArtistTracks from './types/app/rocksky/artist/getArtistTracks'
33
+
import * as AppRockskyChartsGetScrobblesChart from './types/app/rocksky/charts/getScrobblesChart'
34
+
import * as AppRockskyDropboxDownloadFile from './types/app/rocksky/dropbox/downloadFile'
35
+
import * as AppRockskyDropboxGetFiles from './types/app/rocksky/dropbox/getFiles'
36
+
import * as AppRockskyDropboxGetMetadata from './types/app/rocksky/dropbox/getMetadata'
37
+
import * as AppRockskyDropboxGetTemporaryLink from './types/app/rocksky/dropbox/getTemporaryLink'
38
+
import * as AppRockskyFeedDescribeFeedGenerator from './types/app/rocksky/feed/describeFeedGenerator'
39
+
import * as AppRockskyFeedGetFeed from './types/app/rocksky/feed/getFeed'
40
+
import * as AppRockskyFeedGetFeedGenerator from './types/app/rocksky/feed/getFeedGenerator'
41
+
import * as AppRockskyFeedGetFeedGenerators from './types/app/rocksky/feed/getFeedGenerators'
42
+
import * as AppRockskyFeedGetFeedSkeleton from './types/app/rocksky/feed/getFeedSkeleton'
43
+
import * as AppRockskyFeedGetNowPlayings from './types/app/rocksky/feed/getNowPlayings'
44
+
import * as AppRockskyFeedSearch from './types/app/rocksky/feed/search'
45
+
import * as AppRockskyGoogledriveDownloadFile from './types/app/rocksky/googledrive/downloadFile'
46
+
import * as AppRockskyGoogledriveGetFile from './types/app/rocksky/googledrive/getFile'
47
+
import * as AppRockskyGoogledriveGetFiles from './types/app/rocksky/googledrive/getFiles'
48
+
import * as AppRockskyGraphFollowAccount from './types/app/rocksky/graph/followAccount'
49
+
import * as AppRockskyGraphGetFollowers from './types/app/rocksky/graph/getFollowers'
50
+
import * as AppRockskyGraphGetFollows from './types/app/rocksky/graph/getFollows'
51
+
import * as AppRockskyGraphGetKnownFollowers from './types/app/rocksky/graph/getKnownFollowers'
52
+
import * as AppRockskyGraphUnfollowAccount from './types/app/rocksky/graph/unfollowAccount'
53
+
import * as AppRockskyLikeDislikeShout from './types/app/rocksky/like/dislikeShout'
54
+
import * as AppRockskyLikeDislikeSong from './types/app/rocksky/like/dislikeSong'
55
+
import * as AppRockskyLikeLikeShout from './types/app/rocksky/like/likeShout'
56
+
import * as AppRockskyLikeLikeSong from './types/app/rocksky/like/likeSong'
57
+
import * as AppRockskyPlayerAddDirectoryToQueue from './types/app/rocksky/player/addDirectoryToQueue'
58
+
import * as AppRockskyPlayerAddItemsToQueue from './types/app/rocksky/player/addItemsToQueue'
59
+
import * as AppRockskyPlayerGetCurrentlyPlaying from './types/app/rocksky/player/getCurrentlyPlaying'
60
+
import * as AppRockskyPlayerGetPlaybackQueue from './types/app/rocksky/player/getPlaybackQueue'
61
+
import * as AppRockskyPlayerNext from './types/app/rocksky/player/next'
62
+
import * as AppRockskyPlayerPause from './types/app/rocksky/player/pause'
63
+
import * as AppRockskyPlayerPlay from './types/app/rocksky/player/play'
64
+
import * as AppRockskyPlayerPlayDirectory from './types/app/rocksky/player/playDirectory'
65
+
import * as AppRockskyPlayerPlayFile from './types/app/rocksky/player/playFile'
66
+
import * as AppRockskyPlayerPrevious from './types/app/rocksky/player/previous'
67
+
import * as AppRockskyPlayerSeek from './types/app/rocksky/player/seek'
68
+
import * as AppRockskyPlaylistCreatePlaylist from './types/app/rocksky/playlist/createPlaylist'
69
+
import * as AppRockskyPlaylistGetPlaylist from './types/app/rocksky/playlist/getPlaylist'
70
+
import * as AppRockskyPlaylistGetPlaylists from './types/app/rocksky/playlist/getPlaylists'
71
+
import * as AppRockskyPlaylistInsertDirectory from './types/app/rocksky/playlist/insertDirectory'
72
+
import * as AppRockskyPlaylistInsertFiles from './types/app/rocksky/playlist/insertFiles'
73
+
import * as AppRockskyPlaylistRemovePlaylist from './types/app/rocksky/playlist/removePlaylist'
74
+
import * as AppRockskyPlaylistRemoveTrack from './types/app/rocksky/playlist/removeTrack'
75
+
import * as AppRockskyPlaylistStartPlaylist from './types/app/rocksky/playlist/startPlaylist'
76
+
import * as AppRockskyScrobbleCreateScrobble from './types/app/rocksky/scrobble/createScrobble'
77
+
import * as AppRockskyScrobbleGetScrobble from './types/app/rocksky/scrobble/getScrobble'
78
+
import * as AppRockskyScrobbleGetScrobbles from './types/app/rocksky/scrobble/getScrobbles'
79
+
import * as AppRockskyShoutCreateShout from './types/app/rocksky/shout/createShout'
80
+
import * as AppRockskyShoutGetAlbumShouts from './types/app/rocksky/shout/getAlbumShouts'
81
+
import * as AppRockskyShoutGetArtistShouts from './types/app/rocksky/shout/getArtistShouts'
82
+
import * as AppRockskyShoutGetProfileShouts from './types/app/rocksky/shout/getProfileShouts'
83
+
import * as AppRockskyShoutGetShoutReplies from './types/app/rocksky/shout/getShoutReplies'
84
+
import * as AppRockskyShoutGetTrackShouts from './types/app/rocksky/shout/getTrackShouts'
85
+
import * as AppRockskyShoutRemoveShout from './types/app/rocksky/shout/removeShout'
86
+
import * as AppRockskyShoutReplyShout from './types/app/rocksky/shout/replyShout'
87
+
import * as AppRockskyShoutReportShout from './types/app/rocksky/shout/reportShout'
88
+
import * as AppRockskySongCreateSong from './types/app/rocksky/song/createSong'
89
+
import * as AppRockskySongGetSong from './types/app/rocksky/song/getSong'
90
+
import * as AppRockskySongGetSongs from './types/app/rocksky/song/getSongs'
91
+
import * as AppRockskySpotifyGetCurrentlyPlaying from './types/app/rocksky/spotify/getCurrentlyPlaying'
92
+
import * as AppRockskySpotifyNext from './types/app/rocksky/spotify/next'
93
+
import * as AppRockskySpotifyPause from './types/app/rocksky/spotify/pause'
94
+
import * as AppRockskySpotifyPlay from './types/app/rocksky/spotify/play'
95
+
import * as AppRockskySpotifyPrevious from './types/app/rocksky/spotify/previous'
96
+
import * as AppRockskySpotifySeek from './types/app/rocksky/spotify/seek'
97
+
import * as AppRockskyStatsGetStats from './types/app/rocksky/stats/getStats'
98
+
99
+
export function createServer(options?: XrpcOptions): Server {
100
+
return new Server(options)
101
+
}
102
+
103
+
export class Server {
104
+
xrpc: XrpcServer
105
+
app: AppNS
106
+
com: ComNS
107
+
108
+
constructor(options?: XrpcOptions) {
109
+
this.xrpc = createXrpcServer(schemas, options)
110
+
this.app = new AppNS(this)
111
+
this.com = new ComNS(this)
112
+
}
113
+
}
114
+
115
+
export class AppNS {
116
+
_server: Server
117
+
rocksky: AppRockskyNS
118
+
bsky: AppBskyNS
119
+
120
+
constructor(server: Server) {
121
+
this._server = server
122
+
this.rocksky = new AppRockskyNS(server)
123
+
this.bsky = new AppBskyNS(server)
124
+
}
125
+
}
126
+
127
+
export class AppRockskyNS {
128
+
_server: Server
129
+
actor: AppRockskyActorNS
130
+
album: AppRockskyAlbumNS
131
+
apikey: AppRockskyApikeyNS
132
+
artist: AppRockskyArtistNS
133
+
charts: AppRockskyChartsNS
134
+
dropbox: AppRockskyDropboxNS
135
+
feed: AppRockskyFeedNS
136
+
googledrive: AppRockskyGoogledriveNS
137
+
graph: AppRockskyGraphNS
138
+
like: AppRockskyLikeNS
139
+
player: AppRockskyPlayerNS
140
+
playlist: AppRockskyPlaylistNS
141
+
scrobble: AppRockskyScrobbleNS
142
+
shout: AppRockskyShoutNS
143
+
song: AppRockskySongNS
144
+
spotify: AppRockskySpotifyNS
145
+
stats: AppRockskyStatsNS
146
+
147
+
constructor(server: Server) {
148
+
this._server = server
149
+
this.actor = new AppRockskyActorNS(server)
150
+
this.album = new AppRockskyAlbumNS(server)
151
+
this.apikey = new AppRockskyApikeyNS(server)
152
+
this.artist = new AppRockskyArtistNS(server)
153
+
this.charts = new AppRockskyChartsNS(server)
154
+
this.dropbox = new AppRockskyDropboxNS(server)
155
+
this.feed = new AppRockskyFeedNS(server)
156
+
this.googledrive = new AppRockskyGoogledriveNS(server)
157
+
this.graph = new AppRockskyGraphNS(server)
158
+
this.like = new AppRockskyLikeNS(server)
159
+
this.player = new AppRockskyPlayerNS(server)
160
+
this.playlist = new AppRockskyPlaylistNS(server)
161
+
this.scrobble = new AppRockskyScrobbleNS(server)
162
+
this.shout = new AppRockskyShoutNS(server)
163
+
this.song = new AppRockskySongNS(server)
164
+
this.spotify = new AppRockskySpotifyNS(server)
165
+
this.stats = new AppRockskyStatsNS(server)
166
+
}
167
+
}
168
+
169
+
export class AppRockskyActorNS {
170
+
_server: Server
171
+
172
+
constructor(server: Server) {
173
+
this._server = server
174
+
}
175
+
176
+
getActorAlbums<AV extends AuthVerifier>(
177
+
cfg: ConfigOf<
178
+
AV,
179
+
AppRockskyActorGetActorAlbums.Handler<ExtractAuth<AV>>,
180
+
AppRockskyActorGetActorAlbums.HandlerReqCtx<ExtractAuth<AV>>
181
+
>,
182
+
) {
183
+
const nsid = 'app.rocksky.actor.getActorAlbums' // @ts-ignore
184
+
return this._server.xrpc.method(nsid, cfg)
185
+
}
186
+
187
+
getActorArtists<AV extends AuthVerifier>(
188
+
cfg: ConfigOf<
189
+
AV,
190
+
AppRockskyActorGetActorArtists.Handler<ExtractAuth<AV>>,
191
+
AppRockskyActorGetActorArtists.HandlerReqCtx<ExtractAuth<AV>>
192
+
>,
193
+
) {
194
+
const nsid = 'app.rocksky.actor.getActorArtists' // @ts-ignore
195
+
return this._server.xrpc.method(nsid, cfg)
196
+
}
197
+
198
+
getActorCompatibility<AV extends AuthVerifier>(
199
+
cfg: ConfigOf<
200
+
AV,
201
+
AppRockskyActorGetActorCompatibility.Handler<ExtractAuth<AV>>,
202
+
AppRockskyActorGetActorCompatibility.HandlerReqCtx<ExtractAuth<AV>>
203
+
>,
204
+
) {
205
+
const nsid = 'app.rocksky.actor.getActorCompatibility' // @ts-ignore
206
+
return this._server.xrpc.method(nsid, cfg)
207
+
}
208
+
209
+
getActorLovedSongs<AV extends AuthVerifier>(
210
+
cfg: ConfigOf<
211
+
AV,
212
+
AppRockskyActorGetActorLovedSongs.Handler<ExtractAuth<AV>>,
213
+
AppRockskyActorGetActorLovedSongs.HandlerReqCtx<ExtractAuth<AV>>
214
+
>,
215
+
) {
216
+
const nsid = 'app.rocksky.actor.getActorLovedSongs' // @ts-ignore
217
+
return this._server.xrpc.method(nsid, cfg)
218
+
}
219
+
220
+
getActorNeighbours<AV extends AuthVerifier>(
221
+
cfg: ConfigOf<
222
+
AV,
223
+
AppRockskyActorGetActorNeighbours.Handler<ExtractAuth<AV>>,
224
+
AppRockskyActorGetActorNeighbours.HandlerReqCtx<ExtractAuth<AV>>
225
+
>,
226
+
) {
227
+
const nsid = 'app.rocksky.actor.getActorNeighbours' // @ts-ignore
228
+
return this._server.xrpc.method(nsid, cfg)
229
+
}
230
+
231
+
getActorPlaylists<AV extends AuthVerifier>(
232
+
cfg: ConfigOf<
233
+
AV,
234
+
AppRockskyActorGetActorPlaylists.Handler<ExtractAuth<AV>>,
235
+
AppRockskyActorGetActorPlaylists.HandlerReqCtx<ExtractAuth<AV>>
236
+
>,
237
+
) {
238
+
const nsid = 'app.rocksky.actor.getActorPlaylists' // @ts-ignore
239
+
return this._server.xrpc.method(nsid, cfg)
240
+
}
241
+
242
+
getActorScrobbles<AV extends AuthVerifier>(
243
+
cfg: ConfigOf<
244
+
AV,
245
+
AppRockskyActorGetActorScrobbles.Handler<ExtractAuth<AV>>,
246
+
AppRockskyActorGetActorScrobbles.HandlerReqCtx<ExtractAuth<AV>>
247
+
>,
248
+
) {
249
+
const nsid = 'app.rocksky.actor.getActorScrobbles' // @ts-ignore
250
+
return this._server.xrpc.method(nsid, cfg)
251
+
}
252
+
253
+
getActorSongs<AV extends AuthVerifier>(
254
+
cfg: ConfigOf<
255
+
AV,
256
+
AppRockskyActorGetActorSongs.Handler<ExtractAuth<AV>>,
257
+
AppRockskyActorGetActorSongs.HandlerReqCtx<ExtractAuth<AV>>
258
+
>,
259
+
) {
260
+
const nsid = 'app.rocksky.actor.getActorSongs' // @ts-ignore
261
+
return this._server.xrpc.method(nsid, cfg)
262
+
}
263
+
264
+
getProfile<AV extends AuthVerifier>(
265
+
cfg: ConfigOf<
266
+
AV,
267
+
AppRockskyActorGetProfile.Handler<ExtractAuth<AV>>,
268
+
AppRockskyActorGetProfile.HandlerReqCtx<ExtractAuth<AV>>
269
+
>,
270
+
) {
271
+
const nsid = 'app.rocksky.actor.getProfile' // @ts-ignore
272
+
return this._server.xrpc.method(nsid, cfg)
273
+
}
274
+
}
275
+
276
+
export class AppRockskyAlbumNS {
277
+
_server: Server
278
+
279
+
constructor(server: Server) {
280
+
this._server = server
281
+
}
282
+
283
+
getAlbum<AV extends AuthVerifier>(
284
+
cfg: ConfigOf<
285
+
AV,
286
+
AppRockskyAlbumGetAlbum.Handler<ExtractAuth<AV>>,
287
+
AppRockskyAlbumGetAlbum.HandlerReqCtx<ExtractAuth<AV>>
288
+
>,
289
+
) {
290
+
const nsid = 'app.rocksky.album.getAlbum' // @ts-ignore
291
+
return this._server.xrpc.method(nsid, cfg)
292
+
}
293
+
294
+
getAlbums<AV extends AuthVerifier>(
295
+
cfg: ConfigOf<
296
+
AV,
297
+
AppRockskyAlbumGetAlbums.Handler<ExtractAuth<AV>>,
298
+
AppRockskyAlbumGetAlbums.HandlerReqCtx<ExtractAuth<AV>>
299
+
>,
300
+
) {
301
+
const nsid = 'app.rocksky.album.getAlbums' // @ts-ignore
302
+
return this._server.xrpc.method(nsid, cfg)
303
+
}
304
+
305
+
getAlbumTracks<AV extends AuthVerifier>(
306
+
cfg: ConfigOf<
307
+
AV,
308
+
AppRockskyAlbumGetAlbumTracks.Handler<ExtractAuth<AV>>,
309
+
AppRockskyAlbumGetAlbumTracks.HandlerReqCtx<ExtractAuth<AV>>
310
+
>,
311
+
) {
312
+
const nsid = 'app.rocksky.album.getAlbumTracks' // @ts-ignore
313
+
return this._server.xrpc.method(nsid, cfg)
314
+
}
315
+
}
316
+
317
+
export class AppRockskyApikeyNS {
318
+
_server: Server
319
+
320
+
constructor(server: Server) {
321
+
this._server = server
322
+
}
323
+
324
+
createApikey<AV extends AuthVerifier>(
325
+
cfg: ConfigOf<
326
+
AV,
327
+
AppRockskyApikeyCreateApikey.Handler<ExtractAuth<AV>>,
328
+
AppRockskyApikeyCreateApikey.HandlerReqCtx<ExtractAuth<AV>>
329
+
>,
330
+
) {
331
+
const nsid = 'app.rocksky.apikey.createApikey' // @ts-ignore
332
+
return this._server.xrpc.method(nsid, cfg)
333
+
}
334
+
335
+
getApikeys<AV extends AuthVerifier>(
336
+
cfg: ConfigOf<
337
+
AV,
338
+
AppRockskyApikeyGetApikeys.Handler<ExtractAuth<AV>>,
339
+
AppRockskyApikeyGetApikeys.HandlerReqCtx<ExtractAuth<AV>>
340
+
>,
341
+
) {
342
+
const nsid = 'app.rocksky.apikey.getApikeys' // @ts-ignore
343
+
return this._server.xrpc.method(nsid, cfg)
344
+
}
345
+
346
+
removeApikey<AV extends AuthVerifier>(
347
+
cfg: ConfigOf<
348
+
AV,
349
+
AppRockskyApikeyRemoveApikey.Handler<ExtractAuth<AV>>,
350
+
AppRockskyApikeyRemoveApikey.HandlerReqCtx<ExtractAuth<AV>>
351
+
>,
352
+
) {
353
+
const nsid = 'app.rocksky.apikey.removeApikey' // @ts-ignore
354
+
return this._server.xrpc.method(nsid, cfg)
355
+
}
356
+
357
+
updateApikey<AV extends AuthVerifier>(
358
+
cfg: ConfigOf<
359
+
AV,
360
+
AppRockskyApikeyUpdateApikey.Handler<ExtractAuth<AV>>,
361
+
AppRockskyApikeyUpdateApikey.HandlerReqCtx<ExtractAuth<AV>>
362
+
>,
363
+
) {
364
+
const nsid = 'app.rocksky.apikey.updateApikey' // @ts-ignore
365
+
return this._server.xrpc.method(nsid, cfg)
366
+
}
367
+
}
368
+
369
+
export class AppRockskyArtistNS {
370
+
_server: Server
371
+
372
+
constructor(server: Server) {
373
+
this._server = server
374
+
}
375
+
376
+
getArtist<AV extends AuthVerifier>(
377
+
cfg: ConfigOf<
378
+
AV,
379
+
AppRockskyArtistGetArtist.Handler<ExtractAuth<AV>>,
380
+
AppRockskyArtistGetArtist.HandlerReqCtx<ExtractAuth<AV>>
381
+
>,
382
+
) {
383
+
const nsid = 'app.rocksky.artist.getArtist' // @ts-ignore
384
+
return this._server.xrpc.method(nsid, cfg)
385
+
}
386
+
387
+
getArtistAlbums<AV extends AuthVerifier>(
388
+
cfg: ConfigOf<
389
+
AV,
390
+
AppRockskyArtistGetArtistAlbums.Handler<ExtractAuth<AV>>,
391
+
AppRockskyArtistGetArtistAlbums.HandlerReqCtx<ExtractAuth<AV>>
392
+
>,
393
+
) {
394
+
const nsid = 'app.rocksky.artist.getArtistAlbums' // @ts-ignore
395
+
return this._server.xrpc.method(nsid, cfg)
396
+
}
397
+
398
+
getArtistListeners<AV extends AuthVerifier>(
399
+
cfg: ConfigOf<
400
+
AV,
401
+
AppRockskyArtistGetArtistListeners.Handler<ExtractAuth<AV>>,
402
+
AppRockskyArtistGetArtistListeners.HandlerReqCtx<ExtractAuth<AV>>
403
+
>,
404
+
) {
405
+
const nsid = 'app.rocksky.artist.getArtistListeners' // @ts-ignore
406
+
return this._server.xrpc.method(nsid, cfg)
407
+
}
408
+
409
+
getArtists<AV extends AuthVerifier>(
410
+
cfg: ConfigOf<
411
+
AV,
412
+
AppRockskyArtistGetArtists.Handler<ExtractAuth<AV>>,
413
+
AppRockskyArtistGetArtists.HandlerReqCtx<ExtractAuth<AV>>
414
+
>,
415
+
) {
416
+
const nsid = 'app.rocksky.artist.getArtists' // @ts-ignore
417
+
return this._server.xrpc.method(nsid, cfg)
418
+
}
419
+
420
+
getArtistTracks<AV extends AuthVerifier>(
421
+
cfg: ConfigOf<
422
+
AV,
423
+
AppRockskyArtistGetArtistTracks.Handler<ExtractAuth<AV>>,
424
+
AppRockskyArtistGetArtistTracks.HandlerReqCtx<ExtractAuth<AV>>
425
+
>,
426
+
) {
427
+
const nsid = 'app.rocksky.artist.getArtistTracks' // @ts-ignore
428
+
return this._server.xrpc.method(nsid, cfg)
429
+
}
430
+
}
431
+
432
+
export class AppRockskyChartsNS {
433
+
_server: Server
434
+
435
+
constructor(server: Server) {
436
+
this._server = server
437
+
}
438
+
439
+
getScrobblesChart<AV extends AuthVerifier>(
440
+
cfg: ConfigOf<
441
+
AV,
442
+
AppRockskyChartsGetScrobblesChart.Handler<ExtractAuth<AV>>,
443
+
AppRockskyChartsGetScrobblesChart.HandlerReqCtx<ExtractAuth<AV>>
444
+
>,
445
+
) {
446
+
const nsid = 'app.rocksky.charts.getScrobblesChart' // @ts-ignore
447
+
return this._server.xrpc.method(nsid, cfg)
448
+
}
449
+
}
450
+
451
+
export class AppRockskyDropboxNS {
452
+
_server: Server
453
+
454
+
constructor(server: Server) {
455
+
this._server = server
456
+
}
457
+
458
+
downloadFile<AV extends AuthVerifier>(
459
+
cfg: ConfigOf<
460
+
AV,
461
+
AppRockskyDropboxDownloadFile.Handler<ExtractAuth<AV>>,
462
+
AppRockskyDropboxDownloadFile.HandlerReqCtx<ExtractAuth<AV>>
463
+
>,
464
+
) {
465
+
const nsid = 'app.rocksky.dropbox.downloadFile' // @ts-ignore
466
+
return this._server.xrpc.method(nsid, cfg)
467
+
}
468
+
469
+
getFiles<AV extends AuthVerifier>(
470
+
cfg: ConfigOf<
471
+
AV,
472
+
AppRockskyDropboxGetFiles.Handler<ExtractAuth<AV>>,
473
+
AppRockskyDropboxGetFiles.HandlerReqCtx<ExtractAuth<AV>>
474
+
>,
475
+
) {
476
+
const nsid = 'app.rocksky.dropbox.getFiles' // @ts-ignore
477
+
return this._server.xrpc.method(nsid, cfg)
478
+
}
479
+
480
+
getMetadata<AV extends AuthVerifier>(
481
+
cfg: ConfigOf<
482
+
AV,
483
+
AppRockskyDropboxGetMetadata.Handler<ExtractAuth<AV>>,
484
+
AppRockskyDropboxGetMetadata.HandlerReqCtx<ExtractAuth<AV>>
485
+
>,
486
+
) {
487
+
const nsid = 'app.rocksky.dropbox.getMetadata' // @ts-ignore
488
+
return this._server.xrpc.method(nsid, cfg)
489
+
}
490
+
491
+
getTemporaryLink<AV extends AuthVerifier>(
492
+
cfg: ConfigOf<
493
+
AV,
494
+
AppRockskyDropboxGetTemporaryLink.Handler<ExtractAuth<AV>>,
495
+
AppRockskyDropboxGetTemporaryLink.HandlerReqCtx<ExtractAuth<AV>>
496
+
>,
497
+
) {
498
+
const nsid = 'app.rocksky.dropbox.getTemporaryLink' // @ts-ignore
499
+
return this._server.xrpc.method(nsid, cfg)
500
+
}
501
+
}
502
+
503
+
export class AppRockskyFeedNS {
504
+
_server: Server
505
+
506
+
constructor(server: Server) {
507
+
this._server = server
508
+
}
509
+
510
+
describeFeedGenerator<AV extends AuthVerifier>(
511
+
cfg: ConfigOf<
512
+
AV,
513
+
AppRockskyFeedDescribeFeedGenerator.Handler<ExtractAuth<AV>>,
514
+
AppRockskyFeedDescribeFeedGenerator.HandlerReqCtx<ExtractAuth<AV>>
515
+
>,
516
+
) {
517
+
const nsid = 'app.rocksky.feed.describeFeedGenerator' // @ts-ignore
518
+
return this._server.xrpc.method(nsid, cfg)
519
+
}
520
+
521
+
getFeed<AV extends AuthVerifier>(
522
+
cfg: ConfigOf<
523
+
AV,
524
+
AppRockskyFeedGetFeed.Handler<ExtractAuth<AV>>,
525
+
AppRockskyFeedGetFeed.HandlerReqCtx<ExtractAuth<AV>>
526
+
>,
527
+
) {
528
+
const nsid = 'app.rocksky.feed.getFeed' // @ts-ignore
529
+
return this._server.xrpc.method(nsid, cfg)
530
+
}
531
+
532
+
getFeedGenerator<AV extends AuthVerifier>(
533
+
cfg: ConfigOf<
534
+
AV,
535
+
AppRockskyFeedGetFeedGenerator.Handler<ExtractAuth<AV>>,
536
+
AppRockskyFeedGetFeedGenerator.HandlerReqCtx<ExtractAuth<AV>>
537
+
>,
538
+
) {
539
+
const nsid = 'app.rocksky.feed.getFeedGenerator' // @ts-ignore
540
+
return this._server.xrpc.method(nsid, cfg)
541
+
}
542
+
543
+
getFeedGenerators<AV extends AuthVerifier>(
544
+
cfg: ConfigOf<
545
+
AV,
546
+
AppRockskyFeedGetFeedGenerators.Handler<ExtractAuth<AV>>,
547
+
AppRockskyFeedGetFeedGenerators.HandlerReqCtx<ExtractAuth<AV>>
548
+
>,
549
+
) {
550
+
const nsid = 'app.rocksky.feed.getFeedGenerators' // @ts-ignore
551
+
return this._server.xrpc.method(nsid, cfg)
552
+
}
553
+
554
+
getFeedSkeleton<AV extends AuthVerifier>(
555
+
cfg: ConfigOf<
556
+
AV,
557
+
AppRockskyFeedGetFeedSkeleton.Handler<ExtractAuth<AV>>,
558
+
AppRockskyFeedGetFeedSkeleton.HandlerReqCtx<ExtractAuth<AV>>
559
+
>,
560
+
) {
561
+
const nsid = 'app.rocksky.feed.getFeedSkeleton' // @ts-ignore
562
+
return this._server.xrpc.method(nsid, cfg)
563
+
}
564
+
565
+
getNowPlayings<AV extends AuthVerifier>(
566
+
cfg: ConfigOf<
567
+
AV,
568
+
AppRockskyFeedGetNowPlayings.Handler<ExtractAuth<AV>>,
569
+
AppRockskyFeedGetNowPlayings.HandlerReqCtx<ExtractAuth<AV>>
570
+
>,
571
+
) {
572
+
const nsid = 'app.rocksky.feed.getNowPlayings' // @ts-ignore
573
+
return this._server.xrpc.method(nsid, cfg)
574
+
}
575
+
576
+
search<AV extends AuthVerifier>(
577
+
cfg: ConfigOf<
578
+
AV,
579
+
AppRockskyFeedSearch.Handler<ExtractAuth<AV>>,
580
+
AppRockskyFeedSearch.HandlerReqCtx<ExtractAuth<AV>>
581
+
>,
582
+
) {
583
+
const nsid = 'app.rocksky.feed.search' // @ts-ignore
584
+
return this._server.xrpc.method(nsid, cfg)
585
+
}
586
+
}
587
+
588
+
export class AppRockskyGoogledriveNS {
589
+
_server: Server
590
+
591
+
constructor(server: Server) {
592
+
this._server = server
593
+
}
594
+
595
+
downloadFile<AV extends AuthVerifier>(
596
+
cfg: ConfigOf<
597
+
AV,
598
+
AppRockskyGoogledriveDownloadFile.Handler<ExtractAuth<AV>>,
599
+
AppRockskyGoogledriveDownloadFile.HandlerReqCtx<ExtractAuth<AV>>
600
+
>,
601
+
) {
602
+
const nsid = 'app.rocksky.googledrive.downloadFile' // @ts-ignore
603
+
return this._server.xrpc.method(nsid, cfg)
604
+
}
605
+
606
+
getFile<AV extends AuthVerifier>(
607
+
cfg: ConfigOf<
608
+
AV,
609
+
AppRockskyGoogledriveGetFile.Handler<ExtractAuth<AV>>,
610
+
AppRockskyGoogledriveGetFile.HandlerReqCtx<ExtractAuth<AV>>
611
+
>,
612
+
) {
613
+
const nsid = 'app.rocksky.googledrive.getFile' // @ts-ignore
614
+
return this._server.xrpc.method(nsid, cfg)
615
+
}
616
+
617
+
getFiles<AV extends AuthVerifier>(
618
+
cfg: ConfigOf<
619
+
AV,
620
+
AppRockskyGoogledriveGetFiles.Handler<ExtractAuth<AV>>,
621
+
AppRockskyGoogledriveGetFiles.HandlerReqCtx<ExtractAuth<AV>>
622
+
>,
623
+
) {
624
+
const nsid = 'app.rocksky.googledrive.getFiles' // @ts-ignore
625
+
return this._server.xrpc.method(nsid, cfg)
626
+
}
627
+
}
628
+
629
+
export class AppRockskyGraphNS {
630
+
_server: Server
631
+
632
+
constructor(server: Server) {
633
+
this._server = server
634
+
}
635
+
636
+
followAccount<AV extends AuthVerifier>(
637
+
cfg: ConfigOf<
638
+
AV,
639
+
AppRockskyGraphFollowAccount.Handler<ExtractAuth<AV>>,
640
+
AppRockskyGraphFollowAccount.HandlerReqCtx<ExtractAuth<AV>>
641
+
>,
642
+
) {
643
+
const nsid = 'app.rocksky.graph.followAccount' // @ts-ignore
644
+
return this._server.xrpc.method(nsid, cfg)
645
+
}
646
+
647
+
getFollowers<AV extends AuthVerifier>(
648
+
cfg: ConfigOf<
649
+
AV,
650
+
AppRockskyGraphGetFollowers.Handler<ExtractAuth<AV>>,
651
+
AppRockskyGraphGetFollowers.HandlerReqCtx<ExtractAuth<AV>>
652
+
>,
653
+
) {
654
+
const nsid = 'app.rocksky.graph.getFollowers' // @ts-ignore
655
+
return this._server.xrpc.method(nsid, cfg)
656
+
}
657
+
658
+
getFollows<AV extends AuthVerifier>(
659
+
cfg: ConfigOf<
660
+
AV,
661
+
AppRockskyGraphGetFollows.Handler<ExtractAuth<AV>>,
662
+
AppRockskyGraphGetFollows.HandlerReqCtx<ExtractAuth<AV>>
663
+
>,
664
+
) {
665
+
const nsid = 'app.rocksky.graph.getFollows' // @ts-ignore
666
+
return this._server.xrpc.method(nsid, cfg)
667
+
}
668
+
669
+
getKnownFollowers<AV extends AuthVerifier>(
670
+
cfg: ConfigOf<
671
+
AV,
672
+
AppRockskyGraphGetKnownFollowers.Handler<ExtractAuth<AV>>,
673
+
AppRockskyGraphGetKnownFollowers.HandlerReqCtx<ExtractAuth<AV>>
674
+
>,
675
+
) {
676
+
const nsid = 'app.rocksky.graph.getKnownFollowers' // @ts-ignore
677
+
return this._server.xrpc.method(nsid, cfg)
678
+
}
679
+
680
+
unfollowAccount<AV extends AuthVerifier>(
681
+
cfg: ConfigOf<
682
+
AV,
683
+
AppRockskyGraphUnfollowAccount.Handler<ExtractAuth<AV>>,
684
+
AppRockskyGraphUnfollowAccount.HandlerReqCtx<ExtractAuth<AV>>
685
+
>,
686
+
) {
687
+
const nsid = 'app.rocksky.graph.unfollowAccount' // @ts-ignore
688
+
return this._server.xrpc.method(nsid, cfg)
689
+
}
690
+
}
691
+
692
+
export class AppRockskyLikeNS {
693
+
_server: Server
694
+
695
+
constructor(server: Server) {
696
+
this._server = server
697
+
}
698
+
699
+
dislikeShout<AV extends AuthVerifier>(
700
+
cfg: ConfigOf<
701
+
AV,
702
+
AppRockskyLikeDislikeShout.Handler<ExtractAuth<AV>>,
703
+
AppRockskyLikeDislikeShout.HandlerReqCtx<ExtractAuth<AV>>
704
+
>,
705
+
) {
706
+
const nsid = 'app.rocksky.like.dislikeShout' // @ts-ignore
707
+
return this._server.xrpc.method(nsid, cfg)
708
+
}
709
+
710
+
dislikeSong<AV extends AuthVerifier>(
711
+
cfg: ConfigOf<
712
+
AV,
713
+
AppRockskyLikeDislikeSong.Handler<ExtractAuth<AV>>,
714
+
AppRockskyLikeDislikeSong.HandlerReqCtx<ExtractAuth<AV>>
715
+
>,
716
+
) {
717
+
const nsid = 'app.rocksky.like.dislikeSong' // @ts-ignore
718
+
return this._server.xrpc.method(nsid, cfg)
719
+
}
720
+
721
+
likeShout<AV extends AuthVerifier>(
722
+
cfg: ConfigOf<
723
+
AV,
724
+
AppRockskyLikeLikeShout.Handler<ExtractAuth<AV>>,
725
+
AppRockskyLikeLikeShout.HandlerReqCtx<ExtractAuth<AV>>
726
+
>,
727
+
) {
728
+
const nsid = 'app.rocksky.like.likeShout' // @ts-ignore
729
+
return this._server.xrpc.method(nsid, cfg)
730
+
}
731
+
732
+
likeSong<AV extends AuthVerifier>(
733
+
cfg: ConfigOf<
734
+
AV,
735
+
AppRockskyLikeLikeSong.Handler<ExtractAuth<AV>>,
736
+
AppRockskyLikeLikeSong.HandlerReqCtx<ExtractAuth<AV>>
737
+
>,
738
+
) {
739
+
const nsid = 'app.rocksky.like.likeSong' // @ts-ignore
740
+
return this._server.xrpc.method(nsid, cfg)
741
+
}
742
+
}
743
+
744
+
export class AppRockskyPlayerNS {
745
+
_server: Server
746
+
747
+
constructor(server: Server) {
748
+
this._server = server
749
+
}
750
+
751
+
addDirectoryToQueue<AV extends AuthVerifier>(
752
+
cfg: ConfigOf<
753
+
AV,
754
+
AppRockskyPlayerAddDirectoryToQueue.Handler<ExtractAuth<AV>>,
755
+
AppRockskyPlayerAddDirectoryToQueue.HandlerReqCtx<ExtractAuth<AV>>
756
+
>,
757
+
) {
758
+
const nsid = 'app.rocksky.player.addDirectoryToQueue' // @ts-ignore
759
+
return this._server.xrpc.method(nsid, cfg)
760
+
}
761
+
762
+
addItemsToQueue<AV extends AuthVerifier>(
763
+
cfg: ConfigOf<
764
+
AV,
765
+
AppRockskyPlayerAddItemsToQueue.Handler<ExtractAuth<AV>>,
766
+
AppRockskyPlayerAddItemsToQueue.HandlerReqCtx<ExtractAuth<AV>>
767
+
>,
768
+
) {
769
+
const nsid = 'app.rocksky.player.addItemsToQueue' // @ts-ignore
770
+
return this._server.xrpc.method(nsid, cfg)
771
+
}
772
+
773
+
getCurrentlyPlaying<AV extends AuthVerifier>(
774
+
cfg: ConfigOf<
775
+
AV,
776
+
AppRockskyPlayerGetCurrentlyPlaying.Handler<ExtractAuth<AV>>,
777
+
AppRockskyPlayerGetCurrentlyPlaying.HandlerReqCtx<ExtractAuth<AV>>
778
+
>,
779
+
) {
780
+
const nsid = 'app.rocksky.player.getCurrentlyPlaying' // @ts-ignore
781
+
return this._server.xrpc.method(nsid, cfg)
782
+
}
783
+
784
+
getPlaybackQueue<AV extends AuthVerifier>(
785
+
cfg: ConfigOf<
786
+
AV,
787
+
AppRockskyPlayerGetPlaybackQueue.Handler<ExtractAuth<AV>>,
788
+
AppRockskyPlayerGetPlaybackQueue.HandlerReqCtx<ExtractAuth<AV>>
789
+
>,
790
+
) {
791
+
const nsid = 'app.rocksky.player.getPlaybackQueue' // @ts-ignore
792
+
return this._server.xrpc.method(nsid, cfg)
793
+
}
794
+
795
+
next<AV extends AuthVerifier>(
796
+
cfg: ConfigOf<
797
+
AV,
798
+
AppRockskyPlayerNext.Handler<ExtractAuth<AV>>,
799
+
AppRockskyPlayerNext.HandlerReqCtx<ExtractAuth<AV>>
800
+
>,
801
+
) {
802
+
const nsid = 'app.rocksky.player.next' // @ts-ignore
803
+
return this._server.xrpc.method(nsid, cfg)
804
+
}
805
+
806
+
pause<AV extends AuthVerifier>(
807
+
cfg: ConfigOf<
808
+
AV,
809
+
AppRockskyPlayerPause.Handler<ExtractAuth<AV>>,
810
+
AppRockskyPlayerPause.HandlerReqCtx<ExtractAuth<AV>>
811
+
>,
812
+
) {
813
+
const nsid = 'app.rocksky.player.pause' // @ts-ignore
814
+
return this._server.xrpc.method(nsid, cfg)
815
+
}
816
+
817
+
play<AV extends AuthVerifier>(
818
+
cfg: ConfigOf<
819
+
AV,
820
+
AppRockskyPlayerPlay.Handler<ExtractAuth<AV>>,
821
+
AppRockskyPlayerPlay.HandlerReqCtx<ExtractAuth<AV>>
822
+
>,
823
+
) {
824
+
const nsid = 'app.rocksky.player.play' // @ts-ignore
825
+
return this._server.xrpc.method(nsid, cfg)
826
+
}
827
+
828
+
playDirectory<AV extends AuthVerifier>(
829
+
cfg: ConfigOf<
830
+
AV,
831
+
AppRockskyPlayerPlayDirectory.Handler<ExtractAuth<AV>>,
832
+
AppRockskyPlayerPlayDirectory.HandlerReqCtx<ExtractAuth<AV>>
833
+
>,
834
+
) {
835
+
const nsid = 'app.rocksky.player.playDirectory' // @ts-ignore
836
+
return this._server.xrpc.method(nsid, cfg)
837
+
}
838
+
839
+
playFile<AV extends AuthVerifier>(
840
+
cfg: ConfigOf<
841
+
AV,
842
+
AppRockskyPlayerPlayFile.Handler<ExtractAuth<AV>>,
843
+
AppRockskyPlayerPlayFile.HandlerReqCtx<ExtractAuth<AV>>
844
+
>,
845
+
) {
846
+
const nsid = 'app.rocksky.player.playFile' // @ts-ignore
847
+
return this._server.xrpc.method(nsid, cfg)
848
+
}
849
+
850
+
previous<AV extends AuthVerifier>(
851
+
cfg: ConfigOf<
852
+
AV,
853
+
AppRockskyPlayerPrevious.Handler<ExtractAuth<AV>>,
854
+
AppRockskyPlayerPrevious.HandlerReqCtx<ExtractAuth<AV>>
855
+
>,
856
+
) {
857
+
const nsid = 'app.rocksky.player.previous' // @ts-ignore
858
+
return this._server.xrpc.method(nsid, cfg)
859
+
}
860
+
861
+
seek<AV extends AuthVerifier>(
862
+
cfg: ConfigOf<
863
+
AV,
864
+
AppRockskyPlayerSeek.Handler<ExtractAuth<AV>>,
865
+
AppRockskyPlayerSeek.HandlerReqCtx<ExtractAuth<AV>>
866
+
>,
867
+
) {
868
+
const nsid = 'app.rocksky.player.seek' // @ts-ignore
869
+
return this._server.xrpc.method(nsid, cfg)
870
+
}
871
+
}
872
+
873
+
export class AppRockskyPlaylistNS {
874
+
_server: Server
875
+
876
+
constructor(server: Server) {
877
+
this._server = server
878
+
}
879
+
880
+
createPlaylist<AV extends AuthVerifier>(
881
+
cfg: ConfigOf<
882
+
AV,
883
+
AppRockskyPlaylistCreatePlaylist.Handler<ExtractAuth<AV>>,
884
+
AppRockskyPlaylistCreatePlaylist.HandlerReqCtx<ExtractAuth<AV>>
885
+
>,
886
+
) {
887
+
const nsid = 'app.rocksky.playlist.createPlaylist' // @ts-ignore
888
+
return this._server.xrpc.method(nsid, cfg)
889
+
}
890
+
891
+
getPlaylist<AV extends AuthVerifier>(
892
+
cfg: ConfigOf<
893
+
AV,
894
+
AppRockskyPlaylistGetPlaylist.Handler<ExtractAuth<AV>>,
895
+
AppRockskyPlaylistGetPlaylist.HandlerReqCtx<ExtractAuth<AV>>
896
+
>,
897
+
) {
898
+
const nsid = 'app.rocksky.playlist.getPlaylist' // @ts-ignore
899
+
return this._server.xrpc.method(nsid, cfg)
900
+
}
901
+
902
+
getPlaylists<AV extends AuthVerifier>(
903
+
cfg: ConfigOf<
904
+
AV,
905
+
AppRockskyPlaylistGetPlaylists.Handler<ExtractAuth<AV>>,
906
+
AppRockskyPlaylistGetPlaylists.HandlerReqCtx<ExtractAuth<AV>>
907
+
>,
908
+
) {
909
+
const nsid = 'app.rocksky.playlist.getPlaylists' // @ts-ignore
910
+
return this._server.xrpc.method(nsid, cfg)
911
+
}
912
+
913
+
insertDirectory<AV extends AuthVerifier>(
914
+
cfg: ConfigOf<
915
+
AV,
916
+
AppRockskyPlaylistInsertDirectory.Handler<ExtractAuth<AV>>,
917
+
AppRockskyPlaylistInsertDirectory.HandlerReqCtx<ExtractAuth<AV>>
918
+
>,
919
+
) {
920
+
const nsid = 'app.rocksky.playlist.insertDirectory' // @ts-ignore
921
+
return this._server.xrpc.method(nsid, cfg)
922
+
}
923
+
924
+
insertFiles<AV extends AuthVerifier>(
925
+
cfg: ConfigOf<
926
+
AV,
927
+
AppRockskyPlaylistInsertFiles.Handler<ExtractAuth<AV>>,
928
+
AppRockskyPlaylistInsertFiles.HandlerReqCtx<ExtractAuth<AV>>
929
+
>,
930
+
) {
931
+
const nsid = 'app.rocksky.playlist.insertFiles' // @ts-ignore
932
+
return this._server.xrpc.method(nsid, cfg)
933
+
}
934
+
935
+
removePlaylist<AV extends AuthVerifier>(
936
+
cfg: ConfigOf<
937
+
AV,
938
+
AppRockskyPlaylistRemovePlaylist.Handler<ExtractAuth<AV>>,
939
+
AppRockskyPlaylistRemovePlaylist.HandlerReqCtx<ExtractAuth<AV>>
940
+
>,
941
+
) {
942
+
const nsid = 'app.rocksky.playlist.removePlaylist' // @ts-ignore
943
+
return this._server.xrpc.method(nsid, cfg)
944
+
}
945
+
946
+
removeTrack<AV extends AuthVerifier>(
947
+
cfg: ConfigOf<
948
+
AV,
949
+
AppRockskyPlaylistRemoveTrack.Handler<ExtractAuth<AV>>,
950
+
AppRockskyPlaylistRemoveTrack.HandlerReqCtx<ExtractAuth<AV>>
951
+
>,
952
+
) {
953
+
const nsid = 'app.rocksky.playlist.removeTrack' // @ts-ignore
954
+
return this._server.xrpc.method(nsid, cfg)
955
+
}
956
+
957
+
startPlaylist<AV extends AuthVerifier>(
958
+
cfg: ConfigOf<
959
+
AV,
960
+
AppRockskyPlaylistStartPlaylist.Handler<ExtractAuth<AV>>,
961
+
AppRockskyPlaylistStartPlaylist.HandlerReqCtx<ExtractAuth<AV>>
962
+
>,
963
+
) {
964
+
const nsid = 'app.rocksky.playlist.startPlaylist' // @ts-ignore
965
+
return this._server.xrpc.method(nsid, cfg)
966
+
}
967
+
}
968
+
969
+
export class AppRockskyScrobbleNS {
970
+
_server: Server
971
+
972
+
constructor(server: Server) {
973
+
this._server = server
974
+
}
975
+
976
+
createScrobble<AV extends AuthVerifier>(
977
+
cfg: ConfigOf<
978
+
AV,
979
+
AppRockskyScrobbleCreateScrobble.Handler<ExtractAuth<AV>>,
980
+
AppRockskyScrobbleCreateScrobble.HandlerReqCtx<ExtractAuth<AV>>
981
+
>,
982
+
) {
983
+
const nsid = 'app.rocksky.scrobble.createScrobble' // @ts-ignore
984
+
return this._server.xrpc.method(nsid, cfg)
985
+
}
986
+
987
+
getScrobble<AV extends AuthVerifier>(
988
+
cfg: ConfigOf<
989
+
AV,
990
+
AppRockskyScrobbleGetScrobble.Handler<ExtractAuth<AV>>,
991
+
AppRockskyScrobbleGetScrobble.HandlerReqCtx<ExtractAuth<AV>>
992
+
>,
993
+
) {
994
+
const nsid = 'app.rocksky.scrobble.getScrobble' // @ts-ignore
995
+
return this._server.xrpc.method(nsid, cfg)
996
+
}
997
+
998
+
getScrobbles<AV extends AuthVerifier>(
999
+
cfg: ConfigOf<
1000
+
AV,
1001
+
AppRockskyScrobbleGetScrobbles.Handler<ExtractAuth<AV>>,
1002
+
AppRockskyScrobbleGetScrobbles.HandlerReqCtx<ExtractAuth<AV>>
1003
+
>,
1004
+
) {
1005
+
const nsid = 'app.rocksky.scrobble.getScrobbles' // @ts-ignore
1006
+
return this._server.xrpc.method(nsid, cfg)
1007
+
}
1008
+
}
1009
+
1010
+
export class AppRockskyShoutNS {
1011
+
_server: Server
1012
+
1013
+
constructor(server: Server) {
1014
+
this._server = server
1015
+
}
1016
+
1017
+
createShout<AV extends AuthVerifier>(
1018
+
cfg: ConfigOf<
1019
+
AV,
1020
+
AppRockskyShoutCreateShout.Handler<ExtractAuth<AV>>,
1021
+
AppRockskyShoutCreateShout.HandlerReqCtx<ExtractAuth<AV>>
1022
+
>,
1023
+
) {
1024
+
const nsid = 'app.rocksky.shout.createShout' // @ts-ignore
1025
+
return this._server.xrpc.method(nsid, cfg)
1026
+
}
1027
+
1028
+
getAlbumShouts<AV extends AuthVerifier>(
1029
+
cfg: ConfigOf<
1030
+
AV,
1031
+
AppRockskyShoutGetAlbumShouts.Handler<ExtractAuth<AV>>,
1032
+
AppRockskyShoutGetAlbumShouts.HandlerReqCtx<ExtractAuth<AV>>
1033
+
>,
1034
+
) {
1035
+
const nsid = 'app.rocksky.shout.getAlbumShouts' // @ts-ignore
1036
+
return this._server.xrpc.method(nsid, cfg)
1037
+
}
1038
+
1039
+
getArtistShouts<AV extends AuthVerifier>(
1040
+
cfg: ConfigOf<
1041
+
AV,
1042
+
AppRockskyShoutGetArtistShouts.Handler<ExtractAuth<AV>>,
1043
+
AppRockskyShoutGetArtistShouts.HandlerReqCtx<ExtractAuth<AV>>
1044
+
>,
1045
+
) {
1046
+
const nsid = 'app.rocksky.shout.getArtistShouts' // @ts-ignore
1047
+
return this._server.xrpc.method(nsid, cfg)
1048
+
}
1049
+
1050
+
getProfileShouts<AV extends AuthVerifier>(
1051
+
cfg: ConfigOf<
1052
+
AV,
1053
+
AppRockskyShoutGetProfileShouts.Handler<ExtractAuth<AV>>,
1054
+
AppRockskyShoutGetProfileShouts.HandlerReqCtx<ExtractAuth<AV>>
1055
+
>,
1056
+
) {
1057
+
const nsid = 'app.rocksky.shout.getProfileShouts' // @ts-ignore
1058
+
return this._server.xrpc.method(nsid, cfg)
1059
+
}
1060
+
1061
+
getShoutReplies<AV extends AuthVerifier>(
1062
+
cfg: ConfigOf<
1063
+
AV,
1064
+
AppRockskyShoutGetShoutReplies.Handler<ExtractAuth<AV>>,
1065
+
AppRockskyShoutGetShoutReplies.HandlerReqCtx<ExtractAuth<AV>>
1066
+
>,
1067
+
) {
1068
+
const nsid = 'app.rocksky.shout.getShoutReplies' // @ts-ignore
1069
+
return this._server.xrpc.method(nsid, cfg)
1070
+
}
1071
+
1072
+
getTrackShouts<AV extends AuthVerifier>(
1073
+
cfg: ConfigOf<
1074
+
AV,
1075
+
AppRockskyShoutGetTrackShouts.Handler<ExtractAuth<AV>>,
1076
+
AppRockskyShoutGetTrackShouts.HandlerReqCtx<ExtractAuth<AV>>
1077
+
>,
1078
+
) {
1079
+
const nsid = 'app.rocksky.shout.getTrackShouts' // @ts-ignore
1080
+
return this._server.xrpc.method(nsid, cfg)
1081
+
}
1082
+
1083
+
removeShout<AV extends AuthVerifier>(
1084
+
cfg: ConfigOf<
1085
+
AV,
1086
+
AppRockskyShoutRemoveShout.Handler<ExtractAuth<AV>>,
1087
+
AppRockskyShoutRemoveShout.HandlerReqCtx<ExtractAuth<AV>>
1088
+
>,
1089
+
) {
1090
+
const nsid = 'app.rocksky.shout.removeShout' // @ts-ignore
1091
+
return this._server.xrpc.method(nsid, cfg)
1092
+
}
1093
+
1094
+
replyShout<AV extends AuthVerifier>(
1095
+
cfg: ConfigOf<
1096
+
AV,
1097
+
AppRockskyShoutReplyShout.Handler<ExtractAuth<AV>>,
1098
+
AppRockskyShoutReplyShout.HandlerReqCtx<ExtractAuth<AV>>
1099
+
>,
1100
+
) {
1101
+
const nsid = 'app.rocksky.shout.replyShout' // @ts-ignore
1102
+
return this._server.xrpc.method(nsid, cfg)
1103
+
}
1104
+
1105
+
reportShout<AV extends AuthVerifier>(
1106
+
cfg: ConfigOf<
1107
+
AV,
1108
+
AppRockskyShoutReportShout.Handler<ExtractAuth<AV>>,
1109
+
AppRockskyShoutReportShout.HandlerReqCtx<ExtractAuth<AV>>
1110
+
>,
1111
+
) {
1112
+
const nsid = 'app.rocksky.shout.reportShout' // @ts-ignore
1113
+
return this._server.xrpc.method(nsid, cfg)
1114
+
}
1115
+
}
1116
+
1117
+
export class AppRockskySongNS {
1118
+
_server: Server
1119
+
1120
+
constructor(server: Server) {
1121
+
this._server = server
1122
+
}
1123
+
1124
+
createSong<AV extends AuthVerifier>(
1125
+
cfg: ConfigOf<
1126
+
AV,
1127
+
AppRockskySongCreateSong.Handler<ExtractAuth<AV>>,
1128
+
AppRockskySongCreateSong.HandlerReqCtx<ExtractAuth<AV>>
1129
+
>,
1130
+
) {
1131
+
const nsid = 'app.rocksky.song.createSong' // @ts-ignore
1132
+
return this._server.xrpc.method(nsid, cfg)
1133
+
}
1134
+
1135
+
getSong<AV extends AuthVerifier>(
1136
+
cfg: ConfigOf<
1137
+
AV,
1138
+
AppRockskySongGetSong.Handler<ExtractAuth<AV>>,
1139
+
AppRockskySongGetSong.HandlerReqCtx<ExtractAuth<AV>>
1140
+
>,
1141
+
) {
1142
+
const nsid = 'app.rocksky.song.getSong' // @ts-ignore
1143
+
return this._server.xrpc.method(nsid, cfg)
1144
+
}
1145
+
1146
+
getSongs<AV extends AuthVerifier>(
1147
+
cfg: ConfigOf<
1148
+
AV,
1149
+
AppRockskySongGetSongs.Handler<ExtractAuth<AV>>,
1150
+
AppRockskySongGetSongs.HandlerReqCtx<ExtractAuth<AV>>
1151
+
>,
1152
+
) {
1153
+
const nsid = 'app.rocksky.song.getSongs' // @ts-ignore
1154
+
return this._server.xrpc.method(nsid, cfg)
1155
+
}
1156
+
}
1157
+
1158
+
export class AppRockskySpotifyNS {
1159
+
_server: Server
1160
+
1161
+
constructor(server: Server) {
1162
+
this._server = server
1163
+
}
1164
+
1165
+
getCurrentlyPlaying<AV extends AuthVerifier>(
1166
+
cfg: ConfigOf<
1167
+
AV,
1168
+
AppRockskySpotifyGetCurrentlyPlaying.Handler<ExtractAuth<AV>>,
1169
+
AppRockskySpotifyGetCurrentlyPlaying.HandlerReqCtx<ExtractAuth<AV>>
1170
+
>,
1171
+
) {
1172
+
const nsid = 'app.rocksky.spotify.getCurrentlyPlaying' // @ts-ignore
1173
+
return this._server.xrpc.method(nsid, cfg)
1174
+
}
1175
+
1176
+
next<AV extends AuthVerifier>(
1177
+
cfg: ConfigOf<
1178
+
AV,
1179
+
AppRockskySpotifyNext.Handler<ExtractAuth<AV>>,
1180
+
AppRockskySpotifyNext.HandlerReqCtx<ExtractAuth<AV>>
1181
+
>,
1182
+
) {
1183
+
const nsid = 'app.rocksky.spotify.next' // @ts-ignore
1184
+
return this._server.xrpc.method(nsid, cfg)
1185
+
}
1186
+
1187
+
pause<AV extends AuthVerifier>(
1188
+
cfg: ConfigOf<
1189
+
AV,
1190
+
AppRockskySpotifyPause.Handler<ExtractAuth<AV>>,
1191
+
AppRockskySpotifyPause.HandlerReqCtx<ExtractAuth<AV>>
1192
+
>,
1193
+
) {
1194
+
const nsid = 'app.rocksky.spotify.pause' // @ts-ignore
1195
+
return this._server.xrpc.method(nsid, cfg)
1196
+
}
1197
+
1198
+
play<AV extends AuthVerifier>(
1199
+
cfg: ConfigOf<
1200
+
AV,
1201
+
AppRockskySpotifyPlay.Handler<ExtractAuth<AV>>,
1202
+
AppRockskySpotifyPlay.HandlerReqCtx<ExtractAuth<AV>>
1203
+
>,
1204
+
) {
1205
+
const nsid = 'app.rocksky.spotify.play' // @ts-ignore
1206
+
return this._server.xrpc.method(nsid, cfg)
1207
+
}
1208
+
1209
+
previous<AV extends AuthVerifier>(
1210
+
cfg: ConfigOf<
1211
+
AV,
1212
+
AppRockskySpotifyPrevious.Handler<ExtractAuth<AV>>,
1213
+
AppRockskySpotifyPrevious.HandlerReqCtx<ExtractAuth<AV>>
1214
+
>,
1215
+
) {
1216
+
const nsid = 'app.rocksky.spotify.previous' // @ts-ignore
1217
+
return this._server.xrpc.method(nsid, cfg)
1218
+
}
1219
+
1220
+
seek<AV extends AuthVerifier>(
1221
+
cfg: ConfigOf<
1222
+
AV,
1223
+
AppRockskySpotifySeek.Handler<ExtractAuth<AV>>,
1224
+
AppRockskySpotifySeek.HandlerReqCtx<ExtractAuth<AV>>
1225
+
>,
1226
+
) {
1227
+
const nsid = 'app.rocksky.spotify.seek' // @ts-ignore
1228
+
return this._server.xrpc.method(nsid, cfg)
1229
+
}
1230
+
}
1231
+
1232
+
export class AppRockskyStatsNS {
1233
+
_server: Server
1234
+
1235
+
constructor(server: Server) {
1236
+
this._server = server
1237
+
}
1238
+
1239
+
getStats<AV extends AuthVerifier>(
1240
+
cfg: ConfigOf<
1241
+
AV,
1242
+
AppRockskyStatsGetStats.Handler<ExtractAuth<AV>>,
1243
+
AppRockskyStatsGetStats.HandlerReqCtx<ExtractAuth<AV>>
1244
+
>,
1245
+
) {
1246
+
const nsid = 'app.rocksky.stats.getStats' // @ts-ignore
1247
+
return this._server.xrpc.method(nsid, cfg)
1248
+
}
1249
+
}
1250
+
1251
+
export class AppBskyNS {
1252
+
_server: Server
1253
+
actor: AppBskyActorNS
1254
+
1255
+
constructor(server: Server) {
1256
+
this._server = server
1257
+
this.actor = new AppBskyActorNS(server)
1258
+
}
1259
+
}
1260
+
1261
+
export class AppBskyActorNS {
1262
+
_server: Server
1263
+
1264
+
constructor(server: Server) {
1265
+
this._server = server
1266
+
}
1267
+
}
1268
+
1269
+
export class ComNS {
1270
+
_server: Server
1271
+
atproto: ComAtprotoNS
1272
+
1273
+
constructor(server: Server) {
1274
+
this._server = server
1275
+
this.atproto = new ComAtprotoNS(server)
1276
+
}
1277
+
}
1278
+
1279
+
export class ComAtprotoNS {
1280
+
_server: Server
1281
+
repo: ComAtprotoRepoNS
1282
+
1283
+
constructor(server: Server) {
1284
+
this._server = server
1285
+
this.repo = new ComAtprotoRepoNS(server)
1286
+
}
1287
+
}
1288
+
1289
+
export class ComAtprotoRepoNS {
1290
+
_server: Server
1291
+
1292
+
constructor(server: Server) {
1293
+
this._server = server
1294
+
}
1295
+
}
1296
+
1297
+
type SharedRateLimitOpts<T> = {
1298
+
name: string
1299
+
calcKey?: (ctx: T) => string | null
1300
+
calcPoints?: (ctx: T) => number
1301
+
}
1302
+
type RouteRateLimitOpts<T> = {
1303
+
durationMs: number
1304
+
points: number
1305
+
calcKey?: (ctx: T) => string | null
1306
+
calcPoints?: (ctx: T) => number
1307
+
}
1308
+
type HandlerOpts = { blobLimit?: number }
1309
+
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>
1310
+
type ConfigOf<Auth, Handler, ReqCtx> =
1311
+
| Handler
1312
+
| {
1313
+
auth?: Auth
1314
+
opts?: HandlerOpts
1315
+
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]
1316
+
handler: Handler
1317
+
}
1318
+
type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract<
1319
+
Awaited<ReturnType<AV>>,
1320
+
{ credentials: unknown }
1321
+
>
+5453
apps/cli/src/lexicon/lexicons.ts
+5453
apps/cli/src/lexicon/lexicons.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
5
+
6
+
export const schemaDict = {
7
+
AppRockskyActorDefs: {
8
+
lexicon: 1,
9
+
id: 'app.rocksky.actor.defs',
10
+
defs: {
11
+
profileViewDetailed: {
12
+
type: 'object',
13
+
properties: {
14
+
id: {
15
+
type: 'string',
16
+
description: 'The unique identifier of the actor.',
17
+
},
18
+
did: {
19
+
type: 'string',
20
+
description: 'The DID of the actor.',
21
+
},
22
+
handle: {
23
+
type: 'string',
24
+
description: 'The handle of the actor.',
25
+
},
26
+
displayName: {
27
+
type: 'string',
28
+
description: 'The display name of the actor.',
29
+
},
30
+
avatar: {
31
+
type: 'string',
32
+
description: "The URL of the actor's avatar image.",
33
+
format: 'uri',
34
+
},
35
+
createdAt: {
36
+
type: 'string',
37
+
description: 'The date and time when the actor was created.',
38
+
format: 'datetime',
39
+
},
40
+
updatedAt: {
41
+
type: 'string',
42
+
description: 'The date and time when the actor was last updated.',
43
+
format: 'datetime',
44
+
},
45
+
},
46
+
},
47
+
profileViewBasic: {
48
+
type: 'object',
49
+
properties: {
50
+
id: {
51
+
type: 'string',
52
+
description: 'The unique identifier of the actor.',
53
+
},
54
+
did: {
55
+
type: 'string',
56
+
description: 'The DID of the actor.',
57
+
},
58
+
handle: {
59
+
type: 'string',
60
+
description: 'The handle of the actor.',
61
+
},
62
+
displayName: {
63
+
type: 'string',
64
+
description: 'The display name of the actor.',
65
+
},
66
+
avatar: {
67
+
type: 'string',
68
+
description: "The URL of the actor's avatar image.",
69
+
format: 'uri',
70
+
},
71
+
createdAt: {
72
+
type: 'string',
73
+
description: 'The date and time when the actor was created.',
74
+
format: 'datetime',
75
+
},
76
+
updatedAt: {
77
+
type: 'string',
78
+
description: 'The date and time when the actor was last updated.',
79
+
format: 'datetime',
80
+
},
81
+
},
82
+
},
83
+
neighbourViewBasic: {
84
+
type: 'object',
85
+
properties: {
86
+
userId: {
87
+
type: 'string',
88
+
},
89
+
did: {
90
+
type: 'string',
91
+
},
92
+
handle: {
93
+
type: 'string',
94
+
},
95
+
displayName: {
96
+
type: 'string',
97
+
},
98
+
avatar: {
99
+
type: 'string',
100
+
description: "The URL of the actor's avatar image.",
101
+
format: 'uri',
102
+
},
103
+
sharedArtistsCount: {
104
+
type: 'integer',
105
+
description: 'The number of artists shared with the actor.',
106
+
},
107
+
similarityScore: {
108
+
type: 'integer',
109
+
description: 'The similarity score with the actor.',
110
+
},
111
+
topSharedArtistNames: {
112
+
type: 'array',
113
+
description: 'The top shared artist names with the actor.',
114
+
items: {
115
+
type: 'string',
116
+
},
117
+
},
118
+
topSharedArtistsDetails: {
119
+
type: 'array',
120
+
description: 'The top shared artist details with the actor.',
121
+
items: {
122
+
type: 'ref',
123
+
ref: 'lex:app.rocksky.artist.defs#artistViewBasic',
124
+
},
125
+
},
126
+
},
127
+
},
128
+
compatibilityViewBasic: {
129
+
type: 'object',
130
+
properties: {
131
+
compatibilityLevel: {
132
+
type: 'integer',
133
+
},
134
+
compatibilityPercentage: {
135
+
type: 'integer',
136
+
},
137
+
sharedArtists: {
138
+
type: 'integer',
139
+
},
140
+
topSharedArtistNames: {
141
+
type: 'array',
142
+
items: {
143
+
type: 'string',
144
+
},
145
+
},
146
+
topSharedDetailedArtists: {
147
+
type: 'array',
148
+
items: {
149
+
type: 'ref',
150
+
ref: 'lex:app.rocksky.actor.defs#artistViewBasic',
151
+
},
152
+
},
153
+
user1ArtistCount: {
154
+
type: 'integer',
155
+
},
156
+
user2ArtistCount: {
157
+
type: 'integer',
158
+
},
159
+
},
160
+
},
161
+
artistViewBasic: {
162
+
type: 'object',
163
+
properties: {
164
+
id: {
165
+
type: 'string',
166
+
},
167
+
name: {
168
+
type: 'string',
169
+
},
170
+
picture: {
171
+
type: 'string',
172
+
format: 'uri',
173
+
},
174
+
uri: {
175
+
type: 'string',
176
+
format: 'at-uri',
177
+
},
178
+
user1Rank: {
179
+
type: 'integer',
180
+
},
181
+
user2Rank: {
182
+
type: 'integer',
183
+
},
184
+
weight: {
185
+
type: 'integer',
186
+
},
187
+
},
188
+
},
189
+
},
190
+
},
191
+
AppRockskyActorGetActorAlbums: {
192
+
lexicon: 1,
193
+
id: 'app.rocksky.actor.getActorAlbums',
194
+
defs: {
195
+
main: {
196
+
type: 'query',
197
+
description: 'Get albums for an actor',
198
+
parameters: {
199
+
type: 'params',
200
+
required: ['did'],
201
+
properties: {
202
+
did: {
203
+
type: 'string',
204
+
description: 'The DID or handle of the actor',
205
+
format: 'at-identifier',
206
+
},
207
+
limit: {
208
+
type: 'integer',
209
+
description: 'The maximum number of albums to return',
210
+
minimum: 1,
211
+
},
212
+
offset: {
213
+
type: 'integer',
214
+
description: 'The offset for pagination',
215
+
minimum: 0,
216
+
},
217
+
startDate: {
218
+
type: 'string',
219
+
description:
220
+
'The start date to filter albums from (ISO 8601 format)',
221
+
format: 'datetime',
222
+
},
223
+
endDate: {
224
+
type: 'string',
225
+
description: 'The end date to filter albums to (ISO 8601 format)',
226
+
format: 'datetime',
227
+
},
228
+
},
229
+
},
230
+
output: {
231
+
encoding: 'application/json',
232
+
schema: {
233
+
type: 'object',
234
+
properties: {
235
+
albums: {
236
+
type: 'array',
237
+
items: {
238
+
type: 'ref',
239
+
ref: 'lex:app.rocksky.album.defs#albumViewBasic',
240
+
},
241
+
},
242
+
},
243
+
},
244
+
},
245
+
},
246
+
},
247
+
},
248
+
AppRockskyActorGetActorArtists: {
249
+
lexicon: 1,
250
+
id: 'app.rocksky.actor.getActorArtists',
251
+
defs: {
252
+
main: {
253
+
type: 'query',
254
+
description: 'Get artists for an actor',
255
+
parameters: {
256
+
type: 'params',
257
+
required: ['did'],
258
+
properties: {
259
+
did: {
260
+
type: 'string',
261
+
description: 'The DID or handle of the actor',
262
+
format: 'at-identifier',
263
+
},
264
+
limit: {
265
+
type: 'integer',
266
+
description: 'The maximum number of albums to return',
267
+
minimum: 1,
268
+
},
269
+
offset: {
270
+
type: 'integer',
271
+
description: 'The offset for pagination',
272
+
minimum: 0,
273
+
},
274
+
startDate: {
275
+
type: 'string',
276
+
description:
277
+
'The start date to filter albums from (ISO 8601 format)',
278
+
format: 'datetime',
279
+
},
280
+
endDate: {
281
+
type: 'string',
282
+
description: 'The end date to filter albums to (ISO 8601 format)',
283
+
format: 'datetime',
284
+
},
285
+
},
286
+
},
287
+
output: {
288
+
encoding: 'application/json',
289
+
schema: {
290
+
type: 'object',
291
+
properties: {
292
+
artists: {
293
+
type: 'array',
294
+
items: {
295
+
type: 'ref',
296
+
ref: 'lex:app.rocksky.artist.defs#artistViewBasic',
297
+
},
298
+
},
299
+
},
300
+
},
301
+
},
302
+
},
303
+
},
304
+
},
305
+
AppRockskyActorGetActorCompatibility: {
306
+
lexicon: 1,
307
+
id: 'app.rocksky.actor.getActorCompatibility',
308
+
defs: {
309
+
main: {
310
+
type: 'query',
311
+
description: 'Get compatibility for an actor',
312
+
parameters: {
313
+
type: 'params',
314
+
required: ['did'],
315
+
properties: {
316
+
did: {
317
+
type: 'string',
318
+
description: 'DID or handle to get compatibility for',
319
+
format: 'at-identifier',
320
+
},
321
+
},
322
+
},
323
+
output: {
324
+
encoding: 'application/json',
325
+
schema: {
326
+
type: 'object',
327
+
properties: {
328
+
compatibility: {
329
+
type: 'ref',
330
+
ref: 'lex:app.rocksky.actor.defs#compatibilityViewBasic',
331
+
},
332
+
},
333
+
},
334
+
},
335
+
},
336
+
},
337
+
},
338
+
AppRockskyActorGetActorLovedSongs: {
339
+
lexicon: 1,
340
+
id: 'app.rocksky.actor.getActorLovedSongs',
341
+
defs: {
342
+
main: {
343
+
type: 'query',
344
+
description: 'Get loved songs for an actor',
345
+
parameters: {
346
+
type: 'params',
347
+
required: ['did'],
348
+
properties: {
349
+
did: {
350
+
type: 'string',
351
+
description: 'The DID or handle of the actor',
352
+
format: 'at-identifier',
353
+
},
354
+
limit: {
355
+
type: 'integer',
356
+
description: 'The maximum number of albums to return',
357
+
minimum: 1,
358
+
},
359
+
offset: {
360
+
type: 'integer',
361
+
description: 'The offset for pagination',
362
+
minimum: 0,
363
+
},
364
+
},
365
+
},
366
+
output: {
367
+
encoding: 'application/json',
368
+
schema: {
369
+
type: 'object',
370
+
properties: {
371
+
tracks: {
372
+
type: 'array',
373
+
items: {
374
+
type: 'ref',
375
+
ref: 'lex:app.rocksky.song.defs#songViewBasic',
376
+
},
377
+
},
378
+
},
379
+
},
380
+
},
381
+
},
382
+
},
383
+
},
384
+
AppRockskyActorGetActorNeighbours: {
385
+
lexicon: 1,
386
+
id: 'app.rocksky.actor.getActorNeighbours',
387
+
defs: {
388
+
main: {
389
+
type: 'query',
390
+
description: 'Get neighbours for an actor',
391
+
parameters: {
392
+
type: 'params',
393
+
required: ['did'],
394
+
properties: {
395
+
did: {
396
+
type: 'string',
397
+
description: 'The DID or handle of the actor',
398
+
format: 'at-identifier',
399
+
},
400
+
},
401
+
},
402
+
output: {
403
+
encoding: 'application/json',
404
+
schema: {
405
+
type: 'object',
406
+
properties: {
407
+
neighbours: {
408
+
type: 'array',
409
+
items: {
410
+
type: 'ref',
411
+
ref: 'lex:app.rocksky.actor.defs#neighbourViewBasic',
412
+
},
413
+
},
414
+
},
415
+
},
416
+
},
417
+
},
418
+
},
419
+
},
420
+
AppRockskyActorGetActorPlaylists: {
421
+
lexicon: 1,
422
+
id: 'app.rocksky.actor.getActorPlaylists',
423
+
defs: {
424
+
main: {
425
+
type: 'query',
426
+
description: 'Get playlists for an actor',
427
+
parameters: {
428
+
type: 'params',
429
+
required: ['did'],
430
+
properties: {
431
+
did: {
432
+
type: 'string',
433
+
description: 'The DID or handle of the actor',
434
+
format: 'at-identifier',
435
+
},
436
+
limit: {
437
+
type: 'integer',
438
+
description: 'The maximum number of albums to return',
439
+
minimum: 1,
440
+
},
441
+
offset: {
442
+
type: 'integer',
443
+
description: 'The offset for pagination',
444
+
minimum: 0,
445
+
},
446
+
},
447
+
},
448
+
output: {
449
+
encoding: 'application/json',
450
+
schema: {
451
+
type: 'object',
452
+
properties: {
453
+
playlists: {
454
+
type: 'array',
455
+
items: {
456
+
type: 'ref',
457
+
ref: 'lex:app.rocksky.playlist.defs#playlistViewBasic',
458
+
},
459
+
},
460
+
},
461
+
},
462
+
},
463
+
},
464
+
},
465
+
},
466
+
AppRockskyActorGetActorScrobbles: {
467
+
lexicon: 1,
468
+
id: 'app.rocksky.actor.getActorScrobbles',
469
+
defs: {
470
+
main: {
471
+
type: 'query',
472
+
description: 'Get scrobbles for an actor',
473
+
parameters: {
474
+
type: 'params',
475
+
required: ['did'],
476
+
properties: {
477
+
did: {
478
+
type: 'string',
479
+
description: 'The DID or handle of the actor',
480
+
format: 'at-identifier',
481
+
},
482
+
limit: {
483
+
type: 'integer',
484
+
description: 'The maximum number of albums to return',
485
+
minimum: 1,
486
+
},
487
+
offset: {
488
+
type: 'integer',
489
+
description: 'The offset for pagination',
490
+
minimum: 0,
491
+
},
492
+
},
493
+
},
494
+
output: {
495
+
encoding: 'application/json',
496
+
schema: {
497
+
type: 'object',
498
+
properties: {
499
+
scrobbles: {
500
+
type: 'array',
501
+
items: {
502
+
type: 'ref',
503
+
ref: 'lex:app.rocksky.scrobble.defs#scrobbleViewBasic',
504
+
},
505
+
},
506
+
},
507
+
},
508
+
},
509
+
},
510
+
},
511
+
},
512
+
AppRockskyActorGetActorSongs: {
513
+
lexicon: 1,
514
+
id: 'app.rocksky.actor.getActorSongs',
515
+
defs: {
516
+
main: {
517
+
type: 'query',
518
+
description: 'Get songs for an actor',
519
+
parameters: {
520
+
type: 'params',
521
+
required: ['did'],
522
+
properties: {
523
+
did: {
524
+
type: 'string',
525
+
description: 'The DID or handle of the actor',
526
+
format: 'at-identifier',
527
+
},
528
+
limit: {
529
+
type: 'integer',
530
+
description: 'The maximum number of albums to return',
531
+
minimum: 1,
532
+
},
533
+
offset: {
534
+
type: 'integer',
535
+
description: 'The offset for pagination',
536
+
minimum: 0,
537
+
},
538
+
startDate: {
539
+
type: 'string',
540
+
description:
541
+
'The start date to filter albums from (ISO 8601 format)',
542
+
format: 'datetime',
543
+
},
544
+
endDate: {
545
+
type: 'string',
546
+
description: 'The end date to filter albums to (ISO 8601 format)',
547
+
format: 'datetime',
548
+
},
549
+
},
550
+
},
551
+
output: {
552
+
encoding: 'application/json',
553
+
schema: {
554
+
type: 'object',
555
+
properties: {
556
+
songs: {
557
+
type: 'array',
558
+
items: {
559
+
type: 'ref',
560
+
ref: 'lex:app.rocksky.song.defs#songViewBasic',
561
+
},
562
+
},
563
+
},
564
+
},
565
+
},
566
+
},
567
+
},
568
+
},
569
+
AppRockskyActorGetProfile: {
570
+
lexicon: 1,
571
+
id: 'app.rocksky.actor.getProfile',
572
+
defs: {
573
+
main: {
574
+
type: 'query',
575
+
description: 'Get the profile of an actor',
576
+
parameters: {
577
+
type: 'params',
578
+
properties: {
579
+
did: {
580
+
type: 'string',
581
+
description: 'The DID or handle of the actor',
582
+
format: 'at-identifier',
583
+
},
584
+
},
585
+
},
586
+
output: {
587
+
encoding: 'application/json',
588
+
schema: {
589
+
type: 'ref',
590
+
ref: 'lex:app.rocksky.actor.defs#profileViewDetailed',
591
+
},
592
+
},
593
+
},
594
+
},
595
+
},
596
+
AppBskyActorProfile: {
597
+
lexicon: 1,
598
+
id: 'app.bsky.actor.profile',
599
+
defs: {
600
+
main: {
601
+
type: 'record',
602
+
description: 'A declaration of a Bluesky account profile.',
603
+
key: 'literal:self',
604
+
record: {
605
+
type: 'object',
606
+
properties: {
607
+
displayName: {
608
+
type: 'string',
609
+
maxGraphemes: 64,
610
+
maxLength: 640,
611
+
},
612
+
description: {
613
+
type: 'string',
614
+
description: 'Free-form profile description text.',
615
+
maxGraphemes: 256,
616
+
maxLength: 2560,
617
+
},
618
+
avatar: {
619
+
type: 'blob',
620
+
description:
621
+
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
622
+
accept: ['image/png', 'image/jpeg'],
623
+
maxSize: 1000000,
624
+
},
625
+
banner: {
626
+
type: 'blob',
627
+
description:
628
+
'Larger horizontal image to display behind profile view.',
629
+
accept: ['image/png', 'image/jpeg'],
630
+
maxSize: 10000000,
631
+
},
632
+
labels: {
633
+
type: 'union',
634
+
description:
635
+
'Self-label values, specific to the Bluesky application, on the overall account.',
636
+
refs: ['lex:com.atproto.label.defs#selfLabels'],
637
+
},
638
+
joinedViaStarterPack: {
639
+
type: 'ref',
640
+
ref: 'lex:com.atproto.repo.strongRef',
641
+
},
642
+
createdAt: {
643
+
type: 'string',
644
+
format: 'datetime',
645
+
},
646
+
},
647
+
},
648
+
},
649
+
},
650
+
},
651
+
AppRockskyAlbum: {
652
+
lexicon: 1,
653
+
id: 'app.rocksky.album',
654
+
defs: {
655
+
main: {
656
+
type: 'record',
657
+
description: 'A declaration of an album.',
658
+
key: 'tid',
659
+
record: {
660
+
type: 'object',
661
+
required: ['title', 'artist', 'createdAt'],
662
+
properties: {
663
+
title: {
664
+
type: 'string',
665
+
description: 'The title of the album.',
666
+
minLength: 1,
667
+
maxLength: 512,
668
+
},
669
+
artist: {
670
+
type: 'string',
671
+
description: 'The artist of the album.',
672
+
minLength: 1,
673
+
maxLength: 256,
674
+
},
675
+
duration: {
676
+
type: 'integer',
677
+
description: 'The duration of the album in seconds.',
678
+
},
679
+
releaseDate: {
680
+
type: 'string',
681
+
description: 'The release date of the album.',
682
+
format: 'datetime',
683
+
},
684
+
year: {
685
+
type: 'integer',
686
+
description: 'The year the album was released.',
687
+
},
688
+
genre: {
689
+
type: 'string',
690
+
description: 'The genre of the album.',
691
+
maxLength: 256,
692
+
},
693
+
albumArt: {
694
+
type: 'blob',
695
+
description: 'The album art of the album.',
696
+
accept: ['image/png', 'image/jpeg'],
697
+
maxSize: 2000000,
698
+
},
699
+
albumArtUrl: {
700
+
type: 'string',
701
+
description: 'The URL of the album art of the album.',
702
+
format: 'uri',
703
+
},
704
+
tags: {
705
+
type: 'array',
706
+
description: 'The tags of the album.',
707
+
items: {
708
+
type: 'string',
709
+
minLength: 1,
710
+
maxLength: 256,
711
+
},
712
+
},
713
+
youtubeLink: {
714
+
type: 'string',
715
+
description: 'The YouTube link of the album.',
716
+
format: 'uri',
717
+
},
718
+
spotifyLink: {
719
+
type: 'string',
720
+
description: 'The Spotify link of the album.',
721
+
format: 'uri',
722
+
},
723
+
tidalLink: {
724
+
type: 'string',
725
+
description: 'The tidal link of the album.',
726
+
format: 'uri',
727
+
},
728
+
appleMusicLink: {
729
+
type: 'string',
730
+
description: 'The Apple Music link of the album.',
731
+
format: 'uri',
732
+
},
733
+
createdAt: {
734
+
type: 'string',
735
+
description: 'The date and time when the album was created.',
736
+
format: 'datetime',
737
+
},
738
+
},
739
+
},
740
+
},
741
+
},
742
+
},
743
+
AppRockskyAlbumDefs: {
744
+
lexicon: 1,
745
+
id: 'app.rocksky.album.defs',
746
+
defs: {
747
+
albumViewBasic: {
748
+
type: 'object',
749
+
properties: {
750
+
id: {
751
+
type: 'string',
752
+
description: 'The unique identifier of the album.',
753
+
},
754
+
uri: {
755
+
type: 'string',
756
+
description: 'The URI of the album.',
757
+
format: 'at-uri',
758
+
},
759
+
title: {
760
+
type: 'string',
761
+
description: 'The title of the album.',
762
+
},
763
+
artist: {
764
+
type: 'string',
765
+
description: 'The artist of the album.',
766
+
},
767
+
artistUri: {
768
+
type: 'string',
769
+
description: "The URI of the album's artist.",
770
+
format: 'at-uri',
771
+
},
772
+
year: {
773
+
type: 'integer',
774
+
description: 'The year the album was released.',
775
+
},
776
+
albumArt: {
777
+
type: 'string',
778
+
description: 'The URL of the album art image.',
779
+
format: 'uri',
780
+
},
781
+
releaseDate: {
782
+
type: 'string',
783
+
description: 'The release date of the album.',
784
+
},
785
+
sha256: {
786
+
type: 'string',
787
+
description: 'The SHA256 hash of the album.',
788
+
},
789
+
playCount: {
790
+
type: 'integer',
791
+
description: 'The number of times the album has been played.',
792
+
minimum: 0,
793
+
},
794
+
uniqueListeners: {
795
+
type: 'integer',
796
+
description:
797
+
'The number of unique listeners who have played the album.',
798
+
minimum: 0,
799
+
},
800
+
},
801
+
},
802
+
albumViewDetailed: {
803
+
type: 'object',
804
+
properties: {
805
+
id: {
806
+
type: 'string',
807
+
description: 'The unique identifier of the album.',
808
+
},
809
+
uri: {
810
+
type: 'string',
811
+
description: 'The URI of the album.',
812
+
format: 'at-uri',
813
+
},
814
+
title: {
815
+
type: 'string',
816
+
description: 'The title of the album.',
817
+
},
818
+
artist: {
819
+
type: 'string',
820
+
description: 'The artist of the album.',
821
+
},
822
+
artistUri: {
823
+
type: 'string',
824
+
description: "The URI of the album's artist.",
825
+
format: 'at-uri',
826
+
},
827
+
year: {
828
+
type: 'integer',
829
+
description: 'The year the album was released.',
830
+
},
831
+
albumArt: {
832
+
type: 'string',
833
+
description: 'The URL of the album art image.',
834
+
format: 'uri',
835
+
},
836
+
releaseDate: {
837
+
type: 'string',
838
+
description: 'The release date of the album.',
839
+
},
840
+
sha256: {
841
+
type: 'string',
842
+
description: 'The SHA256 hash of the album.',
843
+
},
844
+
playCount: {
845
+
type: 'integer',
846
+
description: 'The number of times the album has been played.',
847
+
minimum: 0,
848
+
},
849
+
uniqueListeners: {
850
+
type: 'integer',
851
+
description:
852
+
'The number of unique listeners who have played the album.',
853
+
minimum: 0,
854
+
},
855
+
tracks: {
856
+
type: 'array',
857
+
items: {
858
+
type: 'ref',
859
+
ref: 'lex:app.rocksky.song.defs.songViewBasic',
860
+
},
861
+
},
862
+
},
863
+
},
864
+
},
865
+
},
866
+
AppRockskyAlbumGetAlbum: {
867
+
lexicon: 1,
868
+
id: 'app.rocksky.album.getAlbum',
869
+
defs: {
870
+
main: {
871
+
type: 'query',
872
+
description: 'Get detailed album view',
873
+
parameters: {
874
+
type: 'params',
875
+
required: ['uri'],
876
+
properties: {
877
+
uri: {
878
+
type: 'string',
879
+
description: 'The URI of the album to retrieve.',
880
+
format: 'at-uri',
881
+
},
882
+
},
883
+
},
884
+
output: {
885
+
encoding: 'application/json',
886
+
schema: {
887
+
type: 'ref',
888
+
ref: 'lex:app.rocksky.album.defs#albumViewDetailed',
889
+
},
890
+
},
891
+
},
892
+
},
893
+
},
894
+
AppRockskyAlbumGetAlbums: {
895
+
lexicon: 1,
896
+
id: 'app.rocksky.album.getAlbums',
897
+
defs: {
898
+
main: {
899
+
type: 'query',
900
+
description: 'Get albums',
901
+
parameters: {
902
+
type: 'params',
903
+
properties: {
904
+
limit: {
905
+
type: 'integer',
906
+
description: 'The maximum number of albums to return',
907
+
minimum: 1,
908
+
},
909
+
offset: {
910
+
type: 'integer',
911
+
description: 'The offset for pagination',
912
+
minimum: 0,
913
+
},
914
+
},
915
+
},
916
+
output: {
917
+
encoding: 'application/json',
918
+
schema: {
919
+
type: 'object',
920
+
properties: {
921
+
albums: {
922
+
type: 'array',
923
+
items: {
924
+
type: 'ref',
925
+
ref: 'lex:app.rocksky.album.defs#albumViewBasic',
926
+
},
927
+
},
928
+
},
929
+
},
930
+
},
931
+
},
932
+
},
933
+
},
934
+
AppRockskyAlbumGetAlbumTracks: {
935
+
lexicon: 1,
936
+
id: 'app.rocksky.album.getAlbumTracks',
937
+
defs: {
938
+
main: {
939
+
type: 'query',
940
+
description: 'Get tracks for an album',
941
+
parameters: {
942
+
type: 'params',
943
+
required: ['uri'],
944
+
properties: {
945
+
uri: {
946
+
type: 'string',
947
+
description: 'The URI of the album to retrieve tracks from',
948
+
format: 'at-uri',
949
+
},
950
+
},
951
+
},
952
+
output: {
953
+
encoding: 'application/json',
954
+
schema: {
955
+
type: 'object',
956
+
properties: {
957
+
tracks: {
958
+
type: 'array',
959
+
items: {
960
+
type: 'ref',
961
+
ref: 'lex:app.rocksky.song.defs#songViewBasic',
962
+
},
963
+
},
964
+
},
965
+
},
966
+
},
967
+
},
968
+
},
969
+
},
970
+
AppRockskyApikeyCreateApikey: {
971
+
lexicon: 1,
972
+
id: 'app.rocksky.apikey.createApikey',
973
+
defs: {
974
+
main: {
975
+
type: 'procedure',
976
+
description: 'Create a new API key for the authenticated user',
977
+
input: {
978
+
encoding: 'application/json',
979
+
schema: {
980
+
type: 'object',
981
+
required: ['name'],
982
+
properties: {
983
+
name: {
984
+
type: 'string',
985
+
description: 'The name of the API key.',
986
+
},
987
+
description: {
988
+
type: 'string',
989
+
description: 'A description for the API key.',
990
+
},
991
+
},
992
+
},
993
+
},
994
+
output: {
995
+
encoding: 'application/json',
996
+
schema: {
997
+
type: 'ref',
998
+
ref: 'lex:app.rocksky.apikey.defs#apiKey',
999
+
},
1000
+
},
1001
+
},
1002
+
},
1003
+
},
1004
+
AppRockskyApikeyDefs: {
1005
+
lexicon: 1,
1006
+
id: 'app.rocksky.apikey.defs',
1007
+
defs: {
1008
+
apiKeyView: {
1009
+
type: 'object',
1010
+
properties: {
1011
+
id: {
1012
+
type: 'string',
1013
+
description: 'The unique identifier of the API key.',
1014
+
},
1015
+
name: {
1016
+
type: 'string',
1017
+
description: 'The name of the API key.',
1018
+
},
1019
+
description: {
1020
+
type: 'string',
1021
+
description: 'A description for the API key.',
1022
+
},
1023
+
createdAt: {
1024
+
type: 'string',
1025
+
description: 'The date and time when the API key was created.',
1026
+
format: 'datetime',
1027
+
},
1028
+
},
1029
+
},
1030
+
},
1031
+
},
1032
+
AppRockskyApikeysDefs: {
1033
+
lexicon: 1,
1034
+
id: 'app.rocksky.apikeys.defs',
1035
+
defs: {},
1036
+
},
1037
+
AppRockskyApikeyGetApikeys: {
1038
+
lexicon: 1,
1039
+
id: 'app.rocksky.apikey.getApikeys',
1040
+
defs: {
1041
+
main: {
1042
+
type: 'query',
1043
+
description: 'Get a list of API keys for the authenticated user',
1044
+
parameters: {
1045
+
type: 'params',
1046
+
properties: {
1047
+
offset: {
1048
+
type: 'integer',
1049
+
description:
1050
+
'The number of API keys to skip before starting to collect the result set.',
1051
+
},
1052
+
limit: {
1053
+
type: 'integer',
1054
+
description: 'The number of API keys to return per page.',
1055
+
},
1056
+
},
1057
+
},
1058
+
output: {
1059
+
encoding: 'application/json',
1060
+
schema: {
1061
+
type: 'object',
1062
+
properties: {
1063
+
apiKeys: {
1064
+
type: 'array',
1065
+
items: {
1066
+
type: 'ref',
1067
+
ref: 'lex:app.rocksky.apikey.defs#apikeyView',
1068
+
},
1069
+
},
1070
+
},
1071
+
},
1072
+
},
1073
+
},
1074
+
},
1075
+
},
1076
+
AppRockskyApikeyRemoveApikey: {
1077
+
lexicon: 1,
1078
+
id: 'app.rocksky.apikey.removeApikey',
1079
+
defs: {
1080
+
main: {
1081
+
type: 'procedure',
1082
+
description: 'Remove an API key for the authenticated user',
1083
+
parameters: {
1084
+
type: 'params',
1085
+
required: ['id'],
1086
+
properties: {
1087
+
id: {
1088
+
type: 'string',
1089
+
description: 'The ID of the API key to remove.',
1090
+
},
1091
+
},
1092
+
},
1093
+
output: {
1094
+
encoding: 'application/json',
1095
+
schema: {
1096
+
type: 'ref',
1097
+
ref: 'lex:app.rocksky.apikey.defs#apiKey',
1098
+
},
1099
+
},
1100
+
},
1101
+
},
1102
+
},
1103
+
AppRockskyApikeyUpdateApikey: {
1104
+
lexicon: 1,
1105
+
id: 'app.rocksky.apikey.updateApikey',
1106
+
defs: {
1107
+
main: {
1108
+
type: 'procedure',
1109
+
description: 'Update an existing API key for the authenticated user',
1110
+
input: {
1111
+
encoding: 'application/json',
1112
+
schema: {
1113
+
type: 'object',
1114
+
required: ['id', 'name'],
1115
+
properties: {
1116
+
id: {
1117
+
type: 'string',
1118
+
description: 'The ID of the API key to update.',
1119
+
},
1120
+
name: {
1121
+
type: 'string',
1122
+
description: 'The new name of the API key.',
1123
+
},
1124
+
description: {
1125
+
type: 'string',
1126
+
description: 'A new description for the API key.',
1127
+
},
1128
+
},
1129
+
},
1130
+
},
1131
+
output: {
1132
+
encoding: 'application/json',
1133
+
schema: {
1134
+
type: 'ref',
1135
+
ref: 'lex:app.rocksky.apikey.defs#apiKey',
1136
+
},
1137
+
},
1138
+
},
1139
+
},
1140
+
},
1141
+
AppRockskyArtist: {
1142
+
lexicon: 1,
1143
+
id: 'app.rocksky.artist',
1144
+
defs: {
1145
+
main: {
1146
+
type: 'record',
1147
+
description: 'A declaration of an artist.',
1148
+
key: 'tid',
1149
+
record: {
1150
+
type: 'object',
1151
+
required: ['name', 'createdAt'],
1152
+
properties: {
1153
+
name: {
1154
+
type: 'string',
1155
+
description: 'The name of the artist.',
1156
+
minLength: 1,
1157
+
maxLength: 512,
1158
+
},
1159
+
bio: {
1160
+
type: 'string',
1161
+
description: 'The biography of the artist.',
1162
+
maxLength: 1000,
1163
+
},
1164
+
picture: {
1165
+
type: 'blob',
1166
+
description: 'The picture of the artist.',
1167
+
accept: ['image/png', 'image/jpeg'],
1168
+
maxSize: 2000000,
1169
+
},
1170
+
pictureUrl: {
1171
+
type: 'string',
1172
+
description: 'The URL of the picture of the artist.',
1173
+
format: 'uri',
1174
+
},
1175
+
tags: {
1176
+
type: 'array',
1177
+
description: 'The tags of the artist.',
1178
+
items: {
1179
+
type: 'string',
1180
+
minLength: 1,
1181
+
maxLength: 256,
1182
+
},
1183
+
},
1184
+
born: {
1185
+
type: 'string',
1186
+
description: 'The birth date of the artist.',
1187
+
format: 'datetime',
1188
+
},
1189
+
died: {
1190
+
type: 'string',
1191
+
description: 'The death date of the artist.',
1192
+
format: 'datetime',
1193
+
},
1194
+
bornIn: {
1195
+
type: 'string',
1196
+
description: 'The birth place of the artist.',
1197
+
maxLength: 256,
1198
+
},
1199
+
createdAt: {
1200
+
type: 'string',
1201
+
description: 'The date when the artist was created.',
1202
+
format: 'datetime',
1203
+
},
1204
+
},
1205
+
},
1206
+
},
1207
+
},
1208
+
},
1209
+
AppRockskyArtistDefs: {
1210
+
lexicon: 1,
1211
+
id: 'app.rocksky.artist.defs',
1212
+
defs: {
1213
+
artistViewBasic: {
1214
+
type: 'object',
1215
+
properties: {
1216
+
id: {
1217
+
type: 'string',
1218
+
description: 'The unique identifier of the artist.',
1219
+
},
1220
+
uri: {
1221
+
type: 'string',
1222
+
description: 'The URI of the artist.',
1223
+
format: 'at-uri',
1224
+
},
1225
+
name: {
1226
+
type: 'string',
1227
+
description: 'The name of the artist.',
1228
+
},
1229
+
picture: {
1230
+
type: 'string',
1231
+
description: 'The picture of the artist.',
1232
+
},
1233
+
sha256: {
1234
+
type: 'string',
1235
+
description: 'The SHA256 hash of the artist.',
1236
+
},
1237
+
playCount: {
1238
+
type: 'integer',
1239
+
description: 'The number of times the artist has been played.',
1240
+
minimum: 0,
1241
+
},
1242
+
uniqueListeners: {
1243
+
type: 'integer',
1244
+
description:
1245
+
'The number of unique listeners who have played the artist.',
1246
+
minimum: 0,
1247
+
},
1248
+
},
1249
+
},
1250
+
artistViewDetailed: {
1251
+
type: 'object',
1252
+
properties: {
1253
+
id: {
1254
+
type: 'string',
1255
+
description: 'The unique identifier of the artist.',
1256
+
},
1257
+
uri: {
1258
+
type: 'string',
1259
+
description: 'The URI of the artist.',
1260
+
format: 'at-uri',
1261
+
},
1262
+
name: {
1263
+
type: 'string',
1264
+
description: 'The name of the artist.',
1265
+
},
1266
+
picture: {
1267
+
type: 'string',
1268
+
description: 'The picture of the artist.',
1269
+
},
1270
+
sha256: {
1271
+
type: 'string',
1272
+
description: 'The SHA256 hash of the artist.',
1273
+
},
1274
+
playCount: {
1275
+
type: 'integer',
1276
+
description: 'The number of times the artist has been played.',
1277
+
minimum: 0,
1278
+
},
1279
+
uniqueListeners: {
1280
+
type: 'integer',
1281
+
description:
1282
+
'The number of unique listeners who have played the artist.',
1283
+
minimum: 0,
1284
+
},
1285
+
},
1286
+
},
1287
+
songViewBasic: {
1288
+
type: 'object',
1289
+
properties: {
1290
+
uri: {
1291
+
type: 'string',
1292
+
description: 'The URI of the song.',
1293
+
format: 'at-uri',
1294
+
},
1295
+
title: {
1296
+
type: 'string',
1297
+
description: 'The title of the song.',
1298
+
},
1299
+
playCount: {
1300
+
type: 'integer',
1301
+
description: 'The number of times the song has been played.',
1302
+
minimum: 0,
1303
+
},
1304
+
},
1305
+
},
1306
+
listenerViewBasic: {
1307
+
type: 'object',
1308
+
properties: {
1309
+
id: {
1310
+
type: 'string',
1311
+
description: 'The unique identifier of the actor.',
1312
+
},
1313
+
did: {
1314
+
type: 'string',
1315
+
description: 'The DID of the listener.',
1316
+
},
1317
+
handle: {
1318
+
type: 'string',
1319
+
description: 'The handle of the listener.',
1320
+
},
1321
+
displayName: {
1322
+
type: 'string',
1323
+
description: 'The display name of the listener.',
1324
+
},
1325
+
avatar: {
1326
+
type: 'string',
1327
+
description: "The URL of the listener's avatar image.",
1328
+
format: 'uri',
1329
+
},
1330
+
mostListenedSong: {
1331
+
type: 'ref',
1332
+
ref: 'lex:app.rocksky.artist.defs#songViewBasic',
1333
+
},
1334
+
totalPlays: {
1335
+
type: 'integer',
1336
+
description: 'The total number of plays by the listener.',
1337
+
minimum: 0,
1338
+
},
1339
+
rank: {
1340
+
type: 'integer',
1341
+
description:
1342
+
'The rank of the listener among all listeners of the artist.',
1343
+
minimum: 1,
1344
+
},
1345
+
},
1346
+
},
1347
+
artistMbid: {
1348
+
type: 'object',
1349
+
properties: {
1350
+
mbid: {
1351
+
type: 'string',
1352
+
description: 'The MusicBrainz Identifier (MBID) of the artist.',
1353
+
},
1354
+
name: {
1355
+
type: 'string',
1356
+
description: 'The name of the artist.',
1357
+
minLength: 1,
1358
+
maxLength: 256,
1359
+
},
1360
+
},
1361
+
},
1362
+
},
1363
+
},
1364
+
AppRockskyArtistGetArtist: {
1365
+
lexicon: 1,
1366
+
id: 'app.rocksky.artist.getArtist',
1367
+
defs: {
1368
+
main: {
1369
+
type: 'query',
1370
+
description: 'Get artist details',
1371
+
parameters: {
1372
+
type: 'params',
1373
+
required: ['uri'],
1374
+
properties: {
1375
+
uri: {
1376
+
type: 'string',
1377
+
description: 'The URI of the artist to retrieve details from',
1378
+
format: 'at-uri',
1379
+
},
1380
+
},
1381
+
},
1382
+
output: {
1383
+
encoding: 'application/json',
1384
+
schema: {
1385
+
type: 'ref',
1386
+
ref: 'lex:app.rocksky.artist.defs#artistViewDetailed',
1387
+
},
1388
+
},
1389
+
},
1390
+
},
1391
+
},
1392
+
AppRockskyArtistGetArtistAlbums: {
1393
+
lexicon: 1,
1394
+
id: 'app.rocksky.artist.getArtistAlbums',
1395
+
defs: {
1396
+
main: {
1397
+
type: 'query',
1398
+
description: "Get artist's albums",
1399
+
parameters: {
1400
+
type: 'params',
1401
+
required: ['uri'],
1402
+
properties: {
1403
+
uri: {
1404
+
type: 'string',
1405
+
description: 'The URI of the artist to retrieve albums from',
1406
+
format: 'at-uri',
1407
+
},
1408
+
},
1409
+
},
1410
+
output: {
1411
+
encoding: 'application/json',
1412
+
schema: {
1413
+
type: 'object',
1414
+
properties: {
1415
+
albums: {
1416
+
type: 'array',
1417
+
items: {
1418
+
type: 'ref',
1419
+
ref: 'lex:app.rocksky.album.defs#albumViewBasic',
1420
+
},
1421
+
},
1422
+
},
1423
+
},
1424
+
},
1425
+
},
1426
+
},
1427
+
},
1428
+
AppRockskyArtistGetArtistListeners: {
1429
+
lexicon: 1,
1430
+
id: 'app.rocksky.artist.getArtistListeners',
1431
+
defs: {
1432
+
main: {
1433
+
type: 'query',
1434
+
description: 'Get artist listeners',
1435
+
parameters: {
1436
+
type: 'params',
1437
+
required: ['uri'],
1438
+
properties: {
1439
+
uri: {
1440
+
type: 'string',
1441
+
description: 'The URI of the artist to retrieve listeners from',
1442
+
format: 'at-uri',
1443
+
},
1444
+
offset: {
1445
+
type: 'integer',
1446
+
description: 'Number of items to skip before returning results',
1447
+
},
1448
+
limit: {
1449
+
type: 'integer',
1450
+
description: 'Maximum number of results to return',
1451
+
},
1452
+
},
1453
+
},
1454
+
output: {
1455
+
encoding: 'application/json',
1456
+
schema: {
1457
+
type: 'object',
1458
+
properties: {
1459
+
listeners: {
1460
+
type: 'array',
1461
+
items: {
1462
+
type: 'ref',
1463
+
ref: 'lex:app.rocksky.artist.defs#listenerViewBasic',
1464
+
},
1465
+
},
1466
+
},
1467
+
},
1468
+
},
1469
+
},
1470
+
},
1471
+
},
1472
+
AppRockskyArtistGetArtists: {
1473
+
lexicon: 1,
1474
+
id: 'app.rocksky.artist.getArtists',
1475
+
defs: {
1476
+
main: {
1477
+
type: 'query',
1478
+
description: 'Get artists',
1479
+
parameters: {
1480
+
type: 'params',
1481
+
properties: {
1482
+
limit: {
1483
+
type: 'integer',
1484
+
description: 'The maximum number of artists to return',
1485
+
minimum: 1,
1486
+
},
1487
+
offset: {
1488
+
type: 'integer',
1489
+
description: 'The offset for pagination',
1490
+
minimum: 0,
1491
+
},
1492
+
names: {
1493
+
type: 'string',
1494
+
description: 'The names of the artists to return',
1495
+
},
1496
+
},
1497
+
},
1498
+
output: {
1499
+
encoding: 'application/json',
1500
+
schema: {
1501
+
type: 'object',
1502
+
properties: {
1503
+
artists: {
1504
+
type: 'array',
1505
+
items: {
1506
+
type: 'ref',
1507
+
ref: 'lex:app.rocksky.artist.defs#artistViewBasic',
1508
+
},
1509
+
},
1510
+
},
1511
+
},
1512
+
},
1513
+
},
1514
+
},
1515
+
},
1516
+
AppRockskyArtistGetArtistTracks: {
1517
+
lexicon: 1,
1518
+
id: 'app.rocksky.artist.getArtistTracks',
1519
+
defs: {
1520
+
main: {
1521
+
type: 'query',
1522
+
description: "Get artist's tracks",
1523
+
parameters: {
1524
+
type: 'params',
1525
+
properties: {
1526
+
uri: {
1527
+
type: 'string',
1528
+
description: 'The URI of the artist to retrieve albums from',
1529
+
format: 'at-uri',
1530
+
},
1531
+
limit: {
1532
+
type: 'integer',
1533
+
description: 'The maximum number of tracks to return',
1534
+
minimum: 1,
1535
+
},
1536
+
offset: {
1537
+
type: 'integer',
1538
+
description: 'The offset for pagination',
1539
+
minimum: 0,
1540
+
},
1541
+
},
1542
+
},
1543
+
output: {
1544
+
encoding: 'application/json',
1545
+
schema: {
1546
+
type: 'object',
1547
+
properties: {
1548
+
tracks: {
1549
+
type: 'array',
1550
+
items: {
1551
+
type: 'ref',
1552
+
ref: 'lex:app.rocksky.song.defs#songViewBasic',
1553
+
},
1554
+
},
1555
+
},
1556
+
},
1557
+
},
1558
+
},
1559
+
},
1560
+
},
1561
+
AppRockskyChartsDefs: {
1562
+
lexicon: 1,
1563
+
id: 'app.rocksky.charts.defs',
1564
+
defs: {
1565
+
chartsView: {
1566
+
type: 'object',
1567
+
properties: {
1568
+
scrobbles: {
1569
+
type: 'array',
1570
+
items: {
1571
+
type: 'ref',
1572
+
ref: 'lex:app.rocksky.charts.defs#scrobbleViewBasic',
1573
+
},
1574
+
},
1575
+
},
1576
+
},
1577
+
scrobbleViewBasic: {
1578
+
type: 'object',
1579
+
properties: {
1580
+
date: {
1581
+
type: 'string',
1582
+
description: 'The date of the scrobble.',
1583
+
format: 'datetime',
1584
+
},
1585
+
count: {
1586
+
type: 'integer',
1587
+
description: 'The number of scrobbles on this date.',
1588
+
},
1589
+
},
1590
+
},
1591
+
},
1592
+
},
1593
+
AppRockskyChartsGetScrobblesChart: {
1594
+
lexicon: 1,
1595
+
id: 'app.rocksky.charts.getScrobblesChart',
1596
+
defs: {
1597
+
main: {
1598
+
type: 'query',
1599
+
description: 'Get the scrobbles chart',
1600
+
parameters: {
1601
+
type: 'params',
1602
+
properties: {
1603
+
did: {
1604
+
type: 'string',
1605
+
description: 'The DID or handle of the actor',
1606
+
format: 'at-identifier',
1607
+
},
1608
+
artisturi: {
1609
+
type: 'string',
1610
+
description: 'The URI of the artist to filter by',
1611
+
format: 'at-uri',
1612
+
},
1613
+
albumuri: {
1614
+
type: 'string',
1615
+
description: 'The URI of the album to filter by',
1616
+
format: 'at-uri',
1617
+
},
1618
+
songuri: {
1619
+
type: 'string',
1620
+
description: 'The URI of the track to filter by',
1621
+
format: 'at-uri',
1622
+
},
1623
+
},
1624
+
},
1625
+
output: {
1626
+
encoding: 'application/json',
1627
+
schema: {
1628
+
type: 'ref',
1629
+
ref: 'lex:app.rocksky.charts.defs#chartsView',
1630
+
},
1631
+
},
1632
+
},
1633
+
},
1634
+
},
1635
+
AppRockskyDropboxDefs: {
1636
+
lexicon: 1,
1637
+
id: 'app.rocksky.dropbox.defs',
1638
+
defs: {
1639
+
fileView: {
1640
+
type: 'object',
1641
+
properties: {
1642
+
id: {
1643
+
type: 'string',
1644
+
description: 'The unique identifier of the file.',
1645
+
},
1646
+
name: {
1647
+
type: 'string',
1648
+
description: 'The name of the file.',
1649
+
},
1650
+
pathLower: {
1651
+
type: 'string',
1652
+
description: 'The lowercased path of the file.',
1653
+
},
1654
+
pathDisplay: {
1655
+
type: 'string',
1656
+
description: 'The display path of the file.',
1657
+
},
1658
+
clientModified: {
1659
+
type: 'string',
1660
+
description:
1661
+
'The last modified date and time of the file on the client.',
1662
+
format: 'datetime',
1663
+
},
1664
+
serverModified: {
1665
+
type: 'string',
1666
+
description:
1667
+
'The last modified date and time of the file on the server.',
1668
+
format: 'datetime',
1669
+
},
1670
+
},
1671
+
},
1672
+
fileListView: {
1673
+
type: 'object',
1674
+
properties: {
1675
+
files: {
1676
+
type: 'array',
1677
+
description: 'A list of files in the Dropbox.',
1678
+
items: {
1679
+
type: 'ref',
1680
+
ref: 'lex:app.rocksky.dropbox.defs#fileView',
1681
+
},
1682
+
},
1683
+
},
1684
+
},
1685
+
temporaryLinkView: {
1686
+
type: 'object',
1687
+
properties: {
1688
+
link: {
1689
+
type: 'string',
1690
+
description: 'The temporary link to access the file.',
1691
+
format: 'uri',
1692
+
},
1693
+
},
1694
+
},
1695
+
},
1696
+
},
1697
+
AppRockskyDropboxDownloadFile: {
1698
+
lexicon: 1,
1699
+
id: 'app.rocksky.dropbox.downloadFile',
1700
+
defs: {
1701
+
main: {
1702
+
type: 'query',
1703
+
description: 'Download a file from Dropbox by its unique identifier',
1704
+
parameters: {
1705
+
type: 'params',
1706
+
required: ['fileId'],
1707
+
properties: {
1708
+
fileId: {
1709
+
type: 'string',
1710
+
description: 'The unique identifier of the file to download',
1711
+
},
1712
+
},
1713
+
},
1714
+
output: {
1715
+
encoding: 'application/octet-stream',
1716
+
},
1717
+
},
1718
+
},
1719
+
},
1720
+
AppRockskyDropboxGetFiles: {
1721
+
lexicon: 1,
1722
+
id: 'app.rocksky.dropbox.getFiles',
1723
+
defs: {
1724
+
main: {
1725
+
type: 'query',
1726
+
description: 'Retrieve a list of files from Dropbox',
1727
+
parameters: {
1728
+
type: 'params',
1729
+
properties: {
1730
+
at: {
1731
+
type: 'string',
1732
+
description: 'Path to the Dropbox folder or root directory',
1733
+
},
1734
+
},
1735
+
},
1736
+
output: {
1737
+
encoding: 'application/json',
1738
+
schema: {
1739
+
type: 'ref',
1740
+
ref: 'lex:app.rocksky.dropbox.defs#fileListView',
1741
+
},
1742
+
},
1743
+
},
1744
+
},
1745
+
},
1746
+
AppRockskyDropboxGetMetadata: {
1747
+
lexicon: 1,
1748
+
id: 'app.rocksky.dropbox.getMetadata',
1749
+
defs: {
1750
+
main: {
1751
+
type: 'query',
1752
+
description: 'Retrieve metadata of a file or folder in Dropbox',
1753
+
parameters: {
1754
+
type: 'params',
1755
+
required: ['path'],
1756
+
properties: {
1757
+
path: {
1758
+
type: 'string',
1759
+
description: 'Path to the file or folder in Dropbox',
1760
+
},
1761
+
},
1762
+
},
1763
+
output: {
1764
+
encoding: 'application/json',
1765
+
schema: {
1766
+
type: 'ref',
1767
+
ref: 'lex:app.rocksky.dropbox.defs#fileView',
1768
+
},
1769
+
},
1770
+
},
1771
+
},
1772
+
},
1773
+
AppRockskyDropboxGetTemporaryLink: {
1774
+
lexicon: 1,
1775
+
id: 'app.rocksky.dropbox.getTemporaryLink',
1776
+
defs: {
1777
+
main: {
1778
+
type: 'query',
1779
+
description: 'Retrieve a temporary link to access a file in Dropbox',
1780
+
parameters: {
1781
+
type: 'params',
1782
+
required: ['path'],
1783
+
properties: {
1784
+
path: {
1785
+
type: 'string',
1786
+
description: 'Path to the file in Dropbox',
1787
+
},
1788
+
},
1789
+
},
1790
+
output: {
1791
+
encoding: 'application/json',
1792
+
schema: {
1793
+
type: 'ref',
1794
+
ref: 'lex:app.rocksky.dropbox.defs#temporaryLinkView',
1795
+
},
1796
+
},
1797
+
},
1798
+
},
1799
+
},
1800
+
AppRockskyFeedDefs: {
1801
+
lexicon: 1,
1802
+
id: 'app.rocksky.feed.defs',
1803
+
defs: {
1804
+
searchResultsView: {
1805
+
type: 'object',
1806
+
properties: {
1807
+
hits: {
1808
+
type: 'array',
1809
+
items: {
1810
+
type: 'union',
1811
+
refs: [
1812
+
'lex:app.rocksky.song.defs#songViewBasic',
1813
+
'lex:app.rocksky.album.defs#albumViewBasic',
1814
+
'lex:app.rocksky.artist.defs#artistViewBasic',
1815
+
'lex:app.rocksky.playlist.defs#playlistViewBasic',
1816
+
'lex:app.rocksky.actor.defs#profileViewBasic',
1817
+
],
1818
+
},
1819
+
},
1820
+
processingTimeMs: {
1821
+
type: 'integer',
1822
+
},
1823
+
limit: {
1824
+
type: 'integer',
1825
+
},
1826
+
offset: {
1827
+
type: 'integer',
1828
+
},
1829
+
estimatedTotalHits: {
1830
+
type: 'integer',
1831
+
},
1832
+
},
1833
+
},
1834
+
nowPlayingView: {
1835
+
type: 'object',
1836
+
properties: {
1837
+
album: {
1838
+
type: 'string',
1839
+
},
1840
+
albumArt: {
1841
+
type: 'string',
1842
+
format: 'uri',
1843
+
},
1844
+
albumArtist: {
1845
+
type: 'string',
1846
+
},
1847
+
albumUri: {
1848
+
type: 'string',
1849
+
format: 'at-uri',
1850
+
},
1851
+
artist: {
1852
+
type: 'string',
1853
+
},
1854
+
artistUri: {
1855
+
type: 'string',
1856
+
format: 'at-uri',
1857
+
},
1858
+
avatar: {
1859
+
type: 'string',
1860
+
format: 'uri',
1861
+
},
1862
+
createdAt: {
1863
+
type: 'string',
1864
+
},
1865
+
did: {
1866
+
type: 'string',
1867
+
format: 'at-identifier',
1868
+
},
1869
+
handle: {
1870
+
type: 'string',
1871
+
},
1872
+
id: {
1873
+
type: 'string',
1874
+
},
1875
+
title: {
1876
+
type: 'string',
1877
+
},
1878
+
trackId: {
1879
+
type: 'string',
1880
+
},
1881
+
trackUri: {
1882
+
type: 'string',
1883
+
format: 'at-uri',
1884
+
},
1885
+
uri: {
1886
+
type: 'string',
1887
+
format: 'at-uri',
1888
+
},
1889
+
},
1890
+
},
1891
+
nowPlayingsView: {
1892
+
type: 'object',
1893
+
properties: {
1894
+
nowPlayings: {
1895
+
type: 'array',
1896
+
items: {
1897
+
type: 'ref',
1898
+
ref: 'lex:app.rocksky.feed.defs#nowPlayingView',
1899
+
},
1900
+
},
1901
+
},
1902
+
},
1903
+
feedGeneratorsView: {
1904
+
type: 'object',
1905
+
properties: {
1906
+
feeds: {
1907
+
type: 'array',
1908
+
items: {
1909
+
type: 'ref',
1910
+
ref: 'lex:app.rocksky.feed.defs#feedGeneratorView',
1911
+
},
1912
+
},
1913
+
},
1914
+
},
1915
+
feedGeneratorView: {
1916
+
type: 'object',
1917
+
properties: {
1918
+
id: {
1919
+
type: 'string',
1920
+
},
1921
+
name: {
1922
+
type: 'string',
1923
+
},
1924
+
description: {
1925
+
type: 'string',
1926
+
},
1927
+
uri: {
1928
+
type: 'string',
1929
+
format: 'at-uri',
1930
+
},
1931
+
avatar: {
1932
+
type: 'string',
1933
+
format: 'uri',
1934
+
},
1935
+
creator: {
1936
+
type: 'ref',
1937
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
1938
+
},
1939
+
},
1940
+
},
1941
+
feedUriView: {
1942
+
type: 'object',
1943
+
properties: {
1944
+
uri: {
1945
+
type: 'string',
1946
+
description: 'The feed URI.',
1947
+
format: 'at-uri',
1948
+
},
1949
+
},
1950
+
},
1951
+
feedItemView: {
1952
+
type: 'object',
1953
+
properties: {
1954
+
scrobble: {
1955
+
type: 'ref',
1956
+
ref: 'lex:app.rocksky.scrobble.defs#scrobbleViewBasic',
1957
+
},
1958
+
},
1959
+
},
1960
+
feedView: {
1961
+
type: 'object',
1962
+
properties: {
1963
+
feed: {
1964
+
type: 'array',
1965
+
items: {
1966
+
type: 'ref',
1967
+
ref: 'lex:app.rocksky.feed.defs#feedItemView',
1968
+
},
1969
+
},
1970
+
cursor: {
1971
+
type: 'string',
1972
+
description: 'The pagination cursor for the next set of results.',
1973
+
},
1974
+
},
1975
+
},
1976
+
},
1977
+
},
1978
+
AppRockskyFeedDescribeFeedGenerator: {
1979
+
lexicon: 1,
1980
+
id: 'app.rocksky.feed.describeFeedGenerator',
1981
+
defs: {
1982
+
main: {
1983
+
type: 'query',
1984
+
description: 'Get information about a feed generator',
1985
+
parameters: {
1986
+
type: 'params',
1987
+
properties: {},
1988
+
},
1989
+
output: {
1990
+
encoding: 'application/json',
1991
+
schema: {
1992
+
type: 'object',
1993
+
properties: {
1994
+
did: {
1995
+
type: 'string',
1996
+
description: 'The DID of the feed generator.',
1997
+
format: 'at-identifier',
1998
+
},
1999
+
feeds: {
2000
+
type: 'array',
2001
+
description:
2002
+
'List of feed URIs generated by this feed generator.',
2003
+
items: {
2004
+
type: 'ref',
2005
+
ref: 'lex:app.rocksky.feed.defs#feedUriView',
2006
+
},
2007
+
},
2008
+
},
2009
+
},
2010
+
},
2011
+
},
2012
+
},
2013
+
},
2014
+
AppRockskyFeedGenerator: {
2015
+
lexicon: 1,
2016
+
id: 'app.rocksky.feed.generator',
2017
+
defs: {
2018
+
main: {
2019
+
type: 'record',
2020
+
description:
2021
+
'Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.',
2022
+
key: 'tid',
2023
+
record: {
2024
+
type: 'object',
2025
+
required: ['did', 'displayName', 'createdAt'],
2026
+
properties: {
2027
+
did: {
2028
+
type: 'string',
2029
+
format: 'did',
2030
+
},
2031
+
avatar: {
2032
+
type: 'blob',
2033
+
accept: ['image/png', 'image/jpeg'],
2034
+
maxSize: 1000000,
2035
+
},
2036
+
displayName: {
2037
+
type: 'string',
2038
+
maxGraphemes: 24,
2039
+
maxLength: 240,
2040
+
},
2041
+
description: {
2042
+
type: 'string',
2043
+
maxGraphemes: 300,
2044
+
maxLength: 3000,
2045
+
},
2046
+
createdAt: {
2047
+
type: 'string',
2048
+
format: 'datetime',
2049
+
},
2050
+
},
2051
+
},
2052
+
},
2053
+
},
2054
+
},
2055
+
AppRockskyFeedGetFeed: {
2056
+
lexicon: 1,
2057
+
id: 'app.rocksky.feed.getFeed',
2058
+
defs: {
2059
+
main: {
2060
+
type: 'query',
2061
+
description: 'Get the feed by uri',
2062
+
parameters: {
2063
+
type: 'params',
2064
+
required: ['feed'],
2065
+
properties: {
2066
+
feed: {
2067
+
type: 'string',
2068
+
description: 'The feed URI.',
2069
+
format: 'at-uri',
2070
+
},
2071
+
limit: {
2072
+
type: 'integer',
2073
+
description: 'The maximum number of scrobbles to return',
2074
+
minimum: 1,
2075
+
},
2076
+
cursor: {
2077
+
type: 'string',
2078
+
description: 'The cursor for pagination',
2079
+
},
2080
+
},
2081
+
},
2082
+
output: {
2083
+
encoding: 'application/json',
2084
+
schema: {
2085
+
type: 'ref',
2086
+
ref: 'lex:app.rocksky.feed.defs#feedView',
2087
+
},
2088
+
},
2089
+
},
2090
+
},
2091
+
},
2092
+
AppRockskyFeedGetFeedGenerator: {
2093
+
lexicon: 1,
2094
+
id: 'app.rocksky.feed.getFeedGenerator',
2095
+
defs: {
2096
+
main: {
2097
+
type: 'query',
2098
+
description: 'Get information about a feed generator',
2099
+
parameters: {
2100
+
type: 'params',
2101
+
required: ['feed'],
2102
+
properties: {
2103
+
feed: {
2104
+
type: 'string',
2105
+
description: 'AT-URI of the feed generator record.',
2106
+
format: 'at-uri',
2107
+
},
2108
+
},
2109
+
},
2110
+
output: {
2111
+
encoding: 'application/json',
2112
+
schema: {
2113
+
type: 'object',
2114
+
properties: {
2115
+
view: {
2116
+
type: 'ref',
2117
+
ref: 'lex:app.rocksky.feed.defs#feedGeneratorView',
2118
+
},
2119
+
},
2120
+
},
2121
+
},
2122
+
},
2123
+
},
2124
+
},
2125
+
AppRockskyFeedGetFeedGenerators: {
2126
+
lexicon: 1,
2127
+
id: 'app.rocksky.feed.getFeedGenerators',
2128
+
defs: {
2129
+
main: {
2130
+
type: 'query',
2131
+
description: 'Get all feed generators',
2132
+
parameters: {
2133
+
type: 'params',
2134
+
properties: {
2135
+
size: {
2136
+
type: 'integer',
2137
+
description: 'The maximum number of feed generators to return.',
2138
+
minimum: 1,
2139
+
},
2140
+
},
2141
+
},
2142
+
output: {
2143
+
encoding: 'application/json',
2144
+
schema: {
2145
+
type: 'ref',
2146
+
ref: 'lex:app.rocksky.feed.defs#feedGeneratorsView',
2147
+
},
2148
+
},
2149
+
},
2150
+
},
2151
+
},
2152
+
AppRockskyFeedGetFeedSkeleton: {
2153
+
lexicon: 1,
2154
+
id: 'app.rocksky.feed.getFeedSkeleton',
2155
+
defs: {
2156
+
main: {
2157
+
type: 'query',
2158
+
description: 'Get the feed by uri',
2159
+
parameters: {
2160
+
type: 'params',
2161
+
required: ['feed'],
2162
+
properties: {
2163
+
feed: {
2164
+
type: 'string',
2165
+
description: 'The feed URI.',
2166
+
format: 'at-uri',
2167
+
},
2168
+
limit: {
2169
+
type: 'integer',
2170
+
description: 'The maximum number of scrobbles to return',
2171
+
minimum: 1,
2172
+
},
2173
+
offset: {
2174
+
type: 'integer',
2175
+
description: 'The offset for pagination',
2176
+
minimum: 0,
2177
+
},
2178
+
cursor: {
2179
+
type: 'string',
2180
+
description: 'The pagination cursor.',
2181
+
},
2182
+
},
2183
+
},
2184
+
output: {
2185
+
encoding: 'application/json',
2186
+
schema: {
2187
+
type: 'object',
2188
+
properties: {
2189
+
scrobbles: {
2190
+
type: 'array',
2191
+
items: {
2192
+
type: 'ref',
2193
+
ref: 'lex:app.rocksky.scrobble.defs#scrobbleViewBasic',
2194
+
},
2195
+
},
2196
+
cursor: {
2197
+
type: 'string',
2198
+
description:
2199
+
'The pagination cursor for the next set of results.',
2200
+
},
2201
+
},
2202
+
},
2203
+
},
2204
+
},
2205
+
},
2206
+
},
2207
+
AppRockskyFeedGetNowPlayings: {
2208
+
lexicon: 1,
2209
+
id: 'app.rocksky.feed.getNowPlayings',
2210
+
defs: {
2211
+
main: {
2212
+
type: 'query',
2213
+
description: 'Get all currently playing tracks by users',
2214
+
parameters: {
2215
+
type: 'params',
2216
+
properties: {
2217
+
size: {
2218
+
type: 'integer',
2219
+
description:
2220
+
'The maximum number of now playing tracks to return.',
2221
+
minimum: 1,
2222
+
},
2223
+
},
2224
+
},
2225
+
output: {
2226
+
encoding: 'application/json',
2227
+
schema: {
2228
+
type: 'ref',
2229
+
ref: 'lex:app.rocksky.feed.defs#nowPlayingsView',
2230
+
},
2231
+
},
2232
+
},
2233
+
},
2234
+
},
2235
+
AppRockskyFeedSearch: {
2236
+
lexicon: 1,
2237
+
id: 'app.rocksky.feed.search',
2238
+
defs: {
2239
+
main: {
2240
+
type: 'query',
2241
+
description: 'Search for content in the feed',
2242
+
parameters: {
2243
+
type: 'params',
2244
+
required: ['query'],
2245
+
properties: {
2246
+
query: {
2247
+
type: 'string',
2248
+
description: 'The search query string',
2249
+
},
2250
+
},
2251
+
},
2252
+
output: {
2253
+
encoding: 'application/json',
2254
+
schema: {
2255
+
type: 'ref',
2256
+
ref: 'lex:app.rocksky.feed.defs#searchResultsView',
2257
+
},
2258
+
},
2259
+
},
2260
+
},
2261
+
},
2262
+
AppRockskyGoogledriveDefs: {
2263
+
lexicon: 1,
2264
+
id: 'app.rocksky.googledrive.defs',
2265
+
defs: {
2266
+
fileView: {
2267
+
type: 'object',
2268
+
properties: {
2269
+
id: {
2270
+
type: 'string',
2271
+
description: 'The unique identifier of the file.',
2272
+
},
2273
+
},
2274
+
},
2275
+
fileListView: {
2276
+
type: 'object',
2277
+
properties: {
2278
+
files: {
2279
+
type: 'array',
2280
+
items: {
2281
+
type: 'ref',
2282
+
ref: 'lex:app.rocksky.googledrive.defs#fileView',
2283
+
},
2284
+
},
2285
+
},
2286
+
},
2287
+
},
2288
+
},
2289
+
AppRockskyGoogledriveDownloadFile: {
2290
+
lexicon: 1,
2291
+
id: 'app.rocksky.googledrive.downloadFile',
2292
+
defs: {
2293
+
main: {
2294
+
type: 'query',
2295
+
description:
2296
+
'Download a file from Google Drive by its unique identifier',
2297
+
parameters: {
2298
+
type: 'params',
2299
+
required: ['fileId'],
2300
+
properties: {
2301
+
fileId: {
2302
+
type: 'string',
2303
+
description: 'The unique identifier of the file to download',
2304
+
},
2305
+
},
2306
+
},
2307
+
output: {
2308
+
encoding: 'application/octet-stream',
2309
+
},
2310
+
},
2311
+
},
2312
+
},
2313
+
AppRockskyGoogledriveGetFile: {
2314
+
lexicon: 1,
2315
+
id: 'app.rocksky.googledrive.getFile',
2316
+
defs: {
2317
+
main: {
2318
+
type: 'query',
2319
+
description: 'Get a file from Google Drive by its unique identifier',
2320
+
parameters: {
2321
+
type: 'params',
2322
+
required: ['fileId'],
2323
+
properties: {
2324
+
fileId: {
2325
+
type: 'string',
2326
+
description: 'The unique identifier of the file to retrieve',
2327
+
},
2328
+
},
2329
+
},
2330
+
output: {
2331
+
encoding: 'application/json',
2332
+
schema: {
2333
+
type: 'ref',
2334
+
ref: 'lex:app.rocksky.googledrive.defs#fileView',
2335
+
},
2336
+
},
2337
+
},
2338
+
},
2339
+
},
2340
+
AppRockskyGoogledriveGetFiles: {
2341
+
lexicon: 1,
2342
+
id: 'app.rocksky.googledrive.getFiles',
2343
+
defs: {
2344
+
main: {
2345
+
type: 'query',
2346
+
description: 'Get a list of files from Google Drive',
2347
+
parameters: {
2348
+
type: 'params',
2349
+
properties: {
2350
+
at: {
2351
+
type: 'string',
2352
+
description: 'Path to the Google Drive folder or root directory',
2353
+
},
2354
+
},
2355
+
},
2356
+
output: {
2357
+
encoding: 'application/json',
2358
+
schema: {
2359
+
type: 'ref',
2360
+
ref: 'lex:app.rocksky.googledrive.defs#fileListView',
2361
+
},
2362
+
},
2363
+
},
2364
+
},
2365
+
},
2366
+
AppRockskyGraphDefs: {
2367
+
lexicon: 1,
2368
+
id: 'app.rocksky.graph.defs',
2369
+
defs: {
2370
+
notFoundActor: {
2371
+
type: 'object',
2372
+
description: 'indicates that a handle or DID could not be resolved',
2373
+
required: ['actor', 'notFound'],
2374
+
properties: {
2375
+
actor: {
2376
+
type: 'string',
2377
+
format: 'at-identifier',
2378
+
},
2379
+
notFound: {
2380
+
type: 'boolean',
2381
+
},
2382
+
},
2383
+
},
2384
+
relationship: {
2385
+
type: 'object',
2386
+
required: ['did'],
2387
+
properties: {
2388
+
did: {
2389
+
type: 'string',
2390
+
format: 'did',
2391
+
},
2392
+
following: {
2393
+
type: 'string',
2394
+
description:
2395
+
'if the actor follows this DID, this is the AT-URI of the follow record',
2396
+
format: 'at-uri',
2397
+
},
2398
+
followedBy: {
2399
+
type: 'string',
2400
+
description:
2401
+
'if the actor is followed by this DID, contains the AT-URI of the follow record',
2402
+
format: 'at-uri',
2403
+
},
2404
+
},
2405
+
},
2406
+
},
2407
+
},
2408
+
AppRockskyGraphFollow: {
2409
+
lexicon: 1,
2410
+
id: 'app.rocksky.graph.follow',
2411
+
defs: {
2412
+
main: {
2413
+
type: 'record',
2414
+
description:
2415
+
"Record declaring a social 'follow' relationship of another account.",
2416
+
key: 'tid',
2417
+
record: {
2418
+
type: 'object',
2419
+
required: ['createdAt', 'subject'],
2420
+
properties: {
2421
+
createdAt: {
2422
+
type: 'string',
2423
+
format: 'datetime',
2424
+
},
2425
+
subject: {
2426
+
type: 'string',
2427
+
format: 'did',
2428
+
},
2429
+
via: {
2430
+
type: 'ref',
2431
+
ref: 'lex:com.atproto.repo.strongRef',
2432
+
},
2433
+
},
2434
+
},
2435
+
},
2436
+
},
2437
+
},
2438
+
AppRockskyGraphFollowAccount: {
2439
+
lexicon: 1,
2440
+
id: 'app.rocksky.graph.followAccount',
2441
+
defs: {
2442
+
main: {
2443
+
type: 'procedure',
2444
+
description:
2445
+
"Creates a 'follow' relationship from the authenticated account to a specified account.",
2446
+
parameters: {
2447
+
type: 'params',
2448
+
required: ['account'],
2449
+
properties: {
2450
+
account: {
2451
+
type: 'string',
2452
+
format: 'at-identifier',
2453
+
},
2454
+
},
2455
+
},
2456
+
output: {
2457
+
encoding: 'application/json',
2458
+
schema: {
2459
+
type: 'object',
2460
+
required: ['subject', 'followers'],
2461
+
properties: {
2462
+
subject: {
2463
+
type: 'ref',
2464
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2465
+
},
2466
+
followers: {
2467
+
type: 'array',
2468
+
items: {
2469
+
type: 'ref',
2470
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2471
+
},
2472
+
},
2473
+
cursor: {
2474
+
type: 'string',
2475
+
description:
2476
+
'A cursor value to pass to subsequent calls to get the next page of results.',
2477
+
},
2478
+
},
2479
+
},
2480
+
},
2481
+
},
2482
+
},
2483
+
},
2484
+
AppRockskyGraphGetFollowers: {
2485
+
lexicon: 1,
2486
+
id: 'app.rocksky.graph.getFollowers',
2487
+
defs: {
2488
+
main: {
2489
+
type: 'query',
2490
+
description:
2491
+
'Enumerates accounts which follow a specified account (actor).',
2492
+
parameters: {
2493
+
type: 'params',
2494
+
required: ['actor'],
2495
+
properties: {
2496
+
actor: {
2497
+
type: 'string',
2498
+
format: 'at-identifier',
2499
+
},
2500
+
limit: {
2501
+
type: 'integer',
2502
+
maximum: 100,
2503
+
minimum: 1,
2504
+
default: 50,
2505
+
},
2506
+
dids: {
2507
+
type: 'array',
2508
+
description:
2509
+
'If provided, filters the followers to only include those with DIDs in this list.',
2510
+
items: {
2511
+
type: 'string',
2512
+
format: 'did',
2513
+
},
2514
+
},
2515
+
cursor: {
2516
+
type: 'string',
2517
+
},
2518
+
},
2519
+
},
2520
+
output: {
2521
+
encoding: 'application/json',
2522
+
schema: {
2523
+
type: 'object',
2524
+
required: ['subject', 'followers'],
2525
+
properties: {
2526
+
subject: {
2527
+
type: 'ref',
2528
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2529
+
},
2530
+
followers: {
2531
+
type: 'array',
2532
+
items: {
2533
+
type: 'ref',
2534
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2535
+
},
2536
+
},
2537
+
cursor: {
2538
+
type: 'string',
2539
+
description:
2540
+
'A cursor value to pass to subsequent calls to get the next page of results.',
2541
+
},
2542
+
count: {
2543
+
type: 'integer',
2544
+
description: 'The total number of followers.',
2545
+
},
2546
+
},
2547
+
},
2548
+
},
2549
+
},
2550
+
},
2551
+
},
2552
+
AppRockskyGraphGetFollows: {
2553
+
lexicon: 1,
2554
+
id: 'app.rocksky.graph.getFollows',
2555
+
defs: {
2556
+
main: {
2557
+
type: 'query',
2558
+
description:
2559
+
'Enumerates accounts which a specified account (actor) follows.',
2560
+
parameters: {
2561
+
type: 'params',
2562
+
required: ['actor'],
2563
+
properties: {
2564
+
actor: {
2565
+
type: 'string',
2566
+
format: 'at-identifier',
2567
+
},
2568
+
limit: {
2569
+
type: 'integer',
2570
+
maximum: 100,
2571
+
minimum: 1,
2572
+
default: 50,
2573
+
},
2574
+
dids: {
2575
+
type: 'array',
2576
+
description:
2577
+
'If provided, filters the follows to only include those with DIDs in this list.',
2578
+
items: {
2579
+
type: 'string',
2580
+
format: 'did',
2581
+
},
2582
+
},
2583
+
cursor: {
2584
+
type: 'string',
2585
+
},
2586
+
},
2587
+
},
2588
+
output: {
2589
+
encoding: 'application/json',
2590
+
schema: {
2591
+
type: 'object',
2592
+
required: ['subject', 'follows'],
2593
+
properties: {
2594
+
subject: {
2595
+
type: 'ref',
2596
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2597
+
},
2598
+
follows: {
2599
+
type: 'array',
2600
+
items: {
2601
+
type: 'ref',
2602
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2603
+
},
2604
+
},
2605
+
cursor: {
2606
+
type: 'string',
2607
+
description:
2608
+
'A cursor value to pass to subsequent calls to get the next page of results.',
2609
+
},
2610
+
count: {
2611
+
type: 'integer',
2612
+
description: 'The total number of follows.',
2613
+
},
2614
+
},
2615
+
},
2616
+
},
2617
+
},
2618
+
},
2619
+
},
2620
+
AppRockskyGraphGetKnownFollowers: {
2621
+
lexicon: 1,
2622
+
id: 'app.rocksky.graph.getKnownFollowers',
2623
+
defs: {
2624
+
main: {
2625
+
type: 'query',
2626
+
description:
2627
+
'Enumerates accounts which follow a specified account (actor) and are followed by the viewer.',
2628
+
parameters: {
2629
+
type: 'params',
2630
+
required: ['actor'],
2631
+
properties: {
2632
+
actor: {
2633
+
type: 'string',
2634
+
format: 'at-identifier',
2635
+
},
2636
+
limit: {
2637
+
type: 'integer',
2638
+
maximum: 100,
2639
+
minimum: 1,
2640
+
default: 50,
2641
+
},
2642
+
cursor: {
2643
+
type: 'string',
2644
+
},
2645
+
},
2646
+
},
2647
+
output: {
2648
+
encoding: 'application/json',
2649
+
schema: {
2650
+
type: 'object',
2651
+
required: ['subject', 'followers'],
2652
+
properties: {
2653
+
subject: {
2654
+
type: 'ref',
2655
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2656
+
},
2657
+
followers: {
2658
+
type: 'array',
2659
+
items: {
2660
+
type: 'ref',
2661
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2662
+
},
2663
+
},
2664
+
cursor: {
2665
+
type: 'string',
2666
+
description:
2667
+
'A cursor value to pass to subsequent calls to get the next page of results.',
2668
+
},
2669
+
},
2670
+
},
2671
+
},
2672
+
},
2673
+
},
2674
+
},
2675
+
AppRockskyGraphUnfollowAccount: {
2676
+
lexicon: 1,
2677
+
id: 'app.rocksky.graph.unfollowAccount',
2678
+
defs: {
2679
+
main: {
2680
+
type: 'procedure',
2681
+
description:
2682
+
"Removes a 'follow' relationship from the authenticated account to a specified account.",
2683
+
parameters: {
2684
+
type: 'params',
2685
+
required: ['account'],
2686
+
properties: {
2687
+
account: {
2688
+
type: 'string',
2689
+
format: 'at-identifier',
2690
+
},
2691
+
},
2692
+
},
2693
+
output: {
2694
+
encoding: 'application/json',
2695
+
schema: {
2696
+
type: 'object',
2697
+
required: ['subject', 'followers'],
2698
+
properties: {
2699
+
subject: {
2700
+
type: 'ref',
2701
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2702
+
},
2703
+
followers: {
2704
+
type: 'array',
2705
+
items: {
2706
+
type: 'ref',
2707
+
ref: 'lex:app.rocksky.actor.defs#profileViewBasic',
2708
+
},
2709
+
},
2710
+
cursor: {
2711
+
type: 'string',
2712
+
description:
2713
+
'A cursor value to pass to subsequent calls to get the next page of results.',
2714
+
},
2715
+
},
2716
+
},
2717
+
},
2718
+
},
2719
+
},
2720
+
},
2721
+
AppRockskyLikeDislikeShout: {
2722
+
lexicon: 1,
2723
+
id: 'app.rocksky.like.dislikeShout',
2724
+
defs: {
2725
+
main: {
2726
+
type: 'procedure',
2727
+
description: 'Dislike a shout',
2728
+
input: {
2729
+
encoding: 'application/json',
2730
+
schema: {
2731
+
type: 'object',
2732
+
properties: {
2733
+
uri: {
2734
+
type: 'string',
2735
+
description: 'The unique identifier of the shout to dislike',
2736
+
format: 'at-uri',
2737
+
},
2738
+
},
2739
+
},
2740
+
},
2741
+
output: {
2742
+
encoding: 'application/json',
2743
+
schema: {
2744
+
type: 'ref',
2745
+
ref: 'lex:app.rocksky.shout.defs#shoutView',
2746
+
},
2747
+
},
2748
+
},
2749
+
},
2750
+
},
2751
+
AppRockskyLikeDislikeSong: {
2752
+
lexicon: 1,
2753
+
id: 'app.rocksky.like.dislikeSong',
2754
+
defs: {
2755
+
main: {
2756
+
type: 'procedure',
2757
+
description: 'Dislike a song',
2758
+
input: {
2759
+
encoding: 'application/json',
2760
+
schema: {
2761
+
type: 'object',
2762
+
properties: {
2763
+
uri: {
2764
+
type: 'string',
2765
+
description: 'The unique identifier of the song to dislike',
2766
+
format: 'at-uri',
2767
+
},
2768
+
},
2769
+
},
2770
+
},
2771
+
output: {
2772
+
encoding: 'application/json',
2773
+
schema: {
2774
+
type: 'ref',
2775
+
ref: 'lex:app.rocksky.song.defs#songViewDetailed',
2776
+
},
2777
+
},
2778
+
},
2779
+
},
2780
+
},
2781
+
AppRockskyLike: {
2782
+
lexicon: 1,
2783
+
id: 'app.rocksky.like',
2784
+
defs: {
2785
+
main: {
2786
+
type: 'record',
2787
+
description: 'A declaration of a like.',
2788
+
key: 'tid',
2789
+
record: {
2790
+
type: 'object',
2791
+
required: ['createdAt', 'subject'],
2792
+
properties: {
2793
+
createdAt: {
2794
+
type: 'string',
2795
+
description: 'The date when the like was created.',
2796
+
format: 'datetime',
2797
+
},
2798
+
subject: {
2799
+
type: 'ref',
2800
+
ref: 'lex:com.atproto.repo.strongRef',
2801
+
},
2802
+
},
2803
+
},
2804
+
},
2805
+
},
2806
+
},
2807
+
AppRockskyLikeLikeShout: {
2808
+
lexicon: 1,
2809
+
id: 'app.rocksky.like.likeShout',
2810
+
defs: {
2811
+
main: {
2812
+
type: 'procedure',
2813
+
description: 'Like a shout',
2814
+
input: {
2815
+
encoding: 'application/json',
2816
+
schema: {
2817
+
type: 'object',
2818
+
properties: {
2819
+
uri: {
2820
+
type: 'string',
2821
+
description: 'The unique identifier of the shout to like',
2822
+
format: 'at-uri',
2823
+
},
2824
+
},
2825
+
},
2826
+
},
2827
+
output: {
2828
+
encoding: 'application/json',
2829
+
schema: {
2830
+
type: 'ref',
2831
+
ref: 'lex:app.rocksky.shout.defs#shoutView',
2832
+
},
2833
+
},
2834
+
},
2835
+
},
2836
+
},
2837
+
AppRockskyLikeLikeSong: {
2838
+
lexicon: 1,
2839
+
id: 'app.rocksky.like.likeSong',
2840
+
defs: {
2841
+
main: {
2842
+
type: 'procedure',
2843
+
description: 'Like a song',
2844
+
input: {
2845
+
encoding: 'application/json',
2846
+
schema: {
2847
+
type: 'object',
2848
+
properties: {
2849
+
uri: {
2850
+
type: 'string',
2851
+
description: 'The unique identifier of the song to like',
2852
+
format: 'at-uri',
2853
+
},
2854
+
},
2855
+
},
2856
+
},
2857
+
output: {
2858
+
encoding: 'application/json',
2859
+
schema: {
2860
+
type: 'ref',
2861
+
ref: 'lex:app.rocksky.song.defs#songViewDetailed',
2862
+
},
2863
+
},
2864
+
},
2865
+
},
2866
+
},
2867
+
AppRockskyPlayerAddDirectoryToQueue: {
2868
+
lexicon: 1,
2869
+
id: 'app.rocksky.player.addDirectoryToQueue',
2870
+
defs: {
2871
+
main: {
2872
+
type: 'procedure',
2873
+
description: "Add directory to the player's queue",
2874
+
parameters: {
2875
+
type: 'params',
2876
+
required: ['directory'],
2877
+
properties: {
2878
+
playerId: {
2879
+
type: 'string',
2880
+
},
2881
+
directory: {
2882
+
type: 'string',
2883
+
description: 'The directory to add to the queue',
2884
+
},
2885
+
position: {
2886
+
type: 'integer',
2887
+
description:
2888
+
'Position in the queue to insert the directory at, defaults to the end if not specified',
2889
+
},
2890
+
shuffle: {
2891
+
type: 'boolean',
2892
+
description:
2893
+
'Whether to shuffle the added directory in the queue',
2894
+
},
2895
+
},
2896
+
},
2897
+
},
2898
+
},
2899
+
},
2900
+
AppRockskyPlayerAddItemsToQueue: {
2901
+
lexicon: 1,
2902
+
id: 'app.rocksky.player.addItemsToQueue',
2903
+
defs: {
2904
+
main: {
2905
+
type: 'procedure',
2906
+
description: "Add items to the player's queue",
2907
+
parameters: {
2908
+
type: 'params',
2909
+
required: ['items'],
2910
+
properties: {
2911
+
playerId: {
2912
+
type: 'string',
2913
+
},
2914
+
items: {
2915
+
type: 'array',
2916
+
items: {
2917
+
type: 'string',
2918
+
description: 'List of file identifiers to add to the queue',
2919
+
},
2920
+
},
2921
+
position: {
2922
+
type: 'integer',
2923
+
description:
2924
+
'Position in the queue to insert the items at, defaults to the end if not specified',
2925
+
},
2926
+
shuffle: {
2927
+
type: 'boolean',
2928
+
description: 'Whether to shuffle the added items in the queue',
2929
+
},
2930
+
},
2931
+
},
2932
+
},
2933
+
},
2934
+
},
2935
+
AppRockskyPlayerDefs: {
2936
+
lexicon: 1,
2937
+
id: 'app.rocksky.player.defs',
2938
+
defs: {
2939
+
currentlyPlayingViewDetailed: {
2940
+
type: 'object',
2941
+
properties: {
2942
+
title: {
2943
+
type: 'string',
2944
+
description: 'The title of the currently playing track',
2945
+
},
2946
+
},
2947
+
},
2948
+
playbackQueueViewDetailed: {
2949
+
type: 'object',
2950
+
properties: {
2951
+
tracks: {
2952
+
type: 'array',
2953
+
items: {
2954
+
type: 'ref',
2955
+
ref: 'lex:app.rocksky.song.defs.songViewBasic',
2956
+
},
2957
+
},
2958
+
},
2959
+
},
2960
+
},
2961
+
},
2962
+
AppRockskyPlayerGetCurrentlyPlaying: {
2963
+
lexicon: 1,
2964
+
id: 'app.rocksky.player.getCurrentlyPlaying',
2965
+
defs: {
2966
+
main: {
2967
+
type: 'query',
2968
+
description: 'Get the currently playing track',
2969
+
parameters: {
2970
+
type: 'params',
2971
+
properties: {
2972
+
playerId: {
2973
+
type: 'string',
2974
+
},
2975
+
actor: {
2976
+
type: 'string',
2977
+
description:
2978
+
'Handle or DID of the actor to retrieve the currently playing track for. If not provided, defaults to the current user.',
2979
+
format: 'at-identifier',
2980
+
},
2981
+
},
2982
+
},
2983
+
output: {
2984
+
encoding: 'application/json',
2985
+
schema: {
2986
+
type: 'ref',
2987
+
ref: 'lex:app.rocksky.player.defs#currentlyPlayingViewDetailed',
2988
+
},
2989
+
},
2990
+
},
2991
+
},
2992
+
},
2993
+
AppRockskyPlayerGetPlaybackQueue: {
2994
+
lexicon: 1,
2995
+
id: 'app.rocksky.player.getPlaybackQueue',
2996
+
defs: {
2997
+
main: {
2998
+
type: 'query',
2999
+
description: 'Retrieve the current playback queue',
3000
+
parameters: {
3001
+
type: 'params',
3002
+
properties: {
3003
+
playerId: {
3004
+
type: 'string',
3005
+
},
3006
+
},
3007
+
},
3008
+
output: {
3009
+
encoding: 'application/json',
3010
+
schema: {
3011
+
type: 'ref',
3012
+
ref: 'lex:app.rocksky.player.defs#playbackQueueViewDetailed',
3013
+
},
3014
+
},
3015
+
},
3016
+
},
3017
+
},
3018
+
AppRockskyPlayerNext: {
3019
+
lexicon: 1,
3020
+
id: 'app.rocksky.player.next',
3021
+
defs: {
3022
+
main: {
3023
+
type: 'procedure',
3024
+
description: 'Play the next track in the queue',
3025
+
parameters: {
3026
+
type: 'params',
3027
+
properties: {
3028
+
playerId: {
3029
+
type: 'string',
3030
+
},
3031
+
},
3032
+
},
3033
+
},
3034
+
},
3035
+
},
3036
+
AppRockskyPlayerPause: {
3037
+
lexicon: 1,
3038
+
id: 'app.rocksky.player.pause',
3039
+
defs: {
3040
+
main: {
3041
+
type: 'procedure',
3042
+
description: 'Pause the currently playing track',
3043
+
parameters: {
3044
+
type: 'params',
3045
+
properties: {
3046
+
playerId: {
3047
+
type: 'string',
3048
+
},
3049
+
},
3050
+
},
3051
+
},
3052
+
},
3053
+
},
3054
+
AppRockskyPlayerPlay: {
3055
+
lexicon: 1,
3056
+
id: 'app.rocksky.player.play',
3057
+
defs: {
3058
+
main: {
3059
+
type: 'procedure',
3060
+
description: 'Resume playback of the currently paused track',
3061
+
parameters: {
3062
+
type: 'params',
3063
+
properties: {
3064
+
playerId: {
3065
+
type: 'string',
3066
+
},
3067
+
},
3068
+
},
3069
+
},
3070
+
},
3071
+
},
3072
+
AppRockskyPlayerPlayDirectory: {
3073
+
lexicon: 1,
3074
+
id: 'app.rocksky.player.playDirectory',
3075
+
defs: {
3076
+
main: {
3077
+
type: 'procedure',
3078
+
description: 'Play all tracks in a directory',
3079
+
parameters: {
3080
+
type: 'params',
3081
+
required: ['directoryId'],
3082
+
properties: {
3083
+
playerId: {
3084
+
type: 'string',
3085
+
},
3086
+
directoryId: {
3087
+
type: 'string',
3088
+
},
3089
+
shuffle: {
3090
+
type: 'boolean',
3091
+
},
3092
+
recurse: {
3093
+
type: 'boolean',
3094
+
},
3095
+
position: {
3096
+
type: 'integer',
3097
+
},
3098
+
},
3099
+
},
3100
+
},
3101
+
},
3102
+
},
3103
+
AppRockskyPlayerPlayFile: {
3104
+
lexicon: 1,
3105
+
id: 'app.rocksky.player.playFile',
3106
+
defs: {
3107
+
main: {
3108
+
type: 'procedure',
3109
+
description: 'Play a specific audio file',
3110
+
parameters: {
3111
+
type: 'params',
3112
+
required: ['fileId'],
3113
+
properties: {
3114
+
playerId: {
3115
+
type: 'string',
3116
+
},
3117
+
fileId: {
3118
+
type: 'string',
3119
+
},
3120
+
},
3121
+
},
3122
+
},
3123
+
},
3124
+
},
3125
+
AppRockskyPlayerPrevious: {
3126
+
lexicon: 1,
3127
+
id: 'app.rocksky.player.previous',
3128
+
defs: {
3129
+
main: {
3130
+
type: 'procedure',
3131
+
description: 'Play the previous track in the queue',
3132
+
parameters: {
3133
+
type: 'params',
3134
+
properties: {
3135
+
playerId: {
3136
+
type: 'string',
3137
+
},
3138
+
},
3139
+
},
3140
+
},
3141
+
},
3142
+
},
3143
+
AppRockskyPlayerSeek: {
3144
+
lexicon: 1,
3145
+
id: 'app.rocksky.player.seek',
3146
+
defs: {
3147
+
main: {
3148
+
type: 'procedure',
3149
+
description:
3150
+
'Seek to a specific position in the currently playing track',
3151
+
parameters: {
3152
+
type: 'params',
3153
+
required: ['position'],
3154
+
properties: {
3155
+
playerId: {
3156
+
type: 'string',
3157
+
},
3158
+
position: {
3159
+
type: 'integer',
3160
+
description: 'The position in seconds to seek to',
3161
+
},
3162
+
},
3163
+
},
3164
+
},
3165
+
},
3166
+
},
3167
+
AppRockskyPlaylistCreatePlaylist: {
3168
+
lexicon: 1,
3169
+
id: 'app.rocksky.playlist.createPlaylist',
3170
+
defs: {
3171
+
main: {
3172
+
type: 'procedure',
3173
+
description: 'Create a new playlist',
3174
+
parameters: {
3175
+
type: 'params',
3176
+
required: ['name'],
3177
+
properties: {
3178
+
name: {
3179
+
type: 'string',
3180
+
description: 'The name of the playlist',
3181
+
},
3182
+
description: {
3183
+
type: 'string',
3184
+
description: 'A brief description of the playlist',
3185
+
},
3186
+
},
3187
+
},
3188
+
},
3189
+
},
3190
+
},
3191
+
AppRockskyPlaylistDefs: {
3192
+
lexicon: 1,
3193
+
id: 'app.rocksky.playlist.defs',
3194
+
defs: {
3195
+
playlistViewDetailed: {
3196
+
type: 'object',
3197
+
description:
3198
+
'Detailed view of a playlist, including its tracks and metadata',
3199
+
properties: {
3200
+
id: {
3201
+
type: 'string',
3202
+
description: 'The unique identifier of the playlist.',
3203
+
},
3204
+
title: {
3205
+
type: 'string',
3206
+
description: 'The title of the playlist.',
3207
+
},
3208
+
uri: {
3209
+
type: 'string',
3210
+
description: 'The URI of the playlist.',
3211
+
format: 'at-uri',
3212
+
},
3213
+
curatorDid: {
3214
+
type: 'string',
3215
+
description: 'The DID of the curator of the playlist.',
3216
+
format: 'at-identifier',
3217
+
},
3218
+
curatorHandle: {
3219
+
type: 'string',
3220
+
description: 'The handle of the curator of the playlist.',
3221
+
format: 'at-identifier',
3222
+
},
3223
+
curatorName: {
3224
+
type: 'string',
3225
+
description: 'The name of the curator of the playlist.',
3226
+
},
3227
+
curatorAvatarUrl: {
3228
+
type: 'string',
3229
+
description: 'The URL of the avatar image of the curator.',
3230
+
format: 'uri',
3231
+
},
3232
+
description: {
3233
+
type: 'string',
3234
+
description: 'A description of the playlist.',
3235
+
},
3236
+
coverImageUrl: {
3237
+
type: 'string',
3238
+
description: 'The URL of the cover image for the playlist.',
3239
+
format: 'uri',
3240
+
},
3241
+
createdAt: {
3242
+
type: 'string',
3243
+
description: 'The date and time when the playlist was created.',
3244
+
format: 'datetime',
3245
+
},
3246
+
tracks: {
3247
+
type: 'array',
3248
+
description: 'A list of tracks in the playlist.',
3249
+
items: {
3250
+
type: 'ref',
3251
+
ref: 'lex:app.rocksky.song.defs#songViewBasic',
3252
+
},
3253
+
},
3254
+
},
3255
+
},
3256
+
playlistViewBasic: {
3257
+
type: 'object',
3258
+
description: 'Basic view of a playlist, including its metadata',
3259
+
properties: {
3260
+
id: {
3261
+
type: 'string',
3262
+
description: 'The unique identifier of the playlist.',
3263
+
},
3264
+
title: {
3265
+
type: 'string',
3266
+
description: 'The title of the playlist.',
3267
+
},
3268
+
uri: {
3269
+
type: 'string',
3270
+
description: 'The URI of the playlist.',
3271
+
format: 'at-uri',
3272
+
},
3273
+
curatorDid: {
3274
+
type: 'string',
3275
+
description: 'The DID of the curator of the playlist.',
3276
+
format: 'at-identifier',
3277
+
},
3278
+
curatorHandle: {
3279
+
type: 'string',
3280
+
description: 'The handle of the curator of the playlist.',
3281
+
format: 'at-identifier',
3282
+
},
3283
+
curatorName: {
3284
+
type: 'string',
3285
+
description: 'The name of the curator of the playlist.',
3286
+
},
3287
+
curatorAvatarUrl: {
3288
+
type: 'string',
3289
+
description: 'The URL of the avatar image of the curator.',
3290
+
format: 'uri',
3291
+
},
3292
+
description: {
3293
+
type: 'string',
3294
+
description: 'A description of the playlist.',
3295
+
},
3296
+
coverImageUrl: {
3297
+
type: 'string',
3298
+
description: 'The URL of the cover image for the playlist.',
3299
+
format: 'uri',
3300
+
},
3301
+
createdAt: {
3302
+
type: 'string',
3303
+
description: 'The date and time when the playlist was created.',
3304
+
format: 'datetime',
3305
+
},
3306
+
trackCount: {
3307
+
type: 'integer',
3308
+
description: 'The number of tracks in the playlist.',
3309
+
minimum: 0,
3310
+
},
3311
+
},
3312
+
},
3313
+
},
3314
+
},
3315
+
AppRockskyPlaylistGetPlaylist: {
3316
+
lexicon: 1,
3317
+
id: 'app.rocksky.playlist.getPlaylist',
3318
+
defs: {
3319
+
main: {
3320
+
type: 'query',
3321
+
description: 'Retrieve a playlist by its ID',
3322
+
parameters: {
3323
+
type: 'params',
3324
+
required: ['uri'],
3325
+
properties: {
3326
+
uri: {
3327
+
type: 'string',
3328
+
description: 'The URI of the playlist to retrieve.',
3329
+
format: 'at-uri',
3330
+
},
3331
+
},
3332
+
},
3333
+
output: {
3334
+
encoding: 'application/json',
3335
+
schema: {
3336
+
type: 'ref',
3337
+
ref: 'lex:app.rocksky.playlist.defs#playlistViewDetailed',
3338
+
},
3339
+
},
3340
+
},
3341
+
},
3342
+
},
3343
+
AppRockskyPlaylistGetPlaylists: {
3344
+
lexicon: 1,
3345
+
id: 'app.rocksky.playlist.getPlaylists',
3346
+
defs: {
3347
+
main: {
3348
+
type: 'query',
3349
+
description: 'Retrieve a list of playlists',
3350
+
parameters: {
3351
+
type: 'params',
3352
+
properties: {
3353
+
limit: {
3354
+
type: 'integer',
3355
+
description: 'The maximum number of playlists to return.',
3356
+
},
3357
+
offset: {
3358
+
type: 'integer',
3359
+
description:
3360
+
'The offset for pagination, used to skip a number of playlists.',
3361
+
},
3362
+
},
3363
+
},
3364
+
output: {
3365
+
encoding: 'application/json',
3366
+
schema: {
3367
+
type: 'object',
3368
+
properties: {
3369
+
playlists: {
3370
+
type: 'array',
3371
+
items: {
3372
+
type: 'ref',
3373
+
ref: 'lex:app.rocksky.playlist.defs#playlistViewBasic',
3374
+
},
3375
+
},
3376
+
},
3377
+
},
3378
+
},
3379
+
},
3380
+
},
3381
+
},
3382
+
AppRockskyPlaylistInsertDirectory: {
3383
+
lexicon: 1,
3384
+
id: 'app.rocksky.playlist.insertDirectory',
3385
+
defs: {
3386
+
main: {
3387
+
type: 'procedure',
3388
+
description: 'Insert a directory into a playlist',
3389
+
parameters: {
3390
+
type: 'params',
3391
+
required: ['uri', 'directory'],
3392
+
properties: {
3393
+
uri: {
3394
+
type: 'string',
3395
+
description: 'The URI of the playlist to start',
3396
+
format: 'at-uri',
3397
+
},
3398
+
directory: {
3399
+
type: 'string',
3400
+
description: 'The directory (id) to insert into the playlist',
3401
+
},
3402
+
position: {
3403
+
type: 'integer',
3404
+
description:
3405
+
'The position in the playlist to insert the directory at, if not specified, the directory will be appended',
3406
+
},
3407
+
},
3408
+
},
3409
+
},
3410
+
},
3411
+
},
3412
+
AppRockskyPlaylistInsertFiles: {
3413
+
lexicon: 1,
3414
+
id: 'app.rocksky.playlist.insertFiles',
3415
+
defs: {
3416
+
main: {
3417
+
type: 'procedure',
3418
+
description: 'Insert files into a playlist',
3419
+
parameters: {
3420
+
type: 'params',
3421
+
required: ['uri', 'files'],
3422
+
properties: {
3423
+
uri: {
3424
+
type: 'string',
3425
+
description: 'The URI of the playlist to start',
3426
+
format: 'at-uri',
3427
+
},
3428
+
files: {
3429
+
type: 'array',
3430
+
items: {
3431
+
type: 'string',
3432
+
description: 'List of file (id) to insert into the playlist',
3433
+
},
3434
+
},
3435
+
position: {
3436
+
type: 'integer',
3437
+
description:
3438
+
'The position in the playlist to insert the files at, if not specified, files will be appended',
3439
+
},
3440
+
},
3441
+
},
3442
+
},
3443
+
},
3444
+
},
3445
+
AppRockskyPlaylist: {
3446
+
lexicon: 1,
3447
+
id: 'app.rocksky.playlist',
3448
+
defs: {
3449
+
main: {
3450
+
type: 'record',
3451
+
description: 'A declaration of a playlist.',
3452
+
key: 'tid',
3453
+
record: {
3454
+
type: 'object',
3455
+
required: ['name', 'createdAt'],
3456
+
properties: {
3457
+
name: {
3458
+
type: 'string',
3459
+
description: 'The name of the playlist.',
3460
+
minLength: 1,
3461
+
maxLength: 512,
3462
+
},
3463
+
description: {
3464
+
type: 'string',
3465
+
description: 'The playlist description.',
3466
+
minLength: 1,
3467
+
maxLength: 256,
3468
+
},
3469
+
picture: {
3470
+
type: 'blob',
3471
+
description: 'The picture of the playlist.',
3472
+
accept: ['image/png', 'image/jpeg'],
3473
+
maxSize: 2000000,
3474
+
},
3475
+
tracks: {
3476
+
type: 'array',
3477
+
description: 'The tracks in the playlist.',
3478
+
items: {
3479
+
type: 'ref',
3480
+
ref: 'lex:app.rocksky.song#record',
3481
+
},
3482
+
},
3483
+
createdAt: {
3484
+
type: 'string',
3485
+
description: 'The date the playlist was created.',
3486
+
format: 'datetime',
3487
+
},
3488
+
spotifyLink: {
3489
+
type: 'string',
3490
+
description: 'The Spotify link of the playlist.',
3491
+
},
3492
+
tidalLink: {
3493
+
type: 'string',
3494
+
description: 'The Tidal link of the playlist.',
3495
+
},
3496
+
youtubeLink: {
3497
+
type: 'string',
3498
+
description: 'The YouTube link of the playlist.',
3499
+
},
3500
+
appleMusicLink: {
3501
+
type: 'string',
3502
+
description: 'The Apple Music link of the playlist.',
3503
+
},
3504
+
},
3505
+
},
3506
+
},
3507
+
},
3508
+
},
3509
+
AppRockskyPlaylistRemovePlaylist: {
3510
+
lexicon: 1,
3511
+
id: 'app.rocksky.playlist.removePlaylist',
3512
+
defs: {
3513
+
main: {
3514
+
type: 'procedure',
3515
+
description: 'Remove a playlist',
3516
+
parameters: {
3517
+
type: 'params',
3518
+
required: ['uri'],
3519
+
properties: {
3520
+
uri: {
3521
+
type: 'string',
3522
+
description: 'The URI of the playlist to remove',
3523
+
format: 'at-uri',
3524
+
},
3525
+
},
3526
+
},
3527
+
},
3528
+
},
3529
+
},
3530
+
AppRockskyPlaylistRemoveTrack: {
3531
+
lexicon: 1,
3532
+
id: 'app.rocksky.playlist.removeTrack',
3533
+
defs: {
3534
+
main: {
3535
+
type: 'procedure',
3536
+
description: 'Remove a track from a playlist',
3537
+
parameters: {
3538
+
type: 'params',
3539
+
required: ['uri', 'position'],
3540
+
properties: {
3541
+
uri: {
3542
+
type: 'string',
3543
+
description: 'The URI of the playlist to remove the track from',
3544
+
format: 'at-uri',
3545
+
},
3546
+
position: {
3547
+
type: 'integer',
3548
+
description:
3549
+
'The position of the track to remove in the playlist',
3550
+
},
3551
+
},
3552
+
},
3553
+
},
3554
+
},
3555
+
},
3556
+
AppRockskyPlaylistStartPlaylist: {
3557
+
lexicon: 1,
3558
+
id: 'app.rocksky.playlist.startPlaylist',
3559
+
defs: {
3560
+
main: {
3561
+
type: 'procedure',
3562
+
description: 'Start a playlist',
3563
+
parameters: {
3564
+
type: 'params',
3565
+
required: ['uri'],
3566
+
properties: {
3567
+
uri: {
3568
+
type: 'string',
3569
+
description: 'The URI of the playlist to start',
3570
+
format: 'at-uri',
3571
+
},
3572
+
shuffle: {
3573
+
type: 'boolean',
3574
+
description: 'Whether to shuffle the playlist when starting it',
3575
+
},
3576
+
position: {
3577
+
type: 'integer',
3578
+
description:
3579
+
'The position in the playlist to start from, if not specified, starts from the beginning',
3580
+
},
3581
+
},
3582
+
},
3583
+
},
3584
+
},
3585
+
},
3586
+
AppRockskyRadioDefs: {
3587
+
lexicon: 1,
3588
+
id: 'app.rocksky.radio.defs',
3589
+
defs: {
3590
+
radioViewBasic: {
3591
+
type: 'object',
3592
+
properties: {
3593
+
id: {
3594
+
type: 'string',
3595
+
description: 'The unique identifier of the radio.',
3596
+
},
3597
+
name: {
3598
+
type: 'string',
3599
+
description: 'The name of the radio.',
3600
+
},
3601
+
description: {
3602
+
type: 'string',
3603
+
description: 'A brief description of the radio.',
3604
+
},
3605
+
createdAt: {
3606
+
type: 'string',
3607
+
description: 'The date and time when the radio was created.',
3608
+
format: 'datetime',
3609
+
},
3610
+
},
3611
+
},
3612
+
radioViewDetailed: {
3613
+
type: 'object',
3614
+
properties: {
3615
+
id: {
3616
+
type: 'string',
3617
+
description: 'The unique identifier of the radio.',
3618
+
},
3619
+
name: {
3620
+
type: 'string',
3621
+
description: 'The name of the radio.',
3622
+
},
3623
+
description: {
3624
+
type: 'string',
3625
+
description: 'A brief description of the radio.',
3626
+
},
3627
+
website: {
3628
+
type: 'string',
3629
+
description: 'The website of the radio.',
3630
+
format: 'uri',
3631
+
},
3632
+
url: {
3633
+
type: 'string',
3634
+
description: 'The streaming URL of the radio.',
3635
+
format: 'uri',
3636
+
},
3637
+
genre: {
3638
+
type: 'string',
3639
+
description: 'The genre of the radio.',
3640
+
},
3641
+
logo: {
3642
+
type: 'string',
3643
+
description: 'The logo of the radio station.',
3644
+
},
3645
+
createdAt: {
3646
+
type: 'string',
3647
+
description: 'The date and time when the radio was created.',
3648
+
format: 'datetime',
3649
+
},
3650
+
},
3651
+
},
3652
+
},
3653
+
},
3654
+
AppRockskyRadio: {
3655
+
lexicon: 1,
3656
+
id: 'app.rocksky.radio',
3657
+
defs: {
3658
+
main: {
3659
+
type: 'record',
3660
+
description: 'A declaration of a radio station.',
3661
+
key: 'tid',
3662
+
record: {
3663
+
type: 'object',
3664
+
required: ['name', 'url', 'createdAt'],
3665
+
properties: {
3666
+
name: {
3667
+
type: 'string',
3668
+
description: 'The name of the radio station.',
3669
+
minLength: 1,
3670
+
maxLength: 512,
3671
+
},
3672
+
url: {
3673
+
type: 'string',
3674
+
description: 'The URL of the radio station.',
3675
+
format: 'uri',
3676
+
},
3677
+
description: {
3678
+
type: 'string',
3679
+
description: 'A description of the radio station.',
3680
+
minLength: 1,
3681
+
maxLength: 1000,
3682
+
},
3683
+
genre: {
3684
+
type: 'string',
3685
+
description: 'The genre of the radio station.',
3686
+
minLength: 1,
3687
+
maxLength: 256,
3688
+
},
3689
+
logo: {
3690
+
type: 'blob',
3691
+
description: 'The logo of the radio station.',
3692
+
accept: ['image/png', 'image/jpeg'],
3693
+
maxSize: 2000000,
3694
+
},
3695
+
website: {
3696
+
type: 'string',
3697
+
description: 'The website of the radio station.',
3698
+
format: 'uri',
3699
+
},
3700
+
createdAt: {
3701
+
type: 'string',
3702
+
description: 'The date when the radio station was created.',
3703
+
format: 'datetime',
3704
+
},
3705
+
},
3706
+
},
3707
+
},
3708
+
},
3709
+
},
3710
+
AppRockskyScrobbleCreateScrobble: {
3711
+
lexicon: 1,
3712
+
id: 'app.rocksky.scrobble.createScrobble',
3713
+
defs: {
3714
+
main: {
3715
+
type: 'procedure',
3716
+
description: 'Create a new scrobble',
3717
+
input: {
3718
+
encoding: 'application/json',
3719
+
schema: {
3720
+
type: 'object',
3721
+
required: ['title', 'artist'],
3722
+
properties: {
3723
+
title: {
3724
+
type: 'string',
3725
+
description: 'The title of the track being scrobbled',
3726
+
},
3727
+
artist: {
3728
+
type: 'string',
3729
+
description: 'The artist of the track being scrobbled',
3730
+
},
3731
+
album: {
3732
+
type: 'string',
3733
+
description: 'The album of the track being scrobbled',
3734
+
},
3735
+
duration: {
3736
+
type: 'integer',
3737
+
description: 'The duration of the track in seconds',
3738
+
},
3739
+
mbId: {
3740
+
type: 'string',
3741
+
description: 'The MusicBrainz ID of the track, if available',
3742
+
},
3743
+
albumArt: {
3744
+
type: 'string',
3745
+
description: 'The URL of the album art for the track',
3746
+
format: 'uri',
3747
+
},
3748
+
trackNumber: {
3749
+
type: 'integer',
3750
+
description: 'The track number of the track in the album',
3751
+
},
3752
+
releaseDate: {
3753
+
type: 'string',
3754
+
description:
3755
+
'The release date of the track, formatted as YYYY-MM-DD',
3756
+
},
3757
+
year: {
3758
+
type: 'integer',
3759
+
description: 'The year the track was released',
3760
+
},
3761
+
discNumber: {
3762
+
type: 'integer',
3763
+
description:
3764
+
'The disc number of the track in the album, if applicable',
3765
+
},
3766
+
lyrics: {
3767
+
type: 'string',
3768
+
description: 'The lyrics of the track, if available',
3769
+
},
3770
+
composer: {
3771
+
type: 'string',
3772
+
description: 'The composer of the track, if available',
3773
+
},
3774
+
copyrightMessage: {
3775
+
type: 'string',
3776
+
description:
3777
+
'The copyright message for the track, if available',
3778
+
},
3779
+
label: {
3780
+
type: 'string',
3781
+
description: 'The record label of the track, if available',
3782
+
},
3783
+
artistPicture: {
3784
+
type: 'string',
3785
+
description: "The URL of the artist's picture, if available",
3786
+
format: 'uri',
3787
+
},
3788
+
spotifyLink: {
3789
+
type: 'string',
3790
+
description: 'The Spotify link for the track, if available',
3791
+
format: 'uri',
3792
+
},
3793
+
lastfmLink: {
3794
+
type: 'string',
3795
+
description: 'The Last.fm link for the track, if available',
3796
+
format: 'uri',
3797
+
},
3798
+
tidalLink: {
3799
+
type: 'string',
3800
+
description: 'The Tidal link for the track, if available',
3801
+
format: 'uri',
3802
+
},
3803
+
appleMusicLink: {
3804
+
type: 'string',
3805
+
description: 'The Apple Music link for the track, if available',
3806
+
format: 'uri',
3807
+
},
3808
+
youtubeLink: {
3809
+
type: 'string',
3810
+
description: 'The Youtube link for the track, if available',
3811
+
format: 'uri',
3812
+
},
3813
+
deezerLink: {
3814
+
type: 'string',
3815
+
description: 'The Deezer link for the track, if available',
3816
+
format: 'uri',
3817
+
},
3818
+
timestamp: {
3819
+
type: 'integer',
3820
+
description:
3821
+
'The timestamp of the scrobble in milliseconds since epoch',
3822
+
},
3823
+
},
3824
+
},
3825
+
},
3826
+
output: {
3827
+
encoding: 'application/json',
3828
+
schema: {
3829
+
type: 'ref',
3830
+
ref: 'lex:app.rocksky.scrobble.defs#scrobbleViewBasic',
3831
+
},
3832
+
},
3833
+
},
3834
+
},
3835
+
},
3836
+
AppRockskyScrobbleDefs: {
3837
+
lexicon: 1,
3838
+
id: 'app.rocksky.scrobble.defs',
3839
+
defs: {
3840
+
scrobbleViewBasic: {
3841
+
type: 'object',
3842
+
properties: {
3843
+
id: {
3844
+
type: 'string',
3845
+
description: 'The unique identifier of the scrobble.',
3846
+
},
3847
+
user: {
3848
+
type: 'string',
3849
+
description: 'The handle of the user who created the scrobble.',
3850
+
},
3851
+
userDisplayName: {
3852
+
type: 'string',
3853
+
description:
3854
+
'The display name of the user who created the scrobble.',
3855
+
},
3856
+
userAvatar: {
3857
+
type: 'string',
3858
+
description: 'The avatar URL of the user who created the scrobble.',
3859
+
format: 'uri',
3860
+
},
3861
+
title: {
3862
+
type: 'string',
3863
+
description: 'The title of the scrobble.',
3864
+
},
3865
+
artist: {
3866
+
type: 'string',
3867
+
description: 'The artist of the song.',
3868
+
},
3869
+
artistUri: {
3870
+
type: 'string',
3871
+
description: 'The URI of the artist.',
3872
+
format: 'at-uri',
3873
+
},
3874
+
album: {
3875
+
type: 'string',
3876
+
description: 'The album of the song.',
3877
+
},
3878
+
albumUri: {
3879
+
type: 'string',
3880
+
description: 'The URI of the album.',
3881
+
format: 'at-uri',
3882
+
},
3883
+
cover: {
3884
+
type: 'string',
3885
+
description: 'The album art URL of the song.',
3886
+
format: 'uri',
3887
+
},
3888
+
date: {
3889
+
type: 'string',
3890
+
description: 'The timestamp when the scrobble was created.',
3891
+
format: 'datetime',
3892
+
},
3893
+
uri: {
3894
+
type: 'string',
3895
+
description: 'The URI of the scrobble.',
3896
+
format: 'uri',
3897
+
},
3898
+
sha256: {
3899
+
type: 'string',
3900
+
description: 'The SHA256 hash of the scrobble data.',
3901
+
},
3902
+
liked: {
3903
+
type: 'boolean',
3904
+
},
3905
+
likesCount: {
3906
+
type: 'integer',
3907
+
},
3908
+
},
3909
+
},
3910
+
scrobbleViewDetailed: {
3911
+
type: 'object',
3912
+
properties: {
3913
+
id: {
3914
+
type: 'string',
3915
+
description: 'The unique identifier of the scrobble.',
3916
+
},
3917
+
user: {
3918
+
type: 'string',
3919
+
description: 'The handle of the user who created the scrobble.',
3920
+
},
3921
+
title: {
3922
+
type: 'string',
3923
+
description: 'The title of the scrobble.',
3924
+
},
3925
+
artist: {
3926
+
type: 'string',
3927
+
description: 'The artist of the song.',
3928
+
},
3929
+
artistUri: {
3930
+
type: 'string',
3931
+
description: 'The URI of the artist.',
3932
+
format: 'at-uri',
3933
+
},
3934
+
album: {
3935
+
type: 'string',
3936
+
description: 'The album of the song.',
3937
+
},
3938
+
albumUri: {
3939
+
type: 'string',
3940
+
description: 'The URI of the album.',
3941
+
format: 'at-uri',
3942
+
},
3943
+
cover: {
3944
+
type: 'string',
3945
+
description: 'The album art URL of the song.',
3946
+
format: 'uri',
3947
+
},
3948
+
date: {
3949
+
type: 'string',
3950
+
description: 'The timestamp when the scrobble was created.',
3951
+
format: 'datetime',
3952
+
},
3953
+
uri: {
3954
+
type: 'string',
3955
+
description: 'The URI of the scrobble.',
3956
+
format: 'uri',
3957
+
},
3958
+
sha256: {
3959
+
type: 'string',
3960
+
description: 'The SHA256 hash of the scrobble data.',
3961
+
},
3962
+
listeners: {
3963
+
type: 'integer',
3964
+
description: 'The number of listeners',
3965
+
},
3966
+
scrobbles: {
3967
+
type: 'integer',
3968
+
description: 'The number of scrobbles for this song',
3969
+
},
3970
+
},
3971
+
},
3972
+
},
3973
+
},
3974
+
AppRockskyScrobbleGetScrobble: {
3975
+
lexicon: 1,
3976
+
id: 'app.rocksky.scrobble.getScrobble',
3977
+
defs: {
3978
+
main: {
3979
+
type: 'query',
3980
+
description: 'Get a scrobble by its unique identifier',
3981
+
parameters: {
3982
+
type: 'params',
3983
+
required: ['uri'],
3984
+
properties: {
3985
+
uri: {
3986
+
type: 'string',
3987
+
description: 'The unique identifier of the scrobble',
3988
+
format: 'at-uri',
3989
+
},
3990
+
},
3991
+
},
3992
+
output: {
3993
+
encoding: 'application/json',
3994
+
schema: {
3995
+
type: 'ref',
3996
+
ref: 'lex:app.rocksky.scrobble.defs#scrobbleViewDetailed',
3997
+
},
3998
+
},
3999
+
},
4000
+
},
4001
+
},
4002
+
AppRockskyScrobbleGetScrobbles: {
4003
+
lexicon: 1,
4004
+
id: 'app.rocksky.scrobble.getScrobbles',
4005
+
defs: {
4006
+
main: {
4007
+
type: 'query',
4008
+
description: 'Get scrobbles all scrobbles',
4009
+
parameters: {
4010
+
type: 'params',
4011
+
properties: {
4012
+
did: {
4013
+
type: 'string',
4014
+
description: 'The DID or handle of the actor',
4015
+
format: 'at-identifier',
4016
+
},
4017
+
following: {
4018
+
type: 'boolean',
4019
+
description:
4020
+
'If true, only return scrobbles from actors the viewer is following.',
4021
+
},
4022
+
limit: {
4023
+
type: 'integer',
4024
+
description: 'The maximum number of scrobbles to return',
4025
+
minimum: 1,
4026
+
},
4027
+
offset: {
4028
+
type: 'integer',
4029
+
description: 'The offset for pagination',
4030
+
minimum: 0,
4031
+
},
4032
+
},
4033
+
},
4034
+
output: {
4035
+
encoding: 'application/json',
4036
+
schema: {
4037
+
type: 'object',
4038
+
properties: {
4039
+
scrobbles: {
4040
+
type: 'array',
4041
+
items: {
4042
+
type: 'ref',
4043
+
ref: 'lex:app.rocksky.scrobble.defs#scrobbleViewBasic',
4044
+
},
4045
+
},
4046
+
},
4047
+
},
4048
+
},
4049
+
},
4050
+
},
4051
+
},
4052
+
AppRockskyScrobble: {
4053
+
lexicon: 1,
4054
+
id: 'app.rocksky.scrobble',
4055
+
defs: {
4056
+
main: {
4057
+
type: 'record',
4058
+
description: 'A declaration of a scrobble.',
4059
+
key: 'tid',
4060
+
record: {
4061
+
type: 'object',
4062
+
required: [
4063
+
'title',
4064
+
'artist',
4065
+
'album',
4066
+
'albumArtist',
4067
+
'duration',
4068
+
'createdAt',
4069
+
],
4070
+
properties: {
4071
+
title: {
4072
+
type: 'string',
4073
+
description: 'The title of the song.',
4074
+
minLength: 1,
4075
+
maxLength: 512,
4076
+
},
4077
+
artist: {
4078
+
type: 'string',
4079
+
description: 'The artist of the song.',
4080
+
minLength: 1,
4081
+
maxLength: 256,
4082
+
},
4083
+
artists: {
4084
+
type: 'array',
4085
+
description: 'The artists of the song with MusicBrainz IDs.',
4086
+
items: {
4087
+
type: 'ref',
4088
+
ref: 'lex:app.rocksky.artist.defs#artistMbid',
4089
+
},
4090
+
},
4091
+
albumArtist: {
4092
+
type: 'string',
4093
+
description: 'The album artist of the song.',
4094
+
minLength: 1,
4095
+
maxLength: 256,
4096
+
},
4097
+
album: {
4098
+
type: 'string',
4099
+
description: 'The album of the song.',
4100
+
minLength: 1,
4101
+
maxLength: 256,
4102
+
},
4103
+
duration: {
4104
+
type: 'integer',
4105
+
description: 'The duration of the song in seconds.',
4106
+
minimum: 1,
4107
+
},
4108
+
trackNumber: {
4109
+
type: 'integer',
4110
+
description: 'The track number of the song in the album.',
4111
+
minimum: 1,
4112
+
},
4113
+
discNumber: {
4114
+
type: 'integer',
4115
+
description: 'The disc number of the song in the album.',
4116
+
minimum: 1,
4117
+
},
4118
+
releaseDate: {
4119
+
type: 'string',
4120
+
description: 'The release date of the song.',
4121
+
format: 'datetime',
4122
+
},
4123
+
year: {
4124
+
type: 'integer',
4125
+
description: 'The year the song was released.',
4126
+
},
4127
+
genre: {
4128
+
type: 'string',
4129
+
description: 'The genre of the song.',
4130
+
maxLength: 256,
4131
+
},
4132
+
tags: {
4133
+
type: 'array',
4134
+
description: 'The tags of the song.',
4135
+
items: {
4136
+
type: 'string',
4137
+
minLength: 1,
4138
+
maxLength: 256,
4139
+
},
4140
+
},
4141
+
composer: {
4142
+
type: 'string',
4143
+
description: 'The composer of the song.',
4144
+
maxLength: 256,
4145
+
},
4146
+
lyrics: {
4147
+
type: 'string',
4148
+
description: 'The lyrics of the song.',
4149
+
maxLength: 10000,
4150
+
},
4151
+
copyrightMessage: {
4152
+
type: 'string',
4153
+
description: 'The copyright message of the song.',
4154
+
maxLength: 256,
4155
+
},
4156
+
wiki: {
4157
+
type: 'string',
4158
+
description: 'Informations about the song',
4159
+
maxLength: 10000,
4160
+
},
4161
+
albumArt: {
4162
+
type: 'blob',
4163
+
description: 'The album art of the song.',
4164
+
accept: ['image/png', 'image/jpeg'],
4165
+
maxSize: 2000000,
4166
+
},
4167
+
albumArtUrl: {
4168
+
type: 'string',
4169
+
description: 'The URL of the album art of the song.',
4170
+
format: 'uri',
4171
+
},
4172
+
youtubeLink: {
4173
+
type: 'string',
4174
+
description: 'The YouTube link of the song.',
4175
+
format: 'uri',
4176
+
},
4177
+
spotifyLink: {
4178
+
type: 'string',
4179
+
description: 'The Spotify link of the song.',
4180
+
format: 'uri',
4181
+
},
4182
+
tidalLink: {
4183
+
type: 'string',
4184
+
description: 'The Tidal link of the song.',
4185
+
format: 'uri',
4186
+
},
4187
+
appleMusicLink: {
4188
+
type: 'string',
4189
+
description: 'The Apple Music link of the song.',
4190
+
format: 'uri',
4191
+
},
4192
+
createdAt: {
4193
+
type: 'string',
4194
+
description: 'The date when the song was created.',
4195
+
format: 'datetime',
4196
+
},
4197
+
mbid: {
4198
+
type: 'string',
4199
+
description: 'The MusicBrainz ID of the song.',
4200
+
},
4201
+
label: {
4202
+
type: 'string',
4203
+
description: 'The label of the song.',
4204
+
maxLength: 256,
4205
+
},
4206
+
},
4207
+
},
4208
+
},
4209
+
},
4210
+
},
4211
+
AppRockskyShoutCreateShout: {
4212
+
lexicon: 1,
4213
+
id: 'app.rocksky.shout.createShout',
4214
+
defs: {
4215
+
main: {
4216
+
type: 'procedure',
4217
+
description: 'Create a new shout',
4218
+
input: {
4219
+
encoding: 'application/json',
4220
+
schema: {
4221
+
type: 'object',
4222
+
properties: {
4223
+
message: {
4224
+
type: 'string',
4225
+
description: 'The content of the shout',
4226
+
minLength: 1,
4227
+
},
4228
+
},
4229
+
},
4230
+
},
4231
+
output: {
4232
+
encoding: 'application/json',
4233
+
schema: {
4234
+
type: 'ref',
4235
+
ref: 'lex:app.rocksky.shout.defs#shoutView',
4236
+
},
4237
+
},
4238
+
},
4239
+
},
4240
+
},
4241
+
AppRockskyShoutDefs: {
4242
+
lexicon: 1,
4243
+
id: 'app.rocksky.shout.defs',
4244
+
defs: {
4245
+
author: {
4246
+
type: 'object',
4247
+
properties: {
4248
+
id: {
4249
+
type: 'string',
4250
+
description: 'The unique identifier of the author.',
4251
+
},
4252
+
did: {
4253
+
type: 'string',
4254
+
description: 'The decentralized identifier (DID) of the author.',
4255
+
format: 'at-identifier',
4256
+
},
4257
+
handle: {
4258
+
type: 'string',
4259
+
description: 'The handle of the author.',
4260
+
format: 'at-identifier',
4261
+
},
4262
+
displayName: {
4263
+
type: 'string',
4264
+
description: 'The display name of the author.',
4265
+
},
4266
+
avatar: {
4267
+
type: 'string',
4268
+
description: "The URL of the author's avatar image.",
4269
+
format: 'uri',
4270
+
},
4271
+
},
4272
+
},
4273
+
shoutView: {
4274
+
type: 'object',
4275
+
properties: {
4276
+
id: {
4277
+
type: 'string',
4278
+
description: 'The unique identifier of the shout.',
4279
+
},
4280
+
message: {
4281
+
type: 'string',
4282
+
description: 'The content of the shout.',
4283
+
},
4284
+
parent: {
4285
+
type: 'string',
4286
+
description:
4287
+
'The ID of the parent shout if this is a reply, otherwise null.',
4288
+
},
4289
+
createdAt: {
4290
+
type: 'string',
4291
+
description: 'The date and time when the shout was created.',
4292
+
format: 'datetime',
4293
+
},
4294
+
author: {
4295
+
type: 'ref',
4296
+
description: 'The author of the shout.',
4297
+
ref: 'lex:app.rocksky.shout.defs#author',
4298
+
},
4299
+
},
4300
+
},
4301
+
},
4302
+
},
4303
+
AppRockskyShoutGetAlbumShouts: {
4304
+
lexicon: 1,
4305
+
id: 'app.rocksky.shout.getAlbumShouts',
4306
+
defs: {
4307
+
main: {
4308
+
type: 'query',
4309
+
description: 'Get shouts for an album',
4310
+
parameters: {
4311
+
type: 'params',
4312
+
required: ['uri'],
4313
+
properties: {
4314
+
uri: {
4315
+
type: 'string',
4316
+
description:
4317
+
'The unique identifier of the album to retrieve shouts for',
4318
+
format: 'at-uri',
4319
+
},
4320
+
limit: {
4321
+
type: 'integer',
4322
+
description: 'The maximum number of shouts to return',
4323
+
minimum: 1,
4324
+
},
4325
+
offset: {
4326
+
type: 'integer',
4327
+
description:
4328
+
'The number of shouts to skip before starting to collect the result set',
4329
+
minimum: 0,
4330
+
},
4331
+
},
4332
+
},
4333
+
output: {
4334
+
encoding: 'application/json',
4335
+
schema: {
4336
+
type: 'object',
4337
+
properties: {
4338
+
shouts: {
4339
+
type: 'array',
4340
+
items: {
4341
+
type: 'ref',
4342
+
ref: 'lex:app.rocksky.shout.defs#shoutViewBasic',
4343
+
},
4344
+
},
4345
+
},
4346
+
},
4347
+
},
4348
+
},
4349
+
},
4350
+
},
4351
+
AppRockskyShoutGetArtistShouts: {
4352
+
lexicon: 1,
4353
+
id: 'app.rocksky.shout.getArtistShouts',
4354
+
defs: {
4355
+
main: {
4356
+
type: 'query',
4357
+
description: 'Get shouts for an artist',
4358
+
parameters: {
4359
+
type: 'params',
4360
+
required: ['uri'],
4361
+
properties: {
4362
+
uri: {
4363
+
type: 'string',
4364
+
description: 'The URI of the artist to retrieve shouts for',
4365
+
format: 'at-uri',
4366
+
},
4367
+
limit: {
4368
+
type: 'integer',
4369
+
description: 'The maximum number of shouts to return',
4370
+
minimum: 1,
4371
+
},
4372
+
offset: {
4373
+
type: 'integer',
4374
+
description:
4375
+
'The number of shouts to skip before starting to collect the result set',
4376
+
minimum: 0,
4377
+
},
4378
+
},
4379
+
},
4380
+
output: {
4381
+
encoding: 'application/json',
4382
+
schema: {
4383
+
type: 'object',
4384
+
properties: {
4385
+
shouts: {
4386
+
type: 'array',
4387
+
items: {
4388
+
type: 'ref',
4389
+
ref: 'lex:app.rocksky.shout.defs#shoutViewBasic',
4390
+
},
4391
+
},
4392
+
},
4393
+
},
4394
+
},
4395
+
},
4396
+
},
4397
+
},
4398
+
AppRockskyShoutGetProfileShouts: {
4399
+
lexicon: 1,
4400
+
id: 'app.rocksky.shout.getProfileShouts',
4401
+
defs: {
4402
+
main: {
4403
+
type: 'query',
4404
+
description: "Get the shouts of an actor's profile",
4405
+
parameters: {
4406
+
type: 'params',
4407
+
required: ['did'],
4408
+
properties: {
4409
+
did: {
4410
+
type: 'string',
4411
+
description: 'The DID or handle of the actor',
4412
+
format: 'at-identifier',
4413
+
},
4414
+
offset: {
4415
+
type: 'integer',
4416
+
description: 'The offset for pagination',
4417
+
minimum: 0,
4418
+
},
4419
+
limit: {
4420
+
type: 'integer',
4421
+
description: 'The maximum number of shouts to return',
4422
+
minimum: 1,
4423
+
},
4424
+
},
4425
+
},
4426
+
output: {
4427
+
encoding: 'application/json',
4428
+
schema: {
4429
+
type: 'object',
4430
+
properties: {
4431
+
shouts: {
4432
+
type: 'array',
4433
+
items: {
4434
+
type: 'ref',
4435
+
ref: 'lex:app.rocksky.shout.defs#shoutViewBasic',
4436
+
},
4437
+
},
4438
+
},
4439
+
},
4440
+
},
4441
+
},
4442
+
},
4443
+
},
4444
+
AppRockskyShoutGetShoutReplies: {
4445
+
lexicon: 1,
4446
+
id: 'app.rocksky.shout.getShoutReplies',
4447
+
defs: {
4448
+
main: {
4449
+
type: 'query',
4450
+
description: 'Get replies to a shout',
4451
+
parameters: {
4452
+
type: 'params',
4453
+
required: ['uri'],
4454
+
properties: {
4455
+
uri: {
4456
+
type: 'string',
4457
+
description: 'The URI of the shout to retrieve replies for',
4458
+
format: 'at-uri',
4459
+
},
4460
+
limit: {
4461
+
type: 'integer',
4462
+
description: 'The maximum number of shouts to return',
4463
+
minimum: 1,
4464
+
},
4465
+
offset: {
4466
+
type: 'integer',
4467
+
description:
4468
+
'The number of shouts to skip before starting to collect the result set',
4469
+
minimum: 0,
4470
+
},
4471
+
},
4472
+
},
4473
+
output: {
4474
+
encoding: 'application/json',
4475
+
schema: {
4476
+
type: 'object',
4477
+
properties: {
4478
+
shouts: {
4479
+
type: 'array',
4480
+
items: {
4481
+
type: 'ref',
4482
+
ref: 'lex:app.rocksky.shout.defs#shoutViewBasic',
4483
+
},
4484
+
},
4485
+
},
4486
+
},
4487
+
},
4488
+
},
4489
+
},
4490
+
},
4491
+
AppRockskyShoutGetTrackShouts: {
4492
+
lexicon: 1,
4493
+
id: 'app.rocksky.shout.getTrackShouts',
4494
+
defs: {
4495
+
main: {
4496
+
type: 'query',
4497
+
description: 'Get all shouts for a specific track',
4498
+
parameters: {
4499
+
type: 'params',
4500
+
required: ['uri'],
4501
+
properties: {
4502
+
uri: {
4503
+
type: 'string',
4504
+
description: 'The URI of the track to retrieve shouts for',
4505
+
format: 'at-uri',
4506
+
},
4507
+
},
4508
+
},
4509
+
output: {
4510
+
encoding: 'application/json',
4511
+
schema: {
4512
+
type: 'object',
4513
+
properties: {
4514
+
shouts: {
4515
+
type: 'array',
4516
+
items: {
4517
+
type: 'ref',
4518
+
ref: 'lex:app.rocksky.shout.defs#shoutViewBasic',
4519
+
},
4520
+
},
4521
+
},
4522
+
},
4523
+
},
4524
+
},
4525
+
},
4526
+
},
4527
+
AppRockskyShoutRemoveShout: {
4528
+
lexicon: 1,
4529
+
id: 'app.rocksky.shout.removeShout',
4530
+
defs: {
4531
+
main: {
4532
+
type: 'procedure',
4533
+
description: 'Remove a shout by its ID',
4534
+
parameters: {
4535
+
type: 'params',
4536
+
required: ['id'],
4537
+
properties: {
4538
+
id: {
4539
+
type: 'string',
4540
+
description: 'The ID of the shout to be removed',
4541
+
},
4542
+
},
4543
+
},
4544
+
output: {
4545
+
encoding: 'application/json',
4546
+
schema: {
4547
+
type: 'ref',
4548
+
ref: 'lex:app.rocksky.shout.defs#shoutView',
4549
+
},
4550
+
},
4551
+
},
4552
+
},
4553
+
},
4554
+
AppRockskyShoutReplyShout: {
4555
+
lexicon: 1,
4556
+
id: 'app.rocksky.shout.replyShout',
4557
+
defs: {
4558
+
main: {
4559
+
type: 'procedure',
4560
+
description: 'Reply to a shout',
4561
+
input: {
4562
+
encoding: 'application/json',
4563
+
schema: {
4564
+
type: 'object',
4565
+
required: ['shoutId', 'message'],
4566
+
properties: {
4567
+
shoutId: {
4568
+
type: 'string',
4569
+
description: 'The unique identifier of the shout to reply to',
4570
+
},
4571
+
message: {
4572
+
type: 'string',
4573
+
description: 'The content of the reply',
4574
+
minLength: 1,
4575
+
},
4576
+
},
4577
+
},
4578
+
},
4579
+
output: {
4580
+
encoding: 'application/json',
4581
+
schema: {
4582
+
type: 'ref',
4583
+
ref: 'lex:app.rocksky.shout.defs#shoutView',
4584
+
},
4585
+
},
4586
+
},
4587
+
},
4588
+
},
4589
+
AppRockskyShoutReportShout: {
4590
+
lexicon: 1,
4591
+
id: 'app.rocksky.shout.reportShout',
4592
+
defs: {
4593
+
main: {
4594
+
type: 'procedure',
4595
+
description: 'Report a shout for moderation',
4596
+
input: {
4597
+
encoding: 'application/json',
4598
+
schema: {
4599
+
type: 'object',
4600
+
required: ['shoutId'],
4601
+
properties: {
4602
+
shoutId: {
4603
+
type: 'string',
4604
+
description: 'The unique identifier of the shout to report',
4605
+
},
4606
+
reason: {
4607
+
type: 'string',
4608
+
description: 'The reason for reporting the shout',
4609
+
minLength: 1,
4610
+
},
4611
+
},
4612
+
},
4613
+
},
4614
+
output: {
4615
+
encoding: 'application/json',
4616
+
schema: {
4617
+
type: 'ref',
4618
+
ref: 'lex:app.rocksky.shout.defs#shoutView',
4619
+
},
4620
+
},
4621
+
},
4622
+
},
4623
+
},
4624
+
AppRockskyShout: {
4625
+
lexicon: 1,
4626
+
id: 'app.rocksky.shout',
4627
+
defs: {
4628
+
main: {
4629
+
type: 'record',
4630
+
description: 'A declaration of a shout.',
4631
+
key: 'tid',
4632
+
record: {
4633
+
type: 'object',
4634
+
required: ['message', 'createdAt', 'subject'],
4635
+
properties: {
4636
+
message: {
4637
+
type: 'string',
4638
+
description: 'The message of the shout.',
4639
+
minLength: 1,
4640
+
maxLength: 1000,
4641
+
},
4642
+
createdAt: {
4643
+
type: 'string',
4644
+
description: 'The date when the shout was created.',
4645
+
format: 'datetime',
4646
+
},
4647
+
parent: {
4648
+
type: 'ref',
4649
+
ref: 'lex:com.atproto.repo.strongRef',
4650
+
},
4651
+
subject: {
4652
+
type: 'ref',
4653
+
ref: 'lex:com.atproto.repo.strongRef',
4654
+
},
4655
+
},
4656
+
},
4657
+
},
4658
+
},
4659
+
},
4660
+
AppRockskySongCreateSong: {
4661
+
lexicon: 1,
4662
+
id: 'app.rocksky.song.createSong',
4663
+
defs: {
4664
+
main: {
4665
+
type: 'procedure',
4666
+
description: 'Create a new song',
4667
+
input: {
4668
+
encoding: 'application/json',
4669
+
schema: {
4670
+
type: 'object',
4671
+
required: ['title', 'artist', 'album', 'albumArtist'],
4672
+
properties: {
4673
+
title: {
4674
+
type: 'string',
4675
+
description: 'The title of the song',
4676
+
},
4677
+
artist: {
4678
+
type: 'string',
4679
+
description: 'The artist of the song',
4680
+
},
4681
+
albumArtist: {
4682
+
type: 'string',
4683
+
description:
4684
+
'The album artist of the song, if different from the main artist',
4685
+
},
4686
+
album: {
4687
+
type: 'string',
4688
+
description: 'The album of the song, if applicable',
4689
+
},
4690
+
duration: {
4691
+
type: 'integer',
4692
+
description: 'The duration of the song in seconds',
4693
+
},
4694
+
mbId: {
4695
+
type: 'string',
4696
+
description: 'The MusicBrainz ID of the song, if available',
4697
+
},
4698
+
albumArt: {
4699
+
type: 'string',
4700
+
description: 'The URL of the album art for the song',
4701
+
format: 'uri',
4702
+
},
4703
+
trackNumber: {
4704
+
type: 'integer',
4705
+
description:
4706
+
'The track number of the song in the album, if applicable',
4707
+
},
4708
+
releaseDate: {
4709
+
type: 'string',
4710
+
description:
4711
+
'The release date of the song, formatted as YYYY-MM-DD',
4712
+
},
4713
+
year: {
4714
+
type: 'integer',
4715
+
description: 'The year the song was released',
4716
+
},
4717
+
discNumber: {
4718
+
type: 'integer',
4719
+
description:
4720
+
'The disc number of the song in the album, if applicable',
4721
+
},
4722
+
lyrics: {
4723
+
type: 'string',
4724
+
description: 'The lyrics of the song, if available',
4725
+
},
4726
+
},
4727
+
},
4728
+
},
4729
+
output: {
4730
+
encoding: 'application/json',
4731
+
schema: {
4732
+
type: 'ref',
4733
+
ref: 'lex:app.rocksky.song.defs#songViewDetailed',
4734
+
},
4735
+
},
4736
+
},
4737
+
},
4738
+
},
4739
+
AppRockskySongDefs: {
4740
+
lexicon: 1,
4741
+
id: 'app.rocksky.song.defs',
4742
+
defs: {
4743
+
songViewBasic: {
4744
+
type: 'object',
4745
+
properties: {
4746
+
id: {
4747
+
type: 'string',
4748
+
description: 'The unique identifier of the song.',
4749
+
},
4750
+
title: {
4751
+
type: 'string',
4752
+
description: 'The title of the song.',
4753
+
},
4754
+
artist: {
4755
+
type: 'string',
4756
+
description: 'The artist of the song.',
4757
+
},
4758
+
albumArtist: {
4759
+
type: 'string',
4760
+
description: 'The artist of the album the song belongs to.',
4761
+
},
4762
+
albumArt: {
4763
+
type: 'string',
4764
+
description: 'The URL of the album art image.',
4765
+
format: 'uri',
4766
+
},
4767
+
uri: {
4768
+
type: 'string',
4769
+
description: 'The URI of the song.',
4770
+
format: 'at-uri',
4771
+
},
4772
+
album: {
4773
+
type: 'string',
4774
+
description: 'The album of the song.',
4775
+
},
4776
+
duration: {
4777
+
type: 'integer',
4778
+
description: 'The duration of the song in milliseconds.',
4779
+
},
4780
+
trackNumber: {
4781
+
type: 'integer',
4782
+
description: 'The track number of the song in the album.',
4783
+
},
4784
+
discNumber: {
4785
+
type: 'integer',
4786
+
description: 'The disc number of the song in the album.',
4787
+
},
4788
+
playCount: {
4789
+
type: 'integer',
4790
+
description: 'The number of times the song has been played.',
4791
+
minimum: 0,
4792
+
},
4793
+
uniqueListeners: {
4794
+
type: 'integer',
4795
+
description:
4796
+
'The number of unique listeners who have played the song.',
4797
+
minimum: 0,
4798
+
},
4799
+
albumUri: {
4800
+
type: 'string',
4801
+
description: 'The URI of the album the song belongs to.',
4802
+
format: 'at-uri',
4803
+
},
4804
+
artistUri: {
4805
+
type: 'string',
4806
+
description: 'The URI of the artist of the song.',
4807
+
format: 'at-uri',
4808
+
},
4809
+
sha256: {
4810
+
type: 'string',
4811
+
description: 'The SHA256 hash of the song.',
4812
+
},
4813
+
createdAt: {
4814
+
type: 'string',
4815
+
description: 'The timestamp when the song was created.',
4816
+
format: 'datetime',
4817
+
},
4818
+
},
4819
+
},
4820
+
songViewDetailed: {
4821
+
type: 'object',
4822
+
properties: {
4823
+
id: {
4824
+
type: 'string',
4825
+
description: 'The unique identifier of the song.',
4826
+
},
4827
+
title: {
4828
+
type: 'string',
4829
+
description: 'The title of the song.',
4830
+
},
4831
+
artist: {
4832
+
type: 'string',
4833
+
description: 'The artist of the song.',
4834
+
},
4835
+
albumArtist: {
4836
+
type: 'string',
4837
+
description: 'The artist of the album the song belongs to.',
4838
+
},
4839
+
albumArt: {
4840
+
type: 'string',
4841
+
description: 'The URL of the album art image.',
4842
+
format: 'uri',
4843
+
},
4844
+
uri: {
4845
+
type: 'string',
4846
+
description: 'The URI of the song.',
4847
+
format: 'at-uri',
4848
+
},
4849
+
album: {
4850
+
type: 'string',
4851
+
description: 'The album of the song.',
4852
+
},
4853
+
duration: {
4854
+
type: 'integer',
4855
+
description: 'The duration of the song in milliseconds.',
4856
+
},
4857
+
trackNumber: {
4858
+
type: 'integer',
4859
+
description: 'The track number of the song in the album.',
4860
+
},
4861
+
discNumber: {
4862
+
type: 'integer',
4863
+
description: 'The disc number of the song in the album.',
4864
+
},
4865
+
playCount: {
4866
+
type: 'integer',
4867
+
description: 'The number of times the song has been played.',
4868
+
minimum: 0,
4869
+
},
4870
+
uniqueListeners: {
4871
+
type: 'integer',
4872
+
description:
4873
+
'The number of unique listeners who have played the song.',
4874
+
minimum: 0,
4875
+
},
4876
+
albumUri: {
4877
+
type: 'string',
4878
+
description: 'The URI of the album the song belongs to.',
4879
+
format: 'at-uri',
4880
+
},
4881
+
artistUri: {
4882
+
type: 'string',
4883
+
description: 'The URI of the artist of the song.',
4884
+
format: 'at-uri',
4885
+
},
4886
+
sha256: {
4887
+
type: 'string',
4888
+
description: 'The SHA256 hash of the song.',
4889
+
},
4890
+
createdAt: {
4891
+
type: 'string',
4892
+
description: 'The timestamp when the song was created.',
4893
+
format: 'datetime',
4894
+
},
4895
+
},
4896
+
},
4897
+
},
4898
+
},
4899
+
AppRockskySongGetSong: {
4900
+
lexicon: 1,
4901
+
id: 'app.rocksky.song.getSong',
4902
+
defs: {
4903
+
main: {
4904
+
type: 'query',
4905
+
description: 'Get a song by its uri',
4906
+
parameters: {
4907
+
type: 'params',
4908
+
required: ['uri'],
4909
+
properties: {
4910
+
uri: {
4911
+
type: 'string',
4912
+
description: 'The unique identifier of the song to retrieve',
4913
+
format: 'at-uri',
4914
+
},
4915
+
},
4916
+
},
4917
+
output: {
4918
+
encoding: 'application/json',
4919
+
schema: {
4920
+
type: 'ref',
4921
+
ref: 'lex:app.rocksky.song.defs#songViewDetailed',
4922
+
},
4923
+
},
4924
+
},
4925
+
},
4926
+
},
4927
+
AppRockskySongGetSongs: {
4928
+
lexicon: 1,
4929
+
id: 'app.rocksky.song.getSongs',
4930
+
defs: {
4931
+
main: {
4932
+
type: 'query',
4933
+
description: 'Get songs',
4934
+
parameters: {
4935
+
type: 'params',
4936
+
properties: {
4937
+
limit: {
4938
+
type: 'integer',
4939
+
description: 'The maximum number of songs to return',
4940
+
minimum: 1,
4941
+
},
4942
+
offset: {
4943
+
type: 'integer',
4944
+
description: 'The offset for pagination',
4945
+
minimum: 0,
4946
+
},
4947
+
},
4948
+
},
4949
+
output: {
4950
+
encoding: 'application/json',
4951
+
schema: {
4952
+
type: 'object',
4953
+
properties: {
4954
+
songs: {
4955
+
type: 'array',
4956
+
items: {
4957
+
type: 'ref',
4958
+
ref: 'lex:app.rocksky.song.defs#songViewBasic',
4959
+
},
4960
+
},
4961
+
},
4962
+
},
4963
+
},
4964
+
},
4965
+
},
4966
+
},
4967
+
AppRockskySong: {
4968
+
lexicon: 1,
4969
+
id: 'app.rocksky.song',
4970
+
defs: {
4971
+
main: {
4972
+
type: 'record',
4973
+
description: 'A declaration of a song.',
4974
+
key: 'tid',
4975
+
record: {
4976
+
type: 'object',
4977
+
required: [
4978
+
'title',
4979
+
'artist',
4980
+
'album',
4981
+
'albumArtist',
4982
+
'duration',
4983
+
'createdAt',
4984
+
],
4985
+
properties: {
4986
+
title: {
4987
+
type: 'string',
4988
+
description: 'The title of the song.',
4989
+
minLength: 1,
4990
+
maxLength: 512,
4991
+
},
4992
+
artist: {
4993
+
type: 'string',
4994
+
description: 'The artist of the song.',
4995
+
minLength: 1,
4996
+
maxLength: 256,
4997
+
},
4998
+
artists: {
4999
+
type: 'array',
5000
+
description: 'The artists of the song with MusicBrainz IDs.',
5001
+
items: {
5002
+
type: 'ref',
5003
+
ref: 'lex:app.rocksky.artist.defs#artistMbid',
5004
+
},
5005
+
},
5006
+
albumArtist: {
5007
+
type: 'string',
5008
+
description: 'The album artist of the song.',
5009
+
minLength: 1,
5010
+
maxLength: 256,
5011
+
},
5012
+
album: {
5013
+
type: 'string',
5014
+
description: 'The album of the song.',
5015
+
minLength: 1,
5016
+
maxLength: 256,
5017
+
},
5018
+
duration: {
5019
+
type: 'integer',
5020
+
description: 'The duration of the song in seconds.',
5021
+
minimum: 1,
5022
+
},
5023
+
trackNumber: {
5024
+
type: 'integer',
5025
+
description: 'The track number of the song in the album.',
5026
+
minimum: 1,
5027
+
},
5028
+
discNumber: {
5029
+
type: 'integer',
5030
+
description: 'The disc number of the song in the album.',
5031
+
minimum: 1,
5032
+
},
5033
+
releaseDate: {
5034
+
type: 'string',
5035
+
description: 'The release date of the song.',
5036
+
format: 'datetime',
5037
+
},
5038
+
year: {
5039
+
type: 'integer',
5040
+
description: 'The year the song was released.',
5041
+
},
5042
+
genre: {
5043
+
type: 'string',
5044
+
description: 'The genre of the song.',
5045
+
minLength: 1,
5046
+
maxLength: 256,
5047
+
},
5048
+
tags: {
5049
+
type: 'array',
5050
+
description: 'The tags of the song.',
5051
+
items: {
5052
+
type: 'string',
5053
+
minLength: 1,
5054
+
maxLength: 256,
5055
+
},
5056
+
},
5057
+
composer: {
5058
+
type: 'string',
5059
+
description: 'The composer of the song.',
5060
+
maxLength: 256,
5061
+
},
5062
+
lyrics: {
5063
+
type: 'string',
5064
+
description: 'The lyrics of the song.',
5065
+
maxLength: 10000,
5066
+
},
5067
+
copyrightMessage: {
5068
+
type: 'string',
5069
+
description: 'The copyright message of the song.',
5070
+
maxLength: 256,
5071
+
},
5072
+
wiki: {
5073
+
type: 'string',
5074
+
description: 'Informations about the song',
5075
+
maxLength: 10000,
5076
+
},
5077
+
albumArt: {
5078
+
type: 'blob',
5079
+
description: 'The album art of the song.',
5080
+
accept: ['image/png', 'image/jpeg'],
5081
+
maxSize: 2000000,
5082
+
},
5083
+
albumArtUrl: {
5084
+
type: 'string',
5085
+
description: 'The URL of the album art of the song.',
5086
+
format: 'uri',
5087
+
},
5088
+
youtubeLink: {
5089
+
type: 'string',
5090
+
description: 'The YouTube link of the song.',
5091
+
format: 'uri',
5092
+
},
5093
+
spotifyLink: {
5094
+
type: 'string',
5095
+
description: 'The Spotify link of the song.',
5096
+
format: 'uri',
5097
+
},
5098
+
tidalLink: {
5099
+
type: 'string',
5100
+
description: 'The Tidal link of the song.',
5101
+
format: 'uri',
5102
+
},
5103
+
appleMusicLink: {
5104
+
type: 'string',
5105
+
description: 'The Apple Music link of the song.',
5106
+
format: 'uri',
5107
+
},
5108
+
createdAt: {
5109
+
type: 'string',
5110
+
description: 'The date when the song was created.',
5111
+
format: 'datetime',
5112
+
},
5113
+
mbid: {
5114
+
type: 'string',
5115
+
description: 'The MusicBrainz ID of the song.',
5116
+
},
5117
+
label: {
5118
+
type: 'string',
5119
+
description: 'The label of the song.',
5120
+
maxLength: 256,
5121
+
},
5122
+
},
5123
+
},
5124
+
},
5125
+
},
5126
+
},
5127
+
AppRockskySpotifyDefs: {
5128
+
lexicon: 1,
5129
+
id: 'app.rocksky.spotify.defs',
5130
+
defs: {
5131
+
spotifyTrackView: {
5132
+
type: 'object',
5133
+
properties: {
5134
+
id: {
5135
+
type: 'string',
5136
+
description: 'The unique identifier of the Spotify track.',
5137
+
},
5138
+
name: {
5139
+
type: 'string',
5140
+
description: 'The name of the track.',
5141
+
},
5142
+
artist: {
5143
+
type: 'string',
5144
+
description: 'The name of the artist.',
5145
+
},
5146
+
album: {
5147
+
type: 'string',
5148
+
description: 'The name of the album.',
5149
+
},
5150
+
duration: {
5151
+
type: 'integer',
5152
+
description: 'The duration of the track in milliseconds.',
5153
+
},
5154
+
previewUrl: {
5155
+
type: 'string',
5156
+
description: 'A URL to a preview of the track.',
5157
+
},
5158
+
},
5159
+
},
5160
+
},
5161
+
},
5162
+
AppRockskySpotifyGetCurrentlyPlaying: {
5163
+
lexicon: 1,
5164
+
id: 'app.rocksky.spotify.getCurrentlyPlaying',
5165
+
defs: {
5166
+
main: {
5167
+
type: 'query',
5168
+
description: 'Get the currently playing track',
5169
+
parameters: {
5170
+
type: 'params',
5171
+
properties: {
5172
+
actor: {
5173
+
type: 'string',
5174
+
description:
5175
+
'Handle or DID of the actor to retrieve the currently playing track for. If not provided, defaults to the current user.',
5176
+
format: 'at-identifier',
5177
+
},
5178
+
},
5179
+
},
5180
+
output: {
5181
+
encoding: 'application/json',
5182
+
schema: {
5183
+
type: 'ref',
5184
+
ref: 'lex:app.rocksky.player.defs#currentlyPlayingViewDetailed',
5185
+
},
5186
+
},
5187
+
},
5188
+
},
5189
+
},
5190
+
AppRockskySpotifyNext: {
5191
+
lexicon: 1,
5192
+
id: 'app.rocksky.spotify.next',
5193
+
defs: {
5194
+
main: {
5195
+
type: 'procedure',
5196
+
description: 'Play the next track in the queue',
5197
+
},
5198
+
},
5199
+
},
5200
+
AppRockskySpotifyPause: {
5201
+
lexicon: 1,
5202
+
id: 'app.rocksky.spotify.pause',
5203
+
defs: {
5204
+
main: {
5205
+
type: 'procedure',
5206
+
description: 'Pause the currently playing track',
5207
+
},
5208
+
},
5209
+
},
5210
+
AppRockskySpotifyPlay: {
5211
+
lexicon: 1,
5212
+
id: 'app.rocksky.spotify.play',
5213
+
defs: {
5214
+
main: {
5215
+
type: 'procedure',
5216
+
description: 'Resume playback of the currently paused track',
5217
+
},
5218
+
},
5219
+
},
5220
+
AppRockskySpotifyPrevious: {
5221
+
lexicon: 1,
5222
+
id: 'app.rocksky.spotify.previous',
5223
+
defs: {
5224
+
main: {
5225
+
type: 'procedure',
5226
+
description: 'Play the previous track in the queue',
5227
+
},
5228
+
},
5229
+
},
5230
+
AppRockskySpotifySeek: {
5231
+
lexicon: 1,
5232
+
id: 'app.rocksky.spotify.seek',
5233
+
defs: {
5234
+
main: {
5235
+
type: 'procedure',
5236
+
description:
5237
+
'Seek to a specific position in the currently playing track',
5238
+
parameters: {
5239
+
type: 'params',
5240
+
required: ['position'],
5241
+
properties: {
5242
+
position: {
5243
+
type: 'integer',
5244
+
description: 'The position in seconds to seek to',
5245
+
},
5246
+
},
5247
+
},
5248
+
},
5249
+
},
5250
+
},
5251
+
AppRockskyStatsDefs: {
5252
+
lexicon: 1,
5253
+
id: 'app.rocksky.stats.defs',
5254
+
defs: {
5255
+
statsView: {
5256
+
type: 'object',
5257
+
properties: {
5258
+
scrobbles: {
5259
+
type: 'integer',
5260
+
description: 'The total number of scrobbles.',
5261
+
},
5262
+
artists: {
5263
+
type: 'integer',
5264
+
description: 'The total number of unique artists scrobbled.',
5265
+
},
5266
+
lovedTracks: {
5267
+
type: 'integer',
5268
+
description: 'The total number of tracks marked as loved.',
5269
+
},
5270
+
albums: {
5271
+
type: 'integer',
5272
+
description: 'The total number of unique albums scrobbled.',
5273
+
},
5274
+
tracks: {
5275
+
type: 'integer',
5276
+
description: 'The total number of unique tracks scrobbled.',
5277
+
},
5278
+
},
5279
+
},
5280
+
},
5281
+
},
5282
+
AppRockskyStatsGetStats: {
5283
+
lexicon: 1,
5284
+
id: 'app.rocksky.stats.getStats',
5285
+
defs: {
5286
+
main: {
5287
+
type: 'query',
5288
+
parameters: {
5289
+
type: 'params',
5290
+
required: ['did'],
5291
+
properties: {
5292
+
did: {
5293
+
type: 'string',
5294
+
description: 'The DID or handle of the user to get stats for.',
5295
+
format: 'at-identifier',
5296
+
},
5297
+
},
5298
+
},
5299
+
output: {
5300
+
encoding: 'application/json',
5301
+
schema: {
5302
+
type: 'ref',
5303
+
ref: 'lex:app.rocksky.stats.defs#statsView',
5304
+
},
5305
+
},
5306
+
},
5307
+
},
5308
+
},
5309
+
ComAtprotoRepoStrongRef: {
5310
+
lexicon: 1,
5311
+
id: 'com.atproto.repo.strongRef',
5312
+
description: 'A URI with a content-hash fingerprint.',
5313
+
defs: {
5314
+
main: {
5315
+
type: 'object',
5316
+
required: ['uri', 'cid'],
5317
+
properties: {
5318
+
uri: {
5319
+
type: 'string',
5320
+
format: 'at-uri',
5321
+
},
5322
+
cid: {
5323
+
type: 'string',
5324
+
format: 'cid',
5325
+
},
5326
+
},
5327
+
},
5328
+
},
5329
+
},
5330
+
} as const satisfies Record<string, LexiconDoc>
5331
+
5332
+
export const schemas = Object.values(schemaDict)
5333
+
export const lexicons: Lexicons = new Lexicons(schemas)
5334
+
export const ids = {
5335
+
AppRockskyActorDefs: 'app.rocksky.actor.defs',
5336
+
AppRockskyActorGetActorAlbums: 'app.rocksky.actor.getActorAlbums',
5337
+
AppRockskyActorGetActorArtists: 'app.rocksky.actor.getActorArtists',
5338
+
AppRockskyActorGetActorCompatibility:
5339
+
'app.rocksky.actor.getActorCompatibility',
5340
+
AppRockskyActorGetActorLovedSongs: 'app.rocksky.actor.getActorLovedSongs',
5341
+
AppRockskyActorGetActorNeighbours: 'app.rocksky.actor.getActorNeighbours',
5342
+
AppRockskyActorGetActorPlaylists: 'app.rocksky.actor.getActorPlaylists',
5343
+
AppRockskyActorGetActorScrobbles: 'app.rocksky.actor.getActorScrobbles',
5344
+
AppRockskyActorGetActorSongs: 'app.rocksky.actor.getActorSongs',
5345
+
AppRockskyActorGetProfile: 'app.rocksky.actor.getProfile',
5346
+
AppBskyActorProfile: 'app.bsky.actor.profile',
5347
+
AppRockskyAlbum: 'app.rocksky.album',
5348
+
AppRockskyAlbumDefs: 'app.rocksky.album.defs',
5349
+
AppRockskyAlbumGetAlbum: 'app.rocksky.album.getAlbum',
5350
+
AppRockskyAlbumGetAlbums: 'app.rocksky.album.getAlbums',
5351
+
AppRockskyAlbumGetAlbumTracks: 'app.rocksky.album.getAlbumTracks',
5352
+
AppRockskyApikeyCreateApikey: 'app.rocksky.apikey.createApikey',
5353
+
AppRockskyApikeyDefs: 'app.rocksky.apikey.defs',
5354
+
AppRockskyApikeysDefs: 'app.rocksky.apikeys.defs',
5355
+
AppRockskyApikeyGetApikeys: 'app.rocksky.apikey.getApikeys',
5356
+
AppRockskyApikeyRemoveApikey: 'app.rocksky.apikey.removeApikey',
5357
+
AppRockskyApikeyUpdateApikey: 'app.rocksky.apikey.updateApikey',
5358
+
AppRockskyArtist: 'app.rocksky.artist',
5359
+
AppRockskyArtistDefs: 'app.rocksky.artist.defs',
5360
+
AppRockskyArtistGetArtist: 'app.rocksky.artist.getArtist',
5361
+
AppRockskyArtistGetArtistAlbums: 'app.rocksky.artist.getArtistAlbums',
5362
+
AppRockskyArtistGetArtistListeners: 'app.rocksky.artist.getArtistListeners',
5363
+
AppRockskyArtistGetArtists: 'app.rocksky.artist.getArtists',
5364
+
AppRockskyArtistGetArtistTracks: 'app.rocksky.artist.getArtistTracks',
5365
+
AppRockskyChartsDefs: 'app.rocksky.charts.defs',
5366
+
AppRockskyChartsGetScrobblesChart: 'app.rocksky.charts.getScrobblesChart',
5367
+
AppRockskyDropboxDefs: 'app.rocksky.dropbox.defs',
5368
+
AppRockskyDropboxDownloadFile: 'app.rocksky.dropbox.downloadFile',
5369
+
AppRockskyDropboxGetFiles: 'app.rocksky.dropbox.getFiles',
5370
+
AppRockskyDropboxGetMetadata: 'app.rocksky.dropbox.getMetadata',
5371
+
AppRockskyDropboxGetTemporaryLink: 'app.rocksky.dropbox.getTemporaryLink',
5372
+
AppRockskyFeedDefs: 'app.rocksky.feed.defs',
5373
+
AppRockskyFeedDescribeFeedGenerator: 'app.rocksky.feed.describeFeedGenerator',
5374
+
AppRockskyFeedGenerator: 'app.rocksky.feed.generator',
5375
+
AppRockskyFeedGetFeed: 'app.rocksky.feed.getFeed',
5376
+
AppRockskyFeedGetFeedGenerator: 'app.rocksky.feed.getFeedGenerator',
5377
+
AppRockskyFeedGetFeedGenerators: 'app.rocksky.feed.getFeedGenerators',
5378
+
AppRockskyFeedGetFeedSkeleton: 'app.rocksky.feed.getFeedSkeleton',
5379
+
AppRockskyFeedGetNowPlayings: 'app.rocksky.feed.getNowPlayings',
5380
+
AppRockskyFeedSearch: 'app.rocksky.feed.search',
5381
+
AppRockskyGoogledriveDefs: 'app.rocksky.googledrive.defs',
5382
+
AppRockskyGoogledriveDownloadFile: 'app.rocksky.googledrive.downloadFile',
5383
+
AppRockskyGoogledriveGetFile: 'app.rocksky.googledrive.getFile',
5384
+
AppRockskyGoogledriveGetFiles: 'app.rocksky.googledrive.getFiles',
5385
+
AppRockskyGraphDefs: 'app.rocksky.graph.defs',
5386
+
AppRockskyGraphFollow: 'app.rocksky.graph.follow',
5387
+
AppRockskyGraphFollowAccount: 'app.rocksky.graph.followAccount',
5388
+
AppRockskyGraphGetFollowers: 'app.rocksky.graph.getFollowers',
5389
+
AppRockskyGraphGetFollows: 'app.rocksky.graph.getFollows',
5390
+
AppRockskyGraphGetKnownFollowers: 'app.rocksky.graph.getKnownFollowers',
5391
+
AppRockskyGraphUnfollowAccount: 'app.rocksky.graph.unfollowAccount',
5392
+
AppRockskyLikeDislikeShout: 'app.rocksky.like.dislikeShout',
5393
+
AppRockskyLikeDislikeSong: 'app.rocksky.like.dislikeSong',
5394
+
AppRockskyLike: 'app.rocksky.like',
5395
+
AppRockskyLikeLikeShout: 'app.rocksky.like.likeShout',
5396
+
AppRockskyLikeLikeSong: 'app.rocksky.like.likeSong',
5397
+
AppRockskyPlayerAddDirectoryToQueue: 'app.rocksky.player.addDirectoryToQueue',
5398
+
AppRockskyPlayerAddItemsToQueue: 'app.rocksky.player.addItemsToQueue',
5399
+
AppRockskyPlayerDefs: 'app.rocksky.player.defs',
5400
+
AppRockskyPlayerGetCurrentlyPlaying: 'app.rocksky.player.getCurrentlyPlaying',
5401
+
AppRockskyPlayerGetPlaybackQueue: 'app.rocksky.player.getPlaybackQueue',
5402
+
AppRockskyPlayerNext: 'app.rocksky.player.next',
5403
+
AppRockskyPlayerPause: 'app.rocksky.player.pause',
5404
+
AppRockskyPlayerPlay: 'app.rocksky.player.play',
5405
+
AppRockskyPlayerPlayDirectory: 'app.rocksky.player.playDirectory',
5406
+
AppRockskyPlayerPlayFile: 'app.rocksky.player.playFile',
5407
+
AppRockskyPlayerPrevious: 'app.rocksky.player.previous',
5408
+
AppRockskyPlayerSeek: 'app.rocksky.player.seek',
5409
+
AppRockskyPlaylistCreatePlaylist: 'app.rocksky.playlist.createPlaylist',
5410
+
AppRockskyPlaylistDefs: 'app.rocksky.playlist.defs',
5411
+
AppRockskyPlaylistGetPlaylist: 'app.rocksky.playlist.getPlaylist',
5412
+
AppRockskyPlaylistGetPlaylists: 'app.rocksky.playlist.getPlaylists',
5413
+
AppRockskyPlaylistInsertDirectory: 'app.rocksky.playlist.insertDirectory',
5414
+
AppRockskyPlaylistInsertFiles: 'app.rocksky.playlist.insertFiles',
5415
+
AppRockskyPlaylist: 'app.rocksky.playlist',
5416
+
AppRockskyPlaylistRemovePlaylist: 'app.rocksky.playlist.removePlaylist',
5417
+
AppRockskyPlaylistRemoveTrack: 'app.rocksky.playlist.removeTrack',
5418
+
AppRockskyPlaylistStartPlaylist: 'app.rocksky.playlist.startPlaylist',
5419
+
AppRockskyRadioDefs: 'app.rocksky.radio.defs',
5420
+
AppRockskyRadio: 'app.rocksky.radio',
5421
+
AppRockskyScrobbleCreateScrobble: 'app.rocksky.scrobble.createScrobble',
5422
+
AppRockskyScrobbleDefs: 'app.rocksky.scrobble.defs',
5423
+
AppRockskyScrobbleGetScrobble: 'app.rocksky.scrobble.getScrobble',
5424
+
AppRockskyScrobbleGetScrobbles: 'app.rocksky.scrobble.getScrobbles',
5425
+
AppRockskyScrobble: 'app.rocksky.scrobble',
5426
+
AppRockskyShoutCreateShout: 'app.rocksky.shout.createShout',
5427
+
AppRockskyShoutDefs: 'app.rocksky.shout.defs',
5428
+
AppRockskyShoutGetAlbumShouts: 'app.rocksky.shout.getAlbumShouts',
5429
+
AppRockskyShoutGetArtistShouts: 'app.rocksky.shout.getArtistShouts',
5430
+
AppRockskyShoutGetProfileShouts: 'app.rocksky.shout.getProfileShouts',
5431
+
AppRockskyShoutGetShoutReplies: 'app.rocksky.shout.getShoutReplies',
5432
+
AppRockskyShoutGetTrackShouts: 'app.rocksky.shout.getTrackShouts',
5433
+
AppRockskyShoutRemoveShout: 'app.rocksky.shout.removeShout',
5434
+
AppRockskyShoutReplyShout: 'app.rocksky.shout.replyShout',
5435
+
AppRockskyShoutReportShout: 'app.rocksky.shout.reportShout',
5436
+
AppRockskyShout: 'app.rocksky.shout',
5437
+
AppRockskySongCreateSong: 'app.rocksky.song.createSong',
5438
+
AppRockskySongDefs: 'app.rocksky.song.defs',
5439
+
AppRockskySongGetSong: 'app.rocksky.song.getSong',
5440
+
AppRockskySongGetSongs: 'app.rocksky.song.getSongs',
5441
+
AppRockskySong: 'app.rocksky.song',
5442
+
AppRockskySpotifyDefs: 'app.rocksky.spotify.defs',
5443
+
AppRockskySpotifyGetCurrentlyPlaying:
5444
+
'app.rocksky.spotify.getCurrentlyPlaying',
5445
+
AppRockskySpotifyNext: 'app.rocksky.spotify.next',
5446
+
AppRockskySpotifyPause: 'app.rocksky.spotify.pause',
5447
+
AppRockskySpotifyPlay: 'app.rocksky.spotify.play',
5448
+
AppRockskySpotifyPrevious: 'app.rocksky.spotify.previous',
5449
+
AppRockskySpotifySeek: 'app.rocksky.spotify.seek',
5450
+
AppRockskyStatsDefs: 'app.rocksky.stats.defs',
5451
+
AppRockskyStatsGetStats: 'app.rocksky.stats.getStats',
5452
+
ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
5453
+
}
+38
apps/cli/src/lexicon/types/app/bsky/actor/profile.ts
+38
apps/cli/src/lexicon/types/app/bsky/actor/profile.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
9
+
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
10
+
11
+
export interface Record {
12
+
displayName?: string
13
+
/** Free-form profile description text. */
14
+
description?: string
15
+
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
16
+
avatar?: BlobRef
17
+
/** Larger horizontal image to display behind profile view. */
18
+
banner?: BlobRef
19
+
labels?:
20
+
| ComAtprotoLabelDefs.SelfLabels
21
+
| { $type: string; [k: string]: unknown }
22
+
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main
23
+
createdAt?: string
24
+
[k: string]: unknown
25
+
}
26
+
27
+
export function isRecord(v: unknown): v is Record {
28
+
return (
29
+
isObj(v) &&
30
+
hasProp(v, '$type') &&
31
+
(v.$type === 'app.bsky.actor.profile#main' ||
32
+
v.$type === 'app.bsky.actor.profile')
33
+
)
34
+
}
35
+
36
+
export function validateRecord(v: unknown): ValidationResult {
37
+
return lexicons.validate('app.bsky.actor.profile#main', v)
38
+
}
+146
apps/cli/src/lexicon/types/app/rocksky/actor/defs.ts
+146
apps/cli/src/lexicon/types/app/rocksky/actor/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppRockskyArtistDefs from '../artist/defs'
9
+
10
+
export interface ProfileViewDetailed {
11
+
/** The unique identifier of the actor. */
12
+
id?: string
13
+
/** The DID of the actor. */
14
+
did?: string
15
+
/** The handle of the actor. */
16
+
handle?: string
17
+
/** The display name of the actor. */
18
+
displayName?: string
19
+
/** The URL of the actor's avatar image. */
20
+
avatar?: string
21
+
/** The date and time when the actor was created. */
22
+
createdAt?: string
23
+
/** The date and time when the actor was last updated. */
24
+
updatedAt?: string
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export function isProfileViewDetailed(v: unknown): v is ProfileViewDetailed {
29
+
return (
30
+
isObj(v) &&
31
+
hasProp(v, '$type') &&
32
+
v.$type === 'app.rocksky.actor.defs#profileViewDetailed'
33
+
)
34
+
}
35
+
36
+
export function validateProfileViewDetailed(v: unknown): ValidationResult {
37
+
return lexicons.validate('app.rocksky.actor.defs#profileViewDetailed', v)
38
+
}
39
+
40
+
export interface ProfileViewBasic {
41
+
/** The unique identifier of the actor. */
42
+
id?: string
43
+
/** The DID of the actor. */
44
+
did?: string
45
+
/** The handle of the actor. */
46
+
handle?: string
47
+
/** The display name of the actor. */
48
+
displayName?: string
49
+
/** The URL of the actor's avatar image. */
50
+
avatar?: string
51
+
/** The date and time when the actor was created. */
52
+
createdAt?: string
53
+
/** The date and time when the actor was last updated. */
54
+
updatedAt?: string
55
+
[k: string]: unknown
56
+
}
57
+
58
+
export function isProfileViewBasic(v: unknown): v is ProfileViewBasic {
59
+
return (
60
+
isObj(v) &&
61
+
hasProp(v, '$type') &&
62
+
v.$type === 'app.rocksky.actor.defs#profileViewBasic'
63
+
)
64
+
}
65
+
66
+
export function validateProfileViewBasic(v: unknown): ValidationResult {
67
+
return lexicons.validate('app.rocksky.actor.defs#profileViewBasic', v)
68
+
}
69
+
70
+
export interface NeighbourViewBasic {
71
+
userId?: string
72
+
did?: string
73
+
handle?: string
74
+
displayName?: string
75
+
/** The URL of the actor's avatar image. */
76
+
avatar?: string
77
+
/** The number of artists shared with the actor. */
78
+
sharedArtistsCount?: number
79
+
/** The similarity score with the actor. */
80
+
similarityScore?: number
81
+
/** The top shared artist names with the actor. */
82
+
topSharedArtistNames?: string[]
83
+
/** The top shared artist details with the actor. */
84
+
topSharedArtistsDetails?: AppRockskyArtistDefs.ArtistViewBasic[]
85
+
[k: string]: unknown
86
+
}
87
+
88
+
export function isNeighbourViewBasic(v: unknown): v is NeighbourViewBasic {
89
+
return (
90
+
isObj(v) &&
91
+
hasProp(v, '$type') &&
92
+
v.$type === 'app.rocksky.actor.defs#neighbourViewBasic'
93
+
)
94
+
}
95
+
96
+
export function validateNeighbourViewBasic(v: unknown): ValidationResult {
97
+
return lexicons.validate('app.rocksky.actor.defs#neighbourViewBasic', v)
98
+
}
99
+
100
+
export interface CompatibilityViewBasic {
101
+
compatibilityLevel?: number
102
+
compatibilityPercentage?: number
103
+
sharedArtists?: number
104
+
topSharedArtistNames?: string[]
105
+
topSharedDetailedArtists?: ArtistViewBasic[]
106
+
user1ArtistCount?: number
107
+
user2ArtistCount?: number
108
+
[k: string]: unknown
109
+
}
110
+
111
+
export function isCompatibilityViewBasic(
112
+
v: unknown,
113
+
): v is CompatibilityViewBasic {
114
+
return (
115
+
isObj(v) &&
116
+
hasProp(v, '$type') &&
117
+
v.$type === 'app.rocksky.actor.defs#compatibilityViewBasic'
118
+
)
119
+
}
120
+
121
+
export function validateCompatibilityViewBasic(v: unknown): ValidationResult {
122
+
return lexicons.validate('app.rocksky.actor.defs#compatibilityViewBasic', v)
123
+
}
124
+
125
+
export interface ArtistViewBasic {
126
+
id?: string
127
+
name?: string
128
+
picture?: string
129
+
uri?: string
130
+
user1Rank?: number
131
+
user2Rank?: number
132
+
weight?: number
133
+
[k: string]: unknown
134
+
}
135
+
136
+
export function isArtistViewBasic(v: unknown): v is ArtistViewBasic {
137
+
return (
138
+
isObj(v) &&
139
+
hasProp(v, '$type') &&
140
+
v.$type === 'app.rocksky.actor.defs#artistViewBasic'
141
+
)
142
+
}
143
+
144
+
export function validateArtistViewBasic(v: unknown): ValidationResult {
145
+
return lexicons.validate('app.rocksky.actor.defs#artistViewBasic', v)
146
+
}
+56
apps/cli/src/lexicon/types/app/rocksky/actor/getActorAlbums.ts
+56
apps/cli/src/lexicon/types/app/rocksky/actor/getActorAlbums.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyAlbumDefs from '../album/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did: string
15
+
/** The maximum number of albums to return */
16
+
limit?: number
17
+
/** The offset for pagination */
18
+
offset?: number
19
+
/** The start date to filter albums from (ISO 8601 format) */
20
+
startDate?: string
21
+
/** The end date to filter albums to (ISO 8601 format) */
22
+
endDate?: string
23
+
}
24
+
25
+
export type InputSchema = undefined
26
+
27
+
export interface OutputSchema {
28
+
albums?: AppRockskyAlbumDefs.AlbumViewBasic[]
29
+
[k: string]: unknown
30
+
}
31
+
32
+
export type HandlerInput = undefined
33
+
34
+
export interface HandlerSuccess {
35
+
encoding: 'application/json'
36
+
body: OutputSchema
37
+
headers?: { [key: string]: string }
38
+
}
39
+
40
+
export interface HandlerError {
41
+
status: number
42
+
message?: string
43
+
}
44
+
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
46
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
+
auth: HA
48
+
params: QueryParams
49
+
input: HandlerInput
50
+
req: express.Request
51
+
res: express.Response
52
+
resetRouteRateLimits: () => Promise<void>
53
+
}
54
+
export type Handler<HA extends HandlerAuth = never> = (
55
+
ctx: HandlerReqCtx<HA>,
56
+
) => Promise<HandlerOutput> | HandlerOutput
+56
apps/cli/src/lexicon/types/app/rocksky/actor/getActorArtists.ts
+56
apps/cli/src/lexicon/types/app/rocksky/actor/getActorArtists.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyArtistDefs from '../artist/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did: string
15
+
/** The maximum number of albums to return */
16
+
limit?: number
17
+
/** The offset for pagination */
18
+
offset?: number
19
+
/** The start date to filter albums from (ISO 8601 format) */
20
+
startDate?: string
21
+
/** The end date to filter albums to (ISO 8601 format) */
22
+
endDate?: string
23
+
}
24
+
25
+
export type InputSchema = undefined
26
+
27
+
export interface OutputSchema {
28
+
artists?: AppRockskyArtistDefs.ArtistViewBasic[]
29
+
[k: string]: unknown
30
+
}
31
+
32
+
export type HandlerInput = undefined
33
+
34
+
export interface HandlerSuccess {
35
+
encoding: 'application/json'
36
+
body: OutputSchema
37
+
headers?: { [key: string]: string }
38
+
}
39
+
40
+
export interface HandlerError {
41
+
status: number
42
+
message?: string
43
+
}
44
+
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
46
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
+
auth: HA
48
+
params: QueryParams
49
+
input: HandlerInput
50
+
req: express.Request
51
+
res: express.Response
52
+
resetRouteRateLimits: () => Promise<void>
53
+
}
54
+
export type Handler<HA extends HandlerAuth = never> = (
55
+
ctx: HandlerReqCtx<HA>,
56
+
) => Promise<HandlerOutput> | HandlerOutput
+48
apps/cli/src/lexicon/types/app/rocksky/actor/getActorCompatibility.ts
+48
apps/cli/src/lexicon/types/app/rocksky/actor/getActorCompatibility.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyActorDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** DID or handle to get compatibility for */
14
+
did: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
19
+
export interface OutputSchema {
20
+
compatibility?: AppRockskyActorDefs.CompatibilityViewBasic
21
+
[k: string]: unknown
22
+
}
23
+
24
+
export type HandlerInput = undefined
25
+
26
+
export interface HandlerSuccess {
27
+
encoding: 'application/json'
28
+
body: OutputSchema
29
+
headers?: { [key: string]: string }
30
+
}
31
+
32
+
export interface HandlerError {
33
+
status: number
34
+
message?: string
35
+
}
36
+
37
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
38
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
39
+
auth: HA
40
+
params: QueryParams
41
+
input: HandlerInput
42
+
req: express.Request
43
+
res: express.Response
44
+
resetRouteRateLimits: () => Promise<void>
45
+
}
46
+
export type Handler<HA extends HandlerAuth = never> = (
47
+
ctx: HandlerReqCtx<HA>,
48
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/actor/getActorLovedSongs.ts
+52
apps/cli/src/lexicon/types/app/rocksky/actor/getActorLovedSongs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from '../song/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did: string
15
+
/** The maximum number of albums to return */
16
+
limit?: number
17
+
/** The offset for pagination */
18
+
offset?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
tracks?: AppRockskySongDefs.SongViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+48
apps/cli/src/lexicon/types/app/rocksky/actor/getActorNeighbours.ts
+48
apps/cli/src/lexicon/types/app/rocksky/actor/getActorNeighbours.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyActorDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
19
+
export interface OutputSchema {
20
+
neighbours?: AppRockskyActorDefs.NeighbourViewBasic[]
21
+
[k: string]: unknown
22
+
}
23
+
24
+
export type HandlerInput = undefined
25
+
26
+
export interface HandlerSuccess {
27
+
encoding: 'application/json'
28
+
body: OutputSchema
29
+
headers?: { [key: string]: string }
30
+
}
31
+
32
+
export interface HandlerError {
33
+
status: number
34
+
message?: string
35
+
}
36
+
37
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
38
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
39
+
auth: HA
40
+
params: QueryParams
41
+
input: HandlerInput
42
+
req: express.Request
43
+
res: express.Response
44
+
resetRouteRateLimits: () => Promise<void>
45
+
}
46
+
export type Handler<HA extends HandlerAuth = never> = (
47
+
ctx: HandlerReqCtx<HA>,
48
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/actor/getActorPlaylists.ts
+52
apps/cli/src/lexicon/types/app/rocksky/actor/getActorPlaylists.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyPlaylistDefs from '../playlist/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did: string
15
+
/** The maximum number of albums to return */
16
+
limit?: number
17
+
/** The offset for pagination */
18
+
offset?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
playlists?: AppRockskyPlaylistDefs.PlaylistViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/actor/getActorScrobbles.ts
+52
apps/cli/src/lexicon/types/app/rocksky/actor/getActorScrobbles.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyScrobbleDefs from '../scrobble/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did: string
15
+
/** The maximum number of albums to return */
16
+
limit?: number
17
+
/** The offset for pagination */
18
+
offset?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
scrobbles?: AppRockskyScrobbleDefs.ScrobbleViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+56
apps/cli/src/lexicon/types/app/rocksky/actor/getActorSongs.ts
+56
apps/cli/src/lexicon/types/app/rocksky/actor/getActorSongs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from '../song/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did: string
15
+
/** The maximum number of albums to return */
16
+
limit?: number
17
+
/** The offset for pagination */
18
+
offset?: number
19
+
/** The start date to filter albums from (ISO 8601 format) */
20
+
startDate?: string
21
+
/** The end date to filter albums to (ISO 8601 format) */
22
+
endDate?: string
23
+
}
24
+
25
+
export type InputSchema = undefined
26
+
27
+
export interface OutputSchema {
28
+
songs?: AppRockskySongDefs.SongViewBasic[]
29
+
[k: string]: unknown
30
+
}
31
+
32
+
export type HandlerInput = undefined
33
+
34
+
export interface HandlerSuccess {
35
+
encoding: 'application/json'
36
+
body: OutputSchema
37
+
headers?: { [key: string]: string }
38
+
}
39
+
40
+
export interface HandlerError {
41
+
status: number
42
+
message?: string
43
+
}
44
+
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
46
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
+
auth: HA
48
+
params: QueryParams
49
+
input: HandlerInput
50
+
req: express.Request
51
+
res: express.Response
52
+
resetRouteRateLimits: () => Promise<void>
53
+
}
54
+
export type Handler<HA extends HandlerAuth = never> = (
55
+
ctx: HandlerReqCtx<HA>,
56
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/actor/getProfile.ts
+43
apps/cli/src/lexicon/types/app/rocksky/actor/getProfile.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyActorDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did?: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyActorDefs.ProfileViewDetailed
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+85
apps/cli/src/lexicon/types/app/rocksky/album/defs.ts
+85
apps/cli/src/lexicon/types/app/rocksky/album/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppRockskySongDefsSongViewBasic from '../song/defs/songViewBasic'
9
+
10
+
export interface AlbumViewBasic {
11
+
/** The unique identifier of the album. */
12
+
id?: string
13
+
/** The URI of the album. */
14
+
uri?: string
15
+
/** The title of the album. */
16
+
title?: string
17
+
/** The artist of the album. */
18
+
artist?: string
19
+
/** The URI of the album's artist. */
20
+
artistUri?: string
21
+
/** The year the album was released. */
22
+
year?: number
23
+
/** The URL of the album art image. */
24
+
albumArt?: string
25
+
/** The release date of the album. */
26
+
releaseDate?: string
27
+
/** The SHA256 hash of the album. */
28
+
sha256?: string
29
+
/** The number of times the album has been played. */
30
+
playCount?: number
31
+
/** The number of unique listeners who have played the album. */
32
+
uniqueListeners?: number
33
+
[k: string]: unknown
34
+
}
35
+
36
+
export function isAlbumViewBasic(v: unknown): v is AlbumViewBasic {
37
+
return (
38
+
isObj(v) &&
39
+
hasProp(v, '$type') &&
40
+
v.$type === 'app.rocksky.album.defs#albumViewBasic'
41
+
)
42
+
}
43
+
44
+
export function validateAlbumViewBasic(v: unknown): ValidationResult {
45
+
return lexicons.validate('app.rocksky.album.defs#albumViewBasic', v)
46
+
}
47
+
48
+
export interface AlbumViewDetailed {
49
+
/** The unique identifier of the album. */
50
+
id?: string
51
+
/** The URI of the album. */
52
+
uri?: string
53
+
/** The title of the album. */
54
+
title?: string
55
+
/** The artist of the album. */
56
+
artist?: string
57
+
/** The URI of the album's artist. */
58
+
artistUri?: string
59
+
/** The year the album was released. */
60
+
year?: number
61
+
/** The URL of the album art image. */
62
+
albumArt?: string
63
+
/** The release date of the album. */
64
+
releaseDate?: string
65
+
/** The SHA256 hash of the album. */
66
+
sha256?: string
67
+
/** The number of times the album has been played. */
68
+
playCount?: number
69
+
/** The number of unique listeners who have played the album. */
70
+
uniqueListeners?: number
71
+
tracks?: AppRockskySongDefsSongViewBasic.Main[]
72
+
[k: string]: unknown
73
+
}
74
+
75
+
export function isAlbumViewDetailed(v: unknown): v is AlbumViewDetailed {
76
+
return (
77
+
isObj(v) &&
78
+
hasProp(v, '$type') &&
79
+
v.$type === 'app.rocksky.album.defs#albumViewDetailed'
80
+
)
81
+
}
82
+
83
+
export function validateAlbumViewDetailed(v: unknown): ValidationResult {
84
+
return lexicons.validate('app.rocksky.album.defs#albumViewDetailed', v)
85
+
}
+43
apps/cli/src/lexicon/types/app/rocksky/album/getAlbum.ts
+43
apps/cli/src/lexicon/types/app/rocksky/album/getAlbum.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyAlbumDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the album to retrieve. */
14
+
uri: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyAlbumDefs.AlbumViewDetailed
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+48
apps/cli/src/lexicon/types/app/rocksky/album/getAlbumTracks.ts
+48
apps/cli/src/lexicon/types/app/rocksky/album/getAlbumTracks.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from '../song/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the album to retrieve tracks from */
14
+
uri: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
19
+
export interface OutputSchema {
20
+
tracks?: AppRockskySongDefs.SongViewBasic[]
21
+
[k: string]: unknown
22
+
}
23
+
24
+
export type HandlerInput = undefined
25
+
26
+
export interface HandlerSuccess {
27
+
encoding: 'application/json'
28
+
body: OutputSchema
29
+
headers?: { [key: string]: string }
30
+
}
31
+
32
+
export interface HandlerError {
33
+
status: number
34
+
message?: string
35
+
}
36
+
37
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
38
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
39
+
auth: HA
40
+
params: QueryParams
41
+
input: HandlerInput
42
+
req: express.Request
43
+
res: express.Response
44
+
resetRouteRateLimits: () => Promise<void>
45
+
}
46
+
export type Handler<HA extends HandlerAuth = never> = (
47
+
ctx: HandlerReqCtx<HA>,
48
+
) => Promise<HandlerOutput> | HandlerOutput
+50
apps/cli/src/lexicon/types/app/rocksky/album/getAlbums.ts
+50
apps/cli/src/lexicon/types/app/rocksky/album/getAlbums.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyAlbumDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The maximum number of albums to return */
14
+
limit?: number
15
+
/** The offset for pagination */
16
+
offset?: number
17
+
}
18
+
19
+
export type InputSchema = undefined
20
+
21
+
export interface OutputSchema {
22
+
albums?: AppRockskyAlbumDefs.AlbumViewBasic[]
23
+
[k: string]: unknown
24
+
}
25
+
26
+
export type HandlerInput = undefined
27
+
28
+
export interface HandlerSuccess {
29
+
encoding: 'application/json'
30
+
body: OutputSchema
31
+
headers?: { [key: string]: string }
32
+
}
33
+
34
+
export interface HandlerError {
35
+
status: number
36
+
message?: string
37
+
}
38
+
39
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
40
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
41
+
auth: HA
42
+
params: QueryParams
43
+
input: HandlerInput
44
+
req: express.Request
45
+
res: express.Response
46
+
resetRouteRateLimits: () => Promise<void>
47
+
}
48
+
export type Handler<HA extends HandlerAuth = never> = (
49
+
ctx: HandlerReqCtx<HA>,
50
+
) => Promise<HandlerOutput> | HandlerOutput
+51
apps/cli/src/lexicon/types/app/rocksky/album.ts
+51
apps/cli/src/lexicon/types/app/rocksky/album.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface Record {
10
+
/** The title of the album. */
11
+
title: string
12
+
/** The artist of the album. */
13
+
artist: string
14
+
/** The duration of the album in seconds. */
15
+
duration?: number
16
+
/** The release date of the album. */
17
+
releaseDate?: string
18
+
/** The year the album was released. */
19
+
year?: number
20
+
/** The genre of the album. */
21
+
genre?: string
22
+
/** The album art of the album. */
23
+
albumArt?: BlobRef
24
+
/** The URL of the album art of the album. */
25
+
albumArtUrl?: string
26
+
/** The tags of the album. */
27
+
tags?: string[]
28
+
/** The YouTube link of the album. */
29
+
youtubeLink?: string
30
+
/** The Spotify link of the album. */
31
+
spotifyLink?: string
32
+
/** The tidal link of the album. */
33
+
tidalLink?: string
34
+
/** The Apple Music link of the album. */
35
+
appleMusicLink?: string
36
+
/** The date and time when the album was created. */
37
+
createdAt: string
38
+
[k: string]: unknown
39
+
}
40
+
41
+
export function isRecord(v: unknown): v is Record {
42
+
return (
43
+
isObj(v) &&
44
+
hasProp(v, '$type') &&
45
+
(v.$type === 'app.rocksky.album#main' || v.$type === 'app.rocksky.album')
46
+
)
47
+
}
48
+
49
+
export function validateRecord(v: unknown): ValidationResult {
50
+
return lexicons.validate('app.rocksky.album#main', v)
51
+
}
+51
apps/cli/src/lexicon/types/app/rocksky/apikey/createApikey.ts
+51
apps/cli/src/lexicon/types/app/rocksky/apikey/createApikey.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyApikeyDefs from './defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The name of the API key. */
16
+
name: string
17
+
/** A description for the API key. */
18
+
description?: string
19
+
[k: string]: unknown
20
+
}
21
+
22
+
export type OutputSchema = AppRockskyApikeyDefs.ApiKey
23
+
24
+
export interface HandlerInput {
25
+
encoding: 'application/json'
26
+
body: InputSchema
27
+
}
28
+
29
+
export interface HandlerSuccess {
30
+
encoding: 'application/json'
31
+
body: OutputSchema
32
+
headers?: { [key: string]: string }
33
+
}
34
+
35
+
export interface HandlerError {
36
+
status: number
37
+
message?: string
38
+
}
39
+
40
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
41
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
42
+
auth: HA
43
+
params: QueryParams
44
+
input: HandlerInput
45
+
req: express.Request
46
+
res: express.Response
47
+
resetRouteRateLimits: () => Promise<void>
48
+
}
49
+
export type Handler<HA extends HandlerAuth = never> = (
50
+
ctx: HandlerReqCtx<HA>,
51
+
) => Promise<HandlerOutput> | HandlerOutput
+31
apps/cli/src/lexicon/types/app/rocksky/apikey/defs.ts
+31
apps/cli/src/lexicon/types/app/rocksky/apikey/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface ApiKeyView {
10
+
/** The unique identifier of the API key. */
11
+
id?: string
12
+
/** The name of the API key. */
13
+
name?: string
14
+
/** A description for the API key. */
15
+
description?: string
16
+
/** The date and time when the API key was created. */
17
+
createdAt?: string
18
+
[k: string]: unknown
19
+
}
20
+
21
+
export function isApiKeyView(v: unknown): v is ApiKeyView {
22
+
return (
23
+
isObj(v) &&
24
+
hasProp(v, '$type') &&
25
+
v.$type === 'app.rocksky.apikey.defs#apiKeyView'
26
+
)
27
+
}
28
+
29
+
export function validateApiKeyView(v: unknown): ValidationResult {
30
+
return lexicons.validate('app.rocksky.apikey.defs#apiKeyView', v)
31
+
}
+50
apps/cli/src/lexicon/types/app/rocksky/apikey/getApikeys.ts
+50
apps/cli/src/lexicon/types/app/rocksky/apikey/getApikeys.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyApikeyDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The number of API keys to skip before starting to collect the result set. */
14
+
offset?: number
15
+
/** The number of API keys to return per page. */
16
+
limit?: number
17
+
}
18
+
19
+
export type InputSchema = undefined
20
+
21
+
export interface OutputSchema {
22
+
apiKeys?: AppRockskyApikeyDefs.ApikeyView[]
23
+
[k: string]: unknown
24
+
}
25
+
26
+
export type HandlerInput = undefined
27
+
28
+
export interface HandlerSuccess {
29
+
encoding: 'application/json'
30
+
body: OutputSchema
31
+
headers?: { [key: string]: string }
32
+
}
33
+
34
+
export interface HandlerError {
35
+
status: number
36
+
message?: string
37
+
}
38
+
39
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
40
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
41
+
auth: HA
42
+
params: QueryParams
43
+
input: HandlerInput
44
+
req: express.Request
45
+
res: express.Response
46
+
resetRouteRateLimits: () => Promise<void>
47
+
}
48
+
export type Handler<HA extends HandlerAuth = never> = (
49
+
ctx: HandlerReqCtx<HA>,
50
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/apikey/removeApikey.ts
+43
apps/cli/src/lexicon/types/app/rocksky/apikey/removeApikey.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyApikeyDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The ID of the API key to remove. */
14
+
id: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyApikeyDefs.ApiKey
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+53
apps/cli/src/lexicon/types/app/rocksky/apikey/updateApikey.ts
+53
apps/cli/src/lexicon/types/app/rocksky/apikey/updateApikey.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyApikeyDefs from './defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The ID of the API key to update. */
16
+
id: string
17
+
/** The new name of the API key. */
18
+
name: string
19
+
/** A new description for the API key. */
20
+
description?: string
21
+
[k: string]: unknown
22
+
}
23
+
24
+
export type OutputSchema = AppRockskyApikeyDefs.ApiKey
25
+
26
+
export interface HandlerInput {
27
+
encoding: 'application/json'
28
+
body: InputSchema
29
+
}
30
+
31
+
export interface HandlerSuccess {
32
+
encoding: 'application/json'
33
+
body: OutputSchema
34
+
headers?: { [key: string]: string }
35
+
}
36
+
37
+
export interface HandlerError {
38
+
status: number
39
+
message?: string
40
+
}
41
+
42
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
43
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
44
+
auth: HA
45
+
params: QueryParams
46
+
input: HandlerInput
47
+
req: express.Request
48
+
res: express.Response
49
+
resetRouteRateLimits: () => Promise<void>
50
+
}
51
+
export type Handler<HA extends HandlerAuth = never> = (
52
+
ctx: HandlerReqCtx<HA>,
53
+
) => Promise<HandlerOutput> | HandlerOutput
+7
apps/cli/src/lexicon/types/app/rocksky/apikeys/defs.ts
+7
apps/cli/src/lexicon/types/app/rocksky/apikeys/defs.ts
+140
apps/cli/src/lexicon/types/app/rocksky/artist/defs.ts
+140
apps/cli/src/lexicon/types/app/rocksky/artist/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface ArtistViewBasic {
10
+
/** The unique identifier of the artist. */
11
+
id?: string
12
+
/** The URI of the artist. */
13
+
uri?: string
14
+
/** The name of the artist. */
15
+
name?: string
16
+
/** The picture of the artist. */
17
+
picture?: string
18
+
/** The SHA256 hash of the artist. */
19
+
sha256?: string
20
+
/** The number of times the artist has been played. */
21
+
playCount?: number
22
+
/** The number of unique listeners who have played the artist. */
23
+
uniqueListeners?: number
24
+
[k: string]: unknown
25
+
}
26
+
27
+
export function isArtistViewBasic(v: unknown): v is ArtistViewBasic {
28
+
return (
29
+
isObj(v) &&
30
+
hasProp(v, '$type') &&
31
+
v.$type === 'app.rocksky.artist.defs#artistViewBasic'
32
+
)
33
+
}
34
+
35
+
export function validateArtistViewBasic(v: unknown): ValidationResult {
36
+
return lexicons.validate('app.rocksky.artist.defs#artistViewBasic', v)
37
+
}
38
+
39
+
export interface ArtistViewDetailed {
40
+
/** The unique identifier of the artist. */
41
+
id?: string
42
+
/** The URI of the artist. */
43
+
uri?: string
44
+
/** The name of the artist. */
45
+
name?: string
46
+
/** The picture of the artist. */
47
+
picture?: string
48
+
/** The SHA256 hash of the artist. */
49
+
sha256?: string
50
+
/** The number of times the artist has been played. */
51
+
playCount?: number
52
+
/** The number of unique listeners who have played the artist. */
53
+
uniqueListeners?: number
54
+
[k: string]: unknown
55
+
}
56
+
57
+
export function isArtistViewDetailed(v: unknown): v is ArtistViewDetailed {
58
+
return (
59
+
isObj(v) &&
60
+
hasProp(v, '$type') &&
61
+
v.$type === 'app.rocksky.artist.defs#artistViewDetailed'
62
+
)
63
+
}
64
+
65
+
export function validateArtistViewDetailed(v: unknown): ValidationResult {
66
+
return lexicons.validate('app.rocksky.artist.defs#artistViewDetailed', v)
67
+
}
68
+
69
+
export interface SongViewBasic {
70
+
/** The URI of the song. */
71
+
uri?: string
72
+
/** The title of the song. */
73
+
title?: string
74
+
/** The number of times the song has been played. */
75
+
playCount?: number
76
+
[k: string]: unknown
77
+
}
78
+
79
+
export function isSongViewBasic(v: unknown): v is SongViewBasic {
80
+
return (
81
+
isObj(v) &&
82
+
hasProp(v, '$type') &&
83
+
v.$type === 'app.rocksky.artist.defs#songViewBasic'
84
+
)
85
+
}
86
+
87
+
export function validateSongViewBasic(v: unknown): ValidationResult {
88
+
return lexicons.validate('app.rocksky.artist.defs#songViewBasic', v)
89
+
}
90
+
91
+
export interface ListenerViewBasic {
92
+
/** The unique identifier of the actor. */
93
+
id?: string
94
+
/** The DID of the listener. */
95
+
did?: string
96
+
/** The handle of the listener. */
97
+
handle?: string
98
+
/** The display name of the listener. */
99
+
displayName?: string
100
+
/** The URL of the listener's avatar image. */
101
+
avatar?: string
102
+
mostListenedSong?: SongViewBasic
103
+
/** The total number of plays by the listener. */
104
+
totalPlays?: number
105
+
/** The rank of the listener among all listeners of the artist. */
106
+
rank?: number
107
+
[k: string]: unknown
108
+
}
109
+
110
+
export function isListenerViewBasic(v: unknown): v is ListenerViewBasic {
111
+
return (
112
+
isObj(v) &&
113
+
hasProp(v, '$type') &&
114
+
v.$type === 'app.rocksky.artist.defs#listenerViewBasic'
115
+
)
116
+
}
117
+
118
+
export function validateListenerViewBasic(v: unknown): ValidationResult {
119
+
return lexicons.validate('app.rocksky.artist.defs#listenerViewBasic', v)
120
+
}
121
+
122
+
export interface ArtistMbid {
123
+
/** The MusicBrainz Identifier (MBID) of the artist. */
124
+
mbid?: string
125
+
/** The name of the artist. */
126
+
name?: string
127
+
[k: string]: unknown
128
+
}
129
+
130
+
export function isArtistMbid(v: unknown): v is ArtistMbid {
131
+
return (
132
+
isObj(v) &&
133
+
hasProp(v, '$type') &&
134
+
v.$type === 'app.rocksky.artist.defs#artistMbid'
135
+
)
136
+
}
137
+
138
+
export function validateArtistMbid(v: unknown): ValidationResult {
139
+
return lexicons.validate('app.rocksky.artist.defs#artistMbid', v)
140
+
}
+43
apps/cli/src/lexicon/types/app/rocksky/artist/getArtist.ts
+43
apps/cli/src/lexicon/types/app/rocksky/artist/getArtist.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyArtistDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the artist to retrieve details from */
14
+
uri: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyArtistDefs.ArtistViewDetailed
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+48
apps/cli/src/lexicon/types/app/rocksky/artist/getArtistAlbums.ts
+48
apps/cli/src/lexicon/types/app/rocksky/artist/getArtistAlbums.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyAlbumDefs from '../album/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the artist to retrieve albums from */
14
+
uri: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
19
+
export interface OutputSchema {
20
+
albums?: AppRockskyAlbumDefs.AlbumViewBasic[]
21
+
[k: string]: unknown
22
+
}
23
+
24
+
export type HandlerInput = undefined
25
+
26
+
export interface HandlerSuccess {
27
+
encoding: 'application/json'
28
+
body: OutputSchema
29
+
headers?: { [key: string]: string }
30
+
}
31
+
32
+
export interface HandlerError {
33
+
status: number
34
+
message?: string
35
+
}
36
+
37
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
38
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
39
+
auth: HA
40
+
params: QueryParams
41
+
input: HandlerInput
42
+
req: express.Request
43
+
res: express.Response
44
+
resetRouteRateLimits: () => Promise<void>
45
+
}
46
+
export type Handler<HA extends HandlerAuth = never> = (
47
+
ctx: HandlerReqCtx<HA>,
48
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/artist/getArtistListeners.ts
+52
apps/cli/src/lexicon/types/app/rocksky/artist/getArtistListeners.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyArtistDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the artist to retrieve listeners from */
14
+
uri: string
15
+
/** Number of items to skip before returning results */
16
+
offset?: number
17
+
/** Maximum number of results to return */
18
+
limit?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
listeners?: AppRockskyArtistDefs.ListenerViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/artist/getArtistTracks.ts
+52
apps/cli/src/lexicon/types/app/rocksky/artist/getArtistTracks.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from '../song/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the artist to retrieve albums from */
14
+
uri?: string
15
+
/** The maximum number of tracks to return */
16
+
limit?: number
17
+
/** The offset for pagination */
18
+
offset?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
tracks?: AppRockskySongDefs.SongViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/artist/getArtists.ts
+52
apps/cli/src/lexicon/types/app/rocksky/artist/getArtists.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyArtistDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The maximum number of artists to return */
14
+
limit?: number
15
+
/** The offset for pagination */
16
+
offset?: number
17
+
/** The names of the artists to return */
18
+
names?: string
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
artists?: AppRockskyArtistDefs.ArtistViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+41
apps/cli/src/lexicon/types/app/rocksky/artist.ts
+41
apps/cli/src/lexicon/types/app/rocksky/artist.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface Record {
10
+
/** The name of the artist. */
11
+
name: string
12
+
/** The biography of the artist. */
13
+
bio?: string
14
+
/** The picture of the artist. */
15
+
picture?: BlobRef
16
+
/** The URL of the picture of the artist. */
17
+
pictureUrl?: string
18
+
/** The tags of the artist. */
19
+
tags?: string[]
20
+
/** The birth date of the artist. */
21
+
born?: string
22
+
/** The death date of the artist. */
23
+
died?: string
24
+
/** The birth place of the artist. */
25
+
bornIn?: string
26
+
/** The date when the artist was created. */
27
+
createdAt: string
28
+
[k: string]: unknown
29
+
}
30
+
31
+
export function isRecord(v: unknown): v is Record {
32
+
return (
33
+
isObj(v) &&
34
+
hasProp(v, '$type') &&
35
+
(v.$type === 'app.rocksky.artist#main' || v.$type === 'app.rocksky.artist')
36
+
)
37
+
}
38
+
39
+
export function validateRecord(v: unknown): ValidationResult {
40
+
return lexicons.validate('app.rocksky.artist#main', v)
41
+
}
+44
apps/cli/src/lexicon/types/app/rocksky/charts/defs.ts
+44
apps/cli/src/lexicon/types/app/rocksky/charts/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface ChartsView {
10
+
scrobbles?: ScrobbleViewBasic[]
11
+
[k: string]: unknown
12
+
}
13
+
14
+
export function isChartsView(v: unknown): v is ChartsView {
15
+
return (
16
+
isObj(v) &&
17
+
hasProp(v, '$type') &&
18
+
v.$type === 'app.rocksky.charts.defs#chartsView'
19
+
)
20
+
}
21
+
22
+
export function validateChartsView(v: unknown): ValidationResult {
23
+
return lexicons.validate('app.rocksky.charts.defs#chartsView', v)
24
+
}
25
+
26
+
export interface ScrobbleViewBasic {
27
+
/** The date of the scrobble. */
28
+
date?: string
29
+
/** The number of scrobbles on this date. */
30
+
count?: number
31
+
[k: string]: unknown
32
+
}
33
+
34
+
export function isScrobbleViewBasic(v: unknown): v is ScrobbleViewBasic {
35
+
return (
36
+
isObj(v) &&
37
+
hasProp(v, '$type') &&
38
+
v.$type === 'app.rocksky.charts.defs#scrobbleViewBasic'
39
+
)
40
+
}
41
+
42
+
export function validateScrobbleViewBasic(v: unknown): ValidationResult {
43
+
return lexicons.validate('app.rocksky.charts.defs#scrobbleViewBasic', v)
44
+
}
+49
apps/cli/src/lexicon/types/app/rocksky/charts/getScrobblesChart.ts
+49
apps/cli/src/lexicon/types/app/rocksky/charts/getScrobblesChart.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyChartsDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did?: string
15
+
/** The URI of the artist to filter by */
16
+
artisturi?: string
17
+
/** The URI of the album to filter by */
18
+
albumuri?: string
19
+
/** The URI of the track to filter by */
20
+
songuri?: string
21
+
}
22
+
23
+
export type InputSchema = undefined
24
+
export type OutputSchema = AppRockskyChartsDefs.ChartsView
25
+
export type HandlerInput = undefined
26
+
27
+
export interface HandlerSuccess {
28
+
encoding: 'application/json'
29
+
body: OutputSchema
30
+
headers?: { [key: string]: string }
31
+
}
32
+
33
+
export interface HandlerError {
34
+
status: number
35
+
message?: string
36
+
}
37
+
38
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
39
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
40
+
auth: HA
41
+
params: QueryParams
42
+
input: HandlerInput
43
+
req: express.Request
44
+
res: express.Response
45
+
resetRouteRateLimits: () => Promise<void>
46
+
}
47
+
export type Handler<HA extends HandlerAuth = never> = (
48
+
ctx: HandlerReqCtx<HA>,
49
+
) => Promise<HandlerOutput> | HandlerOutput
+71
apps/cli/src/lexicon/types/app/rocksky/dropbox/defs.ts
+71
apps/cli/src/lexicon/types/app/rocksky/dropbox/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface FileView {
10
+
/** The unique identifier of the file. */
11
+
id?: string
12
+
/** The name of the file. */
13
+
name?: string
14
+
/** The lowercased path of the file. */
15
+
pathLower?: string
16
+
/** The display path of the file. */
17
+
pathDisplay?: string
18
+
/** The last modified date and time of the file on the client. */
19
+
clientModified?: string
20
+
/** The last modified date and time of the file on the server. */
21
+
serverModified?: string
22
+
[k: string]: unknown
23
+
}
24
+
25
+
export function isFileView(v: unknown): v is FileView {
26
+
return (
27
+
isObj(v) &&
28
+
hasProp(v, '$type') &&
29
+
v.$type === 'app.rocksky.dropbox.defs#fileView'
30
+
)
31
+
}
32
+
33
+
export function validateFileView(v: unknown): ValidationResult {
34
+
return lexicons.validate('app.rocksky.dropbox.defs#fileView', v)
35
+
}
36
+
37
+
export interface FileListView {
38
+
/** A list of files in the Dropbox. */
39
+
files?: FileView[]
40
+
[k: string]: unknown
41
+
}
42
+
43
+
export function isFileListView(v: unknown): v is FileListView {
44
+
return (
45
+
isObj(v) &&
46
+
hasProp(v, '$type') &&
47
+
v.$type === 'app.rocksky.dropbox.defs#fileListView'
48
+
)
49
+
}
50
+
51
+
export function validateFileListView(v: unknown): ValidationResult {
52
+
return lexicons.validate('app.rocksky.dropbox.defs#fileListView', v)
53
+
}
54
+
55
+
export interface TemporaryLinkView {
56
+
/** The temporary link to access the file. */
57
+
link?: string
58
+
[k: string]: unknown
59
+
}
60
+
61
+
export function isTemporaryLinkView(v: unknown): v is TemporaryLinkView {
62
+
return (
63
+
isObj(v) &&
64
+
hasProp(v, '$type') &&
65
+
v.$type === 'app.rocksky.dropbox.defs#temporaryLinkView'
66
+
)
67
+
}
68
+
69
+
export function validateTemporaryLinkView(v: unknown): ValidationResult {
70
+
return lexicons.validate('app.rocksky.dropbox.defs#temporaryLinkView', v)
71
+
}
+42
apps/cli/src/lexicon/types/app/rocksky/dropbox/downloadFile.ts
+42
apps/cli/src/lexicon/types/app/rocksky/dropbox/downloadFile.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import stream from 'stream'
6
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
7
+
import { lexicons } from '../../../../lexicons'
8
+
import { isObj, hasProp } from '../../../../util'
9
+
import { CID } from 'multiformats/cid'
10
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
11
+
12
+
export interface QueryParams {
13
+
/** The unique identifier of the file to download */
14
+
fileId: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type HandlerInput = undefined
19
+
20
+
export interface HandlerSuccess {
21
+
encoding: 'application/octet-stream'
22
+
body: Uint8Array | stream.Readable
23
+
headers?: { [key: string]: string }
24
+
}
25
+
26
+
export interface HandlerError {
27
+
status: number
28
+
message?: string
29
+
}
30
+
31
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
32
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
33
+
auth: HA
34
+
params: QueryParams
35
+
input: HandlerInput
36
+
req: express.Request
37
+
res: express.Response
38
+
resetRouteRateLimits: () => Promise<void>
39
+
}
40
+
export type Handler<HA extends HandlerAuth = never> = (
41
+
ctx: HandlerReqCtx<HA>,
42
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/dropbox/getFiles.ts
+43
apps/cli/src/lexicon/types/app/rocksky/dropbox/getFiles.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyDropboxDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** Path to the Dropbox folder or root directory */
14
+
at?: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyDropboxDefs.FileListView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/dropbox/getMetadata.ts
+43
apps/cli/src/lexicon/types/app/rocksky/dropbox/getMetadata.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyDropboxDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** Path to the file or folder in Dropbox */
14
+
path: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyDropboxDefs.FileView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/dropbox/getTemporaryLink.ts
+43
apps/cli/src/lexicon/types/app/rocksky/dropbox/getTemporaryLink.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyDropboxDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** Path to the file in Dropbox */
14
+
path: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyDropboxDefs.TemporaryLinkView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+182
apps/cli/src/lexicon/types/app/rocksky/feed/defs.ts
+182
apps/cli/src/lexicon/types/app/rocksky/feed/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppRockskySongDefs from '../song/defs'
9
+
import * as AppRockskyAlbumDefs from '../album/defs'
10
+
import * as AppRockskyArtistDefs from '../artist/defs'
11
+
import * as AppRockskyPlaylistDefs from '../playlist/defs'
12
+
import * as AppRockskyActorDefs from '../actor/defs'
13
+
import * as AppRockskyScrobbleDefs from '../scrobble/defs'
14
+
15
+
export interface SearchResultsView {
16
+
hits?: (
17
+
| AppRockskySongDefs.SongViewBasic
18
+
| AppRockskyAlbumDefs.AlbumViewBasic
19
+
| AppRockskyArtistDefs.ArtistViewBasic
20
+
| AppRockskyPlaylistDefs.PlaylistViewBasic
21
+
| AppRockskyActorDefs.ProfileViewBasic
22
+
| { $type: string; [k: string]: unknown }
23
+
)[]
24
+
processingTimeMs?: number
25
+
limit?: number
26
+
offset?: number
27
+
estimatedTotalHits?: number
28
+
[k: string]: unknown
29
+
}
30
+
31
+
export function isSearchResultsView(v: unknown): v is SearchResultsView {
32
+
return (
33
+
isObj(v) &&
34
+
hasProp(v, '$type') &&
35
+
v.$type === 'app.rocksky.feed.defs#searchResultsView'
36
+
)
37
+
}
38
+
39
+
export function validateSearchResultsView(v: unknown): ValidationResult {
40
+
return lexicons.validate('app.rocksky.feed.defs#searchResultsView', v)
41
+
}
42
+
43
+
export interface NowPlayingView {
44
+
album?: string
45
+
albumArt?: string
46
+
albumArtist?: string
47
+
albumUri?: string
48
+
artist?: string
49
+
artistUri?: string
50
+
avatar?: string
51
+
createdAt?: string
52
+
did?: string
53
+
handle?: string
54
+
id?: string
55
+
title?: string
56
+
trackId?: string
57
+
trackUri?: string
58
+
uri?: string
59
+
[k: string]: unknown
60
+
}
61
+
62
+
export function isNowPlayingView(v: unknown): v is NowPlayingView {
63
+
return (
64
+
isObj(v) &&
65
+
hasProp(v, '$type') &&
66
+
v.$type === 'app.rocksky.feed.defs#nowPlayingView'
67
+
)
68
+
}
69
+
70
+
export function validateNowPlayingView(v: unknown): ValidationResult {
71
+
return lexicons.validate('app.rocksky.feed.defs#nowPlayingView', v)
72
+
}
73
+
74
+
export interface NowPlayingsView {
75
+
nowPlayings?: NowPlayingView[]
76
+
[k: string]: unknown
77
+
}
78
+
79
+
export function isNowPlayingsView(v: unknown): v is NowPlayingsView {
80
+
return (
81
+
isObj(v) &&
82
+
hasProp(v, '$type') &&
83
+
v.$type === 'app.rocksky.feed.defs#nowPlayingsView'
84
+
)
85
+
}
86
+
87
+
export function validateNowPlayingsView(v: unknown): ValidationResult {
88
+
return lexicons.validate('app.rocksky.feed.defs#nowPlayingsView', v)
89
+
}
90
+
91
+
export interface FeedGeneratorsView {
92
+
feeds?: FeedGeneratorView[]
93
+
[k: string]: unknown
94
+
}
95
+
96
+
export function isFeedGeneratorsView(v: unknown): v is FeedGeneratorsView {
97
+
return (
98
+
isObj(v) &&
99
+
hasProp(v, '$type') &&
100
+
v.$type === 'app.rocksky.feed.defs#feedGeneratorsView'
101
+
)
102
+
}
103
+
104
+
export function validateFeedGeneratorsView(v: unknown): ValidationResult {
105
+
return lexicons.validate('app.rocksky.feed.defs#feedGeneratorsView', v)
106
+
}
107
+
108
+
export interface FeedGeneratorView {
109
+
id?: string
110
+
name?: string
111
+
description?: string
112
+
uri?: string
113
+
avatar?: string
114
+
creator?: AppRockskyActorDefs.ProfileViewBasic
115
+
[k: string]: unknown
116
+
}
117
+
118
+
export function isFeedGeneratorView(v: unknown): v is FeedGeneratorView {
119
+
return (
120
+
isObj(v) &&
121
+
hasProp(v, '$type') &&
122
+
v.$type === 'app.rocksky.feed.defs#feedGeneratorView'
123
+
)
124
+
}
125
+
126
+
export function validateFeedGeneratorView(v: unknown): ValidationResult {
127
+
return lexicons.validate('app.rocksky.feed.defs#feedGeneratorView', v)
128
+
}
129
+
130
+
export interface FeedUriView {
131
+
/** The feed URI. */
132
+
uri?: string
133
+
[k: string]: unknown
134
+
}
135
+
136
+
export function isFeedUriView(v: unknown): v is FeedUriView {
137
+
return (
138
+
isObj(v) &&
139
+
hasProp(v, '$type') &&
140
+
v.$type === 'app.rocksky.feed.defs#feedUriView'
141
+
)
142
+
}
143
+
144
+
export function validateFeedUriView(v: unknown): ValidationResult {
145
+
return lexicons.validate('app.rocksky.feed.defs#feedUriView', v)
146
+
}
147
+
148
+
export interface FeedItemView {
149
+
scrobble?: AppRockskyScrobbleDefs.ScrobbleViewBasic
150
+
[k: string]: unknown
151
+
}
152
+
153
+
export function isFeedItemView(v: unknown): v is FeedItemView {
154
+
return (
155
+
isObj(v) &&
156
+
hasProp(v, '$type') &&
157
+
v.$type === 'app.rocksky.feed.defs#feedItemView'
158
+
)
159
+
}
160
+
161
+
export function validateFeedItemView(v: unknown): ValidationResult {
162
+
return lexicons.validate('app.rocksky.feed.defs#feedItemView', v)
163
+
}
164
+
165
+
export interface FeedView {
166
+
feed?: FeedItemView[]
167
+
/** The pagination cursor for the next set of results. */
168
+
cursor?: string
169
+
[k: string]: unknown
170
+
}
171
+
172
+
export function isFeedView(v: unknown): v is FeedView {
173
+
return (
174
+
isObj(v) &&
175
+
hasProp(v, '$type') &&
176
+
v.$type === 'app.rocksky.feed.defs#feedView'
177
+
)
178
+
}
179
+
180
+
export function validateFeedView(v: unknown): ValidationResult {
181
+
return lexicons.validate('app.rocksky.feed.defs#feedView', v)
182
+
}
+48
apps/cli/src/lexicon/types/app/rocksky/feed/describeFeedGenerator.ts
+48
apps/cli/src/lexicon/types/app/rocksky/feed/describeFeedGenerator.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyFeedDefs from './defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export type InputSchema = undefined
15
+
16
+
export interface OutputSchema {
17
+
/** The DID of the feed generator. */
18
+
did?: string
19
+
/** List of feed URIs generated by this feed generator. */
20
+
feeds?: AppRockskyFeedDefs.FeedUriView[]
21
+
[k: string]: unknown
22
+
}
23
+
24
+
export type HandlerInput = undefined
25
+
26
+
export interface HandlerSuccess {
27
+
encoding: 'application/json'
28
+
body: OutputSchema
29
+
headers?: { [key: string]: string }
30
+
}
31
+
32
+
export interface HandlerError {
33
+
status: number
34
+
message?: string
35
+
}
36
+
37
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
38
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
39
+
auth: HA
40
+
params: QueryParams
41
+
input: HandlerInput
42
+
req: express.Request
43
+
res: express.Response
44
+
resetRouteRateLimits: () => Promise<void>
45
+
}
46
+
export type Handler<HA extends HandlerAuth = never> = (
47
+
ctx: HandlerReqCtx<HA>,
48
+
) => Promise<HandlerOutput> | HandlerOutput
+29
apps/cli/src/lexicon/types/app/rocksky/feed/generator.ts
+29
apps/cli/src/lexicon/types/app/rocksky/feed/generator.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface Record {
10
+
did: string
11
+
avatar?: BlobRef
12
+
displayName: string
13
+
description?: string
14
+
createdAt: string
15
+
[k: string]: unknown
16
+
}
17
+
18
+
export function isRecord(v: unknown): v is Record {
19
+
return (
20
+
isObj(v) &&
21
+
hasProp(v, '$type') &&
22
+
(v.$type === 'app.rocksky.feed.generator#main' ||
23
+
v.$type === 'app.rocksky.feed.generator')
24
+
)
25
+
}
26
+
27
+
export function validateRecord(v: unknown): ValidationResult {
28
+
return lexicons.validate('app.rocksky.feed.generator#main', v)
29
+
}
+47
apps/cli/src/lexicon/types/app/rocksky/feed/getFeed.ts
+47
apps/cli/src/lexicon/types/app/rocksky/feed/getFeed.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyFeedDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The feed URI. */
14
+
feed: string
15
+
/** The maximum number of scrobbles to return */
16
+
limit?: number
17
+
/** The cursor for pagination */
18
+
cursor?: string
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
export type OutputSchema = AppRockskyFeedDefs.FeedView
23
+
export type HandlerInput = undefined
24
+
25
+
export interface HandlerSuccess {
26
+
encoding: 'application/json'
27
+
body: OutputSchema
28
+
headers?: { [key: string]: string }
29
+
}
30
+
31
+
export interface HandlerError {
32
+
status: number
33
+
message?: string
34
+
}
35
+
36
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
37
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
38
+
auth: HA
39
+
params: QueryParams
40
+
input: HandlerInput
41
+
req: express.Request
42
+
res: express.Response
43
+
resetRouteRateLimits: () => Promise<void>
44
+
}
45
+
export type Handler<HA extends HandlerAuth = never> = (
46
+
ctx: HandlerReqCtx<HA>,
47
+
) => Promise<HandlerOutput> | HandlerOutput
+48
apps/cli/src/lexicon/types/app/rocksky/feed/getFeedGenerator.ts
+48
apps/cli/src/lexicon/types/app/rocksky/feed/getFeedGenerator.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyFeedDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** AT-URI of the feed generator record. */
14
+
feed: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
19
+
export interface OutputSchema {
20
+
view?: AppRockskyFeedDefs.FeedGeneratorView
21
+
[k: string]: unknown
22
+
}
23
+
24
+
export type HandlerInput = undefined
25
+
26
+
export interface HandlerSuccess {
27
+
encoding: 'application/json'
28
+
body: OutputSchema
29
+
headers?: { [key: string]: string }
30
+
}
31
+
32
+
export interface HandlerError {
33
+
status: number
34
+
message?: string
35
+
}
36
+
37
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
38
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
39
+
auth: HA
40
+
params: QueryParams
41
+
input: HandlerInput
42
+
req: express.Request
43
+
res: express.Response
44
+
resetRouteRateLimits: () => Promise<void>
45
+
}
46
+
export type Handler<HA extends HandlerAuth = never> = (
47
+
ctx: HandlerReqCtx<HA>,
48
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/feed/getFeedGenerators.ts
+43
apps/cli/src/lexicon/types/app/rocksky/feed/getFeedGenerators.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyFeedDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The maximum number of feed generators to return. */
14
+
size?: number
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyFeedDefs.FeedGeneratorsView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+56
apps/cli/src/lexicon/types/app/rocksky/feed/getFeedSkeleton.ts
+56
apps/cli/src/lexicon/types/app/rocksky/feed/getFeedSkeleton.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyScrobbleDefs from '../scrobble/defs'
11
+
12
+
export interface QueryParams {
13
+
/** The feed URI. */
14
+
feed: string
15
+
/** The maximum number of scrobbles to return */
16
+
limit?: number
17
+
/** The offset for pagination */
18
+
offset?: number
19
+
/** The pagination cursor. */
20
+
cursor?: string
21
+
}
22
+
23
+
export type InputSchema = undefined
24
+
25
+
export interface OutputSchema {
26
+
scrobbles?: AppRockskyScrobbleDefs.ScrobbleViewBasic[]
27
+
/** The pagination cursor for the next set of results. */
28
+
cursor?: string
29
+
[k: string]: unknown
30
+
}
31
+
32
+
export type HandlerInput = undefined
33
+
34
+
export interface HandlerSuccess {
35
+
encoding: 'application/json'
36
+
body: OutputSchema
37
+
headers?: { [key: string]: string }
38
+
}
39
+
40
+
export interface HandlerError {
41
+
status: number
42
+
message?: string
43
+
}
44
+
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
46
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
+
auth: HA
48
+
params: QueryParams
49
+
input: HandlerInput
50
+
req: express.Request
51
+
res: express.Response
52
+
resetRouteRateLimits: () => Promise<void>
53
+
}
54
+
export type Handler<HA extends HandlerAuth = never> = (
55
+
ctx: HandlerReqCtx<HA>,
56
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/feed/getNowPlayings.ts
+43
apps/cli/src/lexicon/types/app/rocksky/feed/getNowPlayings.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyFeedDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The maximum number of now playing tracks to return. */
14
+
size?: number
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyFeedDefs.NowPlayingsView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/feed/search.ts
+43
apps/cli/src/lexicon/types/app/rocksky/feed/search.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyFeedDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The search query string */
14
+
query: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyFeedDefs.SearchResultsView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+42
apps/cli/src/lexicon/types/app/rocksky/googledrive/defs.ts
+42
apps/cli/src/lexicon/types/app/rocksky/googledrive/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface FileView {
10
+
/** The unique identifier of the file. */
11
+
id?: string
12
+
[k: string]: unknown
13
+
}
14
+
15
+
export function isFileView(v: unknown): v is FileView {
16
+
return (
17
+
isObj(v) &&
18
+
hasProp(v, '$type') &&
19
+
v.$type === 'app.rocksky.googledrive.defs#fileView'
20
+
)
21
+
}
22
+
23
+
export function validateFileView(v: unknown): ValidationResult {
24
+
return lexicons.validate('app.rocksky.googledrive.defs#fileView', v)
25
+
}
26
+
27
+
export interface FileListView {
28
+
files?: FileView[]
29
+
[k: string]: unknown
30
+
}
31
+
32
+
export function isFileListView(v: unknown): v is FileListView {
33
+
return (
34
+
isObj(v) &&
35
+
hasProp(v, '$type') &&
36
+
v.$type === 'app.rocksky.googledrive.defs#fileListView'
37
+
)
38
+
}
39
+
40
+
export function validateFileListView(v: unknown): ValidationResult {
41
+
return lexicons.validate('app.rocksky.googledrive.defs#fileListView', v)
42
+
}
+42
apps/cli/src/lexicon/types/app/rocksky/googledrive/downloadFile.ts
+42
apps/cli/src/lexicon/types/app/rocksky/googledrive/downloadFile.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import stream from 'stream'
6
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
7
+
import { lexicons } from '../../../../lexicons'
8
+
import { isObj, hasProp } from '../../../../util'
9
+
import { CID } from 'multiformats/cid'
10
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
11
+
12
+
export interface QueryParams {
13
+
/** The unique identifier of the file to download */
14
+
fileId: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type HandlerInput = undefined
19
+
20
+
export interface HandlerSuccess {
21
+
encoding: 'application/octet-stream'
22
+
body: Uint8Array | stream.Readable
23
+
headers?: { [key: string]: string }
24
+
}
25
+
26
+
export interface HandlerError {
27
+
status: number
28
+
message?: string
29
+
}
30
+
31
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
32
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
33
+
auth: HA
34
+
params: QueryParams
35
+
input: HandlerInput
36
+
req: express.Request
37
+
res: express.Response
38
+
resetRouteRateLimits: () => Promise<void>
39
+
}
40
+
export type Handler<HA extends HandlerAuth = never> = (
41
+
ctx: HandlerReqCtx<HA>,
42
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/googledrive/getFile.ts
+43
apps/cli/src/lexicon/types/app/rocksky/googledrive/getFile.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyGoogledriveDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The unique identifier of the file to retrieve */
14
+
fileId: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyGoogledriveDefs.FileView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/googledrive/getFiles.ts
+43
apps/cli/src/lexicon/types/app/rocksky/googledrive/getFiles.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyGoogledriveDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** Path to the Google Drive folder or root directory */
14
+
at?: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyGoogledriveDefs.FileListView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+47
apps/cli/src/lexicon/types/app/rocksky/graph/defs.ts
+47
apps/cli/src/lexicon/types/app/rocksky/graph/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
/** indicates that a handle or DID could not be resolved */
10
+
export interface NotFoundActor {
11
+
actor: string
12
+
notFound: boolean
13
+
[k: string]: unknown
14
+
}
15
+
16
+
export function isNotFoundActor(v: unknown): v is NotFoundActor {
17
+
return (
18
+
isObj(v) &&
19
+
hasProp(v, '$type') &&
20
+
v.$type === 'app.rocksky.graph.defs#notFoundActor'
21
+
)
22
+
}
23
+
24
+
export function validateNotFoundActor(v: unknown): ValidationResult {
25
+
return lexicons.validate('app.rocksky.graph.defs#notFoundActor', v)
26
+
}
27
+
28
+
export interface Relationship {
29
+
did: string
30
+
/** if the actor follows this DID, this is the AT-URI of the follow record */
31
+
following?: string
32
+
/** if the actor is followed by this DID, contains the AT-URI of the follow record */
33
+
followedBy?: string
34
+
[k: string]: unknown
35
+
}
36
+
37
+
export function isRelationship(v: unknown): v is Relationship {
38
+
return (
39
+
isObj(v) &&
40
+
hasProp(v, '$type') &&
41
+
v.$type === 'app.rocksky.graph.defs#relationship'
42
+
)
43
+
}
44
+
45
+
export function validateRelationship(v: unknown): ValidationResult {
46
+
return lexicons.validate('app.rocksky.graph.defs#relationship', v)
47
+
}
+28
apps/cli/src/lexicon/types/app/rocksky/graph/follow.ts
+28
apps/cli/src/lexicon/types/app/rocksky/graph/follow.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
9
+
10
+
export interface Record {
11
+
createdAt: string
12
+
subject: string
13
+
via?: ComAtprotoRepoStrongRef.Main
14
+
[k: string]: unknown
15
+
}
16
+
17
+
export function isRecord(v: unknown): v is Record {
18
+
return (
19
+
isObj(v) &&
20
+
hasProp(v, '$type') &&
21
+
(v.$type === 'app.rocksky.graph.follow#main' ||
22
+
v.$type === 'app.rocksky.graph.follow')
23
+
)
24
+
}
25
+
26
+
export function validateRecord(v: unknown): ValidationResult {
27
+
return lexicons.validate('app.rocksky.graph.follow#main', v)
28
+
}
+50
apps/cli/src/lexicon/types/app/rocksky/graph/followAccount.ts
+50
apps/cli/src/lexicon/types/app/rocksky/graph/followAccount.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyActorDefs from '../actor/defs'
11
+
12
+
export interface QueryParams {
13
+
account: string
14
+
}
15
+
16
+
export type InputSchema = undefined
17
+
18
+
export interface OutputSchema {
19
+
subject: AppRockskyActorDefs.ProfileViewBasic
20
+
followers: AppRockskyActorDefs.ProfileViewBasic[]
21
+
/** A cursor value to pass to subsequent calls to get the next page of results. */
22
+
cursor?: string
23
+
[k: string]: unknown
24
+
}
25
+
26
+
export type HandlerInput = undefined
27
+
28
+
export interface HandlerSuccess {
29
+
encoding: 'application/json'
30
+
body: OutputSchema
31
+
headers?: { [key: string]: string }
32
+
}
33
+
34
+
export interface HandlerError {
35
+
status: number
36
+
message?: string
37
+
}
38
+
39
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
40
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
41
+
auth: HA
42
+
params: QueryParams
43
+
input: HandlerInput
44
+
req: express.Request
45
+
res: express.Response
46
+
resetRouteRateLimits: () => Promise<void>
47
+
}
48
+
export type Handler<HA extends HandlerAuth = never> = (
49
+
ctx: HandlerReqCtx<HA>,
50
+
) => Promise<HandlerOutput> | HandlerOutput
+56
apps/cli/src/lexicon/types/app/rocksky/graph/getFollowers.ts
+56
apps/cli/src/lexicon/types/app/rocksky/graph/getFollowers.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyActorDefs from '../actor/defs'
11
+
12
+
export interface QueryParams {
13
+
actor: string
14
+
limit: number
15
+
/** If provided, filters the followers to only include those with DIDs in this list. */
16
+
dids?: string[]
17
+
cursor?: string
18
+
}
19
+
20
+
export type InputSchema = undefined
21
+
22
+
export interface OutputSchema {
23
+
subject: AppRockskyActorDefs.ProfileViewBasic
24
+
followers: AppRockskyActorDefs.ProfileViewBasic[]
25
+
/** A cursor value to pass to subsequent calls to get the next page of results. */
26
+
cursor?: string
27
+
/** The total number of followers. */
28
+
count?: number
29
+
[k: string]: unknown
30
+
}
31
+
32
+
export type HandlerInput = undefined
33
+
34
+
export interface HandlerSuccess {
35
+
encoding: 'application/json'
36
+
body: OutputSchema
37
+
headers?: { [key: string]: string }
38
+
}
39
+
40
+
export interface HandlerError {
41
+
status: number
42
+
message?: string
43
+
}
44
+
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
46
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
+
auth: HA
48
+
params: QueryParams
49
+
input: HandlerInput
50
+
req: express.Request
51
+
res: express.Response
52
+
resetRouteRateLimits: () => Promise<void>
53
+
}
54
+
export type Handler<HA extends HandlerAuth = never> = (
55
+
ctx: HandlerReqCtx<HA>,
56
+
) => Promise<HandlerOutput> | HandlerOutput
+56
apps/cli/src/lexicon/types/app/rocksky/graph/getFollows.ts
+56
apps/cli/src/lexicon/types/app/rocksky/graph/getFollows.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyActorDefs from '../actor/defs'
11
+
12
+
export interface QueryParams {
13
+
actor: string
14
+
limit: number
15
+
/** If provided, filters the follows to only include those with DIDs in this list. */
16
+
dids?: string[]
17
+
cursor?: string
18
+
}
19
+
20
+
export type InputSchema = undefined
21
+
22
+
export interface OutputSchema {
23
+
subject: AppRockskyActorDefs.ProfileViewBasic
24
+
follows: AppRockskyActorDefs.ProfileViewBasic[]
25
+
/** A cursor value to pass to subsequent calls to get the next page of results. */
26
+
cursor?: string
27
+
/** The total number of follows. */
28
+
count?: number
29
+
[k: string]: unknown
30
+
}
31
+
32
+
export type HandlerInput = undefined
33
+
34
+
export interface HandlerSuccess {
35
+
encoding: 'application/json'
36
+
body: OutputSchema
37
+
headers?: { [key: string]: string }
38
+
}
39
+
40
+
export interface HandlerError {
41
+
status: number
42
+
message?: string
43
+
}
44
+
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
46
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
+
auth: HA
48
+
params: QueryParams
49
+
input: HandlerInput
50
+
req: express.Request
51
+
res: express.Response
52
+
resetRouteRateLimits: () => Promise<void>
53
+
}
54
+
export type Handler<HA extends HandlerAuth = never> = (
55
+
ctx: HandlerReqCtx<HA>,
56
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/graph/getKnownFollowers.ts
+52
apps/cli/src/lexicon/types/app/rocksky/graph/getKnownFollowers.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyActorDefs from '../actor/defs'
11
+
12
+
export interface QueryParams {
13
+
actor: string
14
+
limit: number
15
+
cursor?: string
16
+
}
17
+
18
+
export type InputSchema = undefined
19
+
20
+
export interface OutputSchema {
21
+
subject: AppRockskyActorDefs.ProfileViewBasic
22
+
followers: AppRockskyActorDefs.ProfileViewBasic[]
23
+
/** A cursor value to pass to subsequent calls to get the next page of results. */
24
+
cursor?: string
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+50
apps/cli/src/lexicon/types/app/rocksky/graph/unfollowAccount.ts
+50
apps/cli/src/lexicon/types/app/rocksky/graph/unfollowAccount.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyActorDefs from '../actor/defs'
11
+
12
+
export interface QueryParams {
13
+
account: string
14
+
}
15
+
16
+
export type InputSchema = undefined
17
+
18
+
export interface OutputSchema {
19
+
subject: AppRockskyActorDefs.ProfileViewBasic
20
+
followers: AppRockskyActorDefs.ProfileViewBasic[]
21
+
/** A cursor value to pass to subsequent calls to get the next page of results. */
22
+
cursor?: string
23
+
[k: string]: unknown
24
+
}
25
+
26
+
export type HandlerInput = undefined
27
+
28
+
export interface HandlerSuccess {
29
+
encoding: 'application/json'
30
+
body: OutputSchema
31
+
headers?: { [key: string]: string }
32
+
}
33
+
34
+
export interface HandlerError {
35
+
status: number
36
+
message?: string
37
+
}
38
+
39
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
40
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
41
+
auth: HA
42
+
params: QueryParams
43
+
input: HandlerInput
44
+
req: express.Request
45
+
res: express.Response
46
+
resetRouteRateLimits: () => Promise<void>
47
+
}
48
+
export type Handler<HA extends HandlerAuth = never> = (
49
+
ctx: HandlerReqCtx<HA>,
50
+
) => Promise<HandlerOutput> | HandlerOutput
+49
apps/cli/src/lexicon/types/app/rocksky/like/dislikeShout.ts
+49
apps/cli/src/lexicon/types/app/rocksky/like/dislikeShout.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from '../shout/defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The unique identifier of the shout to dislike */
16
+
uri?: string
17
+
[k: string]: unknown
18
+
}
19
+
20
+
export type OutputSchema = AppRockskyShoutDefs.ShoutView
21
+
22
+
export interface HandlerInput {
23
+
encoding: 'application/json'
24
+
body: InputSchema
25
+
}
26
+
27
+
export interface HandlerSuccess {
28
+
encoding: 'application/json'
29
+
body: OutputSchema
30
+
headers?: { [key: string]: string }
31
+
}
32
+
33
+
export interface HandlerError {
34
+
status: number
35
+
message?: string
36
+
}
37
+
38
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
39
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
40
+
auth: HA
41
+
params: QueryParams
42
+
input: HandlerInput
43
+
req: express.Request
44
+
res: express.Response
45
+
resetRouteRateLimits: () => Promise<void>
46
+
}
47
+
export type Handler<HA extends HandlerAuth = never> = (
48
+
ctx: HandlerReqCtx<HA>,
49
+
) => Promise<HandlerOutput> | HandlerOutput
+49
apps/cli/src/lexicon/types/app/rocksky/like/dislikeSong.ts
+49
apps/cli/src/lexicon/types/app/rocksky/like/dislikeSong.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from '../song/defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The unique identifier of the song to dislike */
16
+
uri?: string
17
+
[k: string]: unknown
18
+
}
19
+
20
+
export type OutputSchema = AppRockskySongDefs.SongViewDetailed
21
+
22
+
export interface HandlerInput {
23
+
encoding: 'application/json'
24
+
body: InputSchema
25
+
}
26
+
27
+
export interface HandlerSuccess {
28
+
encoding: 'application/json'
29
+
body: OutputSchema
30
+
headers?: { [key: string]: string }
31
+
}
32
+
33
+
export interface HandlerError {
34
+
status: number
35
+
message?: string
36
+
}
37
+
38
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
39
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
40
+
auth: HA
41
+
params: QueryParams
42
+
input: HandlerInput
43
+
req: express.Request
44
+
res: express.Response
45
+
resetRouteRateLimits: () => Promise<void>
46
+
}
47
+
export type Handler<HA extends HandlerAuth = never> = (
48
+
ctx: HandlerReqCtx<HA>,
49
+
) => Promise<HandlerOutput> | HandlerOutput
+49
apps/cli/src/lexicon/types/app/rocksky/like/likeShout.ts
+49
apps/cli/src/lexicon/types/app/rocksky/like/likeShout.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from '../shout/defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The unique identifier of the shout to like */
16
+
uri?: string
17
+
[k: string]: unknown
18
+
}
19
+
20
+
export type OutputSchema = AppRockskyShoutDefs.ShoutView
21
+
22
+
export interface HandlerInput {
23
+
encoding: 'application/json'
24
+
body: InputSchema
25
+
}
26
+
27
+
export interface HandlerSuccess {
28
+
encoding: 'application/json'
29
+
body: OutputSchema
30
+
headers?: { [key: string]: string }
31
+
}
32
+
33
+
export interface HandlerError {
34
+
status: number
35
+
message?: string
36
+
}
37
+
38
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
39
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
40
+
auth: HA
41
+
params: QueryParams
42
+
input: HandlerInput
43
+
req: express.Request
44
+
res: express.Response
45
+
resetRouteRateLimits: () => Promise<void>
46
+
}
47
+
export type Handler<HA extends HandlerAuth = never> = (
48
+
ctx: HandlerReqCtx<HA>,
49
+
) => Promise<HandlerOutput> | HandlerOutput
+49
apps/cli/src/lexicon/types/app/rocksky/like/likeSong.ts
+49
apps/cli/src/lexicon/types/app/rocksky/like/likeSong.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from '../song/defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The unique identifier of the song to like */
16
+
uri?: string
17
+
[k: string]: unknown
18
+
}
19
+
20
+
export type OutputSchema = AppRockskySongDefs.SongViewDetailed
21
+
22
+
export interface HandlerInput {
23
+
encoding: 'application/json'
24
+
body: InputSchema
25
+
}
26
+
27
+
export interface HandlerSuccess {
28
+
encoding: 'application/json'
29
+
body: OutputSchema
30
+
headers?: { [key: string]: string }
31
+
}
32
+
33
+
export interface HandlerError {
34
+
status: number
35
+
message?: string
36
+
}
37
+
38
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
39
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
40
+
auth: HA
41
+
params: QueryParams
42
+
input: HandlerInput
43
+
req: express.Request
44
+
res: express.Response
45
+
resetRouteRateLimits: () => Promise<void>
46
+
}
47
+
export type Handler<HA extends HandlerAuth = never> = (
48
+
ctx: HandlerReqCtx<HA>,
49
+
) => Promise<HandlerOutput> | HandlerOutput
+27
apps/cli/src/lexicon/types/app/rocksky/like.ts
+27
apps/cli/src/lexicon/types/app/rocksky/like.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef'
9
+
10
+
export interface Record {
11
+
/** The date when the like was created. */
12
+
createdAt: string
13
+
subject: ComAtprotoRepoStrongRef.Main
14
+
[k: string]: unknown
15
+
}
16
+
17
+
export function isRecord(v: unknown): v is Record {
18
+
return (
19
+
isObj(v) &&
20
+
hasProp(v, '$type') &&
21
+
(v.$type === 'app.rocksky.like#main' || v.$type === 'app.rocksky.like')
22
+
)
23
+
}
24
+
25
+
export function validateRecord(v: unknown): ValidationResult {
26
+
return lexicons.validate('app.rocksky.like#main', v)
27
+
}
+40
apps/cli/src/lexicon/types/app/rocksky/player/addDirectoryToQueue.ts
+40
apps/cli/src/lexicon/types/app/rocksky/player/addDirectoryToQueue.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
/** The directory to add to the queue */
14
+
directory: string
15
+
/** Position in the queue to insert the directory at, defaults to the end if not specified */
16
+
position?: number
17
+
/** Whether to shuffle the added directory in the queue */
18
+
shuffle?: boolean
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
export type HandlerInput = undefined
23
+
24
+
export interface HandlerError {
25
+
status: number
26
+
message?: string
27
+
}
28
+
29
+
export type HandlerOutput = HandlerError | void
30
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
31
+
auth: HA
32
+
params: QueryParams
33
+
input: HandlerInput
34
+
req: express.Request
35
+
res: express.Response
36
+
resetRouteRateLimits: () => Promise<void>
37
+
}
38
+
export type Handler<HA extends HandlerAuth = never> = (
39
+
ctx: HandlerReqCtx<HA>,
40
+
) => Promise<HandlerOutput> | HandlerOutput
+39
apps/cli/src/lexicon/types/app/rocksky/player/addItemsToQueue.ts
+39
apps/cli/src/lexicon/types/app/rocksky/player/addItemsToQueue.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
items: string[]
14
+
/** Position in the queue to insert the items at, defaults to the end if not specified */
15
+
position?: number
16
+
/** Whether to shuffle the added items in the queue */
17
+
shuffle?: boolean
18
+
}
19
+
20
+
export type InputSchema = undefined
21
+
export type HandlerInput = undefined
22
+
23
+
export interface HandlerError {
24
+
status: number
25
+
message?: string
26
+
}
27
+
28
+
export type HandlerOutput = HandlerError | void
29
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
30
+
auth: HA
31
+
params: QueryParams
32
+
input: HandlerInput
33
+
req: express.Request
34
+
res: express.Response
35
+
resetRouteRateLimits: () => Promise<void>
36
+
}
37
+
export type Handler<HA extends HandlerAuth = never> = (
38
+
ctx: HandlerReqCtx<HA>,
39
+
) => Promise<HandlerOutput> | HandlerOutput
+57
apps/cli/src/lexicon/types/app/rocksky/player/defs.ts
+57
apps/cli/src/lexicon/types/app/rocksky/player/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppRockskySongDefsSongViewBasic from '../song/defs/songViewBasic'
9
+
10
+
export interface CurrentlyPlayingViewDetailed {
11
+
/** The title of the currently playing track */
12
+
title?: string
13
+
[k: string]: unknown
14
+
}
15
+
16
+
export function isCurrentlyPlayingViewDetailed(
17
+
v: unknown,
18
+
): v is CurrentlyPlayingViewDetailed {
19
+
return (
20
+
isObj(v) &&
21
+
hasProp(v, '$type') &&
22
+
v.$type === 'app.rocksky.player.defs#currentlyPlayingViewDetailed'
23
+
)
24
+
}
25
+
26
+
export function validateCurrentlyPlayingViewDetailed(
27
+
v: unknown,
28
+
): ValidationResult {
29
+
return lexicons.validate(
30
+
'app.rocksky.player.defs#currentlyPlayingViewDetailed',
31
+
v,
32
+
)
33
+
}
34
+
35
+
export interface PlaybackQueueViewDetailed {
36
+
tracks?: AppRockskySongDefsSongViewBasic.Main[]
37
+
[k: string]: unknown
38
+
}
39
+
40
+
export function isPlaybackQueueViewDetailed(
41
+
v: unknown,
42
+
): v is PlaybackQueueViewDetailed {
43
+
return (
44
+
isObj(v) &&
45
+
hasProp(v, '$type') &&
46
+
v.$type === 'app.rocksky.player.defs#playbackQueueViewDetailed'
47
+
)
48
+
}
49
+
50
+
export function validatePlaybackQueueViewDetailed(
51
+
v: unknown,
52
+
): ValidationResult {
53
+
return lexicons.validate(
54
+
'app.rocksky.player.defs#playbackQueueViewDetailed',
55
+
v,
56
+
)
57
+
}
+44
apps/cli/src/lexicon/types/app/rocksky/player/getCurrentlyPlaying.ts
+44
apps/cli/src/lexicon/types/app/rocksky/player/getCurrentlyPlaying.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyPlayerDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
playerId?: string
14
+
/** Handle or DID of the actor to retrieve the currently playing track for. If not provided, defaults to the current user. */
15
+
actor?: string
16
+
}
17
+
18
+
export type InputSchema = undefined
19
+
export type OutputSchema = AppRockskyPlayerDefs.CurrentlyPlayingViewDetailed
20
+
export type HandlerInput = undefined
21
+
22
+
export interface HandlerSuccess {
23
+
encoding: 'application/json'
24
+
body: OutputSchema
25
+
headers?: { [key: string]: string }
26
+
}
27
+
28
+
export interface HandlerError {
29
+
status: number
30
+
message?: string
31
+
}
32
+
33
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
34
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
35
+
auth: HA
36
+
params: QueryParams
37
+
input: HandlerInput
38
+
req: express.Request
39
+
res: express.Response
40
+
resetRouteRateLimits: () => Promise<void>
41
+
}
42
+
export type Handler<HA extends HandlerAuth = never> = (
43
+
ctx: HandlerReqCtx<HA>,
44
+
) => Promise<HandlerOutput> | HandlerOutput
+42
apps/cli/src/lexicon/types/app/rocksky/player/getPlaybackQueue.ts
+42
apps/cli/src/lexicon/types/app/rocksky/player/getPlaybackQueue.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyPlayerDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
playerId?: string
14
+
}
15
+
16
+
export type InputSchema = undefined
17
+
export type OutputSchema = AppRockskyPlayerDefs.PlaybackQueueViewDetailed
18
+
export type HandlerInput = undefined
19
+
20
+
export interface HandlerSuccess {
21
+
encoding: 'application/json'
22
+
body: OutputSchema
23
+
headers?: { [key: string]: string }
24
+
}
25
+
26
+
export interface HandlerError {
27
+
status: number
28
+
message?: string
29
+
}
30
+
31
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
32
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
33
+
auth: HA
34
+
params: QueryParams
35
+
input: HandlerInput
36
+
req: express.Request
37
+
res: express.Response
38
+
resetRouteRateLimits: () => Promise<void>
39
+
}
40
+
export type Handler<HA extends HandlerAuth = never> = (
41
+
ctx: HandlerReqCtx<HA>,
42
+
) => Promise<HandlerOutput> | HandlerOutput
+34
apps/cli/src/lexicon/types/app/rocksky/player/next.ts
+34
apps/cli/src/lexicon/types/app/rocksky/player/next.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
}
14
+
15
+
export type InputSchema = undefined
16
+
export type HandlerInput = undefined
17
+
18
+
export interface HandlerError {
19
+
status: number
20
+
message?: string
21
+
}
22
+
23
+
export type HandlerOutput = HandlerError | void
24
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
25
+
auth: HA
26
+
params: QueryParams
27
+
input: HandlerInput
28
+
req: express.Request
29
+
res: express.Response
30
+
resetRouteRateLimits: () => Promise<void>
31
+
}
32
+
export type Handler<HA extends HandlerAuth = never> = (
33
+
ctx: HandlerReqCtx<HA>,
34
+
) => Promise<HandlerOutput> | HandlerOutput
+34
apps/cli/src/lexicon/types/app/rocksky/player/pause.ts
+34
apps/cli/src/lexicon/types/app/rocksky/player/pause.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
}
14
+
15
+
export type InputSchema = undefined
16
+
export type HandlerInput = undefined
17
+
18
+
export interface HandlerError {
19
+
status: number
20
+
message?: string
21
+
}
22
+
23
+
export type HandlerOutput = HandlerError | void
24
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
25
+
auth: HA
26
+
params: QueryParams
27
+
input: HandlerInput
28
+
req: express.Request
29
+
res: express.Response
30
+
resetRouteRateLimits: () => Promise<void>
31
+
}
32
+
export type Handler<HA extends HandlerAuth = never> = (
33
+
ctx: HandlerReqCtx<HA>,
34
+
) => Promise<HandlerOutput> | HandlerOutput
+34
apps/cli/src/lexicon/types/app/rocksky/player/play.ts
+34
apps/cli/src/lexicon/types/app/rocksky/player/play.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
}
14
+
15
+
export type InputSchema = undefined
16
+
export type HandlerInput = undefined
17
+
18
+
export interface HandlerError {
19
+
status: number
20
+
message?: string
21
+
}
22
+
23
+
export type HandlerOutput = HandlerError | void
24
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
25
+
auth: HA
26
+
params: QueryParams
27
+
input: HandlerInput
28
+
req: express.Request
29
+
res: express.Response
30
+
resetRouteRateLimits: () => Promise<void>
31
+
}
32
+
export type Handler<HA extends HandlerAuth = never> = (
33
+
ctx: HandlerReqCtx<HA>,
34
+
) => Promise<HandlerOutput> | HandlerOutput
+38
apps/cli/src/lexicon/types/app/rocksky/player/playDirectory.ts
+38
apps/cli/src/lexicon/types/app/rocksky/player/playDirectory.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
directoryId: string
14
+
shuffle?: boolean
15
+
recurse?: boolean
16
+
position?: number
17
+
}
18
+
19
+
export type InputSchema = undefined
20
+
export type HandlerInput = undefined
21
+
22
+
export interface HandlerError {
23
+
status: number
24
+
message?: string
25
+
}
26
+
27
+
export type HandlerOutput = HandlerError | void
28
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
29
+
auth: HA
30
+
params: QueryParams
31
+
input: HandlerInput
32
+
req: express.Request
33
+
res: express.Response
34
+
resetRouteRateLimits: () => Promise<void>
35
+
}
36
+
export type Handler<HA extends HandlerAuth = never> = (
37
+
ctx: HandlerReqCtx<HA>,
38
+
) => Promise<HandlerOutput> | HandlerOutput
+35
apps/cli/src/lexicon/types/app/rocksky/player/playFile.ts
+35
apps/cli/src/lexicon/types/app/rocksky/player/playFile.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
fileId: string
14
+
}
15
+
16
+
export type InputSchema = undefined
17
+
export type HandlerInput = undefined
18
+
19
+
export interface HandlerError {
20
+
status: number
21
+
message?: string
22
+
}
23
+
24
+
export type HandlerOutput = HandlerError | void
25
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
26
+
auth: HA
27
+
params: QueryParams
28
+
input: HandlerInput
29
+
req: express.Request
30
+
res: express.Response
31
+
resetRouteRateLimits: () => Promise<void>
32
+
}
33
+
export type Handler<HA extends HandlerAuth = never> = (
34
+
ctx: HandlerReqCtx<HA>,
35
+
) => Promise<HandlerOutput> | HandlerOutput
+34
apps/cli/src/lexicon/types/app/rocksky/player/previous.ts
+34
apps/cli/src/lexicon/types/app/rocksky/player/previous.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
}
14
+
15
+
export type InputSchema = undefined
16
+
export type HandlerInput = undefined
17
+
18
+
export interface HandlerError {
19
+
status: number
20
+
message?: string
21
+
}
22
+
23
+
export type HandlerOutput = HandlerError | void
24
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
25
+
auth: HA
26
+
params: QueryParams
27
+
input: HandlerInput
28
+
req: express.Request
29
+
res: express.Response
30
+
resetRouteRateLimits: () => Promise<void>
31
+
}
32
+
export type Handler<HA extends HandlerAuth = never> = (
33
+
ctx: HandlerReqCtx<HA>,
34
+
) => Promise<HandlerOutput> | HandlerOutput
+36
apps/cli/src/lexicon/types/app/rocksky/player/seek.ts
+36
apps/cli/src/lexicon/types/app/rocksky/player/seek.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
playerId?: string
13
+
/** The position in seconds to seek to */
14
+
position: number
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type HandlerInput = undefined
19
+
20
+
export interface HandlerError {
21
+
status: number
22
+
message?: string
23
+
}
24
+
25
+
export type HandlerOutput = HandlerError | void
26
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
27
+
auth: HA
28
+
params: QueryParams
29
+
input: HandlerInput
30
+
req: express.Request
31
+
res: express.Response
32
+
resetRouteRateLimits: () => Promise<void>
33
+
}
34
+
export type Handler<HA extends HandlerAuth = never> = (
35
+
ctx: HandlerReqCtx<HA>,
36
+
) => Promise<HandlerOutput> | HandlerOutput
+37
apps/cli/src/lexicon/types/app/rocksky/playlist/createPlaylist.ts
+37
apps/cli/src/lexicon/types/app/rocksky/playlist/createPlaylist.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
/** The name of the playlist */
13
+
name: string
14
+
/** A brief description of the playlist */
15
+
description?: string
16
+
}
17
+
18
+
export type InputSchema = undefined
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerError {
22
+
status: number
23
+
message?: string
24
+
}
25
+
26
+
export type HandlerOutput = HandlerError | void
27
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
28
+
auth: HA
29
+
params: QueryParams
30
+
input: HandlerInput
31
+
req: express.Request
32
+
res: express.Response
33
+
resetRouteRateLimits: () => Promise<void>
34
+
}
35
+
export type Handler<HA extends HandlerAuth = never> = (
36
+
ctx: HandlerReqCtx<HA>,
37
+
) => Promise<HandlerOutput> | HandlerOutput
+86
apps/cli/src/lexicon/types/app/rocksky/playlist/defs.ts
+86
apps/cli/src/lexicon/types/app/rocksky/playlist/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppRockskySongDefs from '../song/defs'
9
+
10
+
/** Detailed view of a playlist, including its tracks and metadata */
11
+
export interface PlaylistViewDetailed {
12
+
/** The unique identifier of the playlist. */
13
+
id?: string
14
+
/** The title of the playlist. */
15
+
title?: string
16
+
/** The URI of the playlist. */
17
+
uri?: string
18
+
/** The DID of the curator of the playlist. */
19
+
curatorDid?: string
20
+
/** The handle of the curator of the playlist. */
21
+
curatorHandle?: string
22
+
/** The name of the curator of the playlist. */
23
+
curatorName?: string
24
+
/** The URL of the avatar image of the curator. */
25
+
curatorAvatarUrl?: string
26
+
/** A description of the playlist. */
27
+
description?: string
28
+
/** The URL of the cover image for the playlist. */
29
+
coverImageUrl?: string
30
+
/** The date and time when the playlist was created. */
31
+
createdAt?: string
32
+
/** A list of tracks in the playlist. */
33
+
tracks?: AppRockskySongDefs.SongViewBasic[]
34
+
[k: string]: unknown
35
+
}
36
+
37
+
export function isPlaylistViewDetailed(v: unknown): v is PlaylistViewDetailed {
38
+
return (
39
+
isObj(v) &&
40
+
hasProp(v, '$type') &&
41
+
v.$type === 'app.rocksky.playlist.defs#playlistViewDetailed'
42
+
)
43
+
}
44
+
45
+
export function validatePlaylistViewDetailed(v: unknown): ValidationResult {
46
+
return lexicons.validate('app.rocksky.playlist.defs#playlistViewDetailed', v)
47
+
}
48
+
49
+
/** Basic view of a playlist, including its metadata */
50
+
export interface PlaylistViewBasic {
51
+
/** The unique identifier of the playlist. */
52
+
id?: string
53
+
/** The title of the playlist. */
54
+
title?: string
55
+
/** The URI of the playlist. */
56
+
uri?: string
57
+
/** The DID of the curator of the playlist. */
58
+
curatorDid?: string
59
+
/** The handle of the curator of the playlist. */
60
+
curatorHandle?: string
61
+
/** The name of the curator of the playlist. */
62
+
curatorName?: string
63
+
/** The URL of the avatar image of the curator. */
64
+
curatorAvatarUrl?: string
65
+
/** A description of the playlist. */
66
+
description?: string
67
+
/** The URL of the cover image for the playlist. */
68
+
coverImageUrl?: string
69
+
/** The date and time when the playlist was created. */
70
+
createdAt?: string
71
+
/** The number of tracks in the playlist. */
72
+
trackCount?: number
73
+
[k: string]: unknown
74
+
}
75
+
76
+
export function isPlaylistViewBasic(v: unknown): v is PlaylistViewBasic {
77
+
return (
78
+
isObj(v) &&
79
+
hasProp(v, '$type') &&
80
+
v.$type === 'app.rocksky.playlist.defs#playlistViewBasic'
81
+
)
82
+
}
83
+
84
+
export function validatePlaylistViewBasic(v: unknown): ValidationResult {
85
+
return lexicons.validate('app.rocksky.playlist.defs#playlistViewBasic', v)
86
+
}
+43
apps/cli/src/lexicon/types/app/rocksky/playlist/getPlaylist.ts
+43
apps/cli/src/lexicon/types/app/rocksky/playlist/getPlaylist.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyPlaylistDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the playlist to retrieve. */
14
+
uri: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyPlaylistDefs.PlaylistViewDetailed
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+50
apps/cli/src/lexicon/types/app/rocksky/playlist/getPlaylists.ts
+50
apps/cli/src/lexicon/types/app/rocksky/playlist/getPlaylists.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyPlaylistDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The maximum number of playlists to return. */
14
+
limit?: number
15
+
/** The offset for pagination, used to skip a number of playlists. */
16
+
offset?: number
17
+
}
18
+
19
+
export type InputSchema = undefined
20
+
21
+
export interface OutputSchema {
22
+
playlists?: AppRockskyPlaylistDefs.PlaylistViewBasic[]
23
+
[k: string]: unknown
24
+
}
25
+
26
+
export type HandlerInput = undefined
27
+
28
+
export interface HandlerSuccess {
29
+
encoding: 'application/json'
30
+
body: OutputSchema
31
+
headers?: { [key: string]: string }
32
+
}
33
+
34
+
export interface HandlerError {
35
+
status: number
36
+
message?: string
37
+
}
38
+
39
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
40
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
41
+
auth: HA
42
+
params: QueryParams
43
+
input: HandlerInput
44
+
req: express.Request
45
+
res: express.Response
46
+
resetRouteRateLimits: () => Promise<void>
47
+
}
48
+
export type Handler<HA extends HandlerAuth = never> = (
49
+
ctx: HandlerReqCtx<HA>,
50
+
) => Promise<HandlerOutput> | HandlerOutput
+39
apps/cli/src/lexicon/types/app/rocksky/playlist/insertDirectory.ts
+39
apps/cli/src/lexicon/types/app/rocksky/playlist/insertDirectory.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
/** The URI of the playlist to start */
13
+
uri: string
14
+
/** The directory (id) to insert into the playlist */
15
+
directory: string
16
+
/** The position in the playlist to insert the directory at, if not specified, the directory will be appended */
17
+
position?: number
18
+
}
19
+
20
+
export type InputSchema = undefined
21
+
export type HandlerInput = undefined
22
+
23
+
export interface HandlerError {
24
+
status: number
25
+
message?: string
26
+
}
27
+
28
+
export type HandlerOutput = HandlerError | void
29
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
30
+
auth: HA
31
+
params: QueryParams
32
+
input: HandlerInput
33
+
req: express.Request
34
+
res: express.Response
35
+
resetRouteRateLimits: () => Promise<void>
36
+
}
37
+
export type Handler<HA extends HandlerAuth = never> = (
38
+
ctx: HandlerReqCtx<HA>,
39
+
) => Promise<HandlerOutput> | HandlerOutput
+38
apps/cli/src/lexicon/types/app/rocksky/playlist/insertFiles.ts
+38
apps/cli/src/lexicon/types/app/rocksky/playlist/insertFiles.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
/** The URI of the playlist to start */
13
+
uri: string
14
+
files: string[]
15
+
/** The position in the playlist to insert the files at, if not specified, files will be appended */
16
+
position?: number
17
+
}
18
+
19
+
export type InputSchema = undefined
20
+
export type HandlerInput = undefined
21
+
22
+
export interface HandlerError {
23
+
status: number
24
+
message?: string
25
+
}
26
+
27
+
export type HandlerOutput = HandlerError | void
28
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
29
+
auth: HA
30
+
params: QueryParams
31
+
input: HandlerInput
32
+
req: express.Request
33
+
res: express.Response
34
+
resetRouteRateLimits: () => Promise<void>
35
+
}
36
+
export type Handler<HA extends HandlerAuth = never> = (
37
+
ctx: HandlerReqCtx<HA>,
38
+
) => Promise<HandlerOutput> | HandlerOutput
+35
apps/cli/src/lexicon/types/app/rocksky/playlist/removePlaylist.ts
+35
apps/cli/src/lexicon/types/app/rocksky/playlist/removePlaylist.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
/** The URI of the playlist to remove */
13
+
uri: string
14
+
}
15
+
16
+
export type InputSchema = undefined
17
+
export type HandlerInput = undefined
18
+
19
+
export interface HandlerError {
20
+
status: number
21
+
message?: string
22
+
}
23
+
24
+
export type HandlerOutput = HandlerError | void
25
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
26
+
auth: HA
27
+
params: QueryParams
28
+
input: HandlerInput
29
+
req: express.Request
30
+
res: express.Response
31
+
resetRouteRateLimits: () => Promise<void>
32
+
}
33
+
export type Handler<HA extends HandlerAuth = never> = (
34
+
ctx: HandlerReqCtx<HA>,
35
+
) => Promise<HandlerOutput> | HandlerOutput
+37
apps/cli/src/lexicon/types/app/rocksky/playlist/removeTrack.ts
+37
apps/cli/src/lexicon/types/app/rocksky/playlist/removeTrack.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
/** The URI of the playlist to remove the track from */
13
+
uri: string
14
+
/** The position of the track to remove in the playlist */
15
+
position: number
16
+
}
17
+
18
+
export type InputSchema = undefined
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerError {
22
+
status: number
23
+
message?: string
24
+
}
25
+
26
+
export type HandlerOutput = HandlerError | void
27
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
28
+
auth: HA
29
+
params: QueryParams
30
+
input: HandlerInput
31
+
req: express.Request
32
+
res: express.Response
33
+
resetRouteRateLimits: () => Promise<void>
34
+
}
35
+
export type Handler<HA extends HandlerAuth = never> = (
36
+
ctx: HandlerReqCtx<HA>,
37
+
) => Promise<HandlerOutput> | HandlerOutput
+39
apps/cli/src/lexicon/types/app/rocksky/playlist/startPlaylist.ts
+39
apps/cli/src/lexicon/types/app/rocksky/playlist/startPlaylist.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
/** The URI of the playlist to start */
13
+
uri: string
14
+
/** Whether to shuffle the playlist when starting it */
15
+
shuffle?: boolean
16
+
/** The position in the playlist to start from, if not specified, starts from the beginning */
17
+
position?: number
18
+
}
19
+
20
+
export type InputSchema = undefined
21
+
export type HandlerInput = undefined
22
+
23
+
export interface HandlerError {
24
+
status: number
25
+
message?: string
26
+
}
27
+
28
+
export type HandlerOutput = HandlerError | void
29
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
30
+
auth: HA
31
+
params: QueryParams
32
+
input: HandlerInput
33
+
req: express.Request
34
+
res: express.Response
35
+
resetRouteRateLimits: () => Promise<void>
36
+
}
37
+
export type Handler<HA extends HandlerAuth = never> = (
38
+
ctx: HandlerReqCtx<HA>,
39
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/playlist.ts
+43
apps/cli/src/lexicon/types/app/rocksky/playlist.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppRockskySong from './song'
9
+
10
+
export interface Record {
11
+
/** The name of the playlist. */
12
+
name: string
13
+
/** The playlist description. */
14
+
description?: string
15
+
/** The picture of the playlist. */
16
+
picture?: BlobRef
17
+
/** The tracks in the playlist. */
18
+
tracks?: AppRockskySong.Record[]
19
+
/** The date the playlist was created. */
20
+
createdAt: string
21
+
/** The Spotify link of the playlist. */
22
+
spotifyLink?: string
23
+
/** The Tidal link of the playlist. */
24
+
tidalLink?: string
25
+
/** The YouTube link of the playlist. */
26
+
youtubeLink?: string
27
+
/** The Apple Music link of the playlist. */
28
+
appleMusicLink?: string
29
+
[k: string]: unknown
30
+
}
31
+
32
+
export function isRecord(v: unknown): v is Record {
33
+
return (
34
+
isObj(v) &&
35
+
hasProp(v, '$type') &&
36
+
(v.$type === 'app.rocksky.playlist#main' ||
37
+
v.$type === 'app.rocksky.playlist')
38
+
)
39
+
}
40
+
41
+
export function validateRecord(v: unknown): ValidationResult {
42
+
return lexicons.validate('app.rocksky.playlist#main', v)
43
+
}
+63
apps/cli/src/lexicon/types/app/rocksky/radio/defs.ts
+63
apps/cli/src/lexicon/types/app/rocksky/radio/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface RadioViewBasic {
10
+
/** The unique identifier of the radio. */
11
+
id?: string
12
+
/** The name of the radio. */
13
+
name?: string
14
+
/** A brief description of the radio. */
15
+
description?: string
16
+
/** The date and time when the radio was created. */
17
+
createdAt?: string
18
+
[k: string]: unknown
19
+
}
20
+
21
+
export function isRadioViewBasic(v: unknown): v is RadioViewBasic {
22
+
return (
23
+
isObj(v) &&
24
+
hasProp(v, '$type') &&
25
+
v.$type === 'app.rocksky.radio.defs#radioViewBasic'
26
+
)
27
+
}
28
+
29
+
export function validateRadioViewBasic(v: unknown): ValidationResult {
30
+
return lexicons.validate('app.rocksky.radio.defs#radioViewBasic', v)
31
+
}
32
+
33
+
export interface RadioViewDetailed {
34
+
/** The unique identifier of the radio. */
35
+
id?: string
36
+
/** The name of the radio. */
37
+
name?: string
38
+
/** A brief description of the radio. */
39
+
description?: string
40
+
/** The website of the radio. */
41
+
website?: string
42
+
/** The streaming URL of the radio. */
43
+
url?: string
44
+
/** The genre of the radio. */
45
+
genre?: string
46
+
/** The logo of the radio station. */
47
+
logo?: string
48
+
/** The date and time when the radio was created. */
49
+
createdAt?: string
50
+
[k: string]: unknown
51
+
}
52
+
53
+
export function isRadioViewDetailed(v: unknown): v is RadioViewDetailed {
54
+
return (
55
+
isObj(v) &&
56
+
hasProp(v, '$type') &&
57
+
v.$type === 'app.rocksky.radio.defs#radioViewDetailed'
58
+
)
59
+
}
60
+
61
+
export function validateRadioViewDetailed(v: unknown): ValidationResult {
62
+
return lexicons.validate('app.rocksky.radio.defs#radioViewDetailed', v)
63
+
}
+37
apps/cli/src/lexicon/types/app/rocksky/radio.ts
+37
apps/cli/src/lexicon/types/app/rocksky/radio.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface Record {
10
+
/** The name of the radio station. */
11
+
name: string
12
+
/** The URL of the radio station. */
13
+
url: string
14
+
/** A description of the radio station. */
15
+
description?: string
16
+
/** The genre of the radio station. */
17
+
genre?: string
18
+
/** The logo of the radio station. */
19
+
logo?: BlobRef
20
+
/** The website of the radio station. */
21
+
website?: string
22
+
/** The date when the radio station was created. */
23
+
createdAt: string
24
+
[k: string]: unknown
25
+
}
26
+
27
+
export function isRecord(v: unknown): v is Record {
28
+
return (
29
+
isObj(v) &&
30
+
hasProp(v, '$type') &&
31
+
(v.$type === 'app.rocksky.radio#main' || v.$type === 'app.rocksky.radio')
32
+
)
33
+
}
34
+
35
+
export function validateRecord(v: unknown): ValidationResult {
36
+
return lexicons.validate('app.rocksky.radio#main', v)
37
+
}
+91
apps/cli/src/lexicon/types/app/rocksky/scrobble/createScrobble.ts
+91
apps/cli/src/lexicon/types/app/rocksky/scrobble/createScrobble.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyScrobbleDefs from './defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The title of the track being scrobbled */
16
+
title: string
17
+
/** The artist of the track being scrobbled */
18
+
artist: string
19
+
/** The album of the track being scrobbled */
20
+
album?: string
21
+
/** The duration of the track in seconds */
22
+
duration?: number
23
+
/** The MusicBrainz ID of the track, if available */
24
+
mbId?: string
25
+
/** The URL of the album art for the track */
26
+
albumArt?: string
27
+
/** The track number of the track in the album */
28
+
trackNumber?: number
29
+
/** The release date of the track, formatted as YYYY-MM-DD */
30
+
releaseDate?: string
31
+
/** The year the track was released */
32
+
year?: number
33
+
/** The disc number of the track in the album, if applicable */
34
+
discNumber?: number
35
+
/** The lyrics of the track, if available */
36
+
lyrics?: string
37
+
/** The composer of the track, if available */
38
+
composer?: string
39
+
/** The copyright message for the track, if available */
40
+
copyrightMessage?: string
41
+
/** The record label of the track, if available */
42
+
label?: string
43
+
/** The URL of the artist's picture, if available */
44
+
artistPicture?: string
45
+
/** The Spotify link for the track, if available */
46
+
spotifyLink?: string
47
+
/** The Last.fm link for the track, if available */
48
+
lastfmLink?: string
49
+
/** The Tidal link for the track, if available */
50
+
tidalLink?: string
51
+
/** The Apple Music link for the track, if available */
52
+
appleMusicLink?: string
53
+
/** The Youtube link for the track, if available */
54
+
youtubeLink?: string
55
+
/** The Deezer link for the track, if available */
56
+
deezerLink?: string
57
+
/** The timestamp of the scrobble in milliseconds since epoch */
58
+
timestamp?: number
59
+
[k: string]: unknown
60
+
}
61
+
62
+
export type OutputSchema = AppRockskyScrobbleDefs.ScrobbleViewBasic
63
+
64
+
export interface HandlerInput {
65
+
encoding: 'application/json'
66
+
body: InputSchema
67
+
}
68
+
69
+
export interface HandlerSuccess {
70
+
encoding: 'application/json'
71
+
body: OutputSchema
72
+
headers?: { [key: string]: string }
73
+
}
74
+
75
+
export interface HandlerError {
76
+
status: number
77
+
message?: string
78
+
}
79
+
80
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
81
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
82
+
auth: HA
83
+
params: QueryParams
84
+
input: HandlerInput
85
+
req: express.Request
86
+
res: express.Response
87
+
resetRouteRateLimits: () => Promise<void>
88
+
}
89
+
export type Handler<HA extends HandlerAuth = never> = (
90
+
ctx: HandlerReqCtx<HA>,
91
+
) => Promise<HandlerOutput> | HandlerOutput
+93
apps/cli/src/lexicon/types/app/rocksky/scrobble/defs.ts
+93
apps/cli/src/lexicon/types/app/rocksky/scrobble/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface ScrobbleViewBasic {
10
+
/** The unique identifier of the scrobble. */
11
+
id?: string
12
+
/** The handle of the user who created the scrobble. */
13
+
user?: string
14
+
/** The display name of the user who created the scrobble. */
15
+
userDisplayName?: string
16
+
/** The avatar URL of the user who created the scrobble. */
17
+
userAvatar?: string
18
+
/** The title of the scrobble. */
19
+
title?: string
20
+
/** The artist of the song. */
21
+
artist?: string
22
+
/** The URI of the artist. */
23
+
artistUri?: string
24
+
/** The album of the song. */
25
+
album?: string
26
+
/** The URI of the album. */
27
+
albumUri?: string
28
+
/** The album art URL of the song. */
29
+
cover?: string
30
+
/** The timestamp when the scrobble was created. */
31
+
date?: string
32
+
/** The URI of the scrobble. */
33
+
uri?: string
34
+
/** The SHA256 hash of the scrobble data. */
35
+
sha256?: string
36
+
liked?: boolean
37
+
likesCount?: number
38
+
[k: string]: unknown
39
+
}
40
+
41
+
export function isScrobbleViewBasic(v: unknown): v is ScrobbleViewBasic {
42
+
return (
43
+
isObj(v) &&
44
+
hasProp(v, '$type') &&
45
+
v.$type === 'app.rocksky.scrobble.defs#scrobbleViewBasic'
46
+
)
47
+
}
48
+
49
+
export function validateScrobbleViewBasic(v: unknown): ValidationResult {
50
+
return lexicons.validate('app.rocksky.scrobble.defs#scrobbleViewBasic', v)
51
+
}
52
+
53
+
export interface ScrobbleViewDetailed {
54
+
/** The unique identifier of the scrobble. */
55
+
id?: string
56
+
/** The handle of the user who created the scrobble. */
57
+
user?: string
58
+
/** The title of the scrobble. */
59
+
title?: string
60
+
/** The artist of the song. */
61
+
artist?: string
62
+
/** The URI of the artist. */
63
+
artistUri?: string
64
+
/** The album of the song. */
65
+
album?: string
66
+
/** The URI of the album. */
67
+
albumUri?: string
68
+
/** The album art URL of the song. */
69
+
cover?: string
70
+
/** The timestamp when the scrobble was created. */
71
+
date?: string
72
+
/** The URI of the scrobble. */
73
+
uri?: string
74
+
/** The SHA256 hash of the scrobble data. */
75
+
sha256?: string
76
+
/** The number of listeners */
77
+
listeners?: number
78
+
/** The number of scrobbles for this song */
79
+
scrobbles?: number
80
+
[k: string]: unknown
81
+
}
82
+
83
+
export function isScrobbleViewDetailed(v: unknown): v is ScrobbleViewDetailed {
84
+
return (
85
+
isObj(v) &&
86
+
hasProp(v, '$type') &&
87
+
v.$type === 'app.rocksky.scrobble.defs#scrobbleViewDetailed'
88
+
)
89
+
}
90
+
91
+
export function validateScrobbleViewDetailed(v: unknown): ValidationResult {
92
+
return lexicons.validate('app.rocksky.scrobble.defs#scrobbleViewDetailed', v)
93
+
}
+43
apps/cli/src/lexicon/types/app/rocksky/scrobble/getScrobble.ts
+43
apps/cli/src/lexicon/types/app/rocksky/scrobble/getScrobble.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyScrobbleDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The unique identifier of the scrobble */
14
+
uri: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyScrobbleDefs.ScrobbleViewDetailed
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+54
apps/cli/src/lexicon/types/app/rocksky/scrobble/getScrobbles.ts
+54
apps/cli/src/lexicon/types/app/rocksky/scrobble/getScrobbles.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyScrobbleDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did?: string
15
+
/** If true, only return scrobbles from actors the viewer is following. */
16
+
following?: boolean
17
+
/** The maximum number of scrobbles to return */
18
+
limit?: number
19
+
/** The offset for pagination */
20
+
offset?: number
21
+
}
22
+
23
+
export type InputSchema = undefined
24
+
25
+
export interface OutputSchema {
26
+
scrobbles?: AppRockskyScrobbleDefs.ScrobbleViewBasic[]
27
+
[k: string]: unknown
28
+
}
29
+
30
+
export type HandlerInput = undefined
31
+
32
+
export interface HandlerSuccess {
33
+
encoding: 'application/json'
34
+
body: OutputSchema
35
+
headers?: { [key: string]: string }
36
+
}
37
+
38
+
export interface HandlerError {
39
+
status: number
40
+
message?: string
41
+
}
42
+
43
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
44
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
45
+
auth: HA
46
+
params: QueryParams
47
+
input: HandlerInput
48
+
req: express.Request
49
+
res: express.Response
50
+
resetRouteRateLimits: () => Promise<void>
51
+
}
52
+
export type Handler<HA extends HandlerAuth = never> = (
53
+
ctx: HandlerReqCtx<HA>,
54
+
) => Promise<HandlerOutput> | HandlerOutput
+75
apps/cli/src/lexicon/types/app/rocksky/scrobble.ts
+75
apps/cli/src/lexicon/types/app/rocksky/scrobble.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppRockskyArtistDefs from './artist/defs'
9
+
10
+
export interface Record {
11
+
/** The title of the song. */
12
+
title: string
13
+
/** The artist of the song. */
14
+
artist: string
15
+
/** The artists of the song with MusicBrainz IDs. */
16
+
artists?: AppRockskyArtistDefs.ArtistMbid[]
17
+
/** The album artist of the song. */
18
+
albumArtist: string
19
+
/** The album of the song. */
20
+
album: string
21
+
/** The duration of the song in seconds. */
22
+
duration: number
23
+
/** The track number of the song in the album. */
24
+
trackNumber?: number
25
+
/** The disc number of the song in the album. */
26
+
discNumber?: number
27
+
/** The release date of the song. */
28
+
releaseDate?: string
29
+
/** The year the song was released. */
30
+
year?: number
31
+
/** The genre of the song. */
32
+
genre?: string
33
+
/** The tags of the song. */
34
+
tags?: string[]
35
+
/** The composer of the song. */
36
+
composer?: string
37
+
/** The lyrics of the song. */
38
+
lyrics?: string
39
+
/** The copyright message of the song. */
40
+
copyrightMessage?: string
41
+
/** Informations about the song */
42
+
wiki?: string
43
+
/** The album art of the song. */
44
+
albumArt?: BlobRef
45
+
/** The URL of the album art of the song. */
46
+
albumArtUrl?: string
47
+
/** The YouTube link of the song. */
48
+
youtubeLink?: string
49
+
/** The Spotify link of the song. */
50
+
spotifyLink?: string
51
+
/** The Tidal link of the song. */
52
+
tidalLink?: string
53
+
/** The Apple Music link of the song. */
54
+
appleMusicLink?: string
55
+
/** The date when the song was created. */
56
+
createdAt: string
57
+
/** The MusicBrainz ID of the song. */
58
+
mbid?: string
59
+
/** The label of the song. */
60
+
label?: string
61
+
[k: string]: unknown
62
+
}
63
+
64
+
export function isRecord(v: unknown): v is Record {
65
+
return (
66
+
isObj(v) &&
67
+
hasProp(v, '$type') &&
68
+
(v.$type === 'app.rocksky.scrobble#main' ||
69
+
v.$type === 'app.rocksky.scrobble')
70
+
)
71
+
}
72
+
73
+
export function validateRecord(v: unknown): ValidationResult {
74
+
return lexicons.validate('app.rocksky.scrobble#main', v)
75
+
}
+49
apps/cli/src/lexicon/types/app/rocksky/shout/createShout.ts
+49
apps/cli/src/lexicon/types/app/rocksky/shout/createShout.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The content of the shout */
16
+
message?: string
17
+
[k: string]: unknown
18
+
}
19
+
20
+
export type OutputSchema = AppRockskyShoutDefs.ShoutView
21
+
22
+
export interface HandlerInput {
23
+
encoding: 'application/json'
24
+
body: InputSchema
25
+
}
26
+
27
+
export interface HandlerSuccess {
28
+
encoding: 'application/json'
29
+
body: OutputSchema
30
+
headers?: { [key: string]: string }
31
+
}
32
+
33
+
export interface HandlerError {
34
+
status: number
35
+
message?: string
36
+
}
37
+
38
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
39
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
40
+
auth: HA
41
+
params: QueryParams
42
+
input: HandlerInput
43
+
req: express.Request
44
+
res: express.Response
45
+
resetRouteRateLimits: () => Promise<void>
46
+
}
47
+
export type Handler<HA extends HandlerAuth = never> = (
48
+
ctx: HandlerReqCtx<HA>,
49
+
) => Promise<HandlerOutput> | HandlerOutput
+58
apps/cli/src/lexicon/types/app/rocksky/shout/defs.ts
+58
apps/cli/src/lexicon/types/app/rocksky/shout/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface Author {
10
+
/** The unique identifier of the author. */
11
+
id?: string
12
+
/** The decentralized identifier (DID) of the author. */
13
+
did?: string
14
+
/** The handle of the author. */
15
+
handle?: string
16
+
/** The display name of the author. */
17
+
displayName?: string
18
+
/** The URL of the author's avatar image. */
19
+
avatar?: string
20
+
[k: string]: unknown
21
+
}
22
+
23
+
export function isAuthor(v: unknown): v is Author {
24
+
return (
25
+
isObj(v) &&
26
+
hasProp(v, '$type') &&
27
+
v.$type === 'app.rocksky.shout.defs#author'
28
+
)
29
+
}
30
+
31
+
export function validateAuthor(v: unknown): ValidationResult {
32
+
return lexicons.validate('app.rocksky.shout.defs#author', v)
33
+
}
34
+
35
+
export interface ShoutView {
36
+
/** The unique identifier of the shout. */
37
+
id?: string
38
+
/** The content of the shout. */
39
+
message?: string
40
+
/** The ID of the parent shout if this is a reply, otherwise null. */
41
+
parent?: string
42
+
/** The date and time when the shout was created. */
43
+
createdAt?: string
44
+
author?: Author
45
+
[k: string]: unknown
46
+
}
47
+
48
+
export function isShoutView(v: unknown): v is ShoutView {
49
+
return (
50
+
isObj(v) &&
51
+
hasProp(v, '$type') &&
52
+
v.$type === 'app.rocksky.shout.defs#shoutView'
53
+
)
54
+
}
55
+
56
+
export function validateShoutView(v: unknown): ValidationResult {
57
+
return lexicons.validate('app.rocksky.shout.defs#shoutView', v)
58
+
}
+52
apps/cli/src/lexicon/types/app/rocksky/shout/getAlbumShouts.ts
+52
apps/cli/src/lexicon/types/app/rocksky/shout/getAlbumShouts.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The unique identifier of the album to retrieve shouts for */
14
+
uri: string
15
+
/** The maximum number of shouts to return */
16
+
limit?: number
17
+
/** The number of shouts to skip before starting to collect the result set */
18
+
offset?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
shouts?: AppRockskyShoutDefs.ShoutViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/shout/getArtistShouts.ts
+52
apps/cli/src/lexicon/types/app/rocksky/shout/getArtistShouts.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the artist to retrieve shouts for */
14
+
uri: string
15
+
/** The maximum number of shouts to return */
16
+
limit?: number
17
+
/** The number of shouts to skip before starting to collect the result set */
18
+
offset?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
shouts?: AppRockskyShoutDefs.ShoutViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/shout/getProfileShouts.ts
+52
apps/cli/src/lexicon/types/app/rocksky/shout/getProfileShouts.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the actor */
14
+
did: string
15
+
/** The offset for pagination */
16
+
offset?: number
17
+
/** The maximum number of shouts to return */
18
+
limit?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
shouts?: AppRockskyShoutDefs.ShoutViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+52
apps/cli/src/lexicon/types/app/rocksky/shout/getShoutReplies.ts
+52
apps/cli/src/lexicon/types/app/rocksky/shout/getShoutReplies.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the shout to retrieve replies for */
14
+
uri: string
15
+
/** The maximum number of shouts to return */
16
+
limit?: number
17
+
/** The number of shouts to skip before starting to collect the result set */
18
+
offset?: number
19
+
}
20
+
21
+
export type InputSchema = undefined
22
+
23
+
export interface OutputSchema {
24
+
shouts?: AppRockskyShoutDefs.ShoutViewBasic[]
25
+
[k: string]: unknown
26
+
}
27
+
28
+
export type HandlerInput = undefined
29
+
30
+
export interface HandlerSuccess {
31
+
encoding: 'application/json'
32
+
body: OutputSchema
33
+
headers?: { [key: string]: string }
34
+
}
35
+
36
+
export interface HandlerError {
37
+
status: number
38
+
message?: string
39
+
}
40
+
41
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
42
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
43
+
auth: HA
44
+
params: QueryParams
45
+
input: HandlerInput
46
+
req: express.Request
47
+
res: express.Response
48
+
resetRouteRateLimits: () => Promise<void>
49
+
}
50
+
export type Handler<HA extends HandlerAuth = never> = (
51
+
ctx: HandlerReqCtx<HA>,
52
+
) => Promise<HandlerOutput> | HandlerOutput
+48
apps/cli/src/lexicon/types/app/rocksky/shout/getTrackShouts.ts
+48
apps/cli/src/lexicon/types/app/rocksky/shout/getTrackShouts.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The URI of the track to retrieve shouts for */
14
+
uri: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
19
+
export interface OutputSchema {
20
+
shouts?: AppRockskyShoutDefs.ShoutViewBasic[]
21
+
[k: string]: unknown
22
+
}
23
+
24
+
export type HandlerInput = undefined
25
+
26
+
export interface HandlerSuccess {
27
+
encoding: 'application/json'
28
+
body: OutputSchema
29
+
headers?: { [key: string]: string }
30
+
}
31
+
32
+
export interface HandlerError {
33
+
status: number
34
+
message?: string
35
+
}
36
+
37
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
38
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
39
+
auth: HA
40
+
params: QueryParams
41
+
input: HandlerInput
42
+
req: express.Request
43
+
res: express.Response
44
+
resetRouteRateLimits: () => Promise<void>
45
+
}
46
+
export type Handler<HA extends HandlerAuth = never> = (
47
+
ctx: HandlerReqCtx<HA>,
48
+
) => Promise<HandlerOutput> | HandlerOutput
+43
apps/cli/src/lexicon/types/app/rocksky/shout/removeShout.ts
+43
apps/cli/src/lexicon/types/app/rocksky/shout/removeShout.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The ID of the shout to be removed */
14
+
id: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyShoutDefs.ShoutView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+51
apps/cli/src/lexicon/types/app/rocksky/shout/replyShout.ts
+51
apps/cli/src/lexicon/types/app/rocksky/shout/replyShout.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The unique identifier of the shout to reply to */
16
+
shoutId: string
17
+
/** The content of the reply */
18
+
message: string
19
+
[k: string]: unknown
20
+
}
21
+
22
+
export type OutputSchema = AppRockskyShoutDefs.ShoutView
23
+
24
+
export interface HandlerInput {
25
+
encoding: 'application/json'
26
+
body: InputSchema
27
+
}
28
+
29
+
export interface HandlerSuccess {
30
+
encoding: 'application/json'
31
+
body: OutputSchema
32
+
headers?: { [key: string]: string }
33
+
}
34
+
35
+
export interface HandlerError {
36
+
status: number
37
+
message?: string
38
+
}
39
+
40
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
41
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
42
+
auth: HA
43
+
params: QueryParams
44
+
input: HandlerInput
45
+
req: express.Request
46
+
res: express.Response
47
+
resetRouteRateLimits: () => Promise<void>
48
+
}
49
+
export type Handler<HA extends HandlerAuth = never> = (
50
+
ctx: HandlerReqCtx<HA>,
51
+
) => Promise<HandlerOutput> | HandlerOutput
+51
apps/cli/src/lexicon/types/app/rocksky/shout/reportShout.ts
+51
apps/cli/src/lexicon/types/app/rocksky/shout/reportShout.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyShoutDefs from './defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The unique identifier of the shout to report */
16
+
shoutId: string
17
+
/** The reason for reporting the shout */
18
+
reason?: string
19
+
[k: string]: unknown
20
+
}
21
+
22
+
export type OutputSchema = AppRockskyShoutDefs.ShoutView
23
+
24
+
export interface HandlerInput {
25
+
encoding: 'application/json'
26
+
body: InputSchema
27
+
}
28
+
29
+
export interface HandlerSuccess {
30
+
encoding: 'application/json'
31
+
body: OutputSchema
32
+
headers?: { [key: string]: string }
33
+
}
34
+
35
+
export interface HandlerError {
36
+
status: number
37
+
message?: string
38
+
}
39
+
40
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
41
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
42
+
auth: HA
43
+
params: QueryParams
44
+
input: HandlerInput
45
+
req: express.Request
46
+
res: express.Response
47
+
resetRouteRateLimits: () => Promise<void>
48
+
}
49
+
export type Handler<HA extends HandlerAuth = never> = (
50
+
ctx: HandlerReqCtx<HA>,
51
+
) => Promise<HandlerOutput> | HandlerOutput
+30
apps/cli/src/lexicon/types/app/rocksky/shout.ts
+30
apps/cli/src/lexicon/types/app/rocksky/shout.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef'
9
+
10
+
export interface Record {
11
+
/** The message of the shout. */
12
+
message: string
13
+
/** The date when the shout was created. */
14
+
createdAt: string
15
+
parent?: ComAtprotoRepoStrongRef.Main
16
+
subject: ComAtprotoRepoStrongRef.Main
17
+
[k: string]: unknown
18
+
}
19
+
20
+
export function isRecord(v: unknown): v is Record {
21
+
return (
22
+
isObj(v) &&
23
+
hasProp(v, '$type') &&
24
+
(v.$type === 'app.rocksky.shout#main' || v.$type === 'app.rocksky.shout')
25
+
)
26
+
}
27
+
28
+
export function validateRecord(v: unknown): ValidationResult {
29
+
return lexicons.validate('app.rocksky.shout#main', v)
30
+
}
+71
apps/cli/src/lexicon/types/app/rocksky/song/createSong.ts
+71
apps/cli/src/lexicon/types/app/rocksky/song/createSong.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from './defs'
11
+
12
+
export interface QueryParams {}
13
+
14
+
export interface InputSchema {
15
+
/** The title of the song */
16
+
title: string
17
+
/** The artist of the song */
18
+
artist: string
19
+
/** The album artist of the song, if different from the main artist */
20
+
albumArtist: string
21
+
/** The album of the song, if applicable */
22
+
album: string
23
+
/** The duration of the song in seconds */
24
+
duration?: number
25
+
/** The MusicBrainz ID of the song, if available */
26
+
mbId?: string
27
+
/** The URL of the album art for the song */
28
+
albumArt?: string
29
+
/** The track number of the song in the album, if applicable */
30
+
trackNumber?: number
31
+
/** The release date of the song, formatted as YYYY-MM-DD */
32
+
releaseDate?: string
33
+
/** The year the song was released */
34
+
year?: number
35
+
/** The disc number of the song in the album, if applicable */
36
+
discNumber?: number
37
+
/** The lyrics of the song, if available */
38
+
lyrics?: string
39
+
[k: string]: unknown
40
+
}
41
+
42
+
export type OutputSchema = AppRockskySongDefs.SongViewDetailed
43
+
44
+
export interface HandlerInput {
45
+
encoding: 'application/json'
46
+
body: InputSchema
47
+
}
48
+
49
+
export interface HandlerSuccess {
50
+
encoding: 'application/json'
51
+
body: OutputSchema
52
+
headers?: { [key: string]: string }
53
+
}
54
+
55
+
export interface HandlerError {
56
+
status: number
57
+
message?: string
58
+
}
59
+
60
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
61
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
62
+
auth: HA
63
+
params: QueryParams
64
+
input: HandlerInput
65
+
req: express.Request
66
+
res: express.Response
67
+
resetRouteRateLimits: () => Promise<void>
68
+
}
69
+
export type Handler<HA extends HandlerAuth = never> = (
70
+
ctx: HandlerReqCtx<HA>,
71
+
) => Promise<HandlerOutput> | HandlerOutput
+103
apps/cli/src/lexicon/types/app/rocksky/song/defs.ts
+103
apps/cli/src/lexicon/types/app/rocksky/song/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface SongViewBasic {
10
+
/** The unique identifier of the song. */
11
+
id?: string
12
+
/** The title of the song. */
13
+
title?: string
14
+
/** The artist of the song. */
15
+
artist?: string
16
+
/** The artist of the album the song belongs to. */
17
+
albumArtist?: string
18
+
/** The URL of the album art image. */
19
+
albumArt?: string
20
+
/** The URI of the song. */
21
+
uri?: string
22
+
/** The album of the song. */
23
+
album?: string
24
+
/** The duration of the song in milliseconds. */
25
+
duration?: number
26
+
/** The track number of the song in the album. */
27
+
trackNumber?: number
28
+
/** The disc number of the song in the album. */
29
+
discNumber?: number
30
+
/** The number of times the song has been played. */
31
+
playCount?: number
32
+
/** The number of unique listeners who have played the song. */
33
+
uniqueListeners?: number
34
+
/** The URI of the album the song belongs to. */
35
+
albumUri?: string
36
+
/** The URI of the artist of the song. */
37
+
artistUri?: string
38
+
/** The SHA256 hash of the song. */
39
+
sha256?: string
40
+
/** The timestamp when the song was created. */
41
+
createdAt?: string
42
+
[k: string]: unknown
43
+
}
44
+
45
+
export function isSongViewBasic(v: unknown): v is SongViewBasic {
46
+
return (
47
+
isObj(v) &&
48
+
hasProp(v, '$type') &&
49
+
v.$type === 'app.rocksky.song.defs#songViewBasic'
50
+
)
51
+
}
52
+
53
+
export function validateSongViewBasic(v: unknown): ValidationResult {
54
+
return lexicons.validate('app.rocksky.song.defs#songViewBasic', v)
55
+
}
56
+
57
+
export interface SongViewDetailed {
58
+
/** The unique identifier of the song. */
59
+
id?: string
60
+
/** The title of the song. */
61
+
title?: string
62
+
/** The artist of the song. */
63
+
artist?: string
64
+
/** The artist of the album the song belongs to. */
65
+
albumArtist?: string
66
+
/** The URL of the album art image. */
67
+
albumArt?: string
68
+
/** The URI of the song. */
69
+
uri?: string
70
+
/** The album of the song. */
71
+
album?: string
72
+
/** The duration of the song in milliseconds. */
73
+
duration?: number
74
+
/** The track number of the song in the album. */
75
+
trackNumber?: number
76
+
/** The disc number of the song in the album. */
77
+
discNumber?: number
78
+
/** The number of times the song has been played. */
79
+
playCount?: number
80
+
/** The number of unique listeners who have played the song. */
81
+
uniqueListeners?: number
82
+
/** The URI of the album the song belongs to. */
83
+
albumUri?: string
84
+
/** The URI of the artist of the song. */
85
+
artistUri?: string
86
+
/** The SHA256 hash of the song. */
87
+
sha256?: string
88
+
/** The timestamp when the song was created. */
89
+
createdAt?: string
90
+
[k: string]: unknown
91
+
}
92
+
93
+
export function isSongViewDetailed(v: unknown): v is SongViewDetailed {
94
+
return (
95
+
isObj(v) &&
96
+
hasProp(v, '$type') &&
97
+
v.$type === 'app.rocksky.song.defs#songViewDetailed'
98
+
)
99
+
}
100
+
101
+
export function validateSongViewDetailed(v: unknown): ValidationResult {
102
+
return lexicons.validate('app.rocksky.song.defs#songViewDetailed', v)
103
+
}
+43
apps/cli/src/lexicon/types/app/rocksky/song/getSong.ts
+43
apps/cli/src/lexicon/types/app/rocksky/song/getSong.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The unique identifier of the song to retrieve */
14
+
uri: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskySongDefs.SongViewDetailed
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+50
apps/cli/src/lexicon/types/app/rocksky/song/getSongs.ts
+50
apps/cli/src/lexicon/types/app/rocksky/song/getSongs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskySongDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The maximum number of songs to return */
14
+
limit?: number
15
+
/** The offset for pagination */
16
+
offset?: number
17
+
}
18
+
19
+
export type InputSchema = undefined
20
+
21
+
export interface OutputSchema {
22
+
songs?: AppRockskySongDefs.SongViewBasic[]
23
+
[k: string]: unknown
24
+
}
25
+
26
+
export type HandlerInput = undefined
27
+
28
+
export interface HandlerSuccess {
29
+
encoding: 'application/json'
30
+
body: OutputSchema
31
+
headers?: { [key: string]: string }
32
+
}
33
+
34
+
export interface HandlerError {
35
+
status: number
36
+
message?: string
37
+
}
38
+
39
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
40
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
41
+
auth: HA
42
+
params: QueryParams
43
+
input: HandlerInput
44
+
req: express.Request
45
+
res: express.Response
46
+
resetRouteRateLimits: () => Promise<void>
47
+
}
48
+
export type Handler<HA extends HandlerAuth = never> = (
49
+
ctx: HandlerReqCtx<HA>,
50
+
) => Promise<HandlerOutput> | HandlerOutput
+74
apps/cli/src/lexicon/types/app/rocksky/song.ts
+74
apps/cli/src/lexicon/types/app/rocksky/song.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../lexicons'
6
+
import { isObj, hasProp } from '../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppRockskyArtistDefs from './artist/defs'
9
+
10
+
export interface Record {
11
+
/** The title of the song. */
12
+
title: string
13
+
/** The artist of the song. */
14
+
artist: string
15
+
/** The artists of the song with MusicBrainz IDs. */
16
+
artists?: AppRockskyArtistDefs.ArtistMbid[]
17
+
/** The album artist of the song. */
18
+
albumArtist: string
19
+
/** The album of the song. */
20
+
album: string
21
+
/** The duration of the song in seconds. */
22
+
duration: number
23
+
/** The track number of the song in the album. */
24
+
trackNumber?: number
25
+
/** The disc number of the song in the album. */
26
+
discNumber?: number
27
+
/** The release date of the song. */
28
+
releaseDate?: string
29
+
/** The year the song was released. */
30
+
year?: number
31
+
/** The genre of the song. */
32
+
genre?: string
33
+
/** The tags of the song. */
34
+
tags?: string[]
35
+
/** The composer of the song. */
36
+
composer?: string
37
+
/** The lyrics of the song. */
38
+
lyrics?: string
39
+
/** The copyright message of the song. */
40
+
copyrightMessage?: string
41
+
/** Informations about the song */
42
+
wiki?: string
43
+
/** The album art of the song. */
44
+
albumArt?: BlobRef
45
+
/** The URL of the album art of the song. */
46
+
albumArtUrl?: string
47
+
/** The YouTube link of the song. */
48
+
youtubeLink?: string
49
+
/** The Spotify link of the song. */
50
+
spotifyLink?: string
51
+
/** The Tidal link of the song. */
52
+
tidalLink?: string
53
+
/** The Apple Music link of the song. */
54
+
appleMusicLink?: string
55
+
/** The date when the song was created. */
56
+
createdAt: string
57
+
/** The MusicBrainz ID of the song. */
58
+
mbid?: string
59
+
/** The label of the song. */
60
+
label?: string
61
+
[k: string]: unknown
62
+
}
63
+
64
+
export function isRecord(v: unknown): v is Record {
65
+
return (
66
+
isObj(v) &&
67
+
hasProp(v, '$type') &&
68
+
(v.$type === 'app.rocksky.song#main' || v.$type === 'app.rocksky.song')
69
+
)
70
+
}
71
+
72
+
export function validateRecord(v: unknown): ValidationResult {
73
+
return lexicons.validate('app.rocksky.song#main', v)
74
+
}
+35
apps/cli/src/lexicon/types/app/rocksky/spotify/defs.ts
+35
apps/cli/src/lexicon/types/app/rocksky/spotify/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface SpotifyTrackView {
10
+
/** The unique identifier of the Spotify track. */
11
+
id?: string
12
+
/** The name of the track. */
13
+
name?: string
14
+
/** The name of the artist. */
15
+
artist?: string
16
+
/** The name of the album. */
17
+
album?: string
18
+
/** The duration of the track in milliseconds. */
19
+
duration?: number
20
+
/** A URL to a preview of the track. */
21
+
previewUrl?: string
22
+
[k: string]: unknown
23
+
}
24
+
25
+
export function isSpotifyTrackView(v: unknown): v is SpotifyTrackView {
26
+
return (
27
+
isObj(v) &&
28
+
hasProp(v, '$type') &&
29
+
v.$type === 'app.rocksky.spotify.defs#spotifyTrackView'
30
+
)
31
+
}
32
+
33
+
export function validateSpotifyTrackView(v: unknown): ValidationResult {
34
+
return lexicons.validate('app.rocksky.spotify.defs#spotifyTrackView', v)
35
+
}
+43
apps/cli/src/lexicon/types/app/rocksky/spotify/getCurrentlyPlaying.ts
+43
apps/cli/src/lexicon/types/app/rocksky/spotify/getCurrentlyPlaying.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyPlayerDefs from '../player/defs'
11
+
12
+
export interface QueryParams {
13
+
/** Handle or DID of the actor to retrieve the currently playing track for. If not provided, defaults to the current user. */
14
+
actor?: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyPlayerDefs.CurrentlyPlayingViewDetailed
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+32
apps/cli/src/lexicon/types/app/rocksky/spotify/next.ts
+32
apps/cli/src/lexicon/types/app/rocksky/spotify/next.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {}
12
+
13
+
export type InputSchema = undefined
14
+
export type HandlerInput = undefined
15
+
16
+
export interface HandlerError {
17
+
status: number
18
+
message?: string
19
+
}
20
+
21
+
export type HandlerOutput = HandlerError | void
22
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
23
+
auth: HA
24
+
params: QueryParams
25
+
input: HandlerInput
26
+
req: express.Request
27
+
res: express.Response
28
+
resetRouteRateLimits: () => Promise<void>
29
+
}
30
+
export type Handler<HA extends HandlerAuth = never> = (
31
+
ctx: HandlerReqCtx<HA>,
32
+
) => Promise<HandlerOutput> | HandlerOutput
+32
apps/cli/src/lexicon/types/app/rocksky/spotify/pause.ts
+32
apps/cli/src/lexicon/types/app/rocksky/spotify/pause.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {}
12
+
13
+
export type InputSchema = undefined
14
+
export type HandlerInput = undefined
15
+
16
+
export interface HandlerError {
17
+
status: number
18
+
message?: string
19
+
}
20
+
21
+
export type HandlerOutput = HandlerError | void
22
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
23
+
auth: HA
24
+
params: QueryParams
25
+
input: HandlerInput
26
+
req: express.Request
27
+
res: express.Response
28
+
resetRouteRateLimits: () => Promise<void>
29
+
}
30
+
export type Handler<HA extends HandlerAuth = never> = (
31
+
ctx: HandlerReqCtx<HA>,
32
+
) => Promise<HandlerOutput> | HandlerOutput
+32
apps/cli/src/lexicon/types/app/rocksky/spotify/play.ts
+32
apps/cli/src/lexicon/types/app/rocksky/spotify/play.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {}
12
+
13
+
export type InputSchema = undefined
14
+
export type HandlerInput = undefined
15
+
16
+
export interface HandlerError {
17
+
status: number
18
+
message?: string
19
+
}
20
+
21
+
export type HandlerOutput = HandlerError | void
22
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
23
+
auth: HA
24
+
params: QueryParams
25
+
input: HandlerInput
26
+
req: express.Request
27
+
res: express.Response
28
+
resetRouteRateLimits: () => Promise<void>
29
+
}
30
+
export type Handler<HA extends HandlerAuth = never> = (
31
+
ctx: HandlerReqCtx<HA>,
32
+
) => Promise<HandlerOutput> | HandlerOutput
+32
apps/cli/src/lexicon/types/app/rocksky/spotify/previous.ts
+32
apps/cli/src/lexicon/types/app/rocksky/spotify/previous.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {}
12
+
13
+
export type InputSchema = undefined
14
+
export type HandlerInput = undefined
15
+
16
+
export interface HandlerError {
17
+
status: number
18
+
message?: string
19
+
}
20
+
21
+
export type HandlerOutput = HandlerError | void
22
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
23
+
auth: HA
24
+
params: QueryParams
25
+
input: HandlerInput
26
+
req: express.Request
27
+
res: express.Response
28
+
resetRouteRateLimits: () => Promise<void>
29
+
}
30
+
export type Handler<HA extends HandlerAuth = never> = (
31
+
ctx: HandlerReqCtx<HA>,
32
+
) => Promise<HandlerOutput> | HandlerOutput
+35
apps/cli/src/lexicon/types/app/rocksky/spotify/seek.ts
+35
apps/cli/src/lexicon/types/app/rocksky/spotify/seek.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+
export interface QueryParams {
12
+
/** The position in seconds to seek to */
13
+
position: number
14
+
}
15
+
16
+
export type InputSchema = undefined
17
+
export type HandlerInput = undefined
18
+
19
+
export interface HandlerError {
20
+
status: number
21
+
message?: string
22
+
}
23
+
24
+
export type HandlerOutput = HandlerError | void
25
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
26
+
auth: HA
27
+
params: QueryParams
28
+
input: HandlerInput
29
+
req: express.Request
30
+
res: express.Response
31
+
resetRouteRateLimits: () => Promise<void>
32
+
}
33
+
export type Handler<HA extends HandlerAuth = never> = (
34
+
ctx: HandlerReqCtx<HA>,
35
+
) => Promise<HandlerOutput> | HandlerOutput
+33
apps/cli/src/lexicon/types/app/rocksky/stats/defs.ts
+33
apps/cli/src/lexicon/types/app/rocksky/stats/defs.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface StatsView {
10
+
/** The total number of scrobbles. */
11
+
scrobbles?: number
12
+
/** The total number of unique artists scrobbled. */
13
+
artists?: number
14
+
/** The total number of tracks marked as loved. */
15
+
lovedTracks?: number
16
+
/** The total number of unique albums scrobbled. */
17
+
albums?: number
18
+
/** The total number of unique tracks scrobbled. */
19
+
tracks?: number
20
+
[k: string]: unknown
21
+
}
22
+
23
+
export function isStatsView(v: unknown): v is StatsView {
24
+
return (
25
+
isObj(v) &&
26
+
hasProp(v, '$type') &&
27
+
v.$type === 'app.rocksky.stats.defs#statsView'
28
+
)
29
+
}
30
+
31
+
export function validateStatsView(v: unknown): ValidationResult {
32
+
return lexicons.validate('app.rocksky.stats.defs#statsView', v)
33
+
}
+43
apps/cli/src/lexicon/types/app/rocksky/stats/getStats.ts
+43
apps/cli/src/lexicon/types/app/rocksky/stats/getStats.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import express from 'express'
5
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+
import { lexicons } from '../../../../lexicons'
7
+
import { isObj, hasProp } from '../../../../util'
8
+
import { CID } from 'multiformats/cid'
9
+
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
import * as AppRockskyStatsDefs from './defs'
11
+
12
+
export interface QueryParams {
13
+
/** The DID or handle of the user to get stats for. */
14
+
did: string
15
+
}
16
+
17
+
export type InputSchema = undefined
18
+
export type OutputSchema = AppRockskyStatsDefs.StatsView
19
+
export type HandlerInput = undefined
20
+
21
+
export interface HandlerSuccess {
22
+
encoding: 'application/json'
23
+
body: OutputSchema
24
+
headers?: { [key: string]: string }
25
+
}
26
+
27
+
export interface HandlerError {
28
+
status: number
29
+
message?: string
30
+
}
31
+
32
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
33
+
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
34
+
auth: HA
35
+
params: QueryParams
36
+
input: HandlerInput
37
+
req: express.Request
38
+
res: express.Response
39
+
resetRouteRateLimits: () => Promise<void>
40
+
}
41
+
export type Handler<HA extends HandlerAuth = never> = (
42
+
ctx: HandlerReqCtx<HA>,
43
+
) => Promise<HandlerOutput> | HandlerOutput
+26
apps/cli/src/lexicon/types/com/atproto/repo/strongRef.ts
+26
apps/cli/src/lexicon/types/com/atproto/repo/strongRef.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface Main {
10
+
uri: string
11
+
cid: string
12
+
[k: string]: unknown
13
+
}
14
+
15
+
export function isMain(v: unknown): v is Main {
16
+
return (
17
+
isObj(v) &&
18
+
hasProp(v, '$type') &&
19
+
(v.$type === 'com.atproto.repo.strongRef#main' ||
20
+
v.$type === 'com.atproto.repo.strongRef')
21
+
)
22
+
}
23
+
24
+
export function validateMain(v: unknown): ValidationResult {
25
+
return lexicons.validate('com.atproto.repo.strongRef#main', v)
26
+
}
+13
apps/cli/src/lexicon/util.ts
+13
apps/cli/src/lexicon/util.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
export function isObj(v: unknown): v is Record<string, unknown> {
5
+
return typeof v === 'object' && v !== null
6
+
}
7
+
8
+
export function hasProp<K extends PropertyKey>(
9
+
data: object,
10
+
prop: K,
11
+
): data is Record<K, unknown> {
12
+
return prop in data
13
+
}
+56
apps/cli/src/lib/agent.ts
+56
apps/cli/src/lib/agent.ts
···
1
+
import { Agent, AtpAgent } from "@atproto/api";
2
+
import { ctx } from "context";
3
+
import { eq } from "drizzle-orm";
4
+
import authSessions from "schema/auth-session";
5
+
import extractPdsFromDid from "./extractPdsFromDid";
6
+
import { env } from "./env";
7
+
import { logger } from "logger";
8
+
9
+
export async function createAgent(did: string, handle: string): Promise<Agent> {
10
+
const pds = await extractPdsFromDid(did);
11
+
const agent = new AtpAgent({
12
+
service: new URL(pds),
13
+
});
14
+
15
+
try {
16
+
const [data] = await ctx.db
17
+
.select()
18
+
.from(authSessions)
19
+
.where(eq(authSessions.key, did))
20
+
.execute();
21
+
22
+
if (!data) {
23
+
throw new Error("No session found");
24
+
}
25
+
26
+
await agent.resumeSession(JSON.parse(data.session));
27
+
return agent;
28
+
} catch (e) {
29
+
logger.error`Resuming session ${did}`;
30
+
await ctx.db
31
+
.delete(authSessions)
32
+
.where(eq(authSessions.key, did))
33
+
.execute();
34
+
35
+
await agent.login({
36
+
identifier: handle,
37
+
password: env.ROCKSKY_PASSWORD,
38
+
});
39
+
40
+
await ctx.db
41
+
.insert(authSessions)
42
+
.values({
43
+
key: did,
44
+
session: JSON.stringify(agent.session),
45
+
})
46
+
.onConflictDoUpdate({
47
+
target: authSessions.key,
48
+
set: { session: JSON.stringify(agent.session) },
49
+
})
50
+
.execute();
51
+
52
+
logger.info`Logged in as ${handle}`;
53
+
54
+
return agent;
55
+
}
56
+
}
+66
apps/cli/src/lib/cleanUpJetstreamLock.ts
+66
apps/cli/src/lib/cleanUpJetstreamLock.ts
···
1
+
import fs from "fs";
2
+
import path from "path";
3
+
import os from "os";
4
+
import { logger } from "logger";
5
+
6
+
export function cleanUpJetstreamLockOnExit(did: string) {
7
+
process.on("exit", async () => {
8
+
try {
9
+
await fs.promises.unlink(
10
+
path.join(os.tmpdir(), `rocksky-jetstream-${did}.lock`),
11
+
);
12
+
process.exit(0);
13
+
} catch (error) {
14
+
logger.error`Error cleaning up Jetstream lock: ${error}`;
15
+
process.exit(1);
16
+
}
17
+
});
18
+
19
+
process.on("SIGINT", async () => {
20
+
try {
21
+
await fs.promises.unlink(
22
+
path.join(os.tmpdir(), `rocksky-jetstream-${did}.lock`),
23
+
);
24
+
process.exit(0);
25
+
} catch (error) {
26
+
logger.error`Error cleaning up Jetstream lock: ${error}`;
27
+
process.exit(1);
28
+
}
29
+
});
30
+
31
+
process.on("SIGTERM", async () => {
32
+
try {
33
+
await fs.promises.unlink(
34
+
path.join(os.tmpdir(), `rocksky-jetstream-${did}.lock`),
35
+
);
36
+
process.exit(0);
37
+
} catch (error) {
38
+
logger.error`Error cleaning up Jetstream lock: ${error}`;
39
+
process.exit(1);
40
+
}
41
+
});
42
+
43
+
process.on("uncaughtException", async () => {
44
+
try {
45
+
await fs.promises.unlink(
46
+
path.join(os.tmpdir(), `rocksky-jetstream-${did}.lock`),
47
+
);
48
+
process.exit(1);
49
+
} catch (error) {
50
+
logger.error`Error cleaning up Jetstream lock: ${error}`;
51
+
process.exit(1);
52
+
}
53
+
});
54
+
55
+
process.on("unhandledRejection", async () => {
56
+
try {
57
+
await fs.promises.unlink(
58
+
path.join(os.tmpdir(), `rocksky-jetstream-${did}.lock`),
59
+
);
60
+
process.exit(1);
61
+
} catch (error) {
62
+
logger.error`Error cleaning up Jetstream lock: ${error}`;
63
+
process.exit(1);
64
+
}
65
+
});
66
+
}
+56
apps/cli/src/lib/cleanUpSyncLock.ts
+56
apps/cli/src/lib/cleanUpSyncLock.ts
···
1
+
import fs from "fs";
2
+
import path from "path";
3
+
import os from "os";
4
+
import { logger } from "logger";
5
+
6
+
export function cleanUpSyncLockOnExit(did: string) {
7
+
process.on("exit", async () => {
8
+
try {
9
+
await fs.promises.unlink(path.join(os.tmpdir(), `rocksky-${did}.lock`));
10
+
process.exit(0);
11
+
} catch (error) {
12
+
logger.error`Error cleaning up Sync lock: ${error}`;
13
+
process.exit(1);
14
+
}
15
+
});
16
+
17
+
process.on("SIGINT", async () => {
18
+
try {
19
+
await fs.promises.unlink(path.join(os.tmpdir(), `rocksky-${did}.lock`));
20
+
process.exit(0);
21
+
} catch (error) {
22
+
logger.error`Error cleaning up Sync lock: ${error}`;
23
+
process.exit(1);
24
+
}
25
+
});
26
+
27
+
process.on("SIGTERM", async () => {
28
+
try {
29
+
await fs.promises.unlink(path.join(os.tmpdir(), `rocksky-${did}.lock`));
30
+
process.exit(0);
31
+
} catch (error) {
32
+
logger.error`Error cleaning up Sync lock: ${error}`;
33
+
process.exit(1);
34
+
}
35
+
});
36
+
37
+
process.on("uncaughtException", async () => {
38
+
try {
39
+
await fs.promises.unlink(path.join(os.tmpdir(), `rocksky-${did}.lock`));
40
+
process.exit(1);
41
+
} catch (error) {
42
+
logger.error`Error cleaning up Sync lock: ${error}`;
43
+
process.exit(1);
44
+
}
45
+
});
46
+
47
+
process.on("unhandledRejection", async () => {
48
+
try {
49
+
await fs.promises.unlink(path.join(os.tmpdir(), `rocksky-${did}.lock`));
50
+
process.exit(1);
51
+
} catch (error) {
52
+
logger.error`Error cleaning up Sync lock: ${error}`;
53
+
process.exit(1);
54
+
}
55
+
});
56
+
}
+72
apps/cli/src/lib/didUnstorageCache.ts
+72
apps/cli/src/lib/didUnstorageCache.ts
···
1
+
import type { CacheResult, DidCache, DidDocument } from "@atproto/identity";
2
+
import type { Storage } from "unstorage";
3
+
4
+
const HOUR = 60e3 * 60;
5
+
const DAY = HOUR * 24;
6
+
7
+
type CacheVal = {
8
+
doc: DidDocument;
9
+
updatedAt: number;
10
+
};
11
+
12
+
/**
13
+
* An unstorage based DidCache with staleness and max TTL
14
+
*/
15
+
export class StorageCache implements DidCache {
16
+
public staleTTL: number;
17
+
public maxTTL: number;
18
+
public cache: Storage<CacheVal>;
19
+
private prefix: string;
20
+
constructor({
21
+
store,
22
+
prefix,
23
+
staleTTL,
24
+
maxTTL,
25
+
}: {
26
+
store: Storage;
27
+
prefix: string;
28
+
staleTTL?: number;
29
+
maxTTL?: number;
30
+
}) {
31
+
this.cache = store as Storage<CacheVal>;
32
+
this.prefix = prefix;
33
+
this.staleTTL = staleTTL ?? HOUR;
34
+
this.maxTTL = maxTTL ?? DAY;
35
+
}
36
+
37
+
async cacheDid(did: string, doc: DidDocument): Promise<void> {
38
+
await this.cache.set(this.prefix + did, { doc, updatedAt: Date.now() });
39
+
}
40
+
41
+
async refreshCache(
42
+
did: string,
43
+
getDoc: () => Promise<DidDocument | null>,
44
+
): Promise<void> {
45
+
const doc = await getDoc();
46
+
if (doc) {
47
+
await this.cacheDid(did, doc);
48
+
}
49
+
}
50
+
51
+
async checkCache(did: string): Promise<CacheResult | null> {
52
+
const val = await this.cache.get<CacheVal>(this.prefix + did);
53
+
if (!val) return null;
54
+
const now = Date.now();
55
+
const expired = now > val.updatedAt + this.maxTTL;
56
+
const stale = now > val.updatedAt + this.staleTTL;
57
+
return {
58
+
...val,
59
+
did,
60
+
stale,
61
+
expired,
62
+
};
63
+
}
64
+
65
+
async clearEntry(did: string): Promise<void> {
66
+
await this.cache.remove(this.prefix + did);
67
+
}
68
+
69
+
async clear(): Promise<void> {
70
+
await this.cache.clear(this.prefix);
71
+
}
72
+
}
+13
apps/cli/src/lib/env.ts
+13
apps/cli/src/lib/env.ts
···
1
+
import dotenv from "dotenv";
2
+
import { cleanEnv, str } from "envalid";
3
+
4
+
dotenv.config();
5
+
6
+
export const env = cleanEnv(process.env, {
7
+
ROCKSKY_IDENTIFIER: str({ default: "" }),
8
+
ROCKSKY_HANDLE: str({ default: "" }),
9
+
ROCKSKY_PASSWORD: str({ default: "" }),
10
+
JETSTREAM_SERVER: str({
11
+
default: "wss://jetstream1.us-west.bsky.network/subscribe",
12
+
}),
13
+
});
+33
apps/cli/src/lib/extractPdsFromDid.ts
+33
apps/cli/src/lib/extractPdsFromDid.ts
···
1
+
export default async function extractPdsFromDid(
2
+
did: string,
3
+
): Promise<string | null> {
4
+
let didDocUrl: string;
5
+
6
+
if (did.startsWith("did:plc:")) {
7
+
didDocUrl = `https://plc.directory/${did}`;
8
+
} else if (did.startsWith("did:web:")) {
9
+
const domain = did.substring("did:web:".length);
10
+
didDocUrl = `https://${domain}/.well-known/did.json`;
11
+
} else {
12
+
throw new Error("Unsupported DID method");
13
+
}
14
+
15
+
const response = await fetch(didDocUrl);
16
+
if (!response.ok) throw new Error("Failed to fetch DID doc");
17
+
18
+
const doc: {
19
+
service?: Array<{
20
+
type: string;
21
+
id: string;
22
+
serviceEndpoint: string;
23
+
}>;
24
+
} = await response.json();
25
+
26
+
// Find the atproto PDS service
27
+
const pdsService = doc.service?.find(
28
+
(s: any) =>
29
+
s.type === "AtprotoPersonalDataServer" && s.id.endsWith("#atproto_pds"),
30
+
);
31
+
32
+
return pdsService?.serviceEndpoint ?? null;
33
+
}
+39
apps/cli/src/lib/getDidAndHandle.ts
+39
apps/cli/src/lib/getDidAndHandle.ts
···
1
+
import { isValidHandle } from "@atproto/syntax";
2
+
import { env } from "./env";
3
+
import { logger } from "logger";
4
+
import { ctx } from "context";
5
+
import chalk from "chalk";
6
+
7
+
export async function getDidAndHandle(): Promise<[string, string]> {
8
+
let handle = env.ROCKSKY_HANDLE || env.ROCKSKY_IDENTIFIER;
9
+
let did = env.ROCKSKY_HANDLE || env.ROCKSKY_IDENTIFIER;
10
+
11
+
if (!handle) {
12
+
console.error(
13
+
`❌ No AT Proto handle or DID provided, please provide one in the environment variables ${chalk.bold("ROCKSKY_HANDLE")} or ${chalk.bold("ROCKSKY_IDENTIFIER")}`,
14
+
);
15
+
process.exit(1);
16
+
}
17
+
18
+
if (!env.ROCKSKY_PASSWORD) {
19
+
console.error(
20
+
`❌ No app password provided, please provide one in the environment variable ${chalk.bold("ROCKSKY_PASSWORD")}\nYou can create one at ${chalk.blueBright("https://bsky.app/settings/app-passwords")}`,
21
+
);
22
+
process.exit(1);
23
+
}
24
+
25
+
if (handle.startsWith("did:plc:") || handle.startsWith("did:web:")) {
26
+
handle = await ctx.resolver.resolveDidToHandle(handle);
27
+
}
28
+
29
+
if (!isValidHandle(handle)) {
30
+
logger.error`❌ Invalid handle: ${handle}`;
31
+
process.exit(1);
32
+
}
33
+
34
+
if (!did.startsWith("did:plc:") && !did.startsWith("did:web:")) {
35
+
did = await ctx.baseIdResolver.handle.resolve(did);
36
+
}
37
+
38
+
return [did, handle];
39
+
}
+52
apps/cli/src/lib/idResolver.ts
+52
apps/cli/src/lib/idResolver.ts
···
1
+
import { IdResolver } from "@atproto/identity";
2
+
import type { Storage } from "unstorage";
3
+
import { StorageCache } from "./didUnstorageCache";
4
+
5
+
const HOUR = 60e3 * 60;
6
+
const DAY = HOUR * 24;
7
+
const WEEK = HOUR * 7;
8
+
9
+
export function createIdResolver(kv: Storage) {
10
+
return new IdResolver({
11
+
didCache: new StorageCache({
12
+
store: kv,
13
+
prefix: "didCache:",
14
+
staleTTL: DAY,
15
+
maxTTL: WEEK,
16
+
}),
17
+
});
18
+
}
19
+
20
+
export interface BidirectionalResolver {
21
+
resolveDidToHandle(did: string): Promise<string>;
22
+
resolveDidsToHandles(dids: string[]): Promise<Record<string, string>>;
23
+
}
24
+
25
+
export function createBidirectionalResolver(resolver: IdResolver) {
26
+
return {
27
+
async resolveDidToHandle(did: string): Promise<string> {
28
+
const didDoc = await resolver.did.resolveAtprotoData(did);
29
+
30
+
// asynchronously double check that the handle resolves back
31
+
resolver.handle.resolve(didDoc.handle).then((resolvedHandle) => {
32
+
if (resolvedHandle !== did) {
33
+
resolver.did.ensureResolve(did, true);
34
+
}
35
+
});
36
+
return didDoc?.handle ?? did;
37
+
},
38
+
39
+
async resolveDidsToHandles(
40
+
dids: string[],
41
+
): Promise<Record<string, string>> {
42
+
const didHandleMap: Record<string, string> = {};
43
+
const resolves = await Promise.all(
44
+
dids.map((did) => this.resolveDidToHandle(did).catch((_) => did)),
45
+
);
46
+
for (let i = 0; i < dids.length; i++) {
47
+
didHandleMap[dids[i]] = resolves[i];
48
+
}
49
+
return didHandleMap;
50
+
},
51
+
};
52
+
}
+47
apps/cli/src/lib/matchTrack.ts
+47
apps/cli/src/lib/matchTrack.ts
···
1
+
import { RockskyClient } from "client";
2
+
import { ctx } from "context";
3
+
import { logger } from "logger";
4
+
import { SelectTrack } from "schema/tracks";
5
+
6
+
export type MusicBrainzArtist = {
7
+
mbid: string;
8
+
name: string;
9
+
};
10
+
11
+
export type MatchTrackResult = SelectTrack & {
12
+
genres: string[] | null;
13
+
artistPicture: string | null;
14
+
releaseDate: string | null;
15
+
year: number | null;
16
+
mbArtists: MusicBrainzArtist[] | null;
17
+
};
18
+
19
+
export async function matchTrack(
20
+
track: string,
21
+
artist: string,
22
+
): Promise<MatchTrackResult | null> {
23
+
let match;
24
+
const cached = await ctx.kv.getItem(`${track} - ${artist}`);
25
+
const client = new RockskyClient();
26
+
27
+
if (cached) {
28
+
match = cached;
29
+
client.matchSong(track, artist).then((newMatch) => {
30
+
if (newMatch) {
31
+
ctx.kv.setItem(`${track} - ${artist}`.toLowerCase(), newMatch);
32
+
}
33
+
});
34
+
} else {
35
+
match = await client.matchSong(track, artist);
36
+
await ctx.kv.setItem(`${track} - ${artist}`.toLowerCase(), match);
37
+
}
38
+
39
+
if (!match.title || !match.artist) {
40
+
logger.error`Failed to match track ${track} by ${artist}`;
41
+
return null;
42
+
}
43
+
44
+
logger.info`💿 Matched track ${match.title} by ${match.artist}`;
45
+
46
+
return match;
47
+
}
+18
apps/cli/src/logger.ts
+18
apps/cli/src/logger.ts
···
1
+
import { configure, getConsoleSink, getLogger } from "@logtape/logtape";
2
+
3
+
await configure({
4
+
sinks: {
5
+
console: getConsoleSink(),
6
+
meta: getConsoleSink(),
7
+
},
8
+
loggers: [
9
+
{ category: "@rocksky/cli", lowestLevel: "debug", sinks: ["console"] },
10
+
{
11
+
category: ["logtape", "meta"],
12
+
lowestLevel: "warning",
13
+
sinks: ["meta"],
14
+
},
15
+
],
16
+
});
17
+
18
+
export const logger = getLogger("@rocksky/cli");
+30
apps/cli/src/schema/album-tracks.ts
+30
apps/cli/src/schema/album-tracks.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
3
+
import albums from "./albums";
4
+
import tracks from "./tracks";
5
+
6
+
const albumTracks = sqliteTable(
7
+
"album_tracks",
8
+
{
9
+
id: text("id").primaryKey().notNull(),
10
+
albumId: text("album_id")
11
+
.notNull()
12
+
.references(() => albums.id),
13
+
trackId: text("track_id")
14
+
.notNull()
15
+
.references(() => tracks.id),
16
+
createdAt: integer("created_at")
17
+
.notNull()
18
+
.default(sql`(unixepoch())`),
19
+
updatedAt: integer("updated_at")
20
+
.notNull()
21
+
.default(sql`(unixepoch())`),
22
+
},
23
+
24
+
(t) => [unique("album_tracks_unique_index").on(t.albumId, t.trackId)],
25
+
);
26
+
27
+
export type SelectAlbumTrack = InferSelectModel<typeof albumTracks>;
28
+
export type InsertAlbumTrack = InferInsertModel<typeof albumTracks>;
29
+
30
+
export default albumTracks;
+29
apps/cli/src/schema/albums.ts
+29
apps/cli/src/schema/albums.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+
const albums = sqliteTable("albums", {
5
+
id: text("id").primaryKey().notNull(),
6
+
title: text("title").notNull(),
7
+
artist: text("artist").notNull(),
8
+
releaseDate: text("release_date"),
9
+
year: integer("year"),
10
+
albumArt: text("album_art"),
11
+
uri: text("uri").unique(),
12
+
cid: text("cid").unique().notNull(),
13
+
artistUri: text("artist_uri"),
14
+
appleMusicLink: text("apple_music_link").unique(),
15
+
spotifyLink: text("spotify_link").unique(),
16
+
tidalLink: text("tidal_link").unique(),
17
+
youtubeLink: text("youtube_link").unique(),
18
+
createdAt: integer("created_at", { mode: "timestamp" })
19
+
.notNull()
20
+
.default(sql`(unixepoch())`),
21
+
updatedAt: integer("updated_at", { mode: "timestamp" })
22
+
.notNull()
23
+
.default(sql`(unixepoch())`),
24
+
});
25
+
26
+
export type SelectAlbum = InferSelectModel<typeof albums>;
27
+
export type InsertAlbum = InferInsertModel<typeof albums>;
28
+
29
+
export default albums;
+29
apps/cli/src/schema/artist-albums.ts
+29
apps/cli/src/schema/artist-albums.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
3
+
import albums from "./albums";
4
+
import artists from "./artists";
5
+
6
+
const artistAlbums = sqliteTable(
7
+
"artist_albums",
8
+
{
9
+
id: text("id").primaryKey().notNull(),
10
+
artistId: text("artist_id")
11
+
.notNull()
12
+
.references(() => artists.id),
13
+
albumId: text("album_id")
14
+
.notNull()
15
+
.references(() => albums.id),
16
+
createdAt: integer("created_at")
17
+
.notNull()
18
+
.default(sql`(unixepoch())`),
19
+
updatedAt: integer("updated_at")
20
+
.notNull()
21
+
.default(sql`(unixepoch())`),
22
+
},
23
+
(t) => [unique("artist_albums_unique_index").on(t.artistId, t.albumId)],
24
+
);
25
+
26
+
export type SelectArtistAlbum = InferSelectModel<typeof artistAlbums>;
27
+
export type InsertArtistAlbum = InferInsertModel<typeof artistAlbums>;
28
+
29
+
export default artistAlbums;
+17
apps/cli/src/schema/artist-genres.ts
+17
apps/cli/src/schema/artist-genres.ts
···
1
+
import { type InferInsertModel, type InferSelectModel } from "drizzle-orm";
2
+
import { sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
3
+
4
+
const artistGenres = sqliteTable(
5
+
"artist_genres ",
6
+
{
7
+
id: text("id").primaryKey().notNull(),
8
+
artistId: text("artist_id").notNull(),
9
+
genreId: text("genre_id").notNull(),
10
+
},
11
+
(t) => [unique("artist_genre_unique_index").on(t.artistId, t.genreId)],
12
+
);
13
+
14
+
export type SelectArtistGenre = InferSelectModel<typeof artistGenres>;
15
+
export type InsertArtistGenre = InferInsertModel<typeof artistGenres>;
16
+
17
+
export default artistGenres;
+29
apps/cli/src/schema/artist-tracks.ts
+29
apps/cli/src/schema/artist-tracks.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
3
+
import artists from "./artists";
4
+
import tracks from "./tracks";
5
+
6
+
const artistTracks = sqliteTable(
7
+
"artist_tracks",
8
+
{
9
+
id: text("id").primaryKey().notNull(),
10
+
artistId: text("artist_id")
11
+
.notNull()
12
+
.references(() => artists.id),
13
+
trackId: text("track_id")
14
+
.notNull()
15
+
.references(() => tracks.id),
16
+
createdAt: integer("created_at", { mode: "timestamp" })
17
+
.notNull()
18
+
.default(sql`(unixepoch())`),
19
+
updatedAt: integer("updated_at", { mode: "timestamp" })
20
+
.notNull()
21
+
.default(sql`(unixepoch())`),
22
+
},
23
+
(t) => [unique("artist_tracks_unique_index").on(t.artistId, t.trackId)],
24
+
);
25
+
26
+
export type SelectArtistTrack = InferSelectModel<typeof artistTracks>;
27
+
export type InsertArtistTrack = InferInsertModel<typeof artistTracks>;
28
+
29
+
export default artistTracks;
+30
apps/cli/src/schema/artists.ts
+30
apps/cli/src/schema/artists.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+
const artists = sqliteTable("artists", {
5
+
id: text("id").primaryKey().notNull(),
6
+
name: text("name").notNull(),
7
+
biography: text("biography"),
8
+
born: integer("born", { mode: "timestamp" }),
9
+
bornIn: text("born_in"),
10
+
died: integer("died", { mode: "timestamp" }),
11
+
picture: text("picture"),
12
+
uri: text("uri").unique(),
13
+
cid: text("cid").unique().notNull(),
14
+
appleMusicLink: text("apple_music_link"),
15
+
spotifyLink: text("spotify_link"),
16
+
tidalLink: text("tidal_link"),
17
+
youtubeLink: text("youtube_link"),
18
+
genres: text("genres"),
19
+
createdAt: integer("created_at", { mode: "timestamp" })
20
+
.notNull()
21
+
.default(sql`(unixepoch())`),
22
+
updatedAt: integer("updated_at", { mode: "timestamp" })
23
+
.notNull()
24
+
.default(sql`(unixepoch())`),
25
+
});
26
+
27
+
export type SelectArtist = InferSelectModel<typeof artists>;
28
+
export type InsertArtist = InferInsertModel<typeof artists>;
29
+
30
+
export default artists;
+18
apps/cli/src/schema/auth-session.ts
+18
apps/cli/src/schema/auth-session.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+
const authSessions = sqliteTable("auth_sessions", {
5
+
key: text("key").primaryKey().notNull(),
6
+
session: text("session").notNull(),
7
+
createdAt: integer("created_at", { mode: "timestamp" })
8
+
.notNull()
9
+
.default(sql`(unixepoch())`),
10
+
updatedAt: integer("updated_at", { mode: "timestamp" })
11
+
.notNull()
12
+
.default(sql`(unixepoch())`),
13
+
});
14
+
15
+
export type SelectAuthSession = InferSelectModel<typeof authSessions>;
16
+
export type InsertAuthSession = InferInsertModel<typeof authSessions>;
17
+
18
+
export default authSessions;
+18
apps/cli/src/schema/genres.ts
+18
apps/cli/src/schema/genres.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+
const genres = sqliteTable("genres", {
5
+
id: text("id").primaryKey().notNull(),
6
+
name: text("name").unique().notNull(),
7
+
createdAt: integer("created_at", { mode: "timestamp" })
8
+
.notNull()
9
+
.default(sql`(unixepoch())`),
10
+
updatedAt: integer("updated_at", { mode: "timestamp" })
11
+
.notNull()
12
+
.default(sql`(unixepoch())`),
13
+
});
14
+
15
+
export type SelectGenre = InferSelectModel<typeof genres>;
16
+
export type InsertGenre = InferInsertModel<typeof genres>;
17
+
18
+
export default genres;
+33
apps/cli/src/schema/index.ts
+33
apps/cli/src/schema/index.ts
···
1
+
import albumTracks from "./album-tracks";
2
+
import albums from "./albums";
3
+
import artistAlbums from "./artist-albums";
4
+
import artistGenres from "./artist-genres";
5
+
import artistTracks from "./artist-tracks";
6
+
import artists from "./artists";
7
+
import authSessions from "./auth-session";
8
+
import genres from "./genres";
9
+
import lovedTracks from "./loved-tracks";
10
+
import scrobbles from "./scrobbles";
11
+
import tracks from "./tracks";
12
+
import userAlbums from "./user-albums";
13
+
import userArtists from "./user-artists";
14
+
import userTracks from "./user-tracks";
15
+
import users from "./users";
16
+
17
+
export default {
18
+
users,
19
+
tracks,
20
+
artists,
21
+
albums,
22
+
albumTracks,
23
+
authSessions,
24
+
artistAlbums,
25
+
artistTracks,
26
+
lovedTracks,
27
+
scrobbles,
28
+
userAlbums,
29
+
userArtists,
30
+
userTracks,
31
+
genres,
32
+
artistGenres,
33
+
};
+27
apps/cli/src/schema/loved-tracks.ts
+27
apps/cli/src/schema/loved-tracks.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { sqliteTable, integer, text, unique } from "drizzle-orm/sqlite-core";
3
+
import tracks from "./tracks";
4
+
import users from "./users";
5
+
6
+
const lovedTracks = sqliteTable(
7
+
"loved_tracks",
8
+
{
9
+
id: text("id").primaryKey().notNull(),
10
+
userId: text("user_id")
11
+
.notNull()
12
+
.references(() => users.id),
13
+
trackId: text("track_id")
14
+
.notNull()
15
+
.references(() => tracks.id),
16
+
uri: text("uri").unique(),
17
+
createdAt: integer("created_at")
18
+
.notNull()
19
+
.default(sql`(unixepoch())`),
20
+
},
21
+
(t) => [unique("loved_tracks_unique_index").on(t.userId, t.trackId)],
22
+
);
23
+
24
+
export type SelectLovedTrack = InferSelectModel<typeof lovedTracks>;
25
+
export type InsertLovedTrack = InferInsertModel<typeof lovedTracks>;
26
+
27
+
export default lovedTracks;
+30
apps/cli/src/schema/scrobbles.ts
+30
apps/cli/src/schema/scrobbles.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
import albums from "./albums";
4
+
import artists from "./artists";
5
+
import tracks from "./tracks";
6
+
import users from "./users";
7
+
8
+
const scrobbles = sqliteTable("scrobbles", {
9
+
id: text("xata_id").primaryKey().notNull(),
10
+
userId: text("user_id").references(() => users.id),
11
+
trackId: text("track_id").references(() => tracks.id),
12
+
albumId: text("album_id").references(() => albums.id),
13
+
artistId: text("artist_id").references(() => artists.id),
14
+
uri: text("uri").unique(),
15
+
cid: text("cid").unique(),
16
+
createdAt: integer("created_at", { mode: "timestamp" })
17
+
.notNull()
18
+
.default(sql`(unixepoch())`),
19
+
updatedAt: integer("updated_at", { mode: "timestamp" })
20
+
.notNull()
21
+
.default(sql`(unixepoch())`),
22
+
timestamp: integer("timestamp", { mode: "timestamp" })
23
+
.notNull()
24
+
.default(sql`(unixepoch())`),
25
+
});
26
+
27
+
export type SelectScrobble = InferSelectModel<typeof scrobbles>;
28
+
export type InsertScrobble = InferInsertModel<typeof scrobbles>;
29
+
30
+
export default scrobbles;
+39
apps/cli/src/schema/tracks.ts
+39
apps/cli/src/schema/tracks.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+
const tracks = sqliteTable("tracks", {
5
+
id: text("id").primaryKey().notNull(),
6
+
title: text("title").notNull(),
7
+
artist: text("artist").notNull(),
8
+
albumArtist: text("album_artist").notNull(),
9
+
albumArt: text("album_art"),
10
+
album: text("album").notNull(),
11
+
trackNumber: integer("track_number"),
12
+
duration: integer("duration").notNull(),
13
+
mbId: text("mb_id").unique(),
14
+
youtubeLink: text("youtube_link").unique(),
15
+
spotifyLink: text("spotify_link").unique(),
16
+
appleMusicLink: text("apple_music_link").unique(),
17
+
tidalLink: text("tidal_link").unique(),
18
+
discNumber: integer("disc_number"),
19
+
lyrics: text("lyrics"),
20
+
composer: text("composer"),
21
+
genre: text("genre"),
22
+
label: text("label"),
23
+
copyrightMessage: text("copyright_message"),
24
+
uri: text("uri").unique(),
25
+
cid: text("cid").unique().notNull(),
26
+
albumUri: text("album_uri"),
27
+
artistUri: text("artist_uri"),
28
+
createdAt: integer("created_at", { mode: "timestamp" })
29
+
.notNull()
30
+
.default(sql`(unixepoch())`),
31
+
updatedAt: integer("updated_at", { mode: "timestamp" })
32
+
.notNull()
33
+
.default(sql`(unixepoch())`),
34
+
});
35
+
36
+
export type SelectTrack = InferSelectModel<typeof tracks>;
37
+
export type InsertTrack = InferInsertModel<typeof tracks>;
38
+
39
+
export default tracks;
+31
apps/cli/src/schema/user-albums.ts
+31
apps/cli/src/schema/user-albums.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
3
+
import albums from "./albums";
4
+
import users from "./users";
5
+
6
+
const userAlbums = sqliteTable(
7
+
"user_albums",
8
+
{
9
+
id: text("id").primaryKey().notNull(),
10
+
userId: text("user_id")
11
+
.notNull()
12
+
.references(() => users.id),
13
+
albumId: text("album_id")
14
+
.notNull()
15
+
.references(() => albums.id),
16
+
createdAt: integer("created_at", { mode: "timestamp" })
17
+
.notNull()
18
+
.default(sql`(unixepoch())`),
19
+
updatedAt: integer("updated_at", { mode: "timestamp" })
20
+
.notNull()
21
+
.default(sql`(unixepoch())`),
22
+
scrobbles: integer("scrobbles"),
23
+
uri: text("uri").unique().notNull(),
24
+
},
25
+
(t) => [unique("user_albums_unique_index").on(t.userId, t.albumId)],
26
+
);
27
+
28
+
export type SelectUserAlbum = InferSelectModel<typeof userAlbums>;
29
+
export type InsertUserAlbum = InferInsertModel<typeof userAlbums>;
30
+
31
+
export default userAlbums;
+32
apps/cli/src/schema/user-artists.ts
+32
apps/cli/src/schema/user-artists.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
3
+
import artists from "./artists";
4
+
import users from "./users";
5
+
6
+
const userArtists = sqliteTable(
7
+
"user_artists",
8
+
{
9
+
id: text("id").primaryKey().notNull(),
10
+
userId: text("user_id")
11
+
.notNull()
12
+
.references(() => users.id),
13
+
artistId: text("artist_id")
14
+
.notNull()
15
+
.references(() => artists.id),
16
+
createdAt: integer("created_at", { mode: "timestamp" })
17
+
.notNull()
18
+
.default(sql`(unixepoch())`),
19
+
updatedAt: integer("updated_at", { mode: "timestamp" })
20
+
.notNull()
21
+
.default(sql`(unixepoch())`),
22
+
scrobbles: integer("scrobbles"),
23
+
uri: text("uri").unique().notNull(),
24
+
},
25
+
26
+
(t) => [unique("user_artists_unique_index").on(t.userId, t.artistId)],
27
+
);
28
+
29
+
export type SelectUserArtist = InferSelectModel<typeof userArtists>;
30
+
export type InsertUserArtist = InferInsertModel<typeof userArtists>;
31
+
32
+
export default userArtists;
+31
apps/cli/src/schema/user-tracks.ts
+31
apps/cli/src/schema/user-tracks.ts
···
1
+
import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
3
+
import tracks from "./tracks";
4
+
import users from "./users";
5
+
6
+
const userTracks = sqliteTable(
7
+
"user_tracks",
8
+
{
9
+
id: text("id").primaryKey().notNull(),
10
+
userId: text("user_id")
11
+
.notNull()
12
+
.references(() => users.id),
13
+
trackId: text("track_id")
14
+
.notNull()
15
+
.references(() => tracks.id),
16
+
createdAt: integer("created_at", { mode: "timestamp" })
17
+
.notNull()
18
+
.default(sql`(unixepoch())`),
19
+
updatedAt: integer("updated_at", { mode: "timestamp" })
20
+
.notNull()
21
+
.default(sql`(unixepoch())`),
22
+
scrobbles: integer("scrobbles"),
23
+
uri: text("uri").unique().notNull(),
24
+
},
25
+
(t) => [unique("user_tracks_unique_index").on(t.userId, t.trackId)],
26
+
);
27
+
28
+
export type SelectUser = InferSelectModel<typeof userTracks>;
29
+
export type InsertUserTrack = InferInsertModel<typeof userTracks>;
30
+
31
+
export default userTracks;
+21
apps/cli/src/schema/users.ts
+21
apps/cli/src/schema/users.ts
···
1
+
import { type InferSelectModel, sql } from "drizzle-orm";
2
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+
const users = sqliteTable("users", {
5
+
id: text("id").primaryKey().notNull(),
6
+
did: text("did").unique().notNull(),
7
+
displayName: text("display_name"),
8
+
handle: text("handle").unique().notNull(),
9
+
avatar: text("avatar").notNull(),
10
+
createdAt: integer("created_at", { mode: "timestamp" })
11
+
.notNull()
12
+
.default(sql`(unixepoch())`),
13
+
updatedAt: integer("updated_at", { mode: "timestamp" })
14
+
.notNull()
15
+
.default(sql`(unixepoch())`),
16
+
});
17
+
18
+
export type SelectUser = InferSelectModel<typeof users>;
19
+
export type InsertUser = InferSelectModel<typeof users>;
20
+
21
+
export default users;
+410
apps/cli/src/scrobble.ts
+410
apps/cli/src/scrobble.ts
···
1
+
import { MatchTrackResult } from "lib/matchTrack";
2
+
import { logger } from "logger";
3
+
import dayjs from "dayjs";
4
+
import { createAgent } from "lib/agent";
5
+
import { getDidAndHandle } from "lib/getDidAndHandle";
6
+
import { ctx } from "context";
7
+
import schema from "schema";
8
+
import { and, eq, gte, lte, or, sql } from "drizzle-orm";
9
+
import os from "node:os";
10
+
import path from "node:path";
11
+
import fs from "node:fs";
12
+
import chalk from "chalk";
13
+
import * as Album from "lexicon/types/app/rocksky/album";
14
+
import * as Artist from "lexicon/types/app/rocksky/artist";
15
+
import * as Scrobble from "lexicon/types/app/rocksky/scrobble";
16
+
import * as Song from "lexicon/types/app/rocksky/song";
17
+
import { TID } from "@atproto/common";
18
+
import { Agent } from "@atproto/api";
19
+
import { createUser, subscribeToJetstream, sync } from "cmd/sync";
20
+
import _ from "lodash";
21
+
22
+
export async function publishScrobble(
23
+
track: MatchTrackResult,
24
+
timestamp?: number,
25
+
dryRun?: boolean,
26
+
) {
27
+
const [did, handle] = await getDidAndHandle();
28
+
const agent: Agent = await createAgent(did, handle);
29
+
const recentScrobble = await getRecentScrobble(did, track, timestamp);
30
+
const user = await createUser(agent, did, handle);
31
+
await subscribeToJetstream(user);
32
+
33
+
const lockFilePath = path.join(os.tmpdir(), `rocksky-${did}.lock`);
34
+
35
+
if (fs.existsSync(lockFilePath)) {
36
+
logger.error(
37
+
`${chalk.greenBright(handle)} Scrobble publishing failed: lock file exists, maybe rocksky-cli is still syncing?\nPlease wait for rocksky to finish syncing before publishing scrobbles or delete the lock file manually ${chalk.greenBright(lockFilePath)}`,
38
+
);
39
+
return false;
40
+
}
41
+
42
+
if (recentScrobble) {
43
+
logger.info`${handle} Skipping scrobble for ${track.title} by ${track.artist} at ${timestamp ? dayjs.unix(timestamp).format("YYYY-MM-DD HH:mm:ss") : dayjs().format("YYYY-MM-DD HH:mm:ss")} (already scrobbled)`;
44
+
return true;
45
+
}
46
+
47
+
const totalScrobbles = await countScrobbles(did);
48
+
if (totalScrobbles === 0) {
49
+
logger.warn`${handle} No scrobbles found for this user. Are you sure you have successfully synced your scrobbles locally?\nIf not, please run ${"rocksky sync"} to sync your scrobbles before publishing scrobbles.`;
50
+
}
51
+
52
+
logger.info`${handle} Publishing scrobble for ${track.title} by ${track.artist} at ${timestamp ? dayjs.unix(timestamp).format("YYYY-MM-DD HH:mm:ss") : dayjs().format("YYYY-MM-DD HH:mm:ss")}`;
53
+
54
+
if (await shouldSync(agent)) {
55
+
logger.info`${handle} Syncing scrobbles before publishing`;
56
+
await sync();
57
+
} else {
58
+
logger.info`${handle} Local scrobbles are up-to-date, skipping sync`;
59
+
}
60
+
61
+
if (dryRun) {
62
+
logger.info`${handle} Dry run: Skipping publishing scrobble for ${track.title} by ${track.artist} at ${timestamp ? dayjs.unix(timestamp).format("YYYY-MM-DD HH:mm:ss") : dayjs().format("YYYY-MM-DD HH:mm:ss")}`;
63
+
return true;
64
+
}
65
+
66
+
const existingTrack = await ctx.db
67
+
.select()
68
+
.from(schema.tracks)
69
+
.where(
70
+
or(
71
+
and(
72
+
sql`LOWER(${schema.tracks.title}) = LOWER(${track.title})`,
73
+
sql`LOWER(${schema.tracks.artist}) = LOWER(${track.artist})`,
74
+
),
75
+
and(
76
+
sql`LOWER(${schema.tracks.title}) = LOWER(${track.title})`,
77
+
sql`LOWER(${schema.tracks.albumArtist}) = LOWER(${track.artist})`,
78
+
),
79
+
and(
80
+
sql`LOWER(${schema.tracks.title}) = LOWER(${track.title})`,
81
+
sql`LOWER(${schema.tracks.albumArtist}) = LOWER(${track.albumArtist})`,
82
+
),
83
+
),
84
+
)
85
+
.limit(1)
86
+
.execute()
87
+
.then((rows) => rows[0]);
88
+
89
+
if (!existingTrack) {
90
+
await putSongRecord(agent, track);
91
+
}
92
+
93
+
const existingArtist = await ctx.db
94
+
.select()
95
+
.from(schema.artists)
96
+
.where(
97
+
or(
98
+
sql`LOWER(${schema.artists.name}) = LOWER(${track.artist})`,
99
+
sql`LOWER(${schema.artists.name}) = LOWER(${track.albumArtist})`,
100
+
),
101
+
)
102
+
.limit(1)
103
+
.execute()
104
+
.then((rows) => rows[0]);
105
+
106
+
if (!existingArtist) {
107
+
await putArtistRecord(agent, track);
108
+
}
109
+
110
+
const existingAlbum = await ctx.db
111
+
.select()
112
+
.from(schema.albums)
113
+
.where(
114
+
and(
115
+
sql`LOWER(${schema.albums.title}) = LOWER(${track.album})`,
116
+
sql`LOWER(${schema.albums.artist}) = LOWER(${track.albumArtist})`,
117
+
),
118
+
)
119
+
.limit(1)
120
+
.execute()
121
+
.then((rows) => rows[0]);
122
+
123
+
if (!existingAlbum) {
124
+
await putAlbumRecord(agent, track);
125
+
}
126
+
127
+
const scrobbleUri = await putScrobbleRecord(agent, track, timestamp);
128
+
129
+
// wait for the scrobble to be published
130
+
if (scrobbleUri) {
131
+
const MAX_ATTEMPTS = 40;
132
+
let attempts = 0;
133
+
do {
134
+
const count = await ctx.db
135
+
.select({
136
+
count: sql`COUNT(*)`,
137
+
})
138
+
.from(schema.scrobbles)
139
+
.where(eq(schema.scrobbles.uri, scrobbleUri))
140
+
.execute()
141
+
.then((rows) => _.get(rows, "[0].count", 0) as number);
142
+
143
+
if (count > 0 || attempts >= MAX_ATTEMPTS) {
144
+
if (attempts == MAX_ATTEMPTS) {
145
+
logger.error`Failed to detect published scrobble after ${MAX_ATTEMPTS} attempts`;
146
+
}
147
+
break;
148
+
}
149
+
150
+
await new Promise((resolve) => setTimeout(resolve, 600));
151
+
attempts += 1;
152
+
} while (true);
153
+
}
154
+
155
+
return true;
156
+
}
157
+
158
+
async function getRecentScrobble(
159
+
did: string,
160
+
track: MatchTrackResult,
161
+
timestamp?: number,
162
+
) {
163
+
const scrobbleTime = dayjs.unix(timestamp || dayjs().unix());
164
+
return ctx.db
165
+
.select({
166
+
scrobble: schema.scrobbles,
167
+
user: schema.users,
168
+
track: schema.tracks,
169
+
})
170
+
.from(schema.scrobbles)
171
+
.innerJoin(schema.users, eq(schema.scrobbles.userId, schema.users.id))
172
+
.innerJoin(schema.tracks, eq(schema.scrobbles.trackId, schema.tracks.id))
173
+
.where(
174
+
and(
175
+
eq(schema.users.did, did),
176
+
sql`LOWER(${schema.tracks.title}) = LOWER(${track.title})`,
177
+
sql`LOWER(${schema.tracks.artist}) = LOWER(${track.artist})`,
178
+
gte(
179
+
schema.scrobbles.timestamp,
180
+
scrobbleTime.subtract(60, "seconds").toDate(),
181
+
),
182
+
lte(
183
+
schema.scrobbles.timestamp,
184
+
scrobbleTime.add(60, "seconds").toDate(),
185
+
),
186
+
),
187
+
)
188
+
.limit(1)
189
+
.then((rows) => rows[0]);
190
+
}
191
+
192
+
async function countScrobbles(did: string): Promise<number> {
193
+
return ctx.db
194
+
.select({ count: sql<number>`count(*)` })
195
+
.from(schema.scrobbles)
196
+
.innerJoin(schema.users, eq(schema.scrobbles.userId, schema.users.id))
197
+
.where(eq(schema.users.did, did))
198
+
.then((rows) => rows[0].count);
199
+
}
200
+
201
+
async function putSongRecord(agent: Agent, track: MatchTrackResult) {
202
+
const rkey = TID.nextStr();
203
+
204
+
const record: Song.Record = {
205
+
$type: "app.rocksky.song",
206
+
title: track.title,
207
+
artist: track.artist,
208
+
artists: track.mbArtists === null ? undefined : track.mbArtists,
209
+
album: track.album,
210
+
albumArtist: track.albumArtist,
211
+
duration: track.duration,
212
+
releaseDate: track.releaseDate
213
+
? new Date(track.releaseDate).toISOString()
214
+
: undefined,
215
+
year: track.year === null ? undefined : track.year,
216
+
albumArtUrl: track.albumArt,
217
+
composer: track.composer ? track.composer : undefined,
218
+
lyrics: track.lyrics ? track.lyrics : undefined,
219
+
trackNumber: track.trackNumber,
220
+
discNumber: track.discNumber === 0 ? 1 : track.discNumber,
221
+
copyrightMessage: track.copyrightMessage
222
+
? track.copyrightMessage
223
+
: undefined,
224
+
createdAt: new Date().toISOString(),
225
+
spotifyLink: track.spotifyLink ? track.spotifyLink : undefined,
226
+
tags: track.genres || [],
227
+
mbid: track.mbId,
228
+
};
229
+
230
+
if (!Song.validateRecord(record).success) {
231
+
logger.info`${Song.validateRecord(record)}`;
232
+
logger.info`${record}`;
233
+
throw new Error("Invalid Song record");
234
+
}
235
+
236
+
try {
237
+
const res = await agent.com.atproto.repo.putRecord({
238
+
repo: agent.assertDid,
239
+
collection: "app.rocksky.song",
240
+
rkey,
241
+
record,
242
+
validate: false,
243
+
});
244
+
const uri = res.data.uri;
245
+
logger.info`Song record created at ${uri}`;
246
+
return uri;
247
+
} catch (e) {
248
+
logger.error`Error creating song record: ${e}`;
249
+
return null;
250
+
}
251
+
}
252
+
253
+
async function putArtistRecord(agent: Agent, track: MatchTrackResult) {
254
+
const rkey = TID.nextStr();
255
+
const record: Artist.Record = {
256
+
$type: "app.rocksky.artist",
257
+
name: track.albumArtist,
258
+
createdAt: new Date().toISOString(),
259
+
pictureUrl: track.artistPicture || undefined,
260
+
tags: track.genres || [],
261
+
};
262
+
263
+
if (!Artist.validateRecord(record).success) {
264
+
logger.info`${Artist.validateRecord(record)}`;
265
+
logger.info`${record}`;
266
+
throw new Error("Invalid Artist record");
267
+
}
268
+
269
+
try {
270
+
const res = await agent.com.atproto.repo.putRecord({
271
+
repo: agent.assertDid,
272
+
collection: "app.rocksky.artist",
273
+
rkey,
274
+
record,
275
+
validate: false,
276
+
});
277
+
const uri = res.data.uri;
278
+
logger.info`Artist record created at ${uri}`;
279
+
return uri;
280
+
} catch (e) {
281
+
logger.error`Error creating artist record: ${e}`;
282
+
return null;
283
+
}
284
+
}
285
+
286
+
async function putAlbumRecord(agent: Agent, track: MatchTrackResult) {
287
+
const rkey = TID.nextStr();
288
+
289
+
const record = {
290
+
$type: "app.rocksky.album",
291
+
title: track.album,
292
+
artist: track.albumArtist,
293
+
year: track.year === null ? undefined : track.year,
294
+
releaseDate: track.releaseDate
295
+
? new Date(track.releaseDate).toISOString()
296
+
: undefined,
297
+
createdAt: new Date().toISOString(),
298
+
albumArtUrl: track.albumArt,
299
+
};
300
+
301
+
if (!Album.validateRecord(record).success) {
302
+
logger.info`${Album.validateRecord(record)}`;
303
+
logger.info`${record}`;
304
+
throw new Error("Invalid Album record");
305
+
}
306
+
307
+
try {
308
+
const res = await agent.com.atproto.repo.putRecord({
309
+
repo: agent.assertDid,
310
+
collection: "app.rocksky.album",
311
+
rkey,
312
+
record,
313
+
validate: false,
314
+
});
315
+
const uri = res.data.uri;
316
+
logger.info`Album record created at ${uri}`;
317
+
return uri;
318
+
} catch (e) {
319
+
logger.error`Error creating album record: ${e}`;
320
+
return null;
321
+
}
322
+
}
323
+
324
+
async function putScrobbleRecord(
325
+
agent: Agent,
326
+
track: MatchTrackResult,
327
+
timestamp?: number,
328
+
) {
329
+
const rkey = TID.nextStr();
330
+
331
+
const record: Scrobble.Record = {
332
+
$type: "app.rocksky.scrobble",
333
+
title: track.title,
334
+
albumArtist: track.albumArtist,
335
+
albumArtUrl: track.albumArt,
336
+
artist: track.artist,
337
+
artists: track.mbArtists === null ? undefined : track.mbArtists,
338
+
album: track.album,
339
+
duration: track.duration,
340
+
trackNumber: track.trackNumber,
341
+
discNumber: track.discNumber === 0 ? 1 : track.discNumber,
342
+
releaseDate: track.releaseDate
343
+
? new Date(track.releaseDate).toISOString()
344
+
: undefined,
345
+
year: track.year === null ? undefined : track.year,
346
+
composer: track.composer ? track.composer : undefined,
347
+
lyrics: track.lyrics ? track.lyrics : undefined,
348
+
copyrightMessage: track.copyrightMessage
349
+
? track.copyrightMessage
350
+
: undefined,
351
+
createdAt: timestamp
352
+
? dayjs.unix(timestamp).toISOString()
353
+
: new Date().toISOString(),
354
+
spotifyLink: track.spotifyLink ? track.spotifyLink : undefined,
355
+
tags: track.genres || [],
356
+
mbid: track.mbId,
357
+
};
358
+
359
+
if (!Scrobble.validateRecord(record).success) {
360
+
logger.info`${Scrobble.validateRecord(record)}`;
361
+
logger.info`${record}`;
362
+
throw new Error("Invalid Scrobble record");
363
+
}
364
+
365
+
try {
366
+
const res = await agent.com.atproto.repo.putRecord({
367
+
repo: agent.assertDid,
368
+
collection: "app.rocksky.scrobble",
369
+
rkey,
370
+
record,
371
+
validate: false,
372
+
});
373
+
const uri = res.data.uri;
374
+
logger.info`Scrobble record created at ${uri}`;
375
+
return uri;
376
+
} catch (e) {
377
+
logger.error`Error creating scrobble record: ${e}`;
378
+
return null;
379
+
}
380
+
}
381
+
382
+
async function shouldSync(agent: Agent): Promise<boolean> {
383
+
const res = await agent.com.atproto.repo.listRecords({
384
+
repo: agent.assertDid,
385
+
collection: "app.rocksky.scrobble",
386
+
limit: 1,
387
+
});
388
+
389
+
const records = res.data.records as Array<{
390
+
uri: string;
391
+
cid: string;
392
+
value: Scrobble.Record;
393
+
}>;
394
+
395
+
if (!records.length) {
396
+
logger.info`No scrobble records found`;
397
+
return true;
398
+
}
399
+
400
+
const { count } = await ctx.db
401
+
.select({
402
+
count: sql<number>`count(*)`,
403
+
})
404
+
.from(schema.scrobbles)
405
+
.where(eq(schema.scrobbles.cid, records[0].cid))
406
+
.execute()
407
+
.then((result) => result[0]);
408
+
409
+
return count === 0;
410
+
}
+173
apps/cli/src/sqliteKv.ts
+173
apps/cli/src/sqliteKv.ts
···
1
+
import Database from "better-sqlite3";
2
+
import { Kysely, SqliteDialect } from "kysely";
3
+
import { defineDriver } from "unstorage";
4
+
5
+
interface TableSchema {
6
+
[k: string]: {
7
+
id: string;
8
+
value: string;
9
+
created_at: string;
10
+
updated_at: string;
11
+
};
12
+
}
13
+
14
+
export type KvDb = Kysely<TableSchema>;
15
+
16
+
const DRIVER_NAME = "sqlite";
17
+
18
+
export default defineDriver<
19
+
{
20
+
location?: string;
21
+
table: string;
22
+
getDb?: () => KvDb;
23
+
},
24
+
KvDb
25
+
>(
26
+
({
27
+
location,
28
+
table = "kv",
29
+
getDb = (): KvDb => {
30
+
let _db: KvDb | null = null;
31
+
32
+
return (() => {
33
+
if (_db) {
34
+
return _db;
35
+
}
36
+
37
+
if (!location) {
38
+
throw new Error("SQLite location is required");
39
+
}
40
+
41
+
const sqlite = new Database(location, { fileMustExist: false });
42
+
43
+
// Enable WAL mode
44
+
sqlite.pragma("journal_mode = WAL");
45
+
46
+
_db = new Kysely<TableSchema>({
47
+
dialect: new SqliteDialect({
48
+
database: sqlite,
49
+
}),
50
+
});
51
+
52
+
// Create table if not exists
53
+
_db.schema
54
+
.createTable(table)
55
+
.ifNotExists()
56
+
.addColumn("id", "text", (col) => col.primaryKey())
57
+
.addColumn("value", "text", (col) => col.notNull())
58
+
.addColumn("created_at", "text", (col) => col.notNull())
59
+
.addColumn("updated_at", "text", (col) => col.notNull())
60
+
.execute();
61
+
62
+
return _db;
63
+
})();
64
+
},
65
+
}) => {
66
+
return {
67
+
name: DRIVER_NAME,
68
+
options: { location, table },
69
+
getInstance: getDb,
70
+
71
+
async hasItem(key) {
72
+
const result = await getDb()
73
+
.selectFrom(table)
74
+
.select(["id"])
75
+
.where("id", "=", key)
76
+
.executeTakeFirst();
77
+
return !!result;
78
+
},
79
+
80
+
async getItem(key) {
81
+
const result = await getDb()
82
+
.selectFrom(table)
83
+
.select(["value"])
84
+
.where("id", "=", key)
85
+
.executeTakeFirst();
86
+
return result?.value ?? null;
87
+
},
88
+
89
+
async setItem(key: string, value: string) {
90
+
const now = new Date().toISOString();
91
+
await getDb()
92
+
.insertInto(table)
93
+
.values({
94
+
id: key,
95
+
value,
96
+
created_at: now,
97
+
updated_at: now,
98
+
})
99
+
.onConflict((oc) =>
100
+
oc.column("id").doUpdateSet({
101
+
value,
102
+
updated_at: now,
103
+
}),
104
+
)
105
+
.execute();
106
+
},
107
+
108
+
async setItems(items) {
109
+
const now = new Date().toISOString();
110
+
111
+
await getDb()
112
+
.transaction()
113
+
.execute(async (trx) => {
114
+
await Promise.all(
115
+
items.map(({ key, value }) => {
116
+
return trx
117
+
.insertInto(table)
118
+
.values({
119
+
id: key,
120
+
value,
121
+
created_at: now,
122
+
updated_at: now,
123
+
})
124
+
.onConflict((oc) =>
125
+
oc.column("id").doUpdateSet({
126
+
value,
127
+
updated_at: now,
128
+
}),
129
+
)
130
+
.execute();
131
+
}),
132
+
);
133
+
});
134
+
},
135
+
136
+
async removeItem(key: string) {
137
+
await getDb().deleteFrom(table).where("id", "=", key).execute();
138
+
},
139
+
140
+
async getMeta(key: string) {
141
+
const result = await getDb()
142
+
.selectFrom(table)
143
+
.select(["created_at", "updated_at"])
144
+
.where("id", "=", key)
145
+
.executeTakeFirst();
146
+
if (!result) {
147
+
return null;
148
+
}
149
+
return {
150
+
birthtime: new Date(result.created_at),
151
+
mtime: new Date(result.updated_at),
152
+
};
153
+
},
154
+
155
+
async getKeys(base = "") {
156
+
const results = await getDb()
157
+
.selectFrom(table)
158
+
.select(["id"])
159
+
.where("id", "like", `${base}%`)
160
+
.execute();
161
+
return results.map((r) => r.id);
162
+
},
163
+
164
+
async clear() {
165
+
await getDb().deleteFrom(table).execute();
166
+
},
167
+
168
+
async dispose() {
169
+
await getDb().destroy();
170
+
},
171
+
};
172
+
},
173
+
);
+26
-29
apps/cli/tsconfig.json
+26
-29
apps/cli/tsconfig.json
···
1
1
{
2
-
"compilerOptions": {
3
-
"allowJs": true,
4
-
"allowSyntheticDefaultImports": true,
5
-
"baseUrl": "src",
6
-
"declaration": true,
7
-
"sourceMap": true,
8
-
"esModuleInterop": true,
9
-
"inlineSourceMap": false,
10
-
"lib": ["esnext", "DOM"],
11
-
"listEmittedFiles": false,
12
-
"listFiles": false,
13
-
"moduleResolution": "node",
14
-
"noFallthroughCasesInSwitch": true,
15
-
"pretty": true,
16
-
"resolveJsonModule": true,
17
-
"rootDir": ".",
18
-
"skipLibCheck": true,
19
-
"strict": false,
20
-
"traceResolution": false,
21
-
"outDir": "",
22
-
"target": "esnext",
23
-
"module": "esnext",
24
-
"types": [
25
-
"@types/node",
26
-
"@types/express",
27
-
]
28
-
},
29
-
"exclude": ["node_modules", "dist", "tests"],
30
-
"include": ["src", "scripts"]
2
+
"compilerOptions": {
3
+
"allowJs": true,
4
+
"allowSyntheticDefaultImports": true,
5
+
"baseUrl": "src",
6
+
"declaration": true,
7
+
"sourceMap": true,
8
+
"esModuleInterop": true,
9
+
"inlineSourceMap": false,
10
+
"lib": ["esnext", "DOM"],
11
+
"listEmittedFiles": false,
12
+
"listFiles": false,
13
+
"moduleResolution": "node",
14
+
"noFallthroughCasesInSwitch": true,
15
+
"pretty": true,
16
+
"resolveJsonModule": true,
17
+
"rootDir": ".",
18
+
"skipLibCheck": true,
19
+
"strict": false,
20
+
"traceResolution": false,
21
+
"outDir": "",
22
+
"target": "esnext",
23
+
"module": "esnext",
24
+
"types": ["@types/node", "@types/express"],
25
+
},
26
+
"exclude": ["node_modules", "dist", "tests"],
27
+
"include": ["src", "scripts"],
31
28
}
-1
apps/web/src/hooks/useNowPlaying.tsx
-1
apps/web/src/hooks/useNowPlaying.tsx
-1
apps/web/src/pages/home/feed/Feed.tsx
-1
apps/web/src/pages/home/feed/Feed.tsx
+11
-3
apps/web/src/pages/home/nowplayings/NowPlayings.tsx
+11
-3
apps/web/src/pages/home/nowplayings/NowPlayings.tsx
···
11
11
import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
12
12
import { useNowPlayingsQuery } from "../../../hooks/useNowPlaying";
13
13
import styles, { getModalStyles } from "./styles";
14
+
import _ from "lodash";
14
15
15
16
dayjs.extend(relativeTime);
16
17
dayjs.extend(utc);
···
88
89
89
90
function NowPlayings() {
90
91
const [isOpen, setIsOpen] = useState(false);
91
-
const { data: nowPlayings, isLoading } = useNowPlayingsQuery();
92
+
const { data: rawNowPlayings, isLoading } = useNowPlayingsQuery();
93
+
94
+
// Deduplicate by trackId + did (user) + createdAt to ensure truly unique entries
95
+
const nowPlayings = _.uniqBy(
96
+
rawNowPlayings || [],
97
+
(item) => `${item.trackId}-${item.did}-${item.createdAt}`,
98
+
);
99
+
92
100
const [currentlyPlaying, setCurrentlyPlaying] = useState<{
93
101
id: string;
94
102
title: string;
···
278
286
className="flex overflow-x-auto no-scrollbar h-full"
279
287
style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
280
288
>
281
-
{(nowPlayings || []).map((item, index) => (
289
+
{nowPlayings.map((item, index) => (
282
290
<StoryContainer
283
-
key={item.id}
291
+
key={`${item.id}-${item.did}-${item.createdAt}`}
284
292
onClick={() => {
285
293
setCurrentlyPlaying(item);
286
294
setCurrentIndex(index);
+42
-2
bun.lock
+42
-2
bun.lock
···
41
41
"better-sqlite3": "^12.4.1",
42
42
"chalk": "^5.4.1",
43
43
"chanfana": "^2.0.2",
44
+
"consola": "^3.4.2",
44
45
"cors": "^2.8.5",
45
46
"dayjs": "^1.11.13",
46
47
"dotenv": "^16.4.7",
···
107
108
"rocksky": "./dist/index.js",
108
109
},
109
110
"dependencies": {
111
+
"@atproto/api": "^0.13.31",
112
+
"@atproto/common": "^0.4.6",
113
+
"@atproto/identity": "^0.4.5",
114
+
"@atproto/jwk-jose": "0.1.5",
115
+
"@atproto/lex-cli": "^0.5.6",
116
+
"@atproto/lexicon": "^0.4.5",
117
+
"@atproto/sync": "^0.1.11",
118
+
"@atproto/syntax": "^0.3.1",
119
+
"@logtape/logtape": "^1.3.6",
110
120
"@modelcontextprotocol/sdk": "^1.10.2",
121
+
"@paralleldrive/cuid2": "^3.0.6",
122
+
"@types/better-sqlite3": "^7.6.13",
111
123
"axios": "^1.8.4",
124
+
"better-sqlite3": "^12.4.1",
112
125
"chalk": "^5.4.1",
113
126
"commander": "^13.1.0",
114
127
"cors": "^2.8.5",
115
128
"dayjs": "^1.11.13",
129
+
"dotenv": "^16.4.7",
130
+
"drizzle-kit": "^0.31.1",
131
+
"drizzle-orm": "^0.45.1",
132
+
"effect": "^3.19.14",
133
+
"env-paths": "^3.0.0",
134
+
"envalid": "^8.0.0",
116
135
"express": "^5.1.0",
136
+
"kysely": "^0.27.5",
137
+
"lodash": "^4.17.21",
117
138
"md5": "^2.3.0",
118
139
"open": "^10.1.0",
119
140
"table": "^6.9.0",
141
+
"unstorage": "^1.14.4",
120
142
"zod": "^3.24.3",
121
143
},
122
144
"devDependencies": {
···
708
730
709
731
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
710
732
733
+
"@logtape/logtape": ["@logtape/logtape@1.3.6", "", {}, "sha512-OaK8eal8zcjB0GZbllXKgUC2T9h/GyNLQyQXjJkf1yum7SZKTWs9gs/t8NMS0kVVaSnA7bhU0Sjws/Iy4e0/IQ=="],
734
+
711
735
"@mapbox/geojson-rewind": ["@mapbox/geojson-rewind@0.5.2", "", { "dependencies": { "get-stream": "^6.0.1", "minimist": "^1.2.6" }, "bin": { "geojson-rewind": "geojson-rewind" } }, "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA=="],
712
736
713
737
"@mapbox/geojson-types": ["@mapbox/geojson-types@1.0.2", "", {}, "sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw=="],
···
734
758
735
759
"@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="],
736
760
737
-
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
761
+
"@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
738
762
739
763
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
740
764
···
895
919
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.37.0", "", {}, "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA=="],
896
920
897
921
"@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="],
922
+
923
+
"@paralleldrive/cuid2": ["@paralleldrive/cuid2@3.0.6", "", { "dependencies": { "@noble/hashes": "^2.0.1", "bignumber.js": "^9.3.1", "error-causes": "^3.0.2" }, "bin": { "cuid2": "bin/cuid2.js" } }, "sha512-ujtxTTvr4fwPrzuQT7o6VLKs5BzdWetR9+/zRQ0SyK9hVIwZQllEccxgcHYXN6I3Z429y1yg3F6+uiVxMDPrLQ=="],
898
924
899
925
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
900
926
···
1246
1272
1247
1273
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
1248
1274
1275
+
"@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="],
1276
+
1249
1277
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
1250
1278
1251
1279
"@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="],
···
1546
1574
1547
1575
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
1548
1576
1577
+
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
1578
+
1549
1579
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
1550
1580
1551
1581
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
···
1730
1760
1731
1761
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
1732
1762
1733
-
"effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="],
1763
+
"effect": ["effect@3.19.14", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-3vwdq0zlvQOxXzXNKRIPKTqZNMyGCdaFUBfMPqpsyzZDre67kgC1EEHDV4EoQTovJ4w5fmJW756f86kkuz7WFA=="],
1734
1764
1735
1765
"electron-to-chromium": ["electron-to-chromium@1.5.234", "", {}, "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg=="],
1736
1766
···
1742
1772
1743
1773
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
1744
1774
1775
+
"env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
1776
+
1745
1777
"envalid": ["envalid@8.1.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow=="],
1778
+
1779
+
"error-causes": ["error-causes@3.0.2", "", {}, "sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw=="],
1746
1780
1747
1781
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
1748
1782
···
2902
2936
2903
2937
"@atproto-labs/identity-resolver/@atproto/syntax": ["@atproto/syntax@0.4.0", "", {}, "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA=="],
2904
2938
2939
+
"@atproto/crypto/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
2940
+
2905
2941
"@atproto/jwk-jose/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
2906
2942
2907
2943
"@atproto/lex-cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
···
2961
2997
"@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
2962
2998
2963
2999
"@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
3000
+
3001
+
"@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
2964
3002
2965
3003
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
2966
3004
···
3045
3083
"@poppinss/colors/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
3046
3084
3047
3085
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
3086
+
3087
+
"@rocksky/cli/drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="],
3048
3088
3049
3089
"@rocksky/doc/vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="],
3050
3090
+2
-1
crates/analytics/src/handlers/artists.rs
+2
-1
crates/analytics/src/handlers/artists.rs
···
345
345
LEFT JOIN tracks t ON at.track_id = t.id
346
346
LEFT JOIN artists a ON at.artist_id = a.id
347
347
LEFT JOIN scrobbles s ON s.track_id = t.id
348
-
WHERE at.artist_id = ? OR a.uri = ?
348
+
WHERE (a.id = ? OR a.uri = ?) AND (t.artist_uri = ?)
349
349
GROUP BY
350
350
t.id, t.title, t.artist, t.album_artist, t.album, t.uri, t.album_art, t.duration, t.disc_number, t.track_number, t.artist_uri, t.album_uri, t.sha256, t.copyright_message, t.label, t.created_at
351
351
ORDER BY play_count DESC
···
355
355
356
356
let tracks = stmt.query_map(
357
357
[
358
+
¶ms.artist_id,
358
359
¶ms.artist_id,
359
360
¶ms.artist_id,
360
361
&limit.to_string(),
+15
-2
crates/jetstream/src/repo.rs
+15
-2
crates/jetstream/src/repo.rs
···
23
23
webhook_worker::{push_to_queue, AppState},
24
24
xata::{
25
25
album::Album, album_track::AlbumTrack, artist::Artist, artist_album::ArtistAlbum,
26
-
artist_track::ArtistTrack, track::Track, user::User, user_album::UserAlbum,
27
-
user_artist::UserArtist, user_track::UserTrack,
26
+
artist_track::ArtistTrack, scrobble::Scrobble, track::Track, user::User,
27
+
user_album::UserAlbum, user_artist::UserArtist, user_track::UserTrack,
28
28
},
29
29
};
30
30
···
98
98
.await?;
99
99
100
100
tx.commit().await?;
101
+
102
+
let scrobbles: Vec<Scrobble> = sqlx::query_as::<_, Scrobble>(
103
+
r#"
104
+
SELECT * FROM scrobbles WHERE user_id = $1 ORDER BY xata_createdat DESC LIMIT 5
105
+
"#,
106
+
)
107
+
.bind(&user_id)
108
+
.fetch_all(&*pool)
109
+
.await?;
110
+
111
+
let scrobble_id = scrobbles[0].xata_id.clone();
112
+
nc.publish("rocksky.scrobble.new", scrobble_id.into())
113
+
.await?;
101
114
publish_user(&nc, &pool, &user_id).await?;
102
115
103
116
let users: Vec<User> =
+5
-1
crates/jetstream/src/subscriber.rs
+5
-1
crates/jetstream/src/subscriber.rs
···
1
-
use std::{env, sync::Arc};
1
+
use std::{env, sync::Arc, time::Duration};
2
2
3
3
use anyhow::{Context, Error};
4
4
use futures_util::StreamExt;
···
36
36
37
37
let pool = PgPoolOptions::new()
38
38
.max_connections(5)
39
+
.min_connections(2)
40
+
.acquire_timeout(Duration::from_secs(12))
41
+
.max_lifetime(Some(Duration::from_secs(60 * 14)))
42
+
.test_before_acquire(true)
39
43
.connect(&db_url)
40
44
.await?;
41
45
let pool = Arc::new(Mutex::new(pool));