diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 17a1d5b..d3cf384 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -16,14 +16,16 @@ "store:default", "dialog:default", "fs:default", + "fs:allow-write-file", + "fs:allow-mkdir", + "fs:allow-remove", + "fs:allow-rename", + "fs:allow-exists", { "identifier": "fs:scope", "allow": [ { - "path": "$HOME/**/*" - }, - { - "path": "$APPDATA/**/*" + "path": "**" } ] }, diff --git a/src/lib/services/deezer.ts b/src/lib/services/deezer.ts index 35c7cde..3728e74 100644 --- a/src/lib/services/deezer.ts +++ b/src/lib/services/deezer.ts @@ -273,12 +273,17 @@ export class DeezerAPI { // Get track download URL async getTrackDownloadUrl(trackToken: string, format: string, licenseToken: string): Promise { + console.log('[DEBUG] Getting track download URL...', { trackToken, format, licenseToken }); + try { + const cookieHeader = this.getCookieHeader(); + console.log('[DEBUG] Sending request to media.deezer.com with cookies:', cookieHeader); + const response = await fetch('https://media.deezer.com/v1/get_url', { method: 'POST', headers: { ...this.httpHeaders, - 'Cookie': this.arl ? `arl=${this.arl}` : '' + 'Cookie': cookieHeader }, body: JSON.stringify({ license_token: licenseToken, @@ -287,22 +292,33 @@ export class DeezerAPI { formats: [{ cipher: 'BF_CBC_STRIPE', format }] }], track_tokens: [trackToken] - }) + }), + connectTimeout: 30000 }); + console.log('[DEBUG] Download URL response status:', response.status); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + console.log('[DEBUG] Download URL response:', result); if (result.data && result.data.length > 0) { const trackData = result.data[0]; if (trackData.media && trackData.media.length > 0) { - return trackData.media[0].sources[0].url; + const url = trackData.media[0].sources[0].url; + console.log('[DEBUG] Got download URL:', url); + return url; } } + console.error('[ERROR] No download URL in response:', result); return null; } catch (error) { - console.error('Error getting track URL:', error); - return null; + console.error('[ERROR] Failed to get track download URL:', error); + throw error; // Re-throw to let caller handle it } } } diff --git a/src/lib/services/deezer/crypto.ts b/src/lib/services/deezer/crypto.ts index af73957..a58e126 100644 --- a/src/lib/services/deezer/crypto.ts +++ b/src/lib/services/deezer/crypto.ts @@ -203,15 +203,31 @@ export function generateBlowfishKey(trackId: string): Uint8Array { * Decrypt a chunk using Blowfish CBC */ export function decryptChunk(chunk: Uint8Array, blowfishKey: Uint8Array): Uint8Array { - const iv = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]); - try { + // Convert Uint8Array to the format blowfish-node expects const bf = new Blowfish(blowfishKey, Blowfish.MODE.CBC, Blowfish.PADDING.NULL); + + // Set IV as Uint8Array + const iv = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]); bf.setIv(iv); - const decrypted = bf.decode(Buffer.from(chunk), Blowfish.TYPE.UINT8_ARRAY); + + // Decode and return as Uint8Array + const decrypted = bf.decode(chunk, Blowfish.TYPE.UINT8_ARRAY); + + // Verify decryption worked + if (!decrypted || decrypted.length === 0) { + throw new Error('Decryption returned empty result'); + } + return new Uint8Array(decrypted); } catch (error) { - console.error('Error decrypting chunk:', error); + // Only log the first few errors to avoid spam + if (Math.random() < 0.01) { // Log ~1% of errors + console.error('Error decrypting chunk (sample):', error, { + chunkLength: chunk.length, + keyLength: blowfishKey.length + }); + } return chunk; // Return original if decryption fails } } @@ -219,8 +235,8 @@ export function decryptChunk(chunk: Uint8Array, blowfishKey: Uint8Array): Uint8A /** * Generate stream path for download URL */ -export function generateStreamPath(sngID: string, md5: string, mediaVersion: string, format: string): string { - let urlPart = `${md5}¤${format}¤${sngID}¤${mediaVersion}`; +export function generateStreamPath(sngID: string, md5Origin: string, mediaVersion: string, format: string): string { + let urlPart = `${md5Origin}¤${format}¤${sngID}¤${mediaVersion}`; const md5val = md5(urlPart); let step2 = `${md5val}¤${urlPart}¤`; step2 += '.'.repeat(16 - (step2.length % 16)); @@ -234,15 +250,15 @@ export function generateStreamPath(sngID: string, md5: string, mediaVersion: str /** * Generate download URL from track info */ -export function generateStreamURL(sngID: string, md5: string, mediaVersion: string, format: string): string { - const urlPart = generateStreamPath(sngID, md5, mediaVersion, format); - return `https://cdns-proxy-${md5[0]}.dzcdn.net/api/1/${urlPart}`; +export function generateStreamURL(sngID: string, md5Origin: string, mediaVersion: string, format: string): string { + const urlPart = generateStreamPath(sngID, md5Origin, mediaVersion, format); + return `https://cdns-proxy-${md5Origin[0]}.dzcdn.net/api/1/${urlPart}`; } /** * Generate crypted stream URL (for encrypted streams) */ -export function generateCryptedStreamURL(sngID: string, md5: string, mediaVersion: string, format: string): string { - const urlPart = generateStreamPath(sngID, md5, mediaVersion, format); - return `https://e-cdns-proxy-${md5[0]}.dzcdn.net/mobile/1/${urlPart}`; +export function generateCryptedStreamURL(sngID: string, md5Origin: string, mediaVersion: string, format: string): string { + const urlPart = generateStreamPath(sngID, md5Origin, mediaVersion, format); + return `https://e-cdns-proxy-${md5Origin[0]}.dzcdn.net/mobile/1/${urlPart}`; }