mirror of
https://github.com/markuryy/shark.git
synced 2025-12-12 19:51:01 +00:00
feat(crypto): track download handling
This commit is contained in:
@@ -16,14 +16,16 @@
|
|||||||
"store:default",
|
"store:default",
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
"fs:default",
|
"fs:default",
|
||||||
|
"fs:allow-write-file",
|
||||||
|
"fs:allow-mkdir",
|
||||||
|
"fs:allow-remove",
|
||||||
|
"fs:allow-rename",
|
||||||
|
"fs:allow-exists",
|
||||||
{
|
{
|
||||||
"identifier": "fs:scope",
|
"identifier": "fs:scope",
|
||||||
"allow": [
|
"allow": [
|
||||||
{
|
{
|
||||||
"path": "$HOME/**/*"
|
"path": "**"
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "$APPDATA/**/*"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -273,12 +273,17 @@ export class DeezerAPI {
|
|||||||
|
|
||||||
// Get track download URL
|
// Get track download URL
|
||||||
async getTrackDownloadUrl(trackToken: string, format: string, licenseToken: string): Promise<string | null> {
|
async getTrackDownloadUrl(trackToken: string, format: string, licenseToken: string): Promise<string | null> {
|
||||||
|
console.log('[DEBUG] Getting track download URL...', { trackToken, format, licenseToken });
|
||||||
|
|
||||||
try {
|
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', {
|
const response = await fetch('https://media.deezer.com/v1/get_url', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
...this.httpHeaders,
|
...this.httpHeaders,
|
||||||
'Cookie': this.arl ? `arl=${this.arl}` : ''
|
'Cookie': cookieHeader
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
license_token: licenseToken,
|
license_token: licenseToken,
|
||||||
@@ -287,22 +292,33 @@ export class DeezerAPI {
|
|||||||
formats: [{ cipher: 'BF_CBC_STRIPE', format }]
|
formats: [{ cipher: 'BF_CBC_STRIPE', format }]
|
||||||
}],
|
}],
|
||||||
track_tokens: [trackToken]
|
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();
|
const result = await response.json();
|
||||||
|
console.log('[DEBUG] Download URL response:', result);
|
||||||
|
|
||||||
if (result.data && result.data.length > 0) {
|
if (result.data && result.data.length > 0) {
|
||||||
const trackData = result.data[0];
|
const trackData = result.data[0];
|
||||||
if (trackData.media && trackData.media.length > 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;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting track URL:', error);
|
console.error('[ERROR] Failed to get track download URL:', error);
|
||||||
return null;
|
throw error; // Re-throw to let caller handle it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,15 +203,31 @@ export function generateBlowfishKey(trackId: string): Uint8Array {
|
|||||||
* Decrypt a chunk using Blowfish CBC
|
* Decrypt a chunk using Blowfish CBC
|
||||||
*/
|
*/
|
||||||
export function decryptChunk(chunk: Uint8Array, blowfishKey: Uint8Array): Uint8Array {
|
export function decryptChunk(chunk: Uint8Array, blowfishKey: Uint8Array): Uint8Array {
|
||||||
const iv = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Convert Uint8Array to the format blowfish-node expects
|
||||||
const bf = new Blowfish(blowfishKey, Blowfish.MODE.CBC, Blowfish.PADDING.NULL);
|
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);
|
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);
|
return new Uint8Array(decrypted);
|
||||||
} catch (error) {
|
} 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
|
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
|
* Generate stream path for download URL
|
||||||
*/
|
*/
|
||||||
export function generateStreamPath(sngID: string, md5: string, mediaVersion: string, format: string): string {
|
export function generateStreamPath(sngID: string, md5Origin: string, mediaVersion: string, format: string): string {
|
||||||
let urlPart = `${md5}¤${format}¤${sngID}¤${mediaVersion}`;
|
let urlPart = `${md5Origin}¤${format}¤${sngID}¤${mediaVersion}`;
|
||||||
const md5val = md5(urlPart);
|
const md5val = md5(urlPart);
|
||||||
let step2 = `${md5val}¤${urlPart}¤`;
|
let step2 = `${md5val}¤${urlPart}¤`;
|
||||||
step2 += '.'.repeat(16 - (step2.length % 16));
|
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
|
* Generate download URL from track info
|
||||||
*/
|
*/
|
||||||
export function generateStreamURL(sngID: string, md5: string, mediaVersion: string, format: string): string {
|
export function generateStreamURL(sngID: string, md5Origin: string, mediaVersion: string, format: string): string {
|
||||||
const urlPart = generateStreamPath(sngID, md5, mediaVersion, format);
|
const urlPart = generateStreamPath(sngID, md5Origin, mediaVersion, format);
|
||||||
return `https://cdns-proxy-${md5[0]}.dzcdn.net/api/1/${urlPart}`;
|
return `https://cdns-proxy-${md5Origin[0]}.dzcdn.net/api/1/${urlPart}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate crypted stream URL (for encrypted streams)
|
* Generate crypted stream URL (for encrypted streams)
|
||||||
*/
|
*/
|
||||||
export function generateCryptedStreamURL(sngID: string, md5: string, mediaVersion: string, format: string): string {
|
export function generateCryptedStreamURL(sngID: string, md5Origin: string, mediaVersion: string, format: string): string {
|
||||||
const urlPart = generateStreamPath(sngID, md5, mediaVersion, format);
|
const urlPart = generateStreamPath(sngID, md5Origin, mediaVersion, format);
|
||||||
return `https://e-cdns-proxy-${md5[0]}.dzcdn.net/mobile/1/${urlPart}`;
|
return `https://e-cdns-proxy-${md5Origin[0]}.dzcdn.net/mobile/1/${urlPart}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user