From 387785df7a60c60d10fa53003cf806c4e0ec59ca Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 10 May 2025 23:33:16 -0400 Subject: [PATCH] Add bun-storage dependency and refactor localStorage implementation --- bun.lock | 3 ++ index.ts | 9 ++++-- localStorage.sqlite | Bin 0 -> 12288 bytes package.json | 1 + src/helpers/localStorage.ts | 30 ++---------------- src/helpers/utils.ts | 60 +++++++++++++++++++++++++++--------- tmp.ts | 12 -------- 7 files changed, 59 insertions(+), 56 deletions(-) create mode 100644 localStorage.sqlite delete mode 100644 tmp.ts 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 0000000000000000000000000000000000000000..7d40255bbffee2a9a5b68592e7bdf2f9ce33d382 GIT binary patch literal 12288 zcmeI1X;V{a9>s$M8W4=A2qJ0(ZJ{aJ{g!usTHRi=s*Zk?4ereB}dFM@4D%?ExScHP%|HHE z2c=yhpb$_9Cy)5Kssx1QY__3W1-jY+aq5t#S>Sy26|SQ-Nt;)V=74 z2hrLG5s~rH+5(f-ry!?XYse`xXpQ>(Vq=b<)|6Rj%=z;xU$1?(u9uvxRQdYsoU+p5 zLb<=s$TS*?{yfeoFl7W4nD*}L?e=&&X)dbNwtrY@aNysXT-!gs6~>BAg@8gpA)pZW zmI<`I^iW5SMnxD6c{d^h4Efoah3B(MaqYrv5KqLBGv5221b(exP6Et_{{fMMiZ9M zV1tD-YPRj?nucb-h#=I#@*Q_Dd;Q|W3l92BE@Om4HmE7(((x>Q%w>lP!`QiVgpGlP z=;Zoo$D*^`;BLT8jB)}qdc29{0Edg&9w@U+u`4~=C>R#QsgfGcf*<8m@eOSn^2(ZuRM{c73WwC>Z?sdL?#u^*m zuM(ZZt!c(E=lKN-$v?2A80+Z7mj888xpIbWXE>mLgc-I~KD&)RgF_77pe63LXP_N+ z`3|j5tymY&L2j{HdepL)yBw&%J2iUQ7#bKUlQti%h?Frmxy`wsQ@Mt9eF8e zWGmjW)A4i)XHzl8*-Kn8hCIJw7sLiG-NGDuP=l^AjJvV=3g=&Vp~X|y+=hvZ=P=LT z_ZGxv4~(MgtQQA$74B@q5Vl`DPqyQ>M>2YSq|!}+SEbVPaSzxNE@s%~0D3aegATO2 z%TXuRxheFj(Prg}HzA>zWxvkb9+R94kTt&N>{TnYoZ@si`?#u;5u1&u_QYxsCtv>- z%NcK1uPf5D6895EWcnT>m!FIH>=Q>q*p(tg`Z-&Ycb?t&a+Vf%49ILN=ky7|sg7(w zjXRfpgV1^u?Hm=)u5%)veW*>yG+^K?){3M!t$8P21xNwvgU^d=Y6}C6BBbe@16O6f z!K9N9Sfx`ZM4($#^s?KZGfwA@$3@aYb7ne*Qm{jd?^U$Q1m-&je8kXQl<1^m#;XK1 zSEMq1;o-p?=Hh+=tr*$cF=$6G#An|LU)N9i5zg`2qS>LzFU{QlR4_o?JTx% zh_zfvcr2keQN&1^(lrIdfX5}VH+ zNIBoW4i|ZKZf9XhuPCw8V_a}d+Qe{y8wcD3Z0(I#G>7tIf<$T9D zc*IGc@7=B!u=B)Cwz109FNQT-JQ#_k-z3T_H_*h1N+X-mAQi8tIT_s)f1yV$4s2)t zX=(b%MGo8epK^(T^ zu4va|FUO*-l_Q=hfg=BnM6r$ArT1;UIO>ho3Q5%T6TCm-C^i|0^OkM0l}(R5(ZRP^ zO_U^bd>{Op^L~6MsUJv>L{%CWEBGN7b%or@wUSuwV#z*3^3sqklD4?=nA^@}qID}? zYCY%o5H&gs7gx~idk8CHrXDnVW7k>Fb$)um#egJ3P5^h*2UEprpIl_M^9LUK^Ybyy zE$?4h|0~}+7ukE3KIatMqa}J97&oQ*Jk3c-;96pt?%_=|Jn~D4k_>LM!^;|DNf|x@n7P8;A5s7F zNpa~W=To?ZYVVUW^m_Oa9artMB~10=-DSJSWt`QEPgjm%3tQet15*Qwxa4sceYqTP z!ypFJ_Mc9@t}Szs@~2?9NeE?~A|b&sxdk zma&9O0e8#MqB@2-z4)r@S{gQj1j*8KY{;pwgx$NI&B?=8vCRfkc)Z^aocAd^ZJQ_- z{>+aUOO&fd^|f4la+OQqjyp!mB_0i7oQOG8dXev+1ftqH7=wO?QLR(wi>_T!XOT|J z!BU61OE%WgKYi`me?PO(fUd(5?pYhun>c)qqvbHCVeXeC`N?#l{A(TKa>{+q==NhO zI1IH>zAqnh&Xn-0tvagO>TkMmy@b;oL#sGy%I&i0>_c6aq;pM5HDQW { - 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