A decentralized music tracking and discovery platform built on AT Protocol 🎵
listenbrainz spotify atproto lastfm musicbrainz scrobbling

Compare changes

Choose any two refs to compare.

Changed files
+18457 -434
apps
api
lexicons
pkl
defs
scripts
src
bsky
dropbox
googledrive
lexicon
types
app
rocksky
lib
lovedtracks
nowplaying
scripts
shouts
spotify
subscribers
tealfm
tracks
websocket
xrpc
cli
drizzle
src
cmd
lexicon
types
app
bsky
actor
rocksky
actor
album
apikey
apikeys
artist
charts
dropbox
feed
googledrive
graph
like
player
playlist
radio
scrobble
shout
song
spotify
stats
com
atproto
lib
schema
web
src
hooks
pages
home
feed
nowplayings
crates
analytics
src
handlers
jetstream
+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
··· 58 58 "better-sqlite3": "^12.4.1", 59 59 "chalk": "^5.4.1", 60 60 "chanfana": "^2.0.2", 61 + "consola": "^3.4.2", 61 62 "cors": "^2.8.5", 62 63 "dayjs": "^1.11.13", 63 64 "dotenv": "^16.4.7",
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 1 + { 2 + "version": "7", 3 + "dialect": "sqlite", 4 + "entries": [ 5 + { 6 + "idx": 0, 7 + "version": "6", 8 + "when": 1768065262210, 9 + "tag": "0000_parallel_paper_doll", 10 + "breakpoints": true 11 + } 12 + ] 13 + }
+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 + ../api/lexicons
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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'
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 25 25 { params: { size: 47 } }, 26 26 ), 27 27 select: (res) => res.data.nowPlayings || [], 28 - structuralSharing: false, 29 28 });
-1
apps/web/src/pages/home/feed/Feed.tsx
··· 74 74 } 75 75 76 76 const message = JSON.parse(event.data); 77 - queryClient.setQueryData(["now-playings"], [...message.nowPlayings]); 78 77 queryClient.setQueryData(["scrobblesChart"], message.scrobblesChart); 79 78 80 79 await queryClient.invalidateQueries({
+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
··· 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
··· 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 + &params.artist_id, 358 359 &params.artist_id, 359 360 &params.artist_id, 360 361 &limit.to_string(),
+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
··· 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));