From 5325bbc34a764d3e08ae035ad31dbdcf685aa4bd Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 5 May 2025 20:58:55 -0400 Subject: [PATCH] Refactor authentication and utility functions for improved handling of audio formats and API responses --- index.ts | 17 +++++++++-------- src/helpers/auth.ts | 16 ++++++++-------- src/helpers/utils.ts | 17 ++++++++++------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/index.ts b/index.ts index 7fb8d46..523637e 100644 --- a/index.ts +++ b/index.ts @@ -9,7 +9,7 @@ Bun.serve({ "/api/track/:id": async req => { const trackId = parseInt(req.params.id) - /*const file = Bun.file(`downloaded/${trackId}.flac`) + const file = Bun.file(`downloaded/${trackId}.flac`) if (await file.exists()) { return new Response(await file.arrayBuffer(), { @@ -20,7 +20,7 @@ Bun.serve({ "ETag": trackId.toString(), } }) - }*/ + } const { manifestMimeType, manifest } = await utils.fetchTrack(trackId) @@ -50,9 +50,7 @@ Bun.serve({ }, development: true }) - /* - const tracks = await utils.fetchTracks(); for await (const track of tracks.items) { @@ -67,15 +65,18 @@ for await (const track of tracks.items) { } const trackData = await utils.fetchTrack(parseInt(id)) - + if (trackData?.status === 404 || trackData === null) { + console.error(`track ${trackId} not available`) + continue + } try { const audio = await utils.tagFlac(trackId, await utils.downloadFlac(trackData.manifestMimeType, trackData.manifest)) - await Bun.write(`downloaded/${trackId}.flac`, audio) - await utimes(`downloaded/${trackId}.flac`, createdAt, createdAt) + //await Bun.write(`downloaded/${trackId}.flac`, audio) + //await utimes(`downloaded/${trackId}.flac`, createdAt, createdAt) //console.log(`Downloaded ${trackId}.flac`) } catch (e) { - console.error(`Failed to download ${trackId}.flac`) + console.error(`Failed to download ${trackId}.flac`, e) } } diff --git a/src/helpers/auth.ts b/src/helpers/auth.ts index 15aafa8..a8cb7b9 100644 --- a/src/helpers/auth.ts +++ b/src/helpers/auth.ts @@ -1,27 +1,27 @@ import "./localStorage"; -import { init, initializeDeviceLogin, finalizeDeviceLogin, credentialsProvider } from "@tidal-music/auth"; +import { init, initializeLogin, finalizeLogin, credentialsProvider } from "@tidal-music/auth"; -const clientId = "zU4XHVVkc2tDPo4t"; -const clientSecret = "VJKhDFqJPqvsPVNBV6ukXTJmwlvbttP7wlMlrc72se4="; +const clientId = "mhPVJJEBNRzVjr2p"; export default async () => { await init({ clientId, - clientSecret, credentialsStorageKey: "tidal-credentials", - scopes: ["r_usr", "w_usr", "w_sub"], + scopes: ["r_usr", "w_usr"], }) const credentials = await credentialsProvider.getCredentials(); if (typeof credentials.userId !== "string" || (credentials.expires || 0) < Date.now()) { - const response = await initializeDeviceLogin(); + const response = await initializeLogin({ + redirectUri: "https://desktop.tidal.com/login/auth" + }); - console.log(`Please open https://${response.verificationUriComplete} to login.`) + console.log(`Please open ${response} to login.`) - await finalizeDeviceLogin(); + await finalizeLogin(new URL(prompt("Enter the URL you were redirected to: ") || "").search) } return { diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index fa483e2..35341a3 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -14,9 +14,9 @@ export default class { this.authHeaders = authHeaders } - async convertAacToFlac(buffer: ArrayBuffer) { + async convertAacToFlac(buffer: Buffer | ArrayBuffer) { const inputStream = new PassThrough(); - inputStream.end(Buffer.from(buffer)); + inputStream.end(Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer)); const outputChunks: Buffer[] = []; @@ -52,10 +52,12 @@ export default class { } async fetchTrack(id: number) { - const audio = await fetch(`https://api.tidal.com/v1/tracks/${id}/playbackinfopostpaywall/v4?audioquality=HI_RES_LOSSLESS&playbackmode=STREAM&assetpresentation=FULL`, { + const audio = await fetch(`https://desktop.tidal.com/v1/tracks/${id}/playbackinfo?audioquality=HI_RES_LOSSLESS&playbackmode=STREAM&assetpresentation=FULL`, { headers: this.authHeaders }) + console.log(await audio.text()) + return await audio.json() as { trackId: number, audioPresentation: string, @@ -70,6 +72,7 @@ export default class { trackPeakAmplitude: number, bitDepth: number, sampleRate: number, + status?: number, } } @@ -82,7 +85,6 @@ export default class { } async downloadFlac(manifestMimeType: string, manifest: string) { - const id = Bun.nanoseconds().toString(36); if (manifestMimeType === "application/dash+xml") { const mpd = new DashMPD(); mpd.parse(Buffer.from(manifest, "base64").toString("utf-8")) @@ -124,11 +126,11 @@ export default class { return { buffer: new ArrayBuffer(0), mimeType: "" } // TODO: Handle other mime types } - async tagFlac(id: number, audioBuffer: { buffer: ArrayBuffer, mimeType: string }) { + async tagFlac(id: number, audioBuffer: { buffer: Buffer | ArrayBuffer, mimeType: string }) { const fileId = Bun.nanoseconds().toString(36); if (audioBuffer.mimeType === "audio/mp4") { - audioBuffer.buffer = await this.convertAacToFlac(audioBuffer.buffer) as ArrayBuffer; + audioBuffer.buffer = await this.convertAacToFlac(audioBuffer.buffer); } const trackReq = await fetch(`https://api.tidal.com/v1/tracks/${id}/?countryCode=US`, { @@ -156,6 +158,7 @@ export default class { title: string, cover: string, }, + status?: string, }; const alubmReq = await fetch(`https://api.tidal.com/v1/albums/${track.album.id}/?countryCode=US`, { @@ -175,7 +178,7 @@ export default class { ], } - return FlacStreamTagger.fromBuffer(Buffer.from(audioBuffer.buffer), { + return FlacStreamTagger.fromBuffer(Buffer.from(Buffer.isBuffer(audioBuffer.buffer) ? audioBuffer.buffer : Buffer.from(audioBuffer.buffer)), { tagMap: { title: track.title, trackNumber: track.trackNumber.toString(),