🎧 The official command-line interface for Rocksky — a modern, decentralized music tracking and discovery platform built on the AT Protocol.

add artists, albums and tracks commands

+185 -1
+19 -1
README.md
··· 59 59 60 60 ```bash 61 61 rocksky stats [did] 62 - ``` 62 + ``` 63 + 64 + `artists` - Lists the user's top artists. 65 + 66 + ```bash 67 + rocksky artists [did] 68 + ``` 69 + 70 + `albums` - Lists the user's top albums. 71 + 72 + ```bash 73 + rocksky albums [did] 74 + ``` 75 + 76 + `tracks` - Lists the user's top tracks. 77 + 78 + ```bash 79 + rocksky tracks [did] 80 + ```
+93
src/client.ts
··· 152 152 153 153 return response.json(); 154 154 } 155 + 156 + async getArtists(did?: string, { skip = 0, limit = 20 } = {}) { 157 + if (!did) { 158 + const didFile = path.join(os.homedir(), ".rocksky", "did"); 159 + try { 160 + await fs.promises.access(didFile); 161 + did = await fs.promises.readFile(didFile, "utf-8"); 162 + } catch (err) { 163 + const user = await this.getCurrentUser(); 164 + did = user.did; 165 + const didPath = path.join(os.homedir(), ".rocksky"); 166 + fs.promises.mkdir(didPath, { recursive: true }); 167 + await fs.promises.writeFile(didFile, did); 168 + } 169 + } 170 + 171 + const response = await fetch( 172 + `${ROCKSKY_API_URL}/users/${did}/artists?offset=${skip}&size=${limit}`, 173 + { 174 + method: "GET", 175 + headers: { 176 + Authorization: this.token ? `Bearer ${this.token}` : undefined, 177 + "Content-Type": "application/json", 178 + }, 179 + } 180 + ); 181 + if (!response.ok) { 182 + throw new Error(`Failed to fetch artists data: ${response.statusText}`); 183 + } 184 + return response.json(); 185 + } 186 + 187 + async getAlbums(did?: string, { skip = 0, limit = 20 } = {}) { 188 + if (!did) { 189 + const didFile = path.join(os.homedir(), ".rocksky", "did"); 190 + try { 191 + await fs.promises.access(didFile); 192 + did = await fs.promises.readFile(didFile, "utf-8"); 193 + } catch (err) { 194 + const user = await this.getCurrentUser(); 195 + did = user.did; 196 + const didPath = path.join(os.homedir(), ".rocksky"); 197 + fs.promises.mkdir(didPath, { recursive: true }); 198 + await fs.promises.writeFile(didFile, did); 199 + } 200 + } 201 + 202 + const response = await fetch( 203 + `${ROCKSKY_API_URL}/users/${did}/albums?offset=${skip}&size=${limit}`, 204 + { 205 + method: "GET", 206 + headers: { 207 + Authorization: this.token ? `Bearer ${this.token}` : undefined, 208 + "Content-Type": "application/json", 209 + }, 210 + } 211 + ); 212 + if (!response.ok) { 213 + throw new Error(`Failed to fetch albums data: ${response.statusText}`); 214 + } 215 + return response.json(); 216 + } 217 + 218 + async getTracks(did?: string, { skip = 0, limit = 20 } = {}) { 219 + if (!did) { 220 + const didFile = path.join(os.homedir(), ".rocksky", "did"); 221 + try { 222 + await fs.promises.access(didFile); 223 + did = await fs.promises.readFile(didFile, "utf-8"); 224 + } catch (err) { 225 + const user = await this.getCurrentUser(); 226 + did = user.did; 227 + const didPath = path.join(os.homedir(), ".rocksky"); 228 + fs.promises.mkdir(didPath, { recursive: true }); 229 + await fs.promises.writeFile(didFile, did); 230 + } 231 + } 232 + 233 + const response = await fetch( 234 + `${ROCKSKY_API_URL}/users/${did}/tracks?offset=${skip}&size=${limit}`, 235 + { 236 + method: "GET", 237 + headers: { 238 + Authorization: this.token ? `Bearer ${this.token}` : undefined, 239 + "Content-Type": "application/json", 240 + }, 241 + } 242 + ); 243 + if (!response.ok) { 244 + throw new Error(`Failed to fetch tracks data: ${response.statusText}`); 245 + } 246 + return response.json(); 247 + } 155 248 }
+16
src/cmd/albums.ts
··· 1 + import chalk from "chalk"; 2 + import { RockskyClient } from "client"; 3 + 4 + export async function albums(did, { skip, limit }) { 5 + const client = new RockskyClient(); 6 + const albums = await client.getAlbums(did, { skip, limit }); 7 + let rank = 1; 8 + for (const album of albums) { 9 + console.log( 10 + `${rank} ${chalk.magenta(album.title)} ${album.artist} ${chalk.yellow( 11 + album.play_count + " plays" 12 + )}` 13 + ); 14 + rank++; 15 + } 16 + }
+16
src/cmd/artists.ts
··· 1 + import chalk from "chalk"; 2 + import { RockskyClient } from "client"; 3 + 4 + export async function artists(did, { skip, limit }) { 5 + const client = new RockskyClient(); 6 + const artists = await client.getArtists(did, { skip, limit }); 7 + let rank = 1; 8 + for (const artist of artists) { 9 + console.log( 10 + `${rank} ${chalk.magenta(artist.name)} ${chalk.yellow( 11 + artist.play_count + " plays" 12 + )}` 13 + ); 14 + rank++; 15 + } 16 + }
+16
src/cmd/tracks.ts
··· 1 + import chalk from "chalk"; 2 + import { RockskyClient } from "client"; 3 + 4 + export async function tracks(did, { skip, limit }) { 5 + const client = new RockskyClient(); 6 + const tracks = await client.getTracks(did, { skip, limit }); 7 + let rank = 1; 8 + for (const track of tracks) { 9 + console.log( 10 + `${rank} ${chalk.magenta(track.title)} ${track.artist} ${chalk.yellow( 11 + track.play_count + " plays" 12 + )}` 13 + ); 14 + rank++; 15 + } 16 + }
+25
src/index.ts
··· 1 1 #!/usr/bin/env node 2 2 3 + import { albums } from "cmd/albums"; 4 + import { artists } from "cmd/artists"; 3 5 import { nowplaying } from "cmd/nowplaying"; 4 6 import { scrobbles } from "cmd/scrobbles"; 5 7 import { search } from "cmd/search"; 6 8 import { stats } from "cmd/stats"; 9 + import { tracks } from "cmd/tracks"; 7 10 import { whoami } from "cmd/whoami"; 8 11 import { Command } from "commander"; 9 12 import version from "../package.json" assert { type: "json" }; ··· 61 64 62 65 program 63 66 .command("stats") 67 + .option("-l, --limit <number>", "Number of results to limit") 64 68 .argument("[did]", "The DID or handle of the user to get stats for.") 65 69 .description("Get the user's listening stats.") 66 70 .action(stats); 71 + 72 + program 73 + .command("artists") 74 + .option("-l, --limit <number>", "Number of results to limit") 75 + .argument("[did]", "The DID or handle of the user to get artists for.") 76 + .description("Get the user's top artists.") 77 + .action(artists); 78 + 79 + program 80 + .command("albums") 81 + .option("-l, --limit <number>", "Number of results to limit") 82 + .argument("[did]", "The DID or handle of the user to get albums for.") 83 + .description("Get the user's top albums.") 84 + .action(albums); 85 + 86 + program 87 + .command("tracks") 88 + .option("-l, --limit <number>", "Number of results to limit") 89 + .argument("[did]", "The DID or handle of the user to get tracks for.") 90 + .description("Get the user's top tracks.") 91 + .action(tracks); 67 92 68 93 program.parse(process.argv);