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

Configure Feed

Select the types of activity you want to include in your feed.

wip: add nix flake

Signed-off-by: oppiliappan <me@oppi.li>

+238 -59
+2 -1
.gitignore
··· 1 1 node_modules/ 2 - dist/ 2 + dist/ 3 + .direnv
+2 -2
bun.lock
··· 20 20 "@types/express": "^5.0.1", 21 21 "@types/node": "^22.14.1", 22 22 "pkgroll": "^2.12.1", 23 - "tsx": "^4.19.3", 23 + "tsx": "^4.19.4", 24 24 "typescript": "^5.8.3", 25 25 }, 26 26 }, ··· 464 464 465 465 "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], 466 466 467 - "tsx": ["tsx@4.19.3", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ=="], 467 + "tsx": ["tsx@4.19.4", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q=="], 468 468 469 469 "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], 470 470
+27
flake.lock
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1746576598, 6 + "narHash": "sha256-FshoQvr6Aor5SnORVvh/ZdJ1Sa2U4ZrIMwKBX5k2wu0=", 7 + "owner": "nixos", 8 + "repo": "nixpkgs", 9 + "rev": "b3582c75c7f21ce0b429898980eddbbf05c68e55", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "nixos", 14 + "ref": "nixpkgs-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "root": { 20 + "inputs": { 21 + "nixpkgs": "nixpkgs" 22 + } 23 + } 24 + }, 25 + "root": "root", 26 + "version": 7 27 + }
+150
flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 4 + }; 5 + 6 + outputs = { 7 + self, 8 + nixpkgs, 9 + }: let 10 + supportedSystems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin"]; 11 + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 12 + nixpkgsFor = forAllSystems (system: 13 + import nixpkgs { 14 + inherit system; 15 + overlays = [ 16 + self.overlays.default 17 + ]; 18 + }); 19 + in { 20 + overlays.default = final: prev: let 21 + pname = "rocksky-cli"; 22 + version = "0.1.0"; 23 + in { 24 + node_modules = with final; 25 + stdenv.mkDerivation { 26 + pname = "rocksky-cli-node-modules"; 27 + version = "0.0.1"; 28 + impureEnvVars = 29 + lib.fetchers.proxyImpureEnvVars 30 + ++ ["GIT_PROXY_COMMAND" "SOCKS_SERVER"]; 31 + src = ./.; 32 + nativeBuildInputs = [bun]; 33 + buildInputs = [nodejs-slim_latest]; 34 + dontConfigure = true; 35 + dontFixup = true; 36 + buildPhase = '' 37 + bun install --no-progress --frozen-lockfile 38 + ''; 39 + installPhase = '' 40 + mkdir -p $out/node_modules 41 + cp -R ./node_modules/* $out/node_modules 42 + ls -la $out/node_modules 43 + ''; 44 + outputHash = "sha256-ASETvm6nqZ7hT5whk9ndLd2xeUWB1HpcvNHpUW1rT6U="; 45 + outputHashAlgo = "sha256"; 46 + outputHashMode = "recursive"; 47 + }; 48 + rocksky-cli = with final; 49 + stdenv.mkDerivation { 50 + inherit pname version; 51 + src = ./.; 52 + nativeBuildInputs = [makeBinaryWrapper]; 53 + buildInputs = [bun]; 54 + 55 + buildPhase = '' 56 + runHook preBuild 57 + runHook postBuild 58 + ''; 59 + 60 + dontFixup = true; 61 + 62 + installPhase = '' 63 + runHook preInstall 64 + 65 + mkdir -p $out/bin 66 + 67 + ln -s ${node_modules}/node_modules $out 68 + cp -R ./* $out 69 + 70 + makeBinaryWrapper ${bun}/bin/bun $out/bin/$pname \ 71 + --prefix PATH : ${lib.makeBinPath [bun]} \ 72 + --add-flags "run --prefer-offline --no-install $out/src/index.js" 73 + 74 + ''; 75 + }; 76 + }; 77 + 78 + devShell = forAllSystems (system: let 79 + pkgs = nixpkgsFor."${system}"; 80 + in 81 + pkgs.mkShell { 82 + nativeBuildInputs = [ 83 + pkgs.bun 84 + pkgs.biome 85 + pkgs.typescript 86 + pkgs.nodejs 87 + pkgs.typescript-language-server 88 + ]; 89 + }); 90 + 91 + packages = forAllSystems (system: { 92 + inherit (nixpkgsFor."${system}") rocksky-cli node_modules; 93 + }); 94 + 95 + defaultPackage = forAllSystems (system: nixpkgsFor."${system}".rocksky-cli); 96 + 97 + apps = forAllSystems (system: let 98 + pkgs = nixpkgsFor.${system}; 99 + in { 100 + default = { 101 + type = "app"; 102 + program = "${pkgs.rocksky-cli}/bin/rocksky-cli"; 103 + }; 104 + }); 105 + 106 + formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra); 107 + 108 + nixosModules.default = { 109 + config, 110 + pkgs, 111 + lib, 112 + ... 113 + }: 114 + with lib; { 115 + options = { 116 + services.rocksky-scrobble-api = { 117 + enable = mkOption { 118 + type = types.bool; 119 + default = false; 120 + description = "Enable rocksky-cli scrobble-api server"; 121 + }; 122 + 123 + environmentFile = mkOption { 124 + type = with types; nullOr path; 125 + default = null; 126 + example = "/etc/rocksky.env"; 127 + description = '' 128 + Additional environment file as defined in {manpage}`systemd.exec(5)`. 129 + ''; 130 + }; 131 + }; 132 + }; 133 + 134 + config = mkIf config.services.rocksky-scrobble-api.enable { 135 + nixpkgs.overlays = [self.overlays.default]; 136 + systemd.services.rocksky-scrobble-api = { 137 + description = "rocksky-scrobble-api service"; 138 + wantedBy = ["multi-user.target"]; 139 + 140 + serviceConfig = { 141 + ListenStream = "0.0.0.0:${toString config.services.rocksky-scrobble-api.port}"; 142 + ExecStart = "${pkgs.rocksky-cli}/bin/rocksky-cli scrobble-api"; 143 + Restart = "always"; 144 + EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; 145 + }; 146 + }; 147 + }; 148 + }; 149 + }; 150 + }
+2 -2
package.json
··· 38 38 "@types/express": "^5.0.1", 39 39 "@types/node": "^22.14.1", 40 40 "pkgroll": "^2.12.1", 41 - "tsx": "^4.19.3", 41 + "tsx": "^4.19.4", 42 42 "typescript": "^5.8.3" 43 43 }, 44 44 "exports": { ··· 46 46 "import": "./dist/index.js" 47 47 } 48 48 } 49 - } 49 + }
+1
result
··· 1 + /nix/store/y6b79cr6d4jfm9fm50gb2rbcvfk4z49f-rocksky-cli-0.1.0
+54 -54
src/cmd/scrobble.ts
··· 6 6 import path from "path"; 7 7 8 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 - } 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 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 - } 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 31 32 - const client = new RockskyClient(token); 33 - const apikeys = await client.getApiKeys(); 32 + const client = new RockskyClient(token); 33 + const apikeys = await client.getApiKeys(); 34 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 - } 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 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 - ); 44 + const signature = md5( 45 + `api_key${apikeys[0].apiKey 46 + }artist[0]${artist}methodtrack.scrobblesk${token}timestamp[0]${timestamp || Math.floor(Date.now() / 1000) 47 + }track[0]${track}${apikeys[0].sharedSecret}` 48 + ); 51 49 52 - const response = await client.scrobble( 53 - apikeys[0].apiKey, 54 - signature, 55 - track, 56 - artist, 57 - timestamp 58 - ); 50 + const response = await client.scrobble( 51 + apikeys[0].apiKey, 52 + signature, 53 + track, 54 + artist, 55 + timestamp 56 + ); 59 57 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 - ); 58 + console.log(response) 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 69 }