···11+import chalk from "chalk";
22+import { RockskyClient } from "client";
33+import fs from "fs/promises";
44+import os from "os";
55+import path from "path";
66+77+export async function createApiKey(name, { description }) {
88+ const tokenPath = path.join(os.homedir(), ".rocksky", "token.json");
99+ try {
1010+ await fs.access(tokenPath);
1111+ } catch (err) {
1212+ console.error(
1313+ `You are not logged in. Please run ${chalk.greenBright(
1414+ "`rocksky login <username>.bsky.social`"
1515+ )} first.`
1616+ );
1717+ return;
1818+ }
1919+2020+ const tokenData = await fs.readFile(tokenPath, "utf-8");
2121+ const { token } = JSON.parse(tokenData);
2222+ if (!token) {
2323+ console.error(
2424+ `You are not logged in. Please run ${chalk.greenBright(
2525+ "`rocksky login <username>.bsky.social`"
2626+ )} first.`
2727+ );
2828+ return;
2929+ }
3030+3131+ const client = new RockskyClient(token);
3232+ const apikey = await client.createApiKey(name, description);
3333+ if (!apikey) {
3434+ console.error(`Failed to create API key. Please try again later.`);
3535+ return;
3636+ }
3737+3838+ console.log(`API key created successfully!`);
3939+ console.log(`Name: ${chalk.greenBright(apikey.name)}`);
4040+ if (apikey.description) {
4141+ console.log(`Description: ${chalk.greenBright(apikey.description)}`);
4242+ }
4343+ console.log(`Key: ${chalk.greenBright(apikey.api_key)}`);
4444+ console.log(`Secret: ${chalk.greenBright(apikey.shared_secret)}`);
4545+}
+69
src/cmd/scrobble.ts
···11+import chalk from "chalk";
22+import { RockskyClient } from "client";
33+import fs from "fs/promises";
44+import md5 from "md5";
55+import os from "os";
66+import path from "path";
77+88+export async function scrobble(track, artist, { timestamp }) {
99+ const tokenPath = path.join(os.homedir(), ".rocksky", "token.json");
1010+ try {
1111+ await fs.access(tokenPath);
1212+ } catch (err) {
1313+ console.error(
1414+ `You are not logged in. Please run ${chalk.greenBright(
1515+ "`rocksky login <username>.bsky.social`"
1616+ )} first.`
1717+ );
1818+ return;
1919+ }
2020+2121+ const tokenData = await fs.readFile(tokenPath, "utf-8");
2222+ const { token } = JSON.parse(tokenData);
2323+ if (!token) {
2424+ console.error(
2525+ `You are not logged in. Please run ${chalk.greenBright(
2626+ "`rocksky login <username>.bsky.social`"
2727+ )} first.`
2828+ );
2929+ return;
3030+ }
3131+3232+ const client = new RockskyClient(token);
3333+ const apikeys = await client.getApiKeys();
3434+3535+ if (!apikeys || apikeys.length === 0 || !apikeys[0].enabled) {
3636+ console.error(
3737+ `You don't have any API keys. Please create one using ${chalk.greenBright(
3838+ "`rocksky create apikey`"
3939+ )} command.`
4040+ );
4141+ return;
4242+ }
4343+4444+ const signature = md5(
4545+ `api_key${
4646+ apikeys[0].apiKey
4747+ }artist[0]${artist}methodtrack.scrobblesk${token}timestamp[0]${
4848+ timestamp || Math.floor(Date.now() / 1000)
4949+ }track[0]${track}${apikeys[0].sharedSecret}`
5050+ );
5151+5252+ const response = await client.scrobble(
5353+ apikeys[0].apiKey,
5454+ signature,
5555+ track,
5656+ artist,
5757+ timestamp
5858+ );
5959+6060+ console.log(
6161+ `Scrobbled ${chalk.greenBright(track)} by ${chalk.greenBright(
6262+ artist
6363+ )} at ${chalk.greenBright(
6464+ new Date(
6565+ (timestamp || Math.floor(Date.now() / 1000)) * 1000
6666+ ).toLocaleString()
6767+ )}`
6868+ );
6969+}
+18
src/index.ts
···2233import { albums } from "cmd/albums";
44import { artists } from "cmd/artists";
55+import { createApiKey } from "cmd/create";
56import { nowplaying } from "cmd/nowplaying";
77+import { scrobble } from "cmd/scrobble";
68import { scrobbles } from "cmd/scrobbles";
79import { search } from "cmd/search";
810import { stats } from "cmd/stats";
···8991 .argument("[did]", "The DID or handle of the user to get tracks for.")
9092 .description("Get the user's top tracks.")
9193 .action(tracks);
9494+9595+program
9696+ .command("scrobble")
9797+ .argument("<track>", "The title of the track")
9898+ .argument("<artist>", "The artist of the track")
9999+ .option("-t, --timestamp <timestamp>", "The timestamp of the scrobble")
100100+ .description("Scrobble a track to your profile.")
101101+ .action(scrobble);
102102+103103+program
104104+ .command("create")
105105+ .command("apikey")
106106+ .argument("<name>", "The name of the API key")
107107+ .option("-d, --description <description>", "The description of the API key")
108108+ .description("Create a new API key.")
109109+ .action(createApiKey);
9211093111program.parse(process.argv);