gerdur-core 0.0.0-probe → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/LICENSE +1 -0
- package/README.md +188 -1
- package/dist/api/api.d.ts +78 -0
- package/dist/api/api.js +182 -0
- package/dist/api/cache.d.ts +3 -0
- package/dist/api/cache.js +12 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +18 -0
- package/dist/api/request.d.ts +23 -0
- package/dist/api/request.js +88 -0
- package/dist/converter/deezer.d.ts +3 -0
- package/dist/converter/deezer.js +45 -0
- package/dist/converter/index.d.ts +5 -0
- package/dist/converter/index.js +34 -0
- package/dist/converter/parse.d.ts +18 -0
- package/dist/converter/parse.js +176 -0
- package/dist/converter/spotify.d.ts +40 -0
- package/dist/converter/spotify.js +154 -0
- package/dist/converter/tidal.d.ts +146 -0
- package/dist/converter/tidal.js +199 -0
- package/dist/converter/youtube.d.ts +6 -0
- package/dist/converter/youtube.js +102 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +24 -0
- package/dist/lib/decrypt.d.ts +9 -0
- package/dist/lib/decrypt.js +63 -0
- package/dist/lib/fast-lru.d.ts +63 -0
- package/dist/lib/fast-lru.js +121 -0
- package/dist/lib/get-url.d.ts +16 -0
- package/dist/lib/get-url.js +154 -0
- package/dist/lib/http.d.ts +66 -0
- package/dist/lib/http.js +235 -0
- package/dist/lib/metaflac-js.d.ts +124 -0
- package/dist/lib/metaflac-js.js +343 -0
- package/dist/lib/request.d.ts +18 -0
- package/dist/lib/request.js +86 -0
- package/dist/metadata-writer/abumCover.d.ts +10 -0
- package/dist/metadata-writer/abumCover.js +37 -0
- package/dist/metadata-writer/flacmetata.d.ts +3 -0
- package/dist/metadata-writer/flacmetata.js +80 -0
- package/dist/metadata-writer/getTrackLyrics.d.ts +2 -0
- package/dist/metadata-writer/getTrackLyrics.js +26 -0
- package/dist/metadata-writer/id3.d.ts +3 -0
- package/dist/metadata-writer/id3.js +119 -0
- package/dist/metadata-writer/index.d.ts +9 -0
- package/dist/metadata-writer/index.js +44 -0
- package/dist/metadata-writer/musixmatchLyrics.d.ts +1 -0
- package/dist/metadata-writer/musixmatchLyrics.js +39 -0
- package/dist/metadata-writer/useragents.d.ts +1 -0
- package/dist/metadata-writer/useragents.js +86 -0
- package/dist/types/album.d.ts +123 -0
- package/dist/types/album.js +2 -0
- package/dist/types/artist.d.ts +36 -0
- package/dist/types/artist.js +2 -0
- package/dist/types/channel.d.ts +33 -0
- package/dist/types/channel.js +2 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +26 -0
- package/dist/types/playlist-channel.d.ts +81 -0
- package/dist/types/playlist-channel.js +2 -0
- package/dist/types/playlist.d.ts +49 -0
- package/dist/types/playlist.js +2 -0
- package/dist/types/profile.d.ts +29 -0
- package/dist/types/profile.js +2 -0
- package/dist/types/radio.d.ts +7 -0
- package/dist/types/radio.js +2 -0
- package/dist/types/search.d.ts +79 -0
- package/dist/types/search.js +2 -0
- package/dist/types/show.d.ts +61 -0
- package/dist/types/show.js +2 -0
- package/dist/types/tracks.d.ts +144 -0
- package/dist/types/tracks.js +2 -0
- package/dist/types/user.d.ts +16 -0
- package/dist/types/user.js +2 -0
- package/package.json +67 -5
- package/types/index.d.ts +1 -0
- package/types/index.js +3 -0
- package/types/package.json +4 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.playlist2Deezer = exports.artist2Deezer = exports.albumArtToUrl = exports.getPlaylistTracks = exports.getPlaylist = exports.getArtistTopTracks = exports.getArtistAlbums = exports.getAlbumTracks = exports.album2deezer = exports.getAlbum = exports.track2deezer = exports.getTrack = void 0;
|
|
7
|
+
const p_queue_1 = __importDefault(require("p-queue"));
|
|
8
|
+
const deezer_1 = require("./deezer");
|
|
9
|
+
const http_1 = require("../lib/http");
|
|
10
|
+
const client = new http_1.HttpClient({
|
|
11
|
+
baseURL: 'https://api.tidal.com/v1/',
|
|
12
|
+
timeout: 15000,
|
|
13
|
+
headers: {
|
|
14
|
+
'user-agent': 'TIDAL/3704 CFNetwork/1220.1 Darwin/20.3.0',
|
|
15
|
+
'x-tidal-token': 'i4ZDjcyhed7Mu47q',
|
|
16
|
+
},
|
|
17
|
+
params: { limit: 500, countryCode: 'US' },
|
|
18
|
+
});
|
|
19
|
+
const queue = new p_queue_1.default({ concurrency: 25 });
|
|
20
|
+
/**
|
|
21
|
+
* Get a track by its id
|
|
22
|
+
* @param string} id - track id
|
|
23
|
+
* @example tidal.getTrack('64975224')
|
|
24
|
+
*/
|
|
25
|
+
const getTrack = async (id) => {
|
|
26
|
+
const { data } = await client.get(`tracks/${id}`);
|
|
27
|
+
return data;
|
|
28
|
+
};
|
|
29
|
+
exports.getTrack = getTrack;
|
|
30
|
+
/**
|
|
31
|
+
* Convert a tidal track to deezer
|
|
32
|
+
* @param {string} id - track id
|
|
33
|
+
*/
|
|
34
|
+
const track2deezer = async (id) => {
|
|
35
|
+
const track = await (0, exports.getTrack)(id);
|
|
36
|
+
return await (0, deezer_1.isrc2deezer)(track.title, track.isrc);
|
|
37
|
+
};
|
|
38
|
+
exports.track2deezer = track2deezer;
|
|
39
|
+
/**
|
|
40
|
+
* Get an album by its id
|
|
41
|
+
* @param {string} id - album id
|
|
42
|
+
* @example tidal.getAlbum('80216363')
|
|
43
|
+
*/
|
|
44
|
+
const getAlbum = async (id) => {
|
|
45
|
+
const { data } = await client.get(`albums/${id}`);
|
|
46
|
+
return data;
|
|
47
|
+
};
|
|
48
|
+
exports.getAlbum = getAlbum;
|
|
49
|
+
/**
|
|
50
|
+
* Convert a tidal albums to deezer
|
|
51
|
+
* @param {string} id - album id
|
|
52
|
+
*/
|
|
53
|
+
const album2deezer = async (id) => {
|
|
54
|
+
const album = await (0, exports.getAlbum)(id);
|
|
55
|
+
return await (0, deezer_1.upc2deezer)(album.title, album.upc);
|
|
56
|
+
};
|
|
57
|
+
exports.album2deezer = album2deezer;
|
|
58
|
+
/**
|
|
59
|
+
* Get album tracks by album id
|
|
60
|
+
* @param {string} id - album id
|
|
61
|
+
* @example tidal.getAlbumTracks('80216363')
|
|
62
|
+
*/
|
|
63
|
+
const getAlbumTracks = async (id) => {
|
|
64
|
+
const { data } = await client.get(`albums/${id}/tracks`);
|
|
65
|
+
return data;
|
|
66
|
+
};
|
|
67
|
+
exports.getAlbumTracks = getAlbumTracks;
|
|
68
|
+
/**
|
|
69
|
+
* Get artist albums by artist id
|
|
70
|
+
* @param {string} id - artist id
|
|
71
|
+
* @example tidal.getArtistAlbums('3575680')
|
|
72
|
+
*/
|
|
73
|
+
const getArtistAlbums = async (id) => {
|
|
74
|
+
const { data } = await client.get(`artists/${id}/albums`);
|
|
75
|
+
data.items = data.items.filter((item) => item.artist.id.toString() === id);
|
|
76
|
+
return data;
|
|
77
|
+
};
|
|
78
|
+
exports.getArtistAlbums = getArtistAlbums;
|
|
79
|
+
/**
|
|
80
|
+
* Get top tracks by artist
|
|
81
|
+
* @param {string} id - artist id
|
|
82
|
+
* @example tidal.getArtistTopTracks('3575680')
|
|
83
|
+
*/
|
|
84
|
+
const getArtistTopTracks = async (id) => {
|
|
85
|
+
const { data } = await client.get(`artists/${id}/toptracks`);
|
|
86
|
+
data.items = data.items.filter((item) => item.artist.id.toString() === id);
|
|
87
|
+
return data;
|
|
88
|
+
};
|
|
89
|
+
exports.getArtistTopTracks = getArtistTopTracks;
|
|
90
|
+
/**
|
|
91
|
+
* Get a playlist by its uuid
|
|
92
|
+
* @param {string} uuid - playlist uuid
|
|
93
|
+
* @example tidal.getPlaylist('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
94
|
+
*/
|
|
95
|
+
const getPlaylist = async (uuid) => {
|
|
96
|
+
const { data } = await client.get(`playlists/${uuid}`);
|
|
97
|
+
return data;
|
|
98
|
+
};
|
|
99
|
+
exports.getPlaylist = getPlaylist;
|
|
100
|
+
/**
|
|
101
|
+
* Get playlist tracks by playlist uuid
|
|
102
|
+
* @param {string} uuid - playlist uuid
|
|
103
|
+
* @example tidal.getPlaylistTracks('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
104
|
+
*/
|
|
105
|
+
const getPlaylistTracks = async (uuid) => {
|
|
106
|
+
const { data } = await client.get(`playlists/${uuid}/tracks`);
|
|
107
|
+
return data;
|
|
108
|
+
};
|
|
109
|
+
exports.getPlaylistTracks = getPlaylistTracks;
|
|
110
|
+
/**
|
|
111
|
+
* Get valid urls to album art
|
|
112
|
+
* @param {string} uuid - album art uuid (can be found as cover property in album object)
|
|
113
|
+
* @example tidal.albumArtToUrl('9a56f482-e9cf-46c3-bb21-82710e7854d4')
|
|
114
|
+
* @returns {Object}
|
|
115
|
+
*/
|
|
116
|
+
const albumArtToUrl = (uuid) => {
|
|
117
|
+
const baseUrl = `https://resources.tidal.com/images/${uuid.replace(/-/g, '/')}`;
|
|
118
|
+
return {
|
|
119
|
+
sm: `${baseUrl}/160x160.jpg`,
|
|
120
|
+
md: `${baseUrl}/320x320.jpg`,
|
|
121
|
+
lg: `${baseUrl}/640x640.jpg`,
|
|
122
|
+
xl: `${baseUrl}/1280x1280.jpg`,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
exports.albumArtToUrl = albumArtToUrl;
|
|
126
|
+
/**
|
|
127
|
+
* Find tidal artists tracks on deezer
|
|
128
|
+
* @param {string} id - artist id
|
|
129
|
+
* @example tidal.artist2Deezer('3575680')
|
|
130
|
+
*/
|
|
131
|
+
const artist2Deezer = async (id, onError) => {
|
|
132
|
+
const { items } = await (0, exports.getArtistTopTracks)(id);
|
|
133
|
+
const tracks = [];
|
|
134
|
+
await queue.addAll(items.map((item, index) => {
|
|
135
|
+
return async () => {
|
|
136
|
+
try {
|
|
137
|
+
const track = await (0, deezer_1.isrc2deezer)(item.title, item.isrc);
|
|
138
|
+
// console.log(signale.success(`Track #${index}: ${item.name}`));
|
|
139
|
+
tracks.push(track);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (onError) {
|
|
143
|
+
onError(item, index, err);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}));
|
|
148
|
+
return tracks;
|
|
149
|
+
};
|
|
150
|
+
exports.artist2Deezer = artist2Deezer;
|
|
151
|
+
/**
|
|
152
|
+
* Find same set of playlist tracks on deezer
|
|
153
|
+
* @param {string} uuid - playlist uuid
|
|
154
|
+
* @example tidal.playlist2Deezer('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
155
|
+
*/
|
|
156
|
+
const playlist2Deezer = async (uuid, onError) => {
|
|
157
|
+
const body = await (0, exports.getPlaylist)(uuid);
|
|
158
|
+
const { items } = await (0, exports.getPlaylistTracks)(uuid);
|
|
159
|
+
const tracks = [];
|
|
160
|
+
await queue.addAll(items.map((item, index) => {
|
|
161
|
+
return async () => {
|
|
162
|
+
try {
|
|
163
|
+
const track = await (0, deezer_1.isrc2deezer)(item.title, item.isrc);
|
|
164
|
+
// console.log(signale.success(`Track #${index}: ${item.track.name}`));
|
|
165
|
+
track.TRACK_POSITION = index + 1;
|
|
166
|
+
tracks.push(track);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
if (onError) {
|
|
170
|
+
onError(item, index, err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}));
|
|
175
|
+
const userId = body.creator.id.toString();
|
|
176
|
+
const playlistInfoData = {
|
|
177
|
+
PLAYLIST_ID: body.uuid,
|
|
178
|
+
PARENT_USERNAME: userId,
|
|
179
|
+
PARENT_USER_ID: userId,
|
|
180
|
+
PICTURE_TYPE: 'cover',
|
|
181
|
+
PLAYLIST_PICTURE: body.image,
|
|
182
|
+
TITLE: body.title,
|
|
183
|
+
TYPE: '0',
|
|
184
|
+
STATUS: '0',
|
|
185
|
+
USER_ID: userId,
|
|
186
|
+
DATE_ADD: body.created,
|
|
187
|
+
DATE_MOD: body.lastUpdated,
|
|
188
|
+
DATE_CREATE: body.created,
|
|
189
|
+
NB_SONG: body.numberOfTracks,
|
|
190
|
+
NB_FAN: 0,
|
|
191
|
+
CHECKSUM: body.created,
|
|
192
|
+
HAS_ARTIST_LINKED: false,
|
|
193
|
+
IS_SPONSORED: false,
|
|
194
|
+
IS_EDITO: false,
|
|
195
|
+
__TYPE__: 'playlist',
|
|
196
|
+
};
|
|
197
|
+
return [playlistInfoData, tracks];
|
|
198
|
+
};
|
|
199
|
+
exports.playlist2Deezer = playlist2Deezer;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.track2deezer = void 0;
|
|
4
|
+
const node_html_parser_1 = require("node-html-parser");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const http_1 = require("../lib/http");
|
|
7
|
+
const parseInlineObject = (document, variableName) => {
|
|
8
|
+
const prefix = `${variableName} = `;
|
|
9
|
+
const start = document.indexOf(prefix);
|
|
10
|
+
if (start === -1) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const jsonStart = start + prefix.length;
|
|
14
|
+
let depth = 0;
|
|
15
|
+
let isInString = false;
|
|
16
|
+
let isEscaped = false;
|
|
17
|
+
for (let index = jsonStart; index < document.length; index++) {
|
|
18
|
+
const char = document[index];
|
|
19
|
+
if (isInString) {
|
|
20
|
+
if (isEscaped) {
|
|
21
|
+
isEscaped = false;
|
|
22
|
+
}
|
|
23
|
+
else if (char === '\\') {
|
|
24
|
+
isEscaped = true;
|
|
25
|
+
}
|
|
26
|
+
else if (char === '"') {
|
|
27
|
+
isInString = false;
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (char === '"') {
|
|
32
|
+
isInString = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === '{') {
|
|
36
|
+
depth += 1;
|
|
37
|
+
}
|
|
38
|
+
else if (char === '}') {
|
|
39
|
+
depth -= 1;
|
|
40
|
+
if (depth === 0) {
|
|
41
|
+
return JSON.parse(document.slice(jsonStart, index + 1));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
const searchTrackByTitle = async (title) => {
|
|
48
|
+
const normalizedTitle = title
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/\(off.*\)/i, '')
|
|
51
|
+
.replace(/official video/gi, '')
|
|
52
|
+
.replace(/lyrics?/gi, '')
|
|
53
|
+
.replace(/ft.*/i, '')
|
|
54
|
+
.replace(/[,-.]/g, ' ')
|
|
55
|
+
.replace(/ +/g, ' ')
|
|
56
|
+
.trim();
|
|
57
|
+
if (!normalizedTitle) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const { TRACK } = await (0, api_1.searchMusic)(normalizedTitle, ['TRACK'], 20);
|
|
61
|
+
const filteredTracks = TRACK.data.filter((track) => normalizedTitle.includes(track.ART_NAME.toLowerCase()));
|
|
62
|
+
return filteredTracks[0] || TRACK.data[0];
|
|
63
|
+
};
|
|
64
|
+
const getTrack = async (id) => {
|
|
65
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
66
|
+
const response = await (0, http_1.getText)(`https://www.youtube.com/watch?v=${id}&hl=en`);
|
|
67
|
+
const scripts = (0, node_html_parser_1.parse)(response)
|
|
68
|
+
.querySelectorAll('script')
|
|
69
|
+
.map((script) => script.text)
|
|
70
|
+
.join('\n');
|
|
71
|
+
const playerResponse = parseInlineObject(scripts, 'var ytInitialPlayerResponse');
|
|
72
|
+
const initialData = parseInlineObject(scripts, 'var ytInitialData');
|
|
73
|
+
const rows = (_j = (_h = (_g = (_f = (_e = (_d = (_c = (_b = (_a = initialData === null || initialData === void 0 ? void 0 : initialData.contents) === null || _a === void 0 ? void 0 : _a.twoColumnWatchNextResults) === null || _b === void 0 ? void 0 : _b.results) === null || _c === void 0 ? void 0 : _c.results) === null || _d === void 0 ? void 0 : _d.contents) === null || _e === void 0 ? void 0 : _e.find((content) => Boolean(content.videoSecondaryInfoRenderer))) === null || _f === void 0 ? void 0 : _f.videoSecondaryInfoRenderer) === null || _g === void 0 ? void 0 : _g.metadataRowContainer) === null || _h === void 0 ? void 0 : _h.metadataRowContainerRenderer) === null || _j === void 0 ? void 0 : _j.rows;
|
|
74
|
+
if (rows && rows.length > 0) {
|
|
75
|
+
const song = rows.find((row) => { var _a, _b; return ((_b = (_a = row.metadataRowRenderer) === null || _a === void 0 ? void 0 : _a.title) === null || _b === void 0 ? void 0 : _b.simpleText) === 'Song'; });
|
|
76
|
+
const artist = rows.find((row) => { var _a, _b; return ((_b = (_a = row.metadataRowRenderer) === null || _a === void 0 ? void 0 : _a.title) === null || _b === void 0 ? void 0 : _b.simpleText) === 'Artist'; });
|
|
77
|
+
const songName = (_m = (_l = (_k = song === null || song === void 0 ? void 0 : song.metadataRowRenderer) === null || _k === void 0 ? void 0 : _k.contents) === null || _l === void 0 ? void 0 : _l[0]) === null || _m === void 0 ? void 0 : _m.simpleText;
|
|
78
|
+
const artistName = (_s = (_r = (_q = (_p = (_o = artist === null || artist === void 0 ? void 0 : artist.metadataRowRenderer) === null || _o === void 0 ? void 0 : _o.contents) === null || _p === void 0 ? void 0 : _p[0]) === null || _q === void 0 ? void 0 : _q.runs) === null || _r === void 0 ? void 0 : _r[0]) === null || _s === void 0 ? void 0 : _s.text;
|
|
79
|
+
if (songName && artistName) {
|
|
80
|
+
const { TRACK } = await (0, api_1.searchAlternative)(artistName, songName, 1);
|
|
81
|
+
if (TRACK.data[0]) {
|
|
82
|
+
return TRACK.data[0];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if ((_t = playerResponse === null || playerResponse === void 0 ? void 0 : playerResponse.videoDetails) === null || _t === void 0 ? void 0 : _t.title) {
|
|
87
|
+
return await searchTrackByTitle(playerResponse.videoDetails.title);
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Convert a youtube video to track by video id
|
|
93
|
+
* @param {String} id - video id
|
|
94
|
+
*/
|
|
95
|
+
const track2deezer = async (id) => {
|
|
96
|
+
const track = await getTrack(id);
|
|
97
|
+
if (track) {
|
|
98
|
+
return track;
|
|
99
|
+
}
|
|
100
|
+
throw new Error('No track found for youtube video ' + id);
|
|
101
|
+
};
|
|
102
|
+
exports.track2deezer = track2deezer;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.initDeezerApi = void 0;
|
|
18
|
+
var request_1 = require("./lib/request");
|
|
19
|
+
Object.defineProperty(exports, "initDeezerApi", { enumerable: true, get: function () { return request_1.initDeezerApi; } });
|
|
20
|
+
__exportStar(require("./api"), exports);
|
|
21
|
+
__exportStar(require("./converter"), exports);
|
|
22
|
+
__exportStar(require("./lib/decrypt"), exports);
|
|
23
|
+
__exportStar(require("./lib/get-url"), exports);
|
|
24
|
+
__exportStar(require("./metadata-writer"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { trackType } from '../types';
|
|
3
|
+
export declare const getSongFileName: ({ MD5_ORIGIN, SNG_ID, MEDIA_VERSION }: trackType, quality: number) => string;
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param source Downloaded song from `getTrackDownloadUrl`
|
|
7
|
+
* @param trackId Song ID as string
|
|
8
|
+
*/
|
|
9
|
+
export declare const decryptDownload: (source: Buffer, trackId: string) => Buffer;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.decryptDownload = exports.getSongFileName = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const md5 = (data, type = 'ascii') => {
|
|
9
|
+
const md5sum = crypto_1.default.createHash('md5');
|
|
10
|
+
md5sum.update(data.toString(), type);
|
|
11
|
+
return md5sum.digest('hex');
|
|
12
|
+
};
|
|
13
|
+
const getSongFileName = ({ MD5_ORIGIN, SNG_ID, MEDIA_VERSION }, quality) => {
|
|
14
|
+
if (!MD5_ORIGIN) {
|
|
15
|
+
throw new Error(`Missing MD5_ORIGIN for track ${SNG_ID}`);
|
|
16
|
+
}
|
|
17
|
+
const step1 = [MD5_ORIGIN, quality, SNG_ID, MEDIA_VERSION].join('¤');
|
|
18
|
+
let step2 = md5(step1) + '¤' + step1 + '¤';
|
|
19
|
+
while (step2.length % 16 > 0)
|
|
20
|
+
step2 += ' ';
|
|
21
|
+
return crypto_1.default.createCipheriv('aes-128-ecb', 'jo6aey6haid2Teih', '').update(step2, 'ascii', 'hex');
|
|
22
|
+
};
|
|
23
|
+
exports.getSongFileName = getSongFileName;
|
|
24
|
+
const getBlowfishKey = (trackId) => {
|
|
25
|
+
const SECRET = 'g4el58wc' + '0zvf9na1';
|
|
26
|
+
const idMd5 = md5(trackId);
|
|
27
|
+
let bfKey = '';
|
|
28
|
+
for (let i = 0; i < 16; i++) {
|
|
29
|
+
bfKey += String.fromCharCode(idMd5.charCodeAt(i) ^ idMd5.charCodeAt(i + 16) ^ SECRET.charCodeAt(i));
|
|
30
|
+
}
|
|
31
|
+
return bfKey;
|
|
32
|
+
};
|
|
33
|
+
const decryptChunk = (chunk, blowFishKey) => {
|
|
34
|
+
const cipher = crypto_1.default.createDecipheriv('bf-cbc', blowFishKey, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]));
|
|
35
|
+
cipher.setAutoPadding(false);
|
|
36
|
+
return Buffer.concat([cipher.update(chunk), cipher.final()]);
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param source Downloaded song from `getTrackDownloadUrl`
|
|
41
|
+
* @param trackId Song ID as string
|
|
42
|
+
*/
|
|
43
|
+
const decryptDownload = (source, trackId) => {
|
|
44
|
+
const chunkSize = 2048;
|
|
45
|
+
const blowFishKey = getBlowfishKey(trackId);
|
|
46
|
+
let chunkIndex = 0;
|
|
47
|
+
let position = 0;
|
|
48
|
+
const destBuffer = Buffer.alloc(source.length);
|
|
49
|
+
while (position < source.length) {
|
|
50
|
+
const currentChunkSize = Math.min(chunkSize, source.length - position);
|
|
51
|
+
const sourceChunk = source.subarray(position, position + currentChunkSize);
|
|
52
|
+
if (chunkIndex % 3 > 0 || currentChunkSize < chunkSize) {
|
|
53
|
+
sourceChunk.copy(destBuffer, position);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
decryptChunk(sourceChunk, blowFishKey).copy(destBuffer, position);
|
|
57
|
+
}
|
|
58
|
+
position += currentChunkSize;
|
|
59
|
+
chunkIndex++;
|
|
60
|
+
}
|
|
61
|
+
return destBuffer;
|
|
62
|
+
};
|
|
63
|
+
exports.decryptDownload = decryptDownload;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fast LRU & TTL cache
|
|
3
|
+
* @param {Integer} options.max - Max entries in the cache. @default Infinity
|
|
4
|
+
* @param {Integer} options.ttl - Timeout before removing entries. @default Infinity
|
|
5
|
+
*/
|
|
6
|
+
declare class FastLRU {
|
|
7
|
+
_max: number;
|
|
8
|
+
_ttl: number;
|
|
9
|
+
_cache: Map<any, any>;
|
|
10
|
+
_meta: {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
constructor({ maxSize, ttl }: {
|
|
14
|
+
maxSize?: number | undefined;
|
|
15
|
+
ttl?: number | undefined;
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Add new entry
|
|
19
|
+
*/
|
|
20
|
+
set(key: string, value: any, ttl?: number): void;
|
|
21
|
+
/**
|
|
22
|
+
* Get entry
|
|
23
|
+
*/
|
|
24
|
+
get(key: string): any;
|
|
25
|
+
/**
|
|
26
|
+
* Get without hitting hits
|
|
27
|
+
*/
|
|
28
|
+
peek(key: string): any;
|
|
29
|
+
/**
|
|
30
|
+
* Remove entry
|
|
31
|
+
*/
|
|
32
|
+
delete(key: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Remove all entries
|
|
35
|
+
*/
|
|
36
|
+
clear(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Check has entry
|
|
39
|
+
*/
|
|
40
|
+
has(key: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Get all kies
|
|
43
|
+
* @returns {Iterator} Iterator on all kies
|
|
44
|
+
*/
|
|
45
|
+
keys(): IterableIterator<any>;
|
|
46
|
+
/**
|
|
47
|
+
* Iterate over values
|
|
48
|
+
*/
|
|
49
|
+
values(): IterableIterator<any>;
|
|
50
|
+
/**
|
|
51
|
+
* Iterate over entries
|
|
52
|
+
*/
|
|
53
|
+
entries(): IterableIterator<[any, any]>;
|
|
54
|
+
/**
|
|
55
|
+
* For each
|
|
56
|
+
*/
|
|
57
|
+
forEach(cb: any): void;
|
|
58
|
+
/**
|
|
59
|
+
* Entries total size
|
|
60
|
+
*/
|
|
61
|
+
get size(): number;
|
|
62
|
+
}
|
|
63
|
+
export default FastLRU;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Fast LRU & TTL cache
|
|
5
|
+
* @param {Integer} options.max - Max entries in the cache. @default Infinity
|
|
6
|
+
* @param {Integer} options.ttl - Timeout before removing entries. @default Infinity
|
|
7
|
+
*/
|
|
8
|
+
class FastLRU {
|
|
9
|
+
constructor({ maxSize = Infinity, ttl = 0 }) {
|
|
10
|
+
// Default options
|
|
11
|
+
this._max = maxSize;
|
|
12
|
+
this._ttl = ttl;
|
|
13
|
+
this._cache = new Map();
|
|
14
|
+
// Metadata for entries
|
|
15
|
+
this._meta = {};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Add new entry
|
|
19
|
+
*/
|
|
20
|
+
set(key, value, ttl = this._ttl) {
|
|
21
|
+
// Execution time
|
|
22
|
+
const time = Date.now();
|
|
23
|
+
// Remvove least recently used elements if exceeds max bytes
|
|
24
|
+
if (this._cache.size >= this._max) {
|
|
25
|
+
const items = Object.values(this._meta);
|
|
26
|
+
if (this._ttl > 0) {
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
if (item.expire < time) {
|
|
29
|
+
this.delete(item.key);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (this._cache.size >= this._max) {
|
|
34
|
+
const least = items.sort((a, b) => a.hits - b.hits)[0];
|
|
35
|
+
this.delete(least.key);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Override if key already set
|
|
39
|
+
this._cache.set(key, value);
|
|
40
|
+
this._meta[key] = {
|
|
41
|
+
key,
|
|
42
|
+
hits: 0,
|
|
43
|
+
expire: time + ttl,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get entry
|
|
48
|
+
*/
|
|
49
|
+
get(key) {
|
|
50
|
+
if (this._cache.has(key)) {
|
|
51
|
+
const item = this._cache.get(key);
|
|
52
|
+
if (this._ttl > 0) {
|
|
53
|
+
const time = Date.now();
|
|
54
|
+
if (this._meta[key].expire < time) {
|
|
55
|
+
this.delete(key);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this._meta[key].hits++;
|
|
60
|
+
return item;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get without hitting hits
|
|
65
|
+
*/
|
|
66
|
+
peek(key) {
|
|
67
|
+
return this._cache.get(key);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Remove entry
|
|
71
|
+
*/
|
|
72
|
+
delete(key) {
|
|
73
|
+
delete this._meta[key];
|
|
74
|
+
this._cache.delete(key);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Remove all entries
|
|
78
|
+
*/
|
|
79
|
+
clear() {
|
|
80
|
+
this._cache.clear();
|
|
81
|
+
this._meta = {};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check has entry
|
|
85
|
+
*/
|
|
86
|
+
has(key) {
|
|
87
|
+
return this._cache.has(key);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get all kies
|
|
91
|
+
* @returns {Iterator} Iterator on all kies
|
|
92
|
+
*/
|
|
93
|
+
keys() {
|
|
94
|
+
return this._cache.keys();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Iterate over values
|
|
98
|
+
*/
|
|
99
|
+
values() {
|
|
100
|
+
return this._cache.values();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Iterate over entries
|
|
104
|
+
*/
|
|
105
|
+
entries() {
|
|
106
|
+
return this._cache.entries();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* For each
|
|
110
|
+
*/
|
|
111
|
+
forEach(cb) {
|
|
112
|
+
return this._cache.forEach(cb);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Entries total size
|
|
116
|
+
*/
|
|
117
|
+
get size() {
|
|
118
|
+
return this._cache.size;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.default = FastLRU;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { trackType } from '../types';
|
|
2
|
+
export declare class WrongLicense extends Error {
|
|
3
|
+
constructor(format: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class GeoBlocked extends Error {
|
|
6
|
+
constructor(country: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* @param track Track info json returned from `getTrackInfo`
|
|
10
|
+
* @param quality 1 = 128kbps, 3 = 320kbps and 9 = flac (around 1411kbps)
|
|
11
|
+
*/
|
|
12
|
+
export declare const getTrackDownloadUrl: (track: trackType, quality: number) => Promise<{
|
|
13
|
+
trackUrl: string;
|
|
14
|
+
isEncrypted: boolean;
|
|
15
|
+
fileSize: number;
|
|
16
|
+
} | null>;
|