···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";
7+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+ }
20+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;
30+ }
31+32+ const client = new RockskyClient(token);
33+ const apikeys = await client.getApiKeys();
34+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;
42+ }
43+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+ );
69+}
+18
src/index.ts
···23import { albums } from "cmd/albums";
4import { artists } from "cmd/artists";
05import { nowplaying } from "cmd/nowplaying";
06import { scrobbles } from "cmd/scrobbles";
7import { search } from "cmd/search";
8import { stats } from "cmd/stats";
···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);
00000000000000009293program.parse(process.argv);
···23import { albums } from "cmd/albums";
4import { artists } from "cmd/artists";
5+import { createApiKey } from "cmd/create";
6import { nowplaying } from "cmd/nowplaying";
7+import { scrobble } from "cmd/scrobble";
8import { scrobbles } from "cmd/scrobbles";
9import { search } from "cmd/search";
10import { stats } from "cmd/stats";
···91 .argument("[did]", "The DID or handle of the user to get tracks for.")
92 .description("Get the user's top tracks.")
93 .action(tracks);
94+95+program
96+ .command("scrobble")
97+ .argument("<track>", "The title of the track")
98+ .argument("<artist>", "The artist of the track")
99+ .option("-t, --timestamp <timestamp>", "The timestamp of the scrobble")
100+ .description("Scrobble a track to your profile.")
101+ .action(scrobble);
102+103+program
104+ .command("create")
105+ .command("apikey")
106+ .argument("<name>", "The name of the API key")
107+ .option("-d, --description <description>", "The description of the API key")
108+ .description("Create a new API key.")
109+ .action(createApiKey);
110111program.parse(process.argv);