Merge pull request #5 from forkbomb9/patch-1

getTrackDownloadUrl: return track data in json format
This commit is contained in:
Sayem Chowdhury 2021-08-04 12:31:28 +06:00 committed by GitHub
commit d54d9022d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 27 deletions

View File

@ -1,7 +1,7 @@
import test from 'ava'; import test from 'ava';
import axios from 'axios'; import axios from 'axios';
import * as api from '../src'; import * as api from '../src';
import {trackIsEncrypted, decryptDownload} from '../src/lib/decrypt'; import {decryptDownload} from '../src/lib/decrypt';
import {downloadAlbumCover} from '../src/metadata-writer/abumCover'; import {downloadAlbumCover} from '../src/metadata-writer/abumCover';
import {getLyricsMusixmatch} from '../src/metadata-writer/musixmatchLyrics'; import {getLyricsMusixmatch} from '../src/metadata-writer/musixmatchLyrics';
import {getTrackDownloadUrl} from '../src/lib/get-url'; import {getTrackDownloadUrl} from '../src/lib/get-url';
@ -146,14 +146,15 @@ test('SEARCH TRACK, ALBUM & ARTIST', async (t) => {
if (process.env.CI) { if (process.env.CI) {
test('DOWNLOAD TRACK128 & ADD METADATA', async (t) => { test('DOWNLOAD TRACK128 & ADD METADATA', async (t) => {
const track = await api.getTrackInfo(SNG_ID); const track = await api.getTrackInfo(SNG_ID);
const url = await getTrackDownloadUrl(track, 1); const trackData = await getTrackDownloadUrl(track, 1);
const {data} = await axios.get(url, {responseType: 'arraybuffer'}); if (!trackData) throw new Error("Selected track+quality are unavailable");
const {data} = await axios.get(trackData.trackUrl, {responseType: 'arraybuffer'});
t.truthy(data); t.truthy(data);
t.true(Buffer.isBuffer(data)); t.true(Buffer.isBuffer(data));
t.is(data.length, 3596119); t.is(data.length, 3596119);
const decryptedTrack: Buffer = trackIsEncrypted(url) ? decryptDownload(data, track.SNG_ID) : data; const decryptedTrack: Buffer = trackData.isEncrypted ? decryptDownload(data, track.SNG_ID) : data;
t.true(Buffer.isBuffer(decryptedTrack)); t.true(Buffer.isBuffer(decryptedTrack));
t.is(decryptedTrack.length, 3596119); t.is(decryptedTrack.length, 3596119);
@ -164,14 +165,15 @@ if (process.env.CI) {
// test('TRACK128 WITHOUT ALBUM INFO', async (t) => { // test('TRACK128 WITHOUT ALBUM INFO', async (t) => {
// const track = await api.getTrackInfo('912254892'); // const track = await api.getTrackInfo('912254892');
// const url = await getTrackDownloadUrl(track, 1); // const trackData = await getTrackDownloadUrl(track, 1);
// const {data} = await axios.get(url, {responseType: 'arraybuffer'}); // if (!trackData) throw new Error("Selected track+quality are unavailable");
// const {data} = await axios.get(trackData.trackUrl, {responseType: 'arraybuffer'});
// t.truthy(data); // t.truthy(data);
// t.true(Buffer.isBuffer(data)); // t.true(Buffer.isBuffer(data));
// t.is(data.length, 3262170); // t.is(data.length, 3262170);
// const decryptedTrack: Buffer = trackIsEncrypted(url) ? decryptDownload(data, track.SNG_ID) : data; // const decryptedTrack: Buffer = trackData.isEncrypted ? decryptDownload(data, track.SNG_ID) : data;
// t.true(Buffer.isBuffer(decryptedTrack)); // t.true(Buffer.isBuffer(decryptedTrack));
// t.is(decryptedTrack.length, 3262170); // t.is(decryptedTrack.length, 3262170);
@ -184,14 +186,15 @@ if (process.env.CI) {
test('DOWNLOAD TRACK320 & ADD METADATA', async (t) => { test('DOWNLOAD TRACK320 & ADD METADATA', async (t) => {
const track = await api.getTrackInfo(SNG_ID); const track = await api.getTrackInfo(SNG_ID);
const url = await getTrackDownloadUrl(track, 3); const trackData = await getTrackDownloadUrl(track, 3);
const {data} = await axios.get(url, {responseType: 'arraybuffer'}); if (!trackData) throw new Error("Selected track+quality are unavailable");
const {data} = await axios.get(trackData.trackUrl, {responseType: 'arraybuffer'});
t.truthy(data); t.truthy(data);
t.true(Buffer.isBuffer(data)); t.true(Buffer.isBuffer(data));
t.is(data.length, 8990301); t.is(data.length, 8990301);
const decryptedTrack: Buffer = trackIsEncrypted(url) ? decryptDownload(data, track.SNG_ID) : data; const decryptedTrack: Buffer = trackData.isEncrypted ? decryptDownload(data, track.SNG_ID) : data;
t.true(Buffer.isBuffer(decryptedTrack)); t.true(Buffer.isBuffer(decryptedTrack));
t.is(decryptedTrack.length, 8990301); t.is(decryptedTrack.length, 8990301);
@ -202,14 +205,15 @@ if (process.env.CI) {
test('DOWNLOAD TRACK1411 & ADD METADATA', async (t) => { test('DOWNLOAD TRACK1411 & ADD METADATA', async (t) => {
const track = await api.getTrackInfo(SNG_ID); const track = await api.getTrackInfo(SNG_ID);
const url = await getTrackDownloadUrl(track, 9); const trackData = await getTrackDownloadUrl(track, 9);
const {data} = await axios.get(url, {responseType: 'arraybuffer'}); if (!trackData) throw new Error("Selected track+quality are unavailable");
const {data} = await axios.get(trackData.trackUrl, {responseType: 'arraybuffer'});
t.truthy(data); t.truthy(data);
t.true(Buffer.isBuffer(data)); t.true(Buffer.isBuffer(data));
t.is(data.length, 25418289); t.is(data.length, 25418289);
const decryptedTrack: Buffer = trackIsEncrypted(url) ? decryptDownload(data, track.SNG_ID) : data; const decryptedTrack: Buffer = trackData.isEncrypted ? decryptDownload(data, track.SNG_ID) : data;
t.true(Buffer.isBuffer(decryptedTrack)); t.true(Buffer.isBuffer(decryptedTrack));
t.is(data.length, 25418289); t.is(data.length, 25418289);

View File

@ -67,7 +67,3 @@ export const decryptDownload = (source: Buffer, trackId: string) => {
return destBuffer; return destBuffer;
}; };
export const trackIsEncrypted = (url: string) => {
return url.includes('/mobile/') || url.includes('/media/');
};

View File

@ -65,7 +65,8 @@ const getTrackUrlFromServer = async (track_token: string, format: string): Promi
* @param track Track info json returned from `getTrackInfo` * @param track Track info json returned from `getTrackInfo`
* @param quality 1 = 128kbps, 3 = 320kbps and 9 = flac (around 1411kbps) * @param quality 1 = 128kbps, 3 = 320kbps and 9 = flac (around 1411kbps)
*/ */
export const getTrackDownloadUrl = async (track: trackType, quality: number): Promise<string> => { export const getTrackDownloadUrl = async (track: trackType, quality: number): Promise<{trackUrl: string, isEncrypted: boolean, fileSize: number} | null> => {
let wrongLicense = false;
let formatName: string; let formatName: string;
switch (quality) { switch (quality) {
case 9: case 9:
@ -84,12 +85,19 @@ export const getTrackDownloadUrl = async (track: trackType, quality: number): Pr
// Get URL with the official API // Get URL with the official API
try { try {
const url = await getTrackUrlFromServer(track.TRACK_TOKEN, formatName); const url = await getTrackUrlFromServer(track.TRACK_TOKEN, formatName);
if (url && (await testUrl(url))) { if (url) {
return url; const fileSize = await testUrl(url);
if (fileSize > 0) {
return {
trackUrl: url,
isEncrypted: url.includes('/mobile/') || url.includes('/media/'),
fileSize: fileSize,
};
}
} }
} catch (err) { } catch (err) {
if (err instanceof WrongLicense) { if (err instanceof WrongLicense) {
throw new Error(`Your account can't stream ${formatName} tracks`); wrongLicense = true;
} else { } else {
throw err; throw err;
} }
@ -98,17 +106,23 @@ export const getTrackDownloadUrl = async (track: trackType, quality: number): Pr
// Fallback to the old method // Fallback to the old method
const filename = getSongFileName(track, quality); // encrypted file name const filename = getSongFileName(track, quality); // encrypted file name
const url = `https://e-cdns-proxy-${track.MD5_ORIGIN[0]}.dzcdn.net/mobile/1/${filename}`; const url = `https://e-cdns-proxy-${track.MD5_ORIGIN[0]}.dzcdn.net/mobile/1/${filename}`;
if (await testUrl(url)) { const fileSize = await testUrl(url);
return url; if (fileSize > 0) {
return {
trackUrl: url,
isEncrypted: url.includes('/mobile/') || url.includes('/media/'),
fileSize: fileSize,
};
} }
throw new Error(`Forbidden to access ${url}`); if (wrongLicense) throw new Error(`Your account can't stream ${formatName} tracks`);
return null;
}; };
const testUrl = async (url: string): Promise<boolean> => { const testUrl = async (url: string): Promise<number> => {
try { try {
let response = await axios.head(url); let response = await axios.head(url);
return Number(response.headers['content-length']) > 0 ? true : false; return Number(response.headers['content-length']);
} catch (err) { } catch (err) {
return false; return 0;
} }
}; };