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.
Files changed (79) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/LICENSE +1 -0
  3. package/README.md +186 -0
  4. package/dist/api/api.d.ts +78 -0
  5. package/dist/api/api.js +182 -0
  6. package/dist/api/cache.d.ts +3 -0
  7. package/dist/api/cache.js +12 -0
  8. package/dist/api/index.d.ts +2 -0
  9. package/dist/api/index.js +18 -0
  10. package/dist/api/request.d.ts +23 -0
  11. package/dist/api/request.js +88 -0
  12. package/dist/converter/deezer.d.ts +3 -0
  13. package/dist/converter/deezer.js +45 -0
  14. package/dist/converter/index.d.ts +5 -0
  15. package/dist/converter/index.js +34 -0
  16. package/dist/converter/parse.d.ts +18 -0
  17. package/dist/converter/parse.js +176 -0
  18. package/dist/converter/spotify.d.ts +40 -0
  19. package/dist/converter/spotify.js +154 -0
  20. package/dist/converter/tidal.d.ts +146 -0
  21. package/dist/converter/tidal.js +199 -0
  22. package/dist/converter/youtube.d.ts +6 -0
  23. package/dist/converter/youtube.js +102 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +24 -0
  26. package/dist/lib/decrypt.d.ts +9 -0
  27. package/dist/lib/decrypt.js +63 -0
  28. package/dist/lib/fast-lru.d.ts +63 -0
  29. package/dist/lib/fast-lru.js +121 -0
  30. package/dist/lib/get-url.d.ts +16 -0
  31. package/dist/lib/get-url.js +154 -0
  32. package/dist/lib/http.d.ts +66 -0
  33. package/dist/lib/http.js +235 -0
  34. package/dist/lib/metaflac-js.d.ts +124 -0
  35. package/dist/lib/metaflac-js.js +343 -0
  36. package/dist/lib/request.d.ts +18 -0
  37. package/dist/lib/request.js +86 -0
  38. package/dist/metadata-writer/abumCover.d.ts +10 -0
  39. package/dist/metadata-writer/abumCover.js +37 -0
  40. package/dist/metadata-writer/flacmetata.d.ts +3 -0
  41. package/dist/metadata-writer/flacmetata.js +80 -0
  42. package/dist/metadata-writer/getTrackLyrics.d.ts +2 -0
  43. package/dist/metadata-writer/getTrackLyrics.js +26 -0
  44. package/dist/metadata-writer/id3.d.ts +3 -0
  45. package/dist/metadata-writer/id3.js +119 -0
  46. package/dist/metadata-writer/index.d.ts +9 -0
  47. package/dist/metadata-writer/index.js +44 -0
  48. package/dist/metadata-writer/musixmatchLyrics.d.ts +1 -0
  49. package/dist/metadata-writer/musixmatchLyrics.js +39 -0
  50. package/dist/metadata-writer/useragents.d.ts +1 -0
  51. package/dist/metadata-writer/useragents.js +86 -0
  52. package/dist/types/album.d.ts +123 -0
  53. package/dist/types/album.js +2 -0
  54. package/dist/types/artist.d.ts +36 -0
  55. package/dist/types/artist.js +2 -0
  56. package/dist/types/channel.d.ts +33 -0
  57. package/dist/types/channel.js +2 -0
  58. package/dist/types/index.d.ts +10 -0
  59. package/dist/types/index.js +26 -0
  60. package/dist/types/playlist-channel.d.ts +81 -0
  61. package/dist/types/playlist-channel.js +2 -0
  62. package/dist/types/playlist.d.ts +49 -0
  63. package/dist/types/playlist.js +2 -0
  64. package/dist/types/profile.d.ts +29 -0
  65. package/dist/types/profile.js +2 -0
  66. package/dist/types/radio.d.ts +7 -0
  67. package/dist/types/radio.js +2 -0
  68. package/dist/types/search.d.ts +79 -0
  69. package/dist/types/search.js +2 -0
  70. package/dist/types/show.d.ts +61 -0
  71. package/dist/types/show.js +2 -0
  72. package/dist/types/tracks.d.ts +144 -0
  73. package/dist/types/tracks.js +2 -0
  74. package/dist/types/user.d.ts +16 -0
  75. package/dist/types/user.js +2 -0
  76. package/package.json +69 -0
  77. package/types/index.d.ts +1 -0
  78. package/types/index.js +3 -0
  79. 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,3 @@
1
+ import type { albumType, trackType } from '../types';
2
+ export declare const isrc2deezer: (name: string, isrc?: string) => Promise<trackType>;
3
+ export declare const upc2deezer: (name: string, upc?: string) => Promise<[albumType, trackType[]]>;
@@ -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,5 @@
1
+ export * from './parse';
2
+ export * from './deezer';
3
+ export * as tidal from './tidal';
4
+ export * as spotify from './spotify';
5
+ export * as youtube from './youtube';
@@ -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;