diff --git a/bun.lock b/bun.lock index e009a6d..d0914ef 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "dependencies": { "@tidal-music/auth": "^1.3.4", "@types/fluent-ffmpeg": "^2.1.27", + "bun-storage": "^0.2.1", "dasha": "3", "ffmpeg-static": "^5.2.0", "flac-stream-tagger": "^1.0.10", @@ -41,6 +42,8 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bun-storage": ["bun-storage@0.2.1", "", {}, "sha512-yEgiKZ38eI8v4KO7mQcsRR7suCv+ZVQmM1uETyWc0CRQgJ8vZqyY6AbQCqlLfQ41EBOHiDvHhTxy3EquZomyZg=="], + "bun-types": ["bun-types@1.2.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-tvWMx5vPqbRXgE8WUZI94iS1xAYs8bkqESR9cxBB1Wi+urvfTrF1uzuDgBHFAdO0+d2lmsbG3HmeKMvUyj6pWA=="], "caseless": ["caseless@0.12.0", "", {}, "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="], diff --git a/index.ts b/index.ts index 50daf87..7b4dcf8 100644 --- a/index.ts +++ b/index.ts @@ -2,7 +2,11 @@ import Auth from "./src/helpers/auth"; import Utils from "./src/helpers/utils"; import { utimes } from "fs/promises"; -const utils = new Utils(await Auth()); +const auth = await Auth() + +const utils = new Utils(auth); + +await utils.init() Bun.serve({ routes: { @@ -53,7 +57,7 @@ Bun.serve({ }, development: true }) - +/* const tracks = await utils.fetchTracks(); let i = 1; @@ -102,3 +106,4 @@ for await (const track of tracks.items) { } console.log("Done") +*/ \ No newline at end of file diff --git a/localStorage.sqlite b/localStorage.sqlite new file mode 100644 index 0000000..7d40255 Binary files /dev/null and b/localStorage.sqlite differ diff --git a/package.json b/package.json index aaaf0ea..c59c592 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "@tidal-music/auth": "^1.3.4", "@types/fluent-ffmpeg": "^2.1.27", + "bun-storage": "^0.2.1", "dasha": "3", "ffmpeg-static": "^5.2.0", "flac-stream-tagger": "^1.0.10", diff --git a/src/helpers/localStorage.ts b/src/helpers/localStorage.ts index a3ba487..65a7ecb 100644 --- a/src/helpers/localStorage.ts +++ b/src/helpers/localStorage.ts @@ -1,29 +1,5 @@ -const database = Bun.file('localStorage.json'); +import { createLocalStorage } from "bun-storage"; -if (!await database.exists()) { - await Bun.write(database, JSON.stringify({})); -} +const [localStorage, emitter] = createLocalStorage("./localStorage.sqlite"); -let data = {}; - -data = await database.json() || {}; - -globalThis.localStorage = { - getItem: (key) => { - return data[key] || null; - }, - setItem: (key, value) => { - data[key] = value; - Bun.write(database, JSON.stringify(data)); - }, - removeItem: (key) => { - delete data[key]; - Bun.write(database, JSON.stringify(data)); - }, - clear: () => { - data = {}; - Bun.write(database, JSON.stringify(data)); - } -}; - -export { } \ No newline at end of file +globalThis.localStorage = localStorage; \ No newline at end of file diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index bd03269..274f5ec 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -10,23 +10,55 @@ Ffmpeg.setFfmpegPath(ffmpegPath as string); export default class { private _authHeaders: { [key: string]: string } - private _userId: number; + private _userId: string | undefined; + private _userCountry: string; + private _userLocale: string; constructor(authHeaders: { [key: string]: string }) { - this._authHeaders = authHeaders - this._userId = 0; + this._authHeaders = authHeaders; + this._userCountry = "WW"; // Default to Worldwide + this._userLocale = (process.env.LC_ALL || + process.env.LANG || + process.env.LANGUAGE || + Intl.DateTimeFormat().resolvedOptions().locale || + "en_US").replaceAll("-", "_").split(".")[0]; + } + + async fetch(url: string) { + const parsedUrl = new URL(`https://desktop.tidal.com/v1${url}`); + + parsedUrl.searchParams.set("countryCode", this._userCountry); + parsedUrl.searchParams.set("locale", this._userLocale); + parsedUrl.searchParams.set("deviceType", "DESKTOP"); + + const response = await fetch(parsedUrl, { + headers: { + ...this._authHeaders, + "x-tidal-token": "mhPVJJEBNRzVjr2p", + }, + referrer: "https://desktop.tidal.com/", + }) + + if (!response.ok) { + throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`) + } + + return await response.json() as Object; } async init() { - const { token } = await credentialsProvider.getCredentials() as { token: string }; - this._userId = JSON.parse(Buffer.from(token, "base64").toString("utf-8")).uid; - } - async fetchTrack(id: number) { - const audio = await fetch(`https://desktop.tidal.com/v1/tracks/${id}/playbackinfo?audioquality=HI_RES_LOSSLESS&playbackmode=STREAM&assetpresentation=FULL`, { - headers: this._authHeaders - }) + const creds = await credentialsProvider.getCredentials() - return await audio.json() as { + this._userId = creds.userId + + const { countryCode } = await this.fetch("/country") as { countryCode: string } + this._userCountry = countryCode; + } + + async fetchTrack(id: number) { + const audio = await this.fetch(`/tracks/${id}/playbackinfo?audioquality=HI_RES_LOSSLESS&playbackmode=STREAM&assetpresentation=FULL`) + + return audio as { trackId: number, audioPresentation: string, audioMode: string, @@ -45,11 +77,9 @@ export default class { } async fetchTracks() { - const tracks = await fetch(`https://desktop.tidal.com/v1/users/${this._userId}/favorites/tracks?offset=0&limit=10000&order=DATE&orderDirection=DESC&countryCode=US&locale=en_US&deviceType=DESKTOP`, { - headers: this._authHeaders - }) + const tracks = await this.fetch(`/users/${this._userId}/favorites/tracks?offset=0&limit=10000&order=DATE&orderDirection=DESC`); - return await tracks.json(); + return tracks; } async downloadFlac(manifestMimeType: string, manifest: string) { diff --git a/tmp.ts b/tmp.ts deleted file mode 100644 index 5220a86..0000000 --- a/tmp.ts +++ /dev/null @@ -1,12 +0,0 @@ -Bun.serve({ - routes: { - "/test.mpd": async () => { - return new Response(Bun.file("tmp/fpwp4o.mpd"), { - headers: { - // cors allow all - "Access-Control-Allow-Origin": "*", - } - }) - }, - } -}) \ No newline at end of file