q-fi-core 2.3.1
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 +109 -0
- package/LICENSE +1 -0
- package/README.md +186 -0
- 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 +69 -0
- package/types/index.d.ts +1 -0
- package/types/index.js +3 -0
- package/types/package.json +4 -0
|
@@ -0,0 +1,88 @@
|
|
|
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.requestPublicApi = exports.requestGet = exports.requestLight = exports.request = void 0;
|
|
7
|
+
const request_1 = __importDefault(require("../lib/request"));
|
|
8
|
+
const cache_1 = __importDefault(require("./cache"));
|
|
9
|
+
/**
|
|
10
|
+
* Make POST requests to deezer api
|
|
11
|
+
* @param {Object} body post body
|
|
12
|
+
* @param {String} method request method
|
|
13
|
+
*/
|
|
14
|
+
const request = async (body, method) => {
|
|
15
|
+
const cacheKey = method + ':' + Object.entries(body).join(':');
|
|
16
|
+
const cache = cache_1.default.get(cacheKey);
|
|
17
|
+
if (cache) {
|
|
18
|
+
return cache;
|
|
19
|
+
}
|
|
20
|
+
const { data: { error, results }, } = await request_1.default.post('/gateway.php', body, { params: { method } });
|
|
21
|
+
if (Object.keys(results).length > 0) {
|
|
22
|
+
cache_1.default.set(cacheKey, results);
|
|
23
|
+
return results;
|
|
24
|
+
}
|
|
25
|
+
const errorMessage = Object.entries(error).join(', ');
|
|
26
|
+
throw new Error(errorMessage);
|
|
27
|
+
};
|
|
28
|
+
exports.request = request;
|
|
29
|
+
/**
|
|
30
|
+
* Make POST requests to deezer api
|
|
31
|
+
* @param {Object} body post body
|
|
32
|
+
* @param {String} method request method
|
|
33
|
+
*/
|
|
34
|
+
const requestLight = async (body, method) => {
|
|
35
|
+
const cacheKey = method + ':' + Object.entries(body).join(':');
|
|
36
|
+
const cache = cache_1.default.get(cacheKey);
|
|
37
|
+
if (cache) {
|
|
38
|
+
return cache;
|
|
39
|
+
}
|
|
40
|
+
const { data: { error, results }, } = await request_1.default.post('https://www.deezer.com/ajax/gw-light.php', body, {
|
|
41
|
+
params: { method, api_version: '1.0' },
|
|
42
|
+
});
|
|
43
|
+
if (Object.keys(results).length > 0) {
|
|
44
|
+
cache_1.default.set(cacheKey, results);
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
const errorMessage = Object.entries(error).join(', ');
|
|
48
|
+
throw new Error(errorMessage);
|
|
49
|
+
};
|
|
50
|
+
exports.requestLight = requestLight;
|
|
51
|
+
/**
|
|
52
|
+
* Make GET requests to deezer public api
|
|
53
|
+
* @param {String} method request method
|
|
54
|
+
* @param {Object} params request parameters
|
|
55
|
+
*/
|
|
56
|
+
const requestGet = async (method, params = {}, key = 'get_request') => {
|
|
57
|
+
const cacheKey = method + key;
|
|
58
|
+
const cache = cache_1.default.get(cacheKey);
|
|
59
|
+
if (cache) {
|
|
60
|
+
return cache;
|
|
61
|
+
}
|
|
62
|
+
const { data: { error, results }, } = await request_1.default.get('/gateway.php', { params: { method, ...params } });
|
|
63
|
+
if (Object.keys(results).length > 0) {
|
|
64
|
+
cache_1.default.set(cacheKey, results);
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
const errorMessage = Object.entries(error).join(', ');
|
|
68
|
+
throw new Error(errorMessage);
|
|
69
|
+
};
|
|
70
|
+
exports.requestGet = requestGet;
|
|
71
|
+
/**
|
|
72
|
+
* Make GET requests to deezer public api
|
|
73
|
+
* @param {String} slug endpoint
|
|
74
|
+
*/
|
|
75
|
+
const requestPublicApi = async (slug) => {
|
|
76
|
+
const cache = cache_1.default.get(slug);
|
|
77
|
+
if (cache) {
|
|
78
|
+
return cache;
|
|
79
|
+
}
|
|
80
|
+
const { data } = await request_1.default.get('https://api.deezer.com' + slug);
|
|
81
|
+
if (data.error) {
|
|
82
|
+
const errorMessage = Object.entries(data.error).join(', ');
|
|
83
|
+
throw new Error(errorMessage);
|
|
84
|
+
}
|
|
85
|
+
cache_1.default.set(slug, data);
|
|
86
|
+
return data;
|
|
87
|
+
};
|
|
88
|
+
exports.requestPublicApi = requestPublicApi;
|
|
@@ -0,0 +1,45 @@
|
|
|
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.upc2deezer = exports.isrc2deezer = void 0;
|
|
7
|
+
const delay_1 = __importDefault(require("delay"));
|
|
8
|
+
const api_1 = require("../api");
|
|
9
|
+
const http_1 = require("../lib/http");
|
|
10
|
+
const instance = new http_1.HttpClient({ baseURL: 'https://api.deezer.com/', timeout: 15000 });
|
|
11
|
+
const requestLookup = async (path) => {
|
|
12
|
+
const { data } = await instance.get(path);
|
|
13
|
+
if (data.error && data.error.code === 4) {
|
|
14
|
+
await delay_1.default.range(1000, 1500);
|
|
15
|
+
return await requestLookup(path);
|
|
16
|
+
}
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
const isrc2deezer = async (name, isrc) => {
|
|
20
|
+
if (!isrc) {
|
|
21
|
+
throw new Error('ISRC code not found for ' + name);
|
|
22
|
+
}
|
|
23
|
+
const data = await requestLookup('track/isrc:' + isrc);
|
|
24
|
+
if (data.error) {
|
|
25
|
+
throw new Error(`No match on deezer for ${name} (ISRC: ${isrc})`);
|
|
26
|
+
}
|
|
27
|
+
return await (0, api_1.getTrackInfo)(data.id);
|
|
28
|
+
};
|
|
29
|
+
exports.isrc2deezer = isrc2deezer;
|
|
30
|
+
const upc2deezer = async (name, upc) => {
|
|
31
|
+
if (!upc) {
|
|
32
|
+
throw new Error('UPC code not found for ' + name);
|
|
33
|
+
}
|
|
34
|
+
else if (upc.length > 12 && upc.startsWith('0')) {
|
|
35
|
+
upc = upc.slice(-12);
|
|
36
|
+
}
|
|
37
|
+
const data = await requestLookup('album/upc:' + upc);
|
|
38
|
+
if (data.error) {
|
|
39
|
+
throw new Error(`No match on deezer for ${name} (UPC: ${upc})`);
|
|
40
|
+
}
|
|
41
|
+
const albumInfo = await (0, api_1.getAlbumInfo)(data.id);
|
|
42
|
+
const albumTracks = await (0, api_1.getAlbumTracks)(data.id);
|
|
43
|
+
return [albumInfo, albumTracks.data];
|
|
44
|
+
};
|
|
45
|
+
exports.upc2deezer = upc2deezer;
|
|
@@ -0,0 +1,34 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
19
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
|
+
};
|
|
21
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
22
|
+
if (mod && mod.__esModule) return mod;
|
|
23
|
+
var result = {};
|
|
24
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
25
|
+
__setModuleDefault(result, mod);
|
|
26
|
+
return result;
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.youtube = exports.spotify = exports.tidal = void 0;
|
|
30
|
+
__exportStar(require("./parse"), exports);
|
|
31
|
+
__exportStar(require("./deezer"), exports);
|
|
32
|
+
exports.tidal = __importStar(require("./tidal"));
|
|
33
|
+
exports.spotify = __importStar(require("./spotify"));
|
|
34
|
+
exports.youtube = __importStar(require("./youtube"));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { albumType, artistInfoType, playlistInfo, trackType } from '../types';
|
|
2
|
+
type linkType = 'track' | 'album' | 'artist' | 'playlist';
|
|
3
|
+
export type urlPartsType = {
|
|
4
|
+
id: string;
|
|
5
|
+
type: 'track' | 'album' | 'audiobook' | 'artist' | 'playlist' | 'spotify-track' | 'spotify-album' | 'spotify-playlist' | 'spotify-artist' | 'tidal-track' | 'tidal-album' | 'tidal-playlist' | 'tidal-artist' | 'youtube-track';
|
|
6
|
+
};
|
|
7
|
+
export declare const getUrlParts: (url: string, setToken?: boolean) => Promise<urlPartsType>;
|
|
8
|
+
/**
|
|
9
|
+
* Deezer, Spotify or Tidal links only
|
|
10
|
+
* @param {String} url
|
|
11
|
+
*/
|
|
12
|
+
export declare const parseInfo: (url: string) => Promise<{
|
|
13
|
+
info: urlPartsType;
|
|
14
|
+
linktype: linkType;
|
|
15
|
+
linkinfo: Record<string, any> | artistInfoType | albumType | playlistInfo;
|
|
16
|
+
tracks: trackType[];
|
|
17
|
+
}>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.parseInfo = exports.getUrlParts = void 0;
|
|
30
|
+
const __1 = require("../");
|
|
31
|
+
const spotify_uri_1 = __importDefault(require("spotify-uri"));
|
|
32
|
+
const spotify = __importStar(require("./spotify"));
|
|
33
|
+
const tidal = __importStar(require("./tidal"));
|
|
34
|
+
const youtube = __importStar(require("./youtube"));
|
|
35
|
+
const p_queue_1 = __importDefault(require("p-queue"));
|
|
36
|
+
const http_1 = require("../lib/http");
|
|
37
|
+
const queue = new p_queue_1.default({ concurrency: 10 });
|
|
38
|
+
const getUrlParts = async (url, setToken = false) => {
|
|
39
|
+
if (url.startsWith('spotify:')) {
|
|
40
|
+
const spotify = url.split(':');
|
|
41
|
+
url = 'https://open.spotify.com/' + spotify[1] + '/' + spotify[2];
|
|
42
|
+
}
|
|
43
|
+
const site = url.match(/deezer|spotify|tidal|youtu\.?be/);
|
|
44
|
+
if (!site) {
|
|
45
|
+
throw new Error('Unknown URL: ' + url);
|
|
46
|
+
}
|
|
47
|
+
switch (site[0]) {
|
|
48
|
+
case 'deezer':
|
|
49
|
+
if (url.includes('page.link')) {
|
|
50
|
+
const { request } = await (0, http_1.headRequest)(url);
|
|
51
|
+
url = request.res.responseUrl;
|
|
52
|
+
}
|
|
53
|
+
const deezerUrlParts = url.split(/\/(\w+)\/(\d+)/);
|
|
54
|
+
return { type: deezerUrlParts[1], id: deezerUrlParts[2] };
|
|
55
|
+
case 'spotify':
|
|
56
|
+
const spotifyUrlParts = spotify_uri_1.default.parse(url);
|
|
57
|
+
if (setToken) {
|
|
58
|
+
await spotify.setSpotifyAnonymousToken();
|
|
59
|
+
}
|
|
60
|
+
return { type: ('spotify-' + spotifyUrlParts.type), id: spotifyUrlParts.id };
|
|
61
|
+
case 'tidal':
|
|
62
|
+
const tidalUrlParts = url.split(/\/(\w+)\/(\d+|\w+-\w+-\w+-\w+-\w+)/);
|
|
63
|
+
return { type: ('tidal-' + tidalUrlParts[1]), id: tidalUrlParts[2] };
|
|
64
|
+
case 'youtube':
|
|
65
|
+
let yotubeId = url.split('v=')[1];
|
|
66
|
+
if (yotubeId.includes('&')) {
|
|
67
|
+
yotubeId = yotubeId.split('&')[0];
|
|
68
|
+
}
|
|
69
|
+
return { type: 'youtube-track', id: yotubeId };
|
|
70
|
+
case 'youtu.be':
|
|
71
|
+
return { type: 'youtube-track', id: url.split('/').pop() };
|
|
72
|
+
default:
|
|
73
|
+
throw new Error('Unable to parse URL: ' + url);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
exports.getUrlParts = getUrlParts;
|
|
77
|
+
/**
|
|
78
|
+
* Deezer, Spotify or Tidal links only
|
|
79
|
+
* @param {String} url
|
|
80
|
+
*/
|
|
81
|
+
const parseInfo = async (url) => {
|
|
82
|
+
const info = await (0, exports.getUrlParts)(url, true);
|
|
83
|
+
if (!info.id) {
|
|
84
|
+
throw new Error('Unable to parse id');
|
|
85
|
+
}
|
|
86
|
+
let linktype = 'track';
|
|
87
|
+
let linkinfo = {};
|
|
88
|
+
let tracks = [];
|
|
89
|
+
switch (info.type) {
|
|
90
|
+
case 'track': {
|
|
91
|
+
tracks.push(await (0, __1.getTrackInfo)(info.id));
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case 'album':
|
|
95
|
+
case 'audiobook':
|
|
96
|
+
linkinfo = await (0, __1.getAlbumInfo)(info.id);
|
|
97
|
+
linktype = 'album';
|
|
98
|
+
const albumTracks = await (0, __1.getAlbumTracks)(info.id);
|
|
99
|
+
tracks = albumTracks.data;
|
|
100
|
+
break;
|
|
101
|
+
case 'playlist':
|
|
102
|
+
linkinfo = await (0, __1.getPlaylistInfo)(info.id);
|
|
103
|
+
linktype = 'playlist';
|
|
104
|
+
const playlistTracks = await (0, __1.getPlaylistTracks)(info.id);
|
|
105
|
+
tracks = playlistTracks.data;
|
|
106
|
+
break;
|
|
107
|
+
case 'artist':
|
|
108
|
+
linkinfo = await (0, __1.getArtistInfo)(info.id);
|
|
109
|
+
linktype = 'artist';
|
|
110
|
+
const artistAlbums = await (0, __1.getDiscography)(info.id);
|
|
111
|
+
await queue.addAll(artistAlbums.data.map((album) => {
|
|
112
|
+
return async () => {
|
|
113
|
+
if (album.ARTISTS.find((a) => a.ART_ID === info.id)) {
|
|
114
|
+
const albumTracks = await (0, __1.getAlbumTracks)(album.ALB_ID);
|
|
115
|
+
tracks = [...tracks, ...albumTracks.data.filter((t) => t.ART_ID === info.id)];
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}));
|
|
119
|
+
break;
|
|
120
|
+
case 'spotify-track':
|
|
121
|
+
tracks.push(await spotify.track2deezer(info.id));
|
|
122
|
+
break;
|
|
123
|
+
case 'spotify-album':
|
|
124
|
+
const [spotifyAlbumInfo, spotifyTracks] = await spotify.album2deezer(info.id);
|
|
125
|
+
tracks = spotifyTracks;
|
|
126
|
+
linkinfo = spotifyAlbumInfo;
|
|
127
|
+
linktype = 'album';
|
|
128
|
+
break;
|
|
129
|
+
case 'spotify-playlist':
|
|
130
|
+
const [spotifyPlaylistInfo, spotifyPlaylistTracks] = await spotify.playlist2Deezer(info.id);
|
|
131
|
+
tracks = spotifyPlaylistTracks;
|
|
132
|
+
linkinfo = spotifyPlaylistInfo;
|
|
133
|
+
linktype = 'playlist';
|
|
134
|
+
break;
|
|
135
|
+
case 'spotify-artist':
|
|
136
|
+
tracks = await spotify.artist2Deezer(info.id);
|
|
137
|
+
linktype = 'artist';
|
|
138
|
+
break;
|
|
139
|
+
case 'tidal-track':
|
|
140
|
+
tracks.push(await tidal.track2deezer(info.id));
|
|
141
|
+
break;
|
|
142
|
+
case 'tidal-album':
|
|
143
|
+
const [tidalAlbumInfo, tidalAlbumTracks] = await tidal.album2deezer(info.id);
|
|
144
|
+
tracks = tidalAlbumTracks;
|
|
145
|
+
linkinfo = tidalAlbumInfo;
|
|
146
|
+
linktype = 'album';
|
|
147
|
+
break;
|
|
148
|
+
case 'tidal-playlist':
|
|
149
|
+
const [tidalPlaylistInfo, tidalPlaylistTracks] = await tidal.playlist2Deezer(info.id);
|
|
150
|
+
tracks = tidalPlaylistTracks;
|
|
151
|
+
linkinfo = tidalPlaylistInfo;
|
|
152
|
+
linktype = 'playlist';
|
|
153
|
+
break;
|
|
154
|
+
case 'tidal-artist':
|
|
155
|
+
tracks = await tidal.artist2Deezer(info.id);
|
|
156
|
+
linktype = 'artist';
|
|
157
|
+
break;
|
|
158
|
+
case 'youtube-track':
|
|
159
|
+
tracks.push(await youtube.track2deezer(info.id));
|
|
160
|
+
break;
|
|
161
|
+
default:
|
|
162
|
+
throw new Error('Unknown type: ' + info.type);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
info,
|
|
166
|
+
linktype,
|
|
167
|
+
linkinfo,
|
|
168
|
+
tracks: tracks.map((t) => {
|
|
169
|
+
if (t.VERSION && !t.SNG_TITLE.includes(t.VERSION)) {
|
|
170
|
+
t.SNG_TITLE += ' ' + t.VERSION;
|
|
171
|
+
}
|
|
172
|
+
return t;
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
exports.parseInfo = parseInfo;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/// <reference types="spotify-api" />
|
|
2
|
+
import SpotifyWebApi from 'spotify-web-api-node';
|
|
3
|
+
import type { playlistInfo, trackType } from '../types';
|
|
4
|
+
type tokensType = {
|
|
5
|
+
clientId: string;
|
|
6
|
+
accessToken: string;
|
|
7
|
+
accessTokenExpirationTimestampMs: number;
|
|
8
|
+
isAnonymous: true;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Export core spotify module
|
|
12
|
+
*/
|
|
13
|
+
export declare const spotifyApi: SpotifyWebApi;
|
|
14
|
+
/**
|
|
15
|
+
* Set spotify tokens anonymously. This is required to bypass api limits.
|
|
16
|
+
* @returns {tokensType}
|
|
17
|
+
*/
|
|
18
|
+
export declare const setSpotifyAnonymousToken: () => Promise<tokensType>;
|
|
19
|
+
/**
|
|
20
|
+
* Convert spotify songs to deezer
|
|
21
|
+
* @param {String} id Spotify track id
|
|
22
|
+
* @returns {trackType}
|
|
23
|
+
*/
|
|
24
|
+
export declare const track2deezer: (id: string) => Promise<trackType>;
|
|
25
|
+
/**
|
|
26
|
+
* Convert spotify albums to deezer
|
|
27
|
+
* @param {String} id Spotify track id
|
|
28
|
+
*/
|
|
29
|
+
export declare const album2deezer: (id: string) => Promise<[import("../types").albumType, trackType[]]>;
|
|
30
|
+
/**
|
|
31
|
+
* Convert playlist to deezer
|
|
32
|
+
* @param {String} id Spotify track id
|
|
33
|
+
*/
|
|
34
|
+
export declare const playlist2Deezer: (id: string, onError?: ((item: SpotifyApi.PlaylistTrackObject, index: number, err: Error) => void) | undefined) => Promise<[playlistInfo, trackType[]]>;
|
|
35
|
+
/**
|
|
36
|
+
* Convert artist songs to deezer. Maxium of 10 tracks.
|
|
37
|
+
* @param {String} id Spotify track id
|
|
38
|
+
*/
|
|
39
|
+
export declare const artist2Deezer: (id: string, onError?: ((item: SpotifyApi.TrackObjectFull, index: number, err: Error) => void) | undefined) => Promise<trackType[]>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
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.artist2Deezer = exports.playlist2Deezer = exports.album2deezer = exports.track2deezer = exports.setSpotifyAnonymousToken = exports.spotifyApi = void 0;
|
|
7
|
+
const spotify_web_api_node_1 = __importDefault(require("spotify-web-api-node"));
|
|
8
|
+
const p_queue_1 = __importDefault(require("p-queue"));
|
|
9
|
+
const deezer_1 = require("./deezer");
|
|
10
|
+
const http_1 = require("../lib/http");
|
|
11
|
+
let anonymousToken = null;
|
|
12
|
+
let anonymousTokenPromise = null;
|
|
13
|
+
const hasValidAnonymousToken = () => Boolean(anonymousToken && anonymousToken.accessTokenExpirationTimestampMs - 60000 > Date.now());
|
|
14
|
+
/**
|
|
15
|
+
* Parse offset number
|
|
16
|
+
* @param {String} next next page url
|
|
17
|
+
* @returns {Number}
|
|
18
|
+
*/
|
|
19
|
+
const getOffset = (next) => {
|
|
20
|
+
if (next) {
|
|
21
|
+
const o = next.split('&').find((p) => p.includes('offset='));
|
|
22
|
+
return o ? Number(o.split('=')[1]) : 0;
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Limit process concurrency
|
|
28
|
+
*/
|
|
29
|
+
const queue = new p_queue_1.default({ concurrency: 25 });
|
|
30
|
+
/**
|
|
31
|
+
* Export core spotify module
|
|
32
|
+
*/
|
|
33
|
+
exports.spotifyApi = new spotify_web_api_node_1.default();
|
|
34
|
+
/**
|
|
35
|
+
* Set spotify tokens anonymously. This is required to bypass api limits.
|
|
36
|
+
* @returns {tokensType}
|
|
37
|
+
*/
|
|
38
|
+
const setSpotifyAnonymousToken = async () => {
|
|
39
|
+
if (hasValidAnonymousToken() && anonymousToken) {
|
|
40
|
+
exports.spotifyApi.setAccessToken(anonymousToken.accessToken);
|
|
41
|
+
return anonymousToken;
|
|
42
|
+
}
|
|
43
|
+
if (!anonymousTokenPromise) {
|
|
44
|
+
anonymousTokenPromise = (0, http_1.getJson)('https://open.spotify.com/get_access_token?reason=transport&productType=embed')
|
|
45
|
+
.then((data) => {
|
|
46
|
+
anonymousToken = data;
|
|
47
|
+
exports.spotifyApi.setAccessToken(data.accessToken);
|
|
48
|
+
return data;
|
|
49
|
+
})
|
|
50
|
+
.finally(() => {
|
|
51
|
+
anonymousTokenPromise = null;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return await anonymousTokenPromise;
|
|
55
|
+
};
|
|
56
|
+
exports.setSpotifyAnonymousToken = setSpotifyAnonymousToken;
|
|
57
|
+
/**
|
|
58
|
+
* Convert spotify songs to deezer
|
|
59
|
+
* @param {String} id Spotify track id
|
|
60
|
+
* @returns {trackType}
|
|
61
|
+
*/
|
|
62
|
+
const track2deezer = async (id) => {
|
|
63
|
+
const { body } = await exports.spotifyApi.getTrack(id);
|
|
64
|
+
return await (0, deezer_1.isrc2deezer)(body.name, body.external_ids.isrc);
|
|
65
|
+
};
|
|
66
|
+
exports.track2deezer = track2deezer;
|
|
67
|
+
/**
|
|
68
|
+
* Convert spotify albums to deezer
|
|
69
|
+
* @param {String} id Spotify track id
|
|
70
|
+
*/
|
|
71
|
+
const album2deezer = async (id) => {
|
|
72
|
+
const { body } = await exports.spotifyApi.getAlbum(id);
|
|
73
|
+
return await (0, deezer_1.upc2deezer)(body.name, body.external_ids.upc);
|
|
74
|
+
};
|
|
75
|
+
exports.album2deezer = album2deezer;
|
|
76
|
+
/**
|
|
77
|
+
* Convert playlist to deezer
|
|
78
|
+
* @param {String} id Spotify track id
|
|
79
|
+
*/
|
|
80
|
+
const playlist2Deezer = async (id, onError) => {
|
|
81
|
+
const { body } = await exports.spotifyApi.getPlaylist(id);
|
|
82
|
+
const tracks = [];
|
|
83
|
+
let items = body.tracks.items;
|
|
84
|
+
let offset = getOffset(body.tracks.next);
|
|
85
|
+
while (offset !== 0) {
|
|
86
|
+
const { body } = await exports.spotifyApi.getPlaylistTracks(id, { limit: 100, offset: offset ? offset : 0 });
|
|
87
|
+
offset = getOffset(body.next);
|
|
88
|
+
items = [...items, ...body.items];
|
|
89
|
+
}
|
|
90
|
+
await queue.addAll(items.map((item, index) => {
|
|
91
|
+
return async () => {
|
|
92
|
+
try {
|
|
93
|
+
if (item.track) {
|
|
94
|
+
const track = await (0, deezer_1.isrc2deezer)(item.track.name, item.track.external_ids.isrc);
|
|
95
|
+
track.TRACK_POSITION = index + 1;
|
|
96
|
+
tracks.push(track);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (onError) {
|
|
101
|
+
onError(item, index, err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}));
|
|
106
|
+
const dateCreated = new Date().toISOString();
|
|
107
|
+
const playlistInfoData = {
|
|
108
|
+
PLAYLIST_ID: body.id,
|
|
109
|
+
PARENT_USERNAME: body.owner.id,
|
|
110
|
+
PARENT_USER_ID: body.owner.id,
|
|
111
|
+
PICTURE_TYPE: 'cover',
|
|
112
|
+
PLAYLIST_PICTURE: body.images[0].url,
|
|
113
|
+
TITLE: body.name,
|
|
114
|
+
TYPE: '0',
|
|
115
|
+
STATUS: '0',
|
|
116
|
+
USER_ID: body.owner.id,
|
|
117
|
+
DATE_ADD: dateCreated,
|
|
118
|
+
DATE_MOD: dateCreated,
|
|
119
|
+
DATE_CREATE: dateCreated,
|
|
120
|
+
NB_SONG: body.tracks.total,
|
|
121
|
+
NB_FAN: 0,
|
|
122
|
+
CHECKSUM: body.id,
|
|
123
|
+
HAS_ARTIST_LINKED: false,
|
|
124
|
+
IS_SPONSORED: false,
|
|
125
|
+
IS_EDITO: false,
|
|
126
|
+
__TYPE__: 'playlist',
|
|
127
|
+
};
|
|
128
|
+
return [playlistInfoData, tracks];
|
|
129
|
+
};
|
|
130
|
+
exports.playlist2Deezer = playlist2Deezer;
|
|
131
|
+
/**
|
|
132
|
+
* Convert artist songs to deezer. Maxium of 10 tracks.
|
|
133
|
+
* @param {String} id Spotify track id
|
|
134
|
+
*/
|
|
135
|
+
const artist2Deezer = async (id, onError) => {
|
|
136
|
+
// Artist tracks are limited to 10 items
|
|
137
|
+
const { body } = await exports.spotifyApi.getArtistTopTracks(id, 'GB');
|
|
138
|
+
const tracks = [];
|
|
139
|
+
await queue.addAll(body.tracks.map((item, index) => {
|
|
140
|
+
return async () => {
|
|
141
|
+
try {
|
|
142
|
+
const track = await (0, deezer_1.isrc2deezer)(item.name, item.external_ids.isrc);
|
|
143
|
+
tracks.push(track);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
if (onError) {
|
|
147
|
+
onError(item, index, err);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}));
|
|
152
|
+
return tracks;
|
|
153
|
+
};
|
|
154
|
+
exports.artist2Deezer = artist2Deezer;
|