Initial commit

This commit is contained in:
Sayem Chowdhury 2021-02-23 20:51:36 +06:00
commit 2b2ef342d3
24 changed files with 4265 additions and 0 deletions

6
.eslintrc.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
},
};

22
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn test
env:
CI: true

116
.gitignore vendored Normal file
View File

@ -0,0 +1,116 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

8
.prettierrc.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
printWidth: 120,
bracketSpacing: false,
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: "all",
endOfLine: "lf",
};

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 sayem314
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

130
README.md Normal file
View File

@ -0,0 +1,130 @@
## d-fi-core [![Test](https://github.com/d-fi/d-fi-core/workflows/Test/badge.svg)](https://github.com/d-fi/d-fi-core/actions)
d-fi is a streaming music downloader. This core module is designed to be used on future version of d-fi.
## Installation
```bash
$ yarn add d-fi-core
```
## Usage
Here's a simple example to download tracks.
```ts
import axios from 'axios';
import fs from 'fs';
import * as api from 'd-fi-core';
// Init api with arl
await api.initDeezerApi(arl_from_cookie);
// GET Track Object
const track = await api.getTrackInfo(SNG_ID);
// Parse download URL for 128kbps
const url = getTrackDownloadUrl(track, 1);
// Download encrypted track
const {data} = await axios.get(url, {responseType: 'arraybuffer'});
// Decrypt track
const decryptedTrack = decryptDownload(data, track.SNG_ID);
// Add id3 metadata
const trackWithMetadata = await api.addTrackTags(decryptedTrack, track, false, 500);
// Save file to disk
fs.writeFileSync(track.SNG_TITLE + '.mp3', trackWithMetadata);
```
### [Read FAQ](https://github.com/d-fi/d-fi-core/blob/master/docs/README.md)
## Methods
All method returns `Object` or throws `Error`. Make sure to catch error on your side.
### `.initDeezerApi(arl_cookie);`
> It is recommended that you first init the app with this method using your arl cookie.
| Parameters | Required | Type |
| ------------ | :------: | -------: |
| `arl_cookie` | Yes | `string` |
### `.getTrackInfo(track_id);`
| Parameters | Required | Type |
| ---------- | :------: | -------: |
| `track_id` | Yes | `string` |
### `.getLyrics(track_id);`
| Parameters | Required | Type |
| ---------- | :------: | -------: |
| `track_id` | Yes | `string` |
### `.getAlbumInfo(album_id);`
| Parameters | Required | Type |
| ---------- | :------: | -------: |
| `album_id` | Yes | `string` |
### `.getAlbumTracks(album_id);`
| Parameters | Required | Type |
| ---------- | :------: | -------: |
| `album_id` | Yes | `string` |
### `.getPlaylistInfo(playlist_id);`
| Parameters | Required | Type |
| ------------- | :------: | -------: |
| `playlist_id` | Yes | `string` |
### `.getPlaylistTracks(playlist_id);`
| Parameters | Required | Type |
| ------------- | :------: | -------: |
| `playlist_id` | Yes | `string` |
### `.getArtistInfo(artist_id);`
| Parameters | Required | Type |
| ----------- | :------: | -------: |
| `artist_id` | Yes | `string` |
### `.getDiscography(artist_id, limit);`
| Parameters | Required | Type | Default | Description |
| ----------- | :------: | -------: | ------: | ----------------------: |
| `artist_id` | Yes | `string` | - | artist id |
| `limit` | No | `number` | 500 | maximum tracks to fetch |
### `.getProfile(user_id);`
| Parameters | Required | Type |
| ---------- | :------: | -------: |
| `user_id` | Yes | `string` |
### `.searchAlternative(artist_name, song_name);`
| Parameters | Required | Type |
| ------------- | :------: | -------: |
| `artist_name` | Yes | `string` |
| `song_name` | Yes | `string` |
### `.searchMusic(query, types, limit);`
| Parameters | Required | Type | Default | Description |
| ---------- | :------: | -------: | --------: | ------------------------------: |
| `query` | Yes | `string` | - | search query |
| `types` | No | `array` | ['TRACK'] | array of search types |
| `limit` | No | `number` | 15 | maximum item to fetch per types |
### Donations
If you want to show your appreciation, you can donate me on [ko-fi](https://ko-fi.com/Z8Z5KDA6) or [buy me a coffee](https://www.buymeacoffee.com/sayem). Thanks!
> Made with :heart: & :coffee: by Sayem

17
docs/faq.md Normal file
View File

@ -0,0 +1,17 @@
## Frequently Asked Questions (FAQ)
- How to get arl cookie?
> Open Google Chrome in PC
>
> - Go to www.deezer.com and log into your account
> - After logging in press F12 to open up Developer Tools
> - Go under the Application tab (if you don't see it click the double arrow)
> - Open the cookie dropdown
> - Select www.deezer.com
> - Find the arl cookie (It should be 192 chars long)
> - That's your ARL, now you can use it in the app
Here is a sample gif tutorial.
![](https://media.giphy.com/media/igsGY1z84qpAPGjj1r/giphy.gif)

42
package.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "d-fi-core",
"version": "1.0.0",
"description": "Core module for d-fi",
"main": "dist/index.js",
"scripts": {
"lint": "eslint .",
"prepublish": "eslint . && tsc",
"prebuild": "eslint .",
"build": "tsc",
"test": "ava"
},
"repository": "https://github.com/d-fi/d-fi-core",
"author": "Sayem Chowdhury",
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"browser-id3-writer": "^4.4.0",
"delay": "^5.0.0",
"metaflac-js2": "^1.0.7",
"node-html-parser": "^2.2.0"
},
"devDependencies": {
"@types/node": "^14.14.31",
"ava": "^3.15.0",
"eslint": "^7.20.0",
"eslint-plugin-prettier": "^3.3.1",
"prettier": "^2.2.1",
"ts-node": "^9.1.1",
"typescript": "^4.1.5"
},
"ava": {
"extensions": [
"ts"
],
"require": [
"ts-node/register"
],
"timeout": "30s",
"verbose": true
}
}

120
src/api.ts Normal file
View File

@ -0,0 +1,120 @@
import axios from './lib/request';
import FastLRU from './lib/fast-lru';
import type {
albumType,
trackType,
lyricsType,
albumTracksType,
playlistInfo,
playlistTracksType,
artistInfoType,
discographyType,
profileType,
searchType,
} from './types';
// expire cache in 60 minutes
const lru = new FastLRU({
maxSize: 1000,
ttl: 60 * 60000,
});
/**
* Make post requests to deezer api
* @param {Object} body post body
* @param {String} method request method
*/
export const request = async (body: object, method: string) => {
const cacheKey = method + ':' + Object.entries(body).join(':');
const cache = lru.get(cacheKey);
if (cache) {
return cache;
}
const {
data: {error, results},
} = await axios.post('/gateway.php', body, {params: {method}});
if (Object.keys(results).length > 0) {
lru.set(cacheKey, results);
return results;
}
const errorMessage = Object.entries(error).join(', ');
throw new Error(errorMessage);
};
/**
* @param {String} sng_id song id
*/
export const getTrackInfo = (sng_id: string): Promise<trackType> => request({sng_id}, 'song.getData');
/**
* @param {String} sng_id song id
*/
export const getLyrics = (sng_id: string): Promise<lyricsType> => request({sng_id}, 'song.getLyrics');
/**
* @param {String} alb_id album id
*/
export const getAlbumInfo = (alb_id: string): Promise<albumType> => request({alb_id}, 'album.getData');
/**
* @param {String} alb_id user id
*/
export const getAlbumTracks = (alb_id: string): Promise<albumTracksType> =>
request({alb_id, lang: 'us', nb: -1}, 'song.getListByAlbum');
/**
* @param {String} playlist_id playlist id
*/
export const getPlaylistInfo = (playlist_id: string): Promise<playlistInfo> =>
request({playlist_id, lang: 'en'}, 'playlist.getData');
/**
* @param {String} playlist_id playlist id
*/
export const getPlaylistTracks = (playlist_id: string): Promise<playlistTracksType> =>
request({playlist_id, lang: 'en', nb: -1, start: 0, tab: 0, tags: true, header: true}, 'playlist.getSongs');
/**
* @param {String} art_id artist id
*/
export const getArtistInfo = (art_id: string): Promise<artistInfoType> =>
request({art_id, filter_role_id: [0], lang: 'en', tab: 0, nb: -1, start: 0}, 'artist.getData');
/**
* @param {String} art_id artist id
* @param {String} nb number of total song to fetch
*/
export const getDiscography = (art_id: string, nb = 500): Promise<discographyType> =>
request({art_id, filter_role_id: [0], lang: 'en', nb, nb_songs: -1, start: 0}, 'album.getDiscography');
/**
* @param {String} user_id user id
*/
export const getProfile = (user_id: string): Promise<profileType> =>
request({user_id, tab: 'loved', nb: -1}, 'mobile.pageUser');
/**
* @param {String} artist artist name
* @param {String} song song name
*/
export const searchAlternative = (artist: string, song: string): Promise<searchType> =>
request(
{
query: `artist:'${artist}' track:'${song}'`,
types: ['TRACK'],
nb: 10,
},
'mobile_suggest',
);
type searchTypesProp = 'ALBUM' | 'ARTIST' | 'TRACK' | 'PLAYLIST' | 'RADIO' | 'SHOW' | 'USER' | 'LIVESTREAM' | 'CHANNEL';
/**
* @param {String} query search query
* @param {Array} types search types
* @param {String} nb number of items to fetch
*/
export const searchMusic = (query: string, types: searchTypesProp[] = ['TRACK'], nb = 15): Promise<searchType> =>
request({query, nb, types}, 'mobile_suggest');

4
src/index.ts Normal file
View File

@ -0,0 +1,4 @@
export {initDeezerApi} from './lib/request';
export * from './api';
export * from './lib/decrypt';
export * from './metadata-writer';

79
src/lib/decrypt.ts Normal file
View File

@ -0,0 +1,79 @@
import crypto from 'crypto';
import type {trackType} from '../types';
const md5 = (data: string, type: crypto.Encoding = 'ascii') => {
const md5sum = crypto.createHash('md5');
md5sum.update(data, type);
return md5sum.digest('hex');
};
const getSongFileName = ({MD5_ORIGIN, SNG_ID, MEDIA_VERSION}: trackType, quality: number) => {
const step1 = [MD5_ORIGIN, quality, SNG_ID, MEDIA_VERSION].join('¤');
let step2 = md5(step1) + '¤' + step1 + '¤';
while (step2.length % 16 > 0) step2 += ' ';
return crypto.createCipheriv('aes-128-ecb', 'jo6aey6haid2Teih', '').update(step2, 'ascii', 'hex');
};
const getBlowfishKey = (trackId: string) => {
let SECRET = 'g4el58wc' + '0zvf9na1';
let idMd5 = md5(trackId);
let bfKey = '';
for (let i = 0; i < 16; i++) {
bfKey += String.fromCharCode(idMd5.charCodeAt(i) ^ idMd5.charCodeAt(i + 16) ^ SECRET.charCodeAt(i));
}
return bfKey;
};
const decryptChunk = (chunk: Buffer, blowFishKey: string) => {
let cipher = crypto.createDecipheriv('bf-cbc', blowFishKey, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]));
cipher.setAutoPadding(false);
// @ts-ignore
return cipher.update(chunk, 'binary', 'binary') + cipher.final();
};
/**
*
* @param source Downloaded song from `getTrackDownloadUrl`
* @param trackId Song ID as string
*/
export const decryptDownload = (source: Buffer, trackId: string) => {
// let part_size = 0x1800;
let chunk_size = 2048;
let blowFishKey = getBlowfishKey(trackId);
let i = 0;
let position = 0;
let destBuffer = Buffer.alloc(source.length);
destBuffer.fill(0);
while (position < source.length) {
let chunk;
if (source.length - position >= 2048) chunk_size = 2048;
else chunk_size = source.length - position;
chunk = Buffer.alloc(chunk_size);
let chunkString;
chunk.fill(0);
source.copy(chunk, 0, position, position + chunk_size);
if (i % 3 > 0 || chunk_size < 2048) chunkString = chunk.toString('binary');
else chunkString = decryptChunk(chunk, blowFishKey);
destBuffer.write(chunkString, position, chunkString.length, 'binary');
position += chunk_size;
i++;
}
return destBuffer;
};
/**
* @param track Track info json returned from `getTrackInfo`
* @param quality 1 = 128kbps, 3 = 320kbps and 9 = flac (around 1411kbps)
*/
export const getTrackDownloadUrl = (track: trackType, quality: number) => {
const cdn = track.MD5_ORIGIN[0]; // cdn destination
const filename = getSongFileName(track, quality); // encrypted file name
return `http://e-cdn-proxy-${cdn}.deezer.com/mobile/1/${filename}`;
};

142
src/lib/fast-lru.ts Normal file
View File

@ -0,0 +1,142 @@
/**
* Fast LRU & TTL cache
* @param {Integer} options.max - Max entries in the cache. @default Infinity
* @param {Integer} options.ttl - Timeout before removing entries. @default Infinity
*/
class FastLRU {
_max: number;
_ttl: number;
_cache: Map<any, any>;
_meta: {
[key: string]: any;
};
constructor({maxSize = Infinity, ttl = 0}) {
// Default options
this._max = maxSize;
this._ttl = ttl;
this._cache = new Map();
// Metadata for entries
this._meta = {};
}
/**
* Add new entry
*/
set(key: string, value: any, ttl: number = this._ttl) {
// Execution time
const time = Date.now();
// Remvove least recently used elements if exceeds max bytes
if (this._cache.size >= this._max) {
const items = Object.values(this._meta);
if (this._ttl > 0) {
for (const item of items) {
if (item.expire < time) {
this.delete(item.key);
}
}
}
if (this._cache.size >= this._max) {
const least = items.sort((a, b) => a.hits - b.hits)[0];
this.delete(least.key);
}
}
// Override if key already set
this._cache.set(key, value);
this._meta[key] = {
key,
hits: 0,
expire: time + ttl,
};
}
/**
* Get entry
*/
get(key: string) {
if (this._cache.has(key)) {
const item = this._cache.get(key);
if (this._ttl > 0) {
const time = Date.now();
if (this._meta[key].expire < time) {
this.delete(key);
return undefined;
}
}
this._meta[key].hits++;
return item;
}
}
/**
* Get without hitting hits
*/
peek(key: string) {
return this._cache.get(key);
}
/**
* Remove entry
*/
delete(key: string) {
delete this._meta[key];
this._cache.delete(key);
}
/**
* Remove all entries
*/
clear() {
this._cache.clear();
this._meta = {};
}
/**
* Check has entry
*/
has(key: string) {
return this._cache.has(key);
}
/**
* Get all kies
* @returns {Iterator} Iterator on all kies
*/
keys() {
return this._cache.keys();
}
/**
* Iterate over values
*/
values() {
return this._cache.values();
}
/**
* Iterate over entries
*/
entries() {
return this._cache.entries();
}
/**
* For each
*/
forEach(cb: any) {
return this._cache.forEach(cb);
}
/**
* Entries total size
*/
get size() {
return this._cache.size;
}
}
export default FastLRU;

65
src/lib/request.ts Normal file
View File

@ -0,0 +1,65 @@
import axios, {AxiosRequestConfig} from 'axios';
import delay from 'delay';
const ARL =
'a62f40421c53479e411c78cd7420f7e40e7de97ab9e6436558145bcedf4557bc79946e96c02d7fef9a3166024a11c4a501236eca892b1ef989267244153af6efc4fec75d47a776129e971a4c68cef0b33b1633baf0eb0e8c08e170224e9527fc';
const APIPayload = {
version: '8.32.0',
api_key: 'ZAIVAHCEISOHWAICUQUEXAEPICENGUAFAEZAIPHAELEEVAHPHUCUFONGUAPASUAY',
output: 3,
input: 3,
buildId: 'ios12_universal',
screenHeight: '480',
screenWidth: '320',
lang: 'en',
};
const instance = axios.create({
baseURL: 'https://api.deezer.com/1.0',
withCredentials: true,
timeout: 10000,
headers: {
Accept: '*/*',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US',
'Cache-Control': 'no-cache',
'Content-Type': 'application/json; charset=UTF-8',
'User-Agent': 'Deezer/8.32.0.2 (iOS; 14.4; Mobile; en; iPhone10_5)',
},
params: APIPayload,
data: APIPayload,
});
export const initDeezerApi = async (arl: string): Promise<string> => {
const options: AxiosRequestConfig = {
params: {
method: 'deezer.ping',
api_version: '1.0',
api_token: '',
},
};
if (arl) {
options.headers = {cookie: 'arl=' + arl};
}
const {data} = await instance.get('https://www.deezer.com/ajax/gw-light.php', options);
instance.defaults.params.sid = data.results.SESSION;
return data.results.SESSION;
};
// Add a request interceptor
instance.interceptors.response.use(async (response) => {
if (response.data.error && Object.keys(response.data.error).length > 0) {
if (response.data.error.NEED_API_AUTH_REQUIRED) {
await initDeezerApi(ARL);
return await instance(response.config);
} else if (response.data.error.code == 4) {
await delay(1000);
return await instance(response.config);
}
}
return response;
});
export default instance;

View File

@ -0,0 +1,36 @@
import axios from 'axios';
import FastLRU from '../lib/fast-lru';
import type {trackType} from '../types';
type coverSize = 56 | 250 | 500 | 1000 | 1500 | 1800 | number;
// expire cache in 30 minutes
const lru = new FastLRU({
maxSize: 50,
ttl: 30 * 60000,
});
/**
*
* @param {Object} track track info json from deezer api
* @param {Number} albumCoverSize in pixel, between 56-1800
*/
export const downloadAlbumCover = async (track: trackType, albumCoverSize: coverSize): Promise<Buffer | null> => {
if (!track.ALB_PICTURE) {
return null;
}
const cache = lru.get(track.ALB_PICTURE + albumCoverSize);
if (cache) {
return cache;
}
try {
const url = `https://e-cdns-images.dzcdn.net/images/cover/${track.ALB_PICTURE}/${albumCoverSize}x${albumCoverSize}-000000-80-0-0.jpg`;
const {data} = await axios.get(url, {responseType: 'arraybuffer'});
lru.set(track.ALB_PICTURE + albumCoverSize, data);
return data;
} catch (err) {
return null;
}
};

View File

@ -0,0 +1,57 @@
// @ts-ignore
import Metaflac from 'metaflac-js2';
import type {trackType} from '../types';
export const writeMetadataFlac = (buffer: Buffer, track: trackType, cover?: Buffer | null): Buffer => {
const flac = new Metaflac(buffer);
flac.setTag('TITLE=' + track.SNG_TITLE);
flac.setTag('ALBUM=' + track.ALB_TITLE);
const artists = track.ART_NAME.split(
new RegExp(' featuring | feat. | Ft. | ft. | vs | vs. | x | - |, ', 'g'),
).map((a) => a.trim());
flac.setTag('ARTIST=' + artists.join(', '));
if (track.DISK_NUMBER) {
flac.setTag('DISCNUMBER=' + track.DISK_NUMBER);
}
flac.setTag('LENGTH=' + track.DURATION);
flac.setTag('ISRC=' + track.ISRC);
flac.setTag('MEDIA=Digital Media');
if (track.LYRICS) {
flac.setTag('LYRICS=' + track.LYRICS.LYRICS_TEXT);
}
if (track.SNG_CONTRIBUTORS.composer) {
flac.setTag('COMPOSER=' + track.SNG_CONTRIBUTORS.composer.join(', '));
}
if (track.SNG_CONTRIBUTORS.publisher) {
flac.setTag('ORGANIZATION=' + track.SNG_CONTRIBUTORS.publisher.join(', '));
}
if (track.SNG_CONTRIBUTORS.producer) {
flac.setTag('PRODUCER=' + track.SNG_CONTRIBUTORS.producer.join(', '));
}
if (track.SNG_CONTRIBUTORS.engineer) {
flac.setTag('ENGINEER=' + track.SNG_CONTRIBUTORS.engineer.join(', '));
}
if (track.SNG_CONTRIBUTORS.writer) {
flac.setTag('WRITER=' + track.SNG_CONTRIBUTORS.writer.join(', '));
}
if (track.SNG_CONTRIBUTORS.author) {
flac.setTag('AUTHOR=' + track.SNG_CONTRIBUTORS.author.join(', '));
}
if (track.SNG_CONTRIBUTORS.mixer) {
flac.setTag('MIXER=' + track.SNG_CONTRIBUTORS.mixer.join(', '));
}
if (cover) {
flac.importPicture(cover);
}
flac.setTag('SOURCE=Deezer');
flac.setTag('SOURCEID=' + track.SNG_ID);
return Buffer.from(flac.save());
};

View File

@ -0,0 +1,24 @@
import {getLyricsMusixmatch} from './musixmatchLyrics';
import {getLyrics} from '../api';
import type {lyricsType, trackType} from '../types';
const getTrackLyricsWeb = async (track: trackType): Promise<lyricsType | null> => {
try {
const LYRICS_TEXT = await getLyricsMusixmatch(`${track.ART_NAME} - ${track.SNG_TITLE}`);
return {LYRICS_TEXT};
} catch (err) {
return null;
}
};
export const getTrackLyrics = async (track: trackType): Promise<lyricsType | null> => {
if (track.LYRICS_ID > 0) {
try {
return await getLyrics(track.SNG_ID);
} catch (err) {
return await getTrackLyricsWeb(track);
}
}
return await getTrackLyricsWeb(track);
};

View File

@ -0,0 +1,88 @@
// @ts-ignore
import id3Writer from 'browser-id3-writer';
import type {trackType} from '../types';
export const writeMetadataMp3 = (buffer: Buffer, track: trackType, cover?: Buffer | null): Buffer => {
const writer = new id3Writer(buffer);
writer.setFrame('TIT2', track.SNG_TITLE).setFrame('TALB', track.ALB_TITLE);
const artists = track.ART_NAME.split(
new RegExp(' featuring | feat. | Ft. | ft. | vs | vs. | x | - |, ', 'g'),
).map((a) => a.trim());
writer.setFrame('TPE2', artists).setFrame('TPE1', [artists.join(', ')]);
writer
.setFrame('TMED', 'Digital Media')
.setFrame('TXXX', {
description: 'Artists',
value: artists.join(', '),
})
.setFrame('TXXX', {
description: 'ISRC',
value: track.ISRC,
})
.setFrame('TXXX', {
description: 'SOURCE',
value: 'Deezer',
})
.setFrame('TXXX', {
description: 'SOURCEID',
value: track.SNG_ID,
});
if (track.DISK_NUMBER) {
writer.setFrame('TPOS', track.DISK_NUMBER).setFrame('TXXX', {
description: 'DISCNUMBER',
value: track.DISK_NUMBER,
});
}
writer.setFrame('TXXX', {
description: 'LENGTH',
value: track.DURATION,
});
if (track.SNG_CONTRIBUTORS.composer) {
writer.setFrame('TXXX', {
description: 'COMPOSER',
value: track.SNG_CONTRIBUTORS.composer.join(', '),
});
}
if (track.SNG_CONTRIBUTORS.writer) {
writer.setFrame('TXXX', {
description: 'LYRICIST',
value: track.SNG_CONTRIBUTORS.writer.join(', '),
});
}
if (track.SNG_CONTRIBUTORS.mixer) {
writer.setFrame('TXXX', {
description: 'MIXARTIST',
value: track.SNG_CONTRIBUTORS.mixer.join(', '),
});
}
if (track.SNG_CONTRIBUTORS.producer && track.SNG_CONTRIBUTORS.engineer) {
writer.setFrame('TXXX', {
description: 'INVOLVEDPEOPLE',
value: track.SNG_CONTRIBUTORS.producer.concat(track.SNG_CONTRIBUTORS.engineer).join(', '),
});
}
if (track.LYRICS) {
writer.setFrame('USLT', {
description: '',
lyrics: track.LYRICS.LYRICS_TEXT,
});
}
if (cover) {
writer.setFrame('APIC', {
type: 3,
data: cover,
description: '',
});
}
writer.addTag();
return Buffer.from(writer.arrayBuffer);
};

View File

@ -0,0 +1,28 @@
import {downloadAlbumCover} from './abumCover';
import {getTrackLyrics} from './getTrackLyrics';
// @ts-ignore
import {writeMetadataMp3} from './id3';
import {writeMetadataFlac} from './flacmetata';
import type {trackType} from '../types';
/**
* Add metdata to the mp3
* @param {Buffer} trackBuffer decrypted track buffer
* @param {Object} track json containing track infos
* @param {Boolean} fileType buffer type, mp3 or flac
* @param {Number} albumCoverSize album cover size in pixel
*/
export const addTrackTags = async (
trackBuffer: Buffer,
track: trackType,
isFlac: boolean,
albumCoverSize = 1000,
): Promise<Buffer> => {
const [cover, lyrics] = await Promise.all([downloadAlbumCover(track, albumCoverSize), getTrackLyrics(track)]);
if (lyrics) {
track.LYRICS = lyrics;
}
return isFlac ? writeMetadataFlac(trackBuffer, track, cover) : writeMetadataMp3(trackBuffer, track, cover);
};

View File

@ -0,0 +1,37 @@
import axios from 'axios';
import {parse} from 'node-html-parser';
import {randomUseragent} from './useragents';
const baseUrl = 'https://musixmatch.com';
const getUrlMusixmatch = async (query: string) => {
const {data} = await axios.get(`${baseUrl}/search/${encodeURI(query)}/tracks`, {
headers: {
'User-Agent': randomUseragent(),
referer: 'https://l.facebook.com/',
},
});
// @ts-ignore
const url = parse(data).querySelector('h2').childNodes[0].attributes.href.replace('/add', '');
if (url.includes('/lyrics/')) {
return url.startsWith('/lyrics/') ? baseUrl + url : url;
}
throw new Error('No song found!');
};
export const getLyricsMusixmatch = async (query: string): Promise<string> => {
const url = await getUrlMusixmatch(query);
const {data} = await axios.get(url, {
headers: {
'User-Agent': randomUseragent(),
referer: baseUrl + '/',
},
});
let lyrics = data.match(/("body":".*","language")/)[0];
lyrics = lyrics.replace('"body":"', '').replace('","language"', '');
return lyrics.split('\\n').join('\n');
};

View File

@ -0,0 +1,83 @@
const useragents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Edg/87.0.664.75',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.56',
'Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 OPR/73.0.3856.344',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:85.0) Gecko/20100101 Firefox/85.0',
'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63',
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.60 YaBrowser/20.12.0.963 Yowser/2.5 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 OPR/73.0.3856.329',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 OPR/73.0.3856.344 (Edition Yx 05)',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.53',
'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.7113.93 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66',
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.3 Safari/605.1.15',
'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0',
];
export const randomUseragent = () => useragents[Math.floor(Math.random() * useragents.length)];

138
src/tests/api.ts Normal file
View File

@ -0,0 +1,138 @@
import test from 'ava';
import axios from 'axios';
import * as api from '../';
import {decryptDownload, getTrackDownloadUrl} from '../lib/decrypt';
import {downloadAlbumCover} from '../metadata-writer/abumCover';
import {getLyricsMusixmatch} from '../metadata-writer/musixmatchLyrics';
// Harder, Better, Faster, Stronger by Daft Punk
const SNG_ID = '3135556';
// Discovery by Daft Punk
const ALB_ID = '302127';
test.serial('GET TRACK INFO', async (t) => {
const response = await api.getTrackInfo(SNG_ID);
t.is(response.SNG_ID, SNG_ID);
t.is(response.ISRC, 'GBDUW0000059');
t.is(response.__TYPE__, 'song');
});
test('GET TRACK COVER', async (t) => {
const track = await api.getTrackInfo(SNG_ID);
const cover = await downloadAlbumCover(track, 500);
t.truthy(cover);
t.true(Buffer.isBuffer(cover));
t.is(cover ? cover.length : 0, 24646);
});
test('GET TRACK LYRICS', async (t) => {
const response = await api.getLyrics(SNG_ID);
t.is(response.LYRICS_ID, '2780622');
t.is(response.LYRICS_TEXT.length, 1719);
});
test('GET MUSIXMATCH LYRICS', async (t) => {
const track = await api.getTrackInfo(SNG_ID);
const lyrics = await getLyricsMusixmatch(`${track.ART_NAME} - ${track.SNG_TITLE}`);
t.truthy(lyrics);
t.true(lyrics.length > 7000);
t.true(lyrics.length < 8000);
});
test('GET ALBUM INFO', async (t) => {
const response = await api.getAlbumInfo(ALB_ID);
t.is(response.ALB_ID, ALB_ID);
t.is(response.UPC, '724384960650');
t.is(response.__TYPE__, 'album');
});
test('GET ALBUM TRACKS', async (t) => {
const response = await api.getAlbumTracks(ALB_ID);
t.is(response.count, 14);
t.is(response.data.length, response.count);
});
test('GET PLAYLIST INFO', async (t) => {
const PLAYLIST_ID = '4523119944';
const response = await api.getPlaylistInfo(PLAYLIST_ID);
t.truthy(response.NB_SONG > 0);
t.is(response.PARENT_USERNAME, 'sayem314');
t.is(response.__TYPE__, 'playlist');
});
test('GET PLAYLIST TRACKS', async (t) => {
const PLAYLIST_ID = '4523119944';
const response = await api.getPlaylistTracks(PLAYLIST_ID);
t.truthy(response.count > 0);
t.is(response.data.length, response.count);
});
test('GET ARTIST INFO', async (t) => {
const ART_ID = '13';
const response = await api.getArtistInfo(ART_ID);
t.is(response.ART_NAME, 'Eminem');
t.is(response.__TYPE__, 'artist');
});
test('GET ARTIST TRACKS', async (t) => {
const ART_ID = '13';
const response = await api.getDiscography(ART_ID, 10);
t.is(response.count, 10);
t.is(response.data.length, response.count);
});
test('GET USER PROFILE', async (t) => {
const USER_ID = '2064440442';
const response = await api.getProfile(USER_ID);
t.is(response.USER.BLOG_NAME, 'sayem314');
t.is(response.USER.__TYPE__, 'user');
});
test('GET TRACK ALTERNATIVE', async (t) => {
const ARTIST = 'Eminem';
const TRACK = 'The Real Slim Shady';
const response = await api.searchAlternative(ARTIST, TRACK);
t.is(response.QUERY, `artist:'${ARTIST.toLowerCase()}' track:'${TRACK.toLowerCase()}'`);
t.is(response.TRACK.data.length, response.TRACK.count);
});
test('SEARCH TRACK, ALBUM & ARTIST', async (t) => {
const QUERY = 'Eminem';
const response = await api.searchMusic(QUERY, ['TRACK', 'ALBUM', 'ARTIST'], 1);
t.is(response.QUERY, QUERY.toLowerCase());
t.truthy(response.TRACK.count > 0);
t.truthy(response.ALBUM.count > 0);
t.truthy(response.ARTIST.count > 0);
});
test('DOWNLOAD TRACK & ADD METADATA', async (t) => {
const track = await api.getTrackInfo(SNG_ID);
const url = getTrackDownloadUrl(track, 1);
const {data} = await axios.get(url, {responseType: 'arraybuffer'});
t.truthy(data);
t.true(Buffer.isBuffer(data));
t.is(data.length, 3596119);
const decryptedTrack = decryptDownload(data, track.SNG_ID);
t.true(Buffer.isBuffer(decryptedTrack));
t.is(data.length, 3596119);
const trackWithMetadata = await api.addTrackTags(decryptedTrack, track, false, 500);
t.true(Buffer.isBuffer(trackWithMetadata));
t.is(trackWithMetadata.length, 3628837);
});

244
src/types.ts Normal file
View File

@ -0,0 +1,244 @@
interface localesType {
[key: string]: {
name: string;
};
}
interface artistType {
ART_ID: string; // '27'
ROLE_ID: string; // '0'
ARTISTS_SONGS_ORDER: string; // '0'
ART_NAME: string; // 'Daft Punk'
ARTIST_IS_DUMMY: boolean; // false
ART_PICTURE: string; // 'f2bc007e9133c946ac3c3907ddc5d2ea'
RANK: string; // '836071'
LOCALES?: localesType;
__TYPE__: 'artist';
}
interface mediaType {
TYPE: 'preview';
HREF: string; // 'https://cdns-preview-d.dzcdn.net/stream/c-deda7fa9316d9e9e880d2c6207e92260-8.mp3';
}
interface lyricsSync {
lrc_timestamp: string; //'[00:03.58]',
milliseconds: string; // '3580',
duration: string; // '8660',
line: string; // "Hey brother! There's an endless road to rediscover"
}
export interface lyricsType {
LYRICS_ID?: string; // '2310758',
LYRICS_SYNC_JSON?: lyricsSync[];
LYRICS_TEXT: string;
LYRICS_COPYRIGHTS?: string;
LYRICS_WRITERS?: string;
}
interface songType {
ALB_ID: string; // '302127'
ALB_TITLE: string; // 'Discovery'
ALB_PICTURE: string; // '2e018122cb56986277102d2041a592c8'
ARTISTS: artistType[];
ART_ID: '27';
ART_NAME: 'Daft Punk';
ARTIST_IS_DUMMY: boolean; // false
ART_PICTURE: string; //'f2bc007e9133c946ac3c3907ddc5d2ea'
DATE_START: string; // '0000-00-00'
DISK_NUMBER?: string; // '1'
DURATION: string; // '224'
EXPLICIT_TRACK_CONTENT: {
EXPLICIT_LYRICS_STATUS: number; // 0
EXPLICIT_COVER_STATUS: number; // 0
};
ISRC: string; // 'GBDUW0000059'
LYRICS_ID: number; // 2780622
LYRICS?: lyricsType;
EXPLICIT_LYRICS?: string;
RANK: string; // '787708'
SMARTRADIO: string; // 0
SNG_ID: string; // '3135556'
SNG_TITLE: string; // 'Harder, Better, Faster, Stronger'
SNG_CONTRIBUTORS: {
main_artist: string[]; //['Daft Punk']
author?: string[]; // ['Edwin Birdsong', 'Guy-Manuel de Homem-Christo', 'Thomas Bangalter']
composer?: string[];
musicpublisher?: string[];
producer?: string[];
publisher: string[];
engineer?: string[];
writer?: string[];
mixer?: string[];
};
STATUS: number; // 3
S_MOD: number; // 0
S_PREMIUM: number; // 0
TRACK_NUMBER: number; // '4'
URL_REWRITING: string; // 'daft-punk'
VERSION: string; // ''
MD5_ORIGIN: string; // '51afcde9f56a132096c0496cc95eb24b'
FILESIZE_AAC_64: '0';
FILESIZE_MP3_64: string; // '1798059'
FILESIZE_MP3_128: string; // '3596119'
FILESIZE_MP3_256: '0';
FILESIZE_MP3_320: '0';
FILESIZE_MP4_RA1: '0';
FILESIZE_MP4_RA2: '0';
FILESIZE_MP4_RA3: '0';
FILESIZE_FLAC: '0';
FILESIZE: string; //'3596119'
GAIN: string; // '-12.4'
MEDIA_VERSION: string; // '8'
TRACK_TOKEN: string; // 'AAAAAWAzlaRgNK7kyEh8dI3tpyObkIpy15hgDXr4GGiFTJakRmh5F7rMVf6-cYTWZNUIq4TLZj6x68mFstAqp9bml_eUzbfFbvIkpmx_hhDRZJhqLsHe-aBRZ9VdHEBr7LYSE3qKpmpTdDp6Odkrw3f-pNQW'
TRACK_TOKEN_EXPIRE: number; // 1614065380
MEDIA: [mediaType];
RIGHTS: {
STREAM_ADS_AVAILABLE?: boolean;
STREAM_ADS?: string; // '2000-01-01'
STREAM_SUB_AVAILABLE?: boolean; // true,
STREAM_SUB?: string; // '2000-01-01'
};
PROVIDER_ID: string; // '3'
__TYPE__: 'song';
}
export interface trackType extends songType {
FALLBACK?: songType;
}
export interface albumType {
ALB_CONTRIBUTORS: {
main_artist: string[]; // ['Avicii']
};
ALB_ID: string; // '9188269'
ALB_PICTURE: string; // '6e58a99f59a150e9b4aefbeb2d6fc856'
EXPLICIT_ALBUM_CONTENT: {
EXPLICIT_LYRICS_STATUS: number; // 0
EXPLICIT_COVER_STATUS: number; // 0
};
ALB_TITLE: string; // 'The Days / Nights'
ARTISTS: artistType[];
ART_ID: string; // '293585'
ART_NAME: string; // 'Avicii'
ARTIST_IS_DUMMY: boolean;
DIGITAL_RELEASE_DATE: string; //'2014-12-01'
EXPLICIT_LYRICS?: string; // '0'
NB_FAN: number; // 36285
NUMBER_DISK: string; // '1'
NUMBER_TRACK: string; // '4'
PHYSICAL_RELEASE_DATE?: string; // '2014-01-01'
PRODUCER_LINE: string; // '℗ 2014 Avicii Music AB'
PROVIDER_ID: string; // '427'
RANK: string; // '601128'
RANK_ART: string; // '861905'
STATUS: string; // '1'
TYPE: string; // '1'
UPC: string; // '602547151544'
__TYPE__: 'album';
}
export interface albumTracksType {
data: songType[];
count: number;
total: number;
filtered_count: number;
filtered_items?: number[];
next?: number;
}
export interface playlistInfo {
PLAYLIST_ID: string; // '4523119944'
DESCRIPTION?: string; // ''
PARENT_USERNAME: string; // 'sayem314'
PARENT_USER_PICTURE?: string; // ''
PARENT_USER_ID: string; // '2064440442'
PICTURE_TYPE: string; // 'cover'
PLAYLIST_PICTURE: string; // 'e206dafb59a3d378d7ffacc989bc4e35'
TITLE: string; // 'wtf playlist '
TYPE: string; // '0'
STATUS: string; // 0
USER_ID: string; // '2064440442'
DATE_ADD: string; // '2018-09-08 19:13:57'
DATE_MOD: string; //'2018-09-08 19:14:11'
DATE_CREATE: string; // '2018-05-31 00:01:05'
NB_SONG: number; // 3
NB_FAN: number; // 0
CHECKSUM: string; // 'c185d123834444e3c8869e235dd6f0a6'
HAS_ARTIST_LINKED: boolean;
IS_SPONSORED: boolean;
IS_EDITO: boolean;
__TYPE__: 'playlist';
}
export interface playlistTracksType extends albumTracksType {
checksum: string;
}
export interface artistInfoType {
ART_ID: string; // "293585",
ART_NAME: string; // "Avicii",
ARTIST_IS_DUMMY: boolean;
ART_PICTURE: string; // "82e214b0cb39316f4a12a082fded54f6",
FACEBOOK?: string; // "https://www.facebook.com/avicii?fref=ts",
NB_FAN: number; // 7140516,
TWITTER?: string; // "https://twitter.com/Avicii",
__TYPE__: 'artist';
}
export interface discographyType extends albumTracksType {
cache_version: number; // 2,
art_id: number; // 293585,
start: number; // 0,
nb: number; // 2
}
export interface profileType {
IS_FOLLOW: boolean;
NB_ARTISTS: number;
NB_FOLLOWERS: number;
NB_FOLLOWINGS: number;
NB_MP3S: number;
TOP_TRACK: albumTracksType;
USER: {
USER_ID: string; // '2064440442'
BLOG_NAME: string; // 'sayem314'
SEX?: string; // ''
COUNTRY: string; // 'BD'
USER_PICTURE?: string; // ''
COUNTRY_NAME: string; // 'Bangladesh'
PRIVATE: boolean;
DISPLAY_NAME: string; // 'sayem314'
__TYPE__: 'user';
};
}
export interface searchType {
QUERY: string; //;
FUZZINNESS: boolean;
AUTOCORRECT: boolean;
TOP_RESULT: [albumType | artistType | trackType | playlistInfo | artistType | unknown] | [];
ORDER: [
'TOP_RESULT',
'TRACK',
'PLAYLIST',
'ALBUM',
'ARTIST',
'LIVESTREAM',
'EPISODE',
'SHOW',
'CHANNEL',
'RADIO',
'USER',
'LYRICS',
];
ALBUM: albumTracksType;
ARTIST: albumTracksType;
TRACK: albumTracksType;
PLAYLIST: albumTracksType;
RADIO: albumTracksType;
SHOW: albumTracksType;
USER: albumTracksType;
LIVESTREAM: albumTracksType;
CHANNEL: albumTracksType;
}

13
tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"outDir": "dist",
"target": "esnext",
"module": "commonjs",
"moduleResolution": "node",
"strict": true,
"declaration": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}

2745
yarn.lock Normal file

File diff suppressed because it is too large Load Diff