Compare commits
	
		
			No commits in common. "7bad8d2efad6e136b011480df4e0ffdb30b9402f" and "0625b72a687f7a6e9351ae55aa8b96a66f1bb5e5" have entirely different histories.
		
	
	
		
			7bad8d2efa
			...
			0625b72a68
		
	
		
@ -1,8 +0,0 @@
 | 
			
		||||
ACOUSTID_API_KEY=YOUR ACOUSTID API KEY
 | 
			
		||||
AUTHKEY_PATH=PATH TO YOUR APPLE MUSIC AUTHKEY
 | 
			
		||||
KEY_ID=YOUR APPLE MUSIC KEY ID
 | 
			
		||||
TEAM_ID=YOUR APPLE MUSIC TEAM ID
 | 
			
		||||
CLIENT_ID=YOUR APPLE MUSIC CLIENT ID
 | 
			
		||||
 | 
			
		||||
SPOTIFY_CLIENT=YOUR SPOTIFY CLIENT ID
 | 
			
		||||
SPOTIFY_SECRET=YOUR SPOTIFY CLIENT SECRET
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -135,7 +135,3 @@ dist
 | 
			
		||||
.yarn/install-state.gz
 | 
			
		||||
.pnp.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.direnv
 | 
			
		||||
 | 
			
		||||
cache/
 | 
			
		||||
@ -1,27 +0,0 @@
 | 
			
		||||
import { importPKCS8, SignJWT, GeneralSign } from "jose";
 | 
			
		||||
 | 
			
		||||
const TEAM_ID = process.env.TEAM_ID!;
 | 
			
		||||
const KEY_ID = process.env.KEY_ID!;
 | 
			
		||||
const privateKey = await Bun.file(process.env.AUTHKEY_PATH!).text();
 | 
			
		||||
const realKey = await importPKCS8(privateKey, "ES256");
 | 
			
		||||
 | 
			
		||||
let currentTime = Date.now();
 | 
			
		||||
const jwt = new SignJWT()
 | 
			
		||||
    .setIssuer(TEAM_ID)
 | 
			
		||||
    .setIssuedAt(Math.floor(currentTime / 1000))
 | 
			
		||||
    .setExpirationTime(Math.floor(currentTime / 1000) + 60 * 60 * 72)
 | 
			
		||||
 | 
			
		||||
jwt.setProtectedHeader({
 | 
			
		||||
    alg: 'ES256',
 | 
			
		||||
    kid: KEY_ID
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const apmToken = await jwt.sign(realKey);
 | 
			
		||||
 | 
			
		||||
export async function appleMusicFetch(url: string): Promise<any> {
 | 
			
		||||
    return fetch("https://api.music.apple.com/v1" + url, {
 | 
			
		||||
        headers: {
 | 
			
		||||
            "Authorization": `Bearer ${apmToken}`
 | 
			
		||||
        }
 | 
			
		||||
    }).then(res => res.json());
 | 
			
		||||
}
 | 
			
		||||
@ -1,20 +0,0 @@
 | 
			
		||||
const s = new URLSearchParams();
 | 
			
		||||
s.append("grant_type", "client_credentials");
 | 
			
		||||
const spotify_response = await fetch("https://accounts.spotify.com/api/token", {
 | 
			
		||||
    headers: {
 | 
			
		||||
        "Authorization": `Basic ${Buffer.from(process.env.SPOTIFY_CLIENT + ":" + process.env.SPOTIFY_SECRET).toString('base64')}`,
 | 
			
		||||
        "Content-Type": "application/x-www-form-urlencoded"
 | 
			
		||||
    },
 | 
			
		||||
    body: s.toString(),
 | 
			
		||||
    method: "POST"
 | 
			
		||||
}).then(res => res.json());
 | 
			
		||||
 | 
			
		||||
const sptToken = spotify_response.access_token;
 | 
			
		||||
 | 
			
		||||
export async function spotifyFetch(url: string): Promise<any> {
 | 
			
		||||
    return fetch("https://api.spotify.com/v1" + url, {
 | 
			
		||||
        headers: {
 | 
			
		||||
            "Authorization": `Bearer ${sptToken}`
 | 
			
		||||
        }
 | 
			
		||||
    }).then(res => res.json());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								bun.lock
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								bun.lock
									
									
									
									
									
								
							@ -1,65 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "workspaces": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "name": "hellhound",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "ejs": "^3.1.10",
 | 
			
		||||
        "jose": "^5.9.6",
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@types/bun": "latest",
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "typescript": "^5.0.0",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="],
 | 
			
		||||
 | 
			
		||||
    "@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
 | 
			
		||||
 | 
			
		||||
    "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
 | 
			
		||||
 | 
			
		||||
    "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
 | 
			
		||||
 | 
			
		||||
    "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
 | 
			
		||||
 | 
			
		||||
    "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
 | 
			
		||||
 | 
			
		||||
    "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
 | 
			
		||||
 | 
			
		||||
    "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="],
 | 
			
		||||
 | 
			
		||||
    "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
 | 
			
		||||
 | 
			
		||||
    "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
 | 
			
		||||
 | 
			
		||||
    "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
 | 
			
		||||
 | 
			
		||||
    "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
 | 
			
		||||
 | 
			
		||||
    "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
 | 
			
		||||
 | 
			
		||||
    "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
 | 
			
		||||
 | 
			
		||||
    "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
 | 
			
		||||
 | 
			
		||||
    "jake": ["jake@10.9.2", "", { "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", "filelist": "^1.0.4", "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" } }, "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA=="],
 | 
			
		||||
 | 
			
		||||
    "jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
 | 
			
		||||
 | 
			
		||||
    "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
 | 
			
		||||
 | 
			
		||||
    "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
 | 
			
		||||
 | 
			
		||||
    "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
 | 
			
		||||
 | 
			
		||||
    "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
 | 
			
		||||
 | 
			
		||||
    "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
 | 
			
		||||
 | 
			
		||||
    "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										61
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							@ -1,61 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "nodes": {
 | 
			
		||||
    "nixpkgs": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1739019272,
 | 
			
		||||
        "narHash": "sha256-7Fu7oazPoYCbDzb9k8D/DdbKrC3aU1zlnc39Y8jy/s8=",
 | 
			
		||||
        "owner": "NixOS",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "rev": "fa35a3c8e17a3de613240fea68f876e5b4896aec",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "NixOS",
 | 
			
		||||
        "ref": "nixpkgs-unstable",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "root": {
 | 
			
		||||
      "inputs": {
 | 
			
		||||
        "nixpkgs": "nixpkgs",
 | 
			
		||||
        "utils": "utils"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "systems": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1681028828,
 | 
			
		||||
        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
 | 
			
		||||
        "owner": "nix-systems",
 | 
			
		||||
        "repo": "default",
 | 
			
		||||
        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "nix-systems",
 | 
			
		||||
        "repo": "default",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "utils": {
 | 
			
		||||
      "inputs": {
 | 
			
		||||
        "systems": "systems"
 | 
			
		||||
      },
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1731533236,
 | 
			
		||||
        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
 | 
			
		||||
        "owner": "numtide",
 | 
			
		||||
        "repo": "flake-utils",
 | 
			
		||||
        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "numtide",
 | 
			
		||||
        "repo": "flake-utils",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "root": "root",
 | 
			
		||||
  "version": 7
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								flake.nix
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								flake.nix
									
									
									
									
									
								
							@ -1,19 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  inputs = {
 | 
			
		||||
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
 | 
			
		||||
    utils.url = "github:numtide/flake-utils";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  outputs = { self, nixpkgs, utils }:
 | 
			
		||||
      utils.lib.eachDefaultSystem (system:
 | 
			
		||||
        let 
 | 
			
		||||
          pkgs = import nixpkgs { inherit system; config.allowUnfree = true; };
 | 
			
		||||
          ffmpreg = pkgs.ffmpeg;
 | 
			
		||||
        in {
 | 
			
		||||
          devShell = with pkgs; mkShell {
 | 
			
		||||
            buildInputs = with pkgs; [ sqlite bun bash yt-dlp mkvtoolnix ffmpreg chromaprint opustags ];
 | 
			
		||||
            LD_LIBRARY_PATH = lib.makeLibraryPath [ stdenv.cc.cc.lib ];
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								package.json
									
									
									
									
									
								
							@ -1,15 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "hellhound",
 | 
			
		||||
  "module": "index.ts",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/bun": "latest"
 | 
			
		||||
  },
 | 
			
		||||
  "peerDependencies": {
 | 
			
		||||
    "typescript": "^5.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "ejs": "^3.1.10",
 | 
			
		||||
    "jose": "^5.9.6"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,47 +0,0 @@
 | 
			
		||||
import { appleMusicFetch } from "../3p/appleMusicClient";
 | 
			
		||||
import { spotifyFetch } from "../3p/spotifyClient";
 | 
			
		||||
 | 
			
		||||
const APM_ID = "1552965331";
 | 
			
		||||
const SPT_ID = "0kDiUhIznBbi6Y3slWiOBS";
 | 
			
		||||
 | 
			
		||||
const appleResult = await appleMusicFetch("/catalog/us/albums/" + APM_ID);
 | 
			
		||||
const spotiResult = await spotifyFetch("/albums/" + SPT_ID + "?market=US");
 | 
			
		||||
 | 
			
		||||
const appleAlbum = appleResult.data[0];
 | 
			
		||||
const appleUpc = appleAlbum.attributes.upc;
 | 
			
		||||
 | 
			
		||||
const spotifyUpc = spotiResult.external_ids.upc;
 | 
			
		||||
 | 
			
		||||
if (appleUpc !== spotifyUpc) {
 | 
			
		||||
    throw "UPC mismatch :/";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const appleISRCs = appleAlbum.relationships.tracks.data.map(a => a.attributes.isrc);
 | 
			
		||||
const spotifyISRCs = spotiResult.tracks.items.map(a => a.external_ids ? a.external_ids.isrc : null);
 | 
			
		||||
 | 
			
		||||
if (appleISRCs.length !== spotifyISRCs.length) {
 | 
			
		||||
    throw "ISRC or something mismatch :/";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const a = new URLSearchParams();
 | 
			
		||||
 | 
			
		||||
for (let i = 0; i < appleISRCs.length; i++) {
 | 
			
		||||
    const cands = new Set([appleISRCs[i], spotifyISRCs[i]].filter(a => !!a));
 | 
			
		||||
    if (cands.size !== 1) throw "skill issue";
 | 
			
		||||
    const c = Array.from(cands);
 | 
			
		||||
 | 
			
		||||
    a.append(`isrc${i + 1}`, c[0]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.append("edit-note", `Data obtained via authenticated APIs from:
 | 
			
		||||
- Apple Music: https://api.music.apple.com/v1/catalog/us/albums/${APM_ID}
 | 
			
		||||
- Spotify: https://api.spotify.com/v1/albums/${SPT_ID}?market=US`);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
console.log("UPC: " + appleUpc);
 | 
			
		||||
console.log(`https://magicisrc.kepstin.ca/?${a.toString()}`);
 | 
			
		||||
 | 
			
		||||
console.log("\n\n");
 | 
			
		||||
console.log(`Data obtained via authenticated APIs from:
 | 
			
		||||
- Apple Music: https://api.music.apple.com/v1/catalog/us/albums/${APM_ID}
 | 
			
		||||
- Spotify: https://api.spotify.com/v1/albums/${SPT_ID}?market=US`)
 | 
			
		||||
@ -1,27 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    // Enable latest features
 | 
			
		||||
    "lib": ["ESNext", "DOM"],
 | 
			
		||||
    "target": "ESNext",
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "moduleDetection": "force",
 | 
			
		||||
    "jsx": "react-jsx",
 | 
			
		||||
    "allowJs": true,
 | 
			
		||||
 | 
			
		||||
    // Bundler mode
 | 
			
		||||
    "moduleResolution": "bundler",
 | 
			
		||||
    "allowImportingTsExtensions": true,
 | 
			
		||||
    "verbatimModuleSyntax": true,
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
 | 
			
		||||
    // Best practices
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "noFallthroughCasesInSwitch": true,
 | 
			
		||||
 | 
			
		||||
    // Some stricter flags (disabled by default)
 | 
			
		||||
    "noUnusedLocals": false,
 | 
			
		||||
    "noUnusedParameters": false,
 | 
			
		||||
    "noPropertyAccessFromIndexSignature": false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user