poru 1.2.3 → 2.0.2
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/.gitpod.yml +8 -0
- package/README.md +41 -28
- package/package.json +13 -2
- package/src/Node.js +126 -106
- package/src/Player.js +39 -20
- package/src/Poru.js +37 -12
- package/src/config.js +20 -0
- package/src/guild/Filter.js +3 -4
- package/src/guild/PoruTrack.js +61 -0
- package/src/guild/Track.js +55 -13
- package/src/platform/AppleMusic.js +248 -0
- package/src/platform/Deezer.js +233 -0
- package/src/platform/Spotify.js +246 -252
- package/typings/index.d.ts +28 -50
- package/src/config.json +0 -3
package/src/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
clientName: "Poru",
|
|
3
|
+
autoResume : true,
|
|
4
|
+
version :"2.0",
|
|
5
|
+
OPCodes: {
|
|
6
|
+
CONFIGURE_RESUMING : 'configureResuming',
|
|
7
|
+
DESTROY : 'destroy',
|
|
8
|
+
FILTERS : 'filters',
|
|
9
|
+
EVENT : 'event',
|
|
10
|
+
PAUSE : 'pause',
|
|
11
|
+
PLAY : 'play',
|
|
12
|
+
PLAYER_UPDATE : 'playerUpdate',
|
|
13
|
+
SEEK : 'seek',
|
|
14
|
+
STATS : 'stats',
|
|
15
|
+
STOP : 'stop',
|
|
16
|
+
VOICE_UPDATE : 'voiceUpdate',
|
|
17
|
+
VOLUME : 'volume'
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/guild/Filter.js
CHANGED
|
@@ -29,8 +29,8 @@ class Filters {
|
|
|
29
29
|
return this;
|
|
30
30
|
}
|
|
31
31
|
setTimescale(timescale) {
|
|
32
|
-
this.updateFilters();
|
|
33
32
|
this.timescale = timescale || null;
|
|
33
|
+
this.updateFilters();
|
|
34
34
|
return this;
|
|
35
35
|
}
|
|
36
36
|
setTremolo(tremolo) {
|
|
@@ -90,7 +90,7 @@ class Filters {
|
|
|
90
90
|
this.doubleTime = false;
|
|
91
91
|
this.vaporwave = false;
|
|
92
92
|
}
|
|
93
|
-
return
|
|
93
|
+
return val;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
setSlowmode(val) {
|
|
@@ -141,7 +141,6 @@ class Filters {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
updateFilters(){
|
|
144
|
-
console.log(this.node.send)
|
|
145
144
|
const { volume, equalizer, karaoke, timescale, tremolo, vibrato, rotation, distortion, channelMix, lowPass } = this;
|
|
146
145
|
this.node.send({
|
|
147
146
|
op:"filters",
|
|
@@ -165,4 +164,4 @@ class Filters {
|
|
|
165
164
|
|
|
166
165
|
|
|
167
166
|
|
|
168
|
-
module.exports = Filters;
|
|
167
|
+
module.exports = Filters;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2
|
+
class PoruTrack {
|
|
3
|
+
constructor(data) {
|
|
4
|
+
this.track = data.track
|
|
5
|
+
this.info = {
|
|
6
|
+
identifier: null,
|
|
7
|
+
isSeekable: data.info.isSeekable,
|
|
8
|
+
author: data.info.author,
|
|
9
|
+
length: data.info.length,
|
|
10
|
+
isStream: data.info.isStream,
|
|
11
|
+
sourceName: data.info.sourceName,
|
|
12
|
+
title: data.info.title,
|
|
13
|
+
uri: data.info.uri,
|
|
14
|
+
image: data.info.image || null
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async resolve(manager) {
|
|
20
|
+
|
|
21
|
+
const query = [this.info.author, this.info.title].filter((x) => !!x).join(' - ');
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const result = await manager.resolve(query);
|
|
25
|
+
if (!result || !result.tracks.length) return;
|
|
26
|
+
|
|
27
|
+
if (this.info.author) {
|
|
28
|
+
const author = [this.info.author, `${this.info.author} - Topic`];
|
|
29
|
+
const officialAudio = result.tracks.find(
|
|
30
|
+
(track) =>
|
|
31
|
+
author.some((name) => new RegExp(`^${escapeRegExp(name)}$`, 'i').test(track.info.author)) ||
|
|
32
|
+
new RegExp(`^${escapeRegExp(this.info.title)}$`, 'i').test(track.info.title),
|
|
33
|
+
);
|
|
34
|
+
if (officialAudio) {
|
|
35
|
+
this.info.identifier = officialAudio.info.identifier
|
|
36
|
+
this.track = officialAudio.track;
|
|
37
|
+
this.info.length = officialAudio.info.length
|
|
38
|
+
return this
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (this.info.length) {
|
|
42
|
+
const sameDuration = result.tracks.find(
|
|
43
|
+
(track) =>
|
|
44
|
+
track.info.length >= (this.info.length ? this.length : 0) - 2000 &&
|
|
45
|
+
track.info.length <= (this.info.length ? this.length : 0) + 2000,
|
|
46
|
+
);
|
|
47
|
+
if (sameDuration) {
|
|
48
|
+
this.info.identifier = sameDuration.info.identifier
|
|
49
|
+
this.track = sameDuration.track;
|
|
50
|
+
this.info.length = sameDuration.length
|
|
51
|
+
return this
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
this.info.identifier = result.tracks[0].info.identifier
|
|
55
|
+
this.track = result.tracks[0].track;
|
|
56
|
+
this.info.length = result.tracks[0].info.length
|
|
57
|
+
return this
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = PoruTrack;
|
package/src/guild/Track.js
CHANGED
|
@@ -1,19 +1,61 @@
|
|
|
1
|
+
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1
2
|
class Track {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
constructor(data) {
|
|
4
|
+
this.track = data.track
|
|
5
|
+
this.info = {
|
|
6
|
+
identifier: data.info.identifier,
|
|
7
|
+
isSeekable: data.info.isSeekable,
|
|
8
|
+
author: data.info.author,
|
|
9
|
+
length: data.info.length,
|
|
10
|
+
isStream: data.info.isStream,
|
|
11
|
+
sourceName: data.info.sourceName,
|
|
12
|
+
title: data.info.title,
|
|
13
|
+
uri: data.info.uri,
|
|
14
|
+
image: `https://i.ytimg.com/vi/${data.info.identifier}/maxresdefault.jpg` || null
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async resolve(manager) {
|
|
20
|
+
|
|
21
|
+
const query = [this.info.author, this.info.title].filter((x) => !!x).join(' - ');
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const result = await manager.resolve(query);
|
|
25
|
+
if (!result || !result.tracks.length) return;
|
|
26
|
+
|
|
27
|
+
if (this.info.author) {
|
|
28
|
+
const author = [this.info.author, `${this.info.author} - Topic`];
|
|
29
|
+
const officialAudio = result.tracks.find(
|
|
30
|
+
(track) =>
|
|
31
|
+
author.some((name) => new RegExp(`^${escapeRegExp(name)}$`, 'i').test(track.info.author)) ||
|
|
32
|
+
new RegExp(`^${escapeRegExp(this.info.title)}$`, 'i').test(track.info.title),
|
|
33
|
+
);
|
|
34
|
+
if (officialAudio) {
|
|
35
|
+
this.info.identifier = officialAudio.info.identifier
|
|
36
|
+
this.image =`https://i.ytimg.com/vi/${this.info.identifier}/maxresdefault.jpg`
|
|
37
|
+
this.track = officialAudio.track;
|
|
38
|
+
return this
|
|
15
39
|
}
|
|
40
|
+
}
|
|
41
|
+
if (this.info.length) {
|
|
42
|
+
const sameDuration = result.tracks.find(
|
|
43
|
+
(track) =>
|
|
44
|
+
track.info.length >= (this.info.length ? this.length : 0) - 2000 &&
|
|
45
|
+
track.info.length <= (this.info.length ? this.length : 0) + 2000,
|
|
46
|
+
);
|
|
47
|
+
if (sameDuration) {
|
|
48
|
+
this.info.identifier = sameDuration.info.identifier
|
|
49
|
+
this.image =`https://i.ytimg.com/vi/${this.info.identifier}/maxresdefault.jpg`
|
|
50
|
+
this.track = sameDuration.track;
|
|
51
|
+
return this
|
|
16
52
|
}
|
|
53
|
+
}
|
|
54
|
+
this.info.identifier = result.tracks[0].info.identifier
|
|
55
|
+
this.image =`https://i.ytimg.com/vi/${this.info.identifier}/maxresdefault.jpg`
|
|
56
|
+
this.track = result.tracks[0].track;
|
|
57
|
+
return this
|
|
58
|
+
}
|
|
17
59
|
}
|
|
18
60
|
|
|
19
61
|
module.exports = Track;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
const fetch = (...args) => import('node-fetch').then(({
|
|
2
|
+
default: fetch
|
|
3
|
+
}) => fetch(...args));
|
|
4
|
+
const PoruTrack = require("../guild/PoruTrack")
|
|
5
|
+
let baseURL = /(?:https:\/\/music\.apple\.com\/)(?:.+)?(artist|album|music-video|playlist)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)/;
|
|
6
|
+
|
|
7
|
+
class AppleMusic {
|
|
8
|
+
constructor(manager, options) {
|
|
9
|
+
this.manager = manager;
|
|
10
|
+
this.options = {
|
|
11
|
+
playlistLimit: options.apple.playlistLimit || null,
|
|
12
|
+
albumLimit: options.apple.albumLimit || null,
|
|
13
|
+
artistLimit: options.apple.artistLimit || null,
|
|
14
|
+
searchMarket: options.apple.searchMarket || "us",
|
|
15
|
+
imageHeight: options.apple.imageHeight || 500,
|
|
16
|
+
imageWeight: options.apple.imageWeight || 500,
|
|
17
|
+
}
|
|
18
|
+
this.url = `https://amp-api.music.apple.com/v1/catalog/${this.options.searchMarket}`
|
|
19
|
+
this.token = null;
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
check(url) {
|
|
24
|
+
return baseURL.test(url);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async requestToken() {
|
|
29
|
+
try {
|
|
30
|
+
|
|
31
|
+
let req = await fetch('https://music.apple.com/us/browse');
|
|
32
|
+
let json = await req.text();
|
|
33
|
+
let config = /<meta name="desktop-music-app\/config\/environment" content="(.*?)">/.exec(json);
|
|
34
|
+
|
|
35
|
+
let key = config = JSON.parse(decodeURIComponent(config[1]));
|
|
36
|
+
let { token } = key?.MEDIA_API
|
|
37
|
+
|
|
38
|
+
if (!token) throw new Error("No acess key found for apple music")
|
|
39
|
+
|
|
40
|
+
this.token = `Bearer ${token}`;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (e.status === 400) {
|
|
43
|
+
throw new Error(`[Poru Apple Music]:${e}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async requestData(param) {
|
|
50
|
+
if (!this.token) await this.requestToken();
|
|
51
|
+
|
|
52
|
+
let req = await fetch(`${this.url}${param}`, {
|
|
53
|
+
headers: {
|
|
54
|
+
Authorization: `${this.token}`,
|
|
55
|
+
origin: 'https://music.apple.com'
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
let body = await req.json();
|
|
60
|
+
|
|
61
|
+
return body;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async resolve(url) {
|
|
69
|
+
let [, type, id] = await baseURL.exec(url)
|
|
70
|
+
|
|
71
|
+
switch (type) {
|
|
72
|
+
case "playlist": {
|
|
73
|
+
return this.fetchPlaylist(url);
|
|
74
|
+
}
|
|
75
|
+
case "album": {
|
|
76
|
+
return this.fetchAlbum(url);
|
|
77
|
+
}
|
|
78
|
+
case "artist": {
|
|
79
|
+
return this.fetchArtist(url);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async fetch(query) {
|
|
85
|
+
if (this.check(query)) return this.resolve(query);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
|
|
89
|
+
let tracks = await this.requestData(`/search?types=songs&term=${query}`)
|
|
90
|
+
|
|
91
|
+
let track = await this.buildUnresolved(tracks.results.songs.data[0])
|
|
92
|
+
|
|
93
|
+
return this.buildResponse('TRACK_LOADED', [track]);
|
|
94
|
+
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return this.buildResponse(
|
|
97
|
+
'LOAD_FAILED',
|
|
98
|
+
[],
|
|
99
|
+
undefined,
|
|
100
|
+
e.body?.error.message ?? e.message,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async fetchPlaylist(url) {
|
|
108
|
+
try {
|
|
109
|
+
let query = new URL(url).pathname.split('/');
|
|
110
|
+
let id = query.pop();
|
|
111
|
+
let playlist = await this.requestData(`/playlists/${id}`)
|
|
112
|
+
let name = playlist.data[0].attributes.name
|
|
113
|
+
|
|
114
|
+
const limitedTracks = this.options.playlistLimit
|
|
115
|
+
? playlist.data[0].relationships.tracks.data.slice(0, this.options.playlistLimit * 100)
|
|
116
|
+
: playlist.data[0].relationships.tracks.data;
|
|
117
|
+
|
|
118
|
+
let tracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)))
|
|
119
|
+
return this.buildResponse('PLAYLIST_LOADED', tracks, name);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return this.buildResponse(
|
|
122
|
+
'LOAD_FAILED',
|
|
123
|
+
[],
|
|
124
|
+
undefined,
|
|
125
|
+
e.body?.error.message ?? e.message,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async fetchAlbum(url) {
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
let query = new URL(url).pathname.split('/');
|
|
136
|
+
let id = query.pop();
|
|
137
|
+
let album = await this.requestData(`/albums/${id}`)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
const limitedTracks = this.options.albumLimit
|
|
141
|
+
? album.data[0].relationships.tracks.data.slice(0, this.options.albumLimit * 100)
|
|
142
|
+
: album.data[0].relationships.tracks.data;
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
let name = album.data[0].attributes.name
|
|
146
|
+
let tracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)));
|
|
147
|
+
return this.buildResponse('PLAYLIST_LOADED', tracks, name);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return this.buildResponse(
|
|
150
|
+
'LOAD_FAILED',
|
|
151
|
+
[],
|
|
152
|
+
undefined,
|
|
153
|
+
e.body?.error.message ?? e.message,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async fetchArtist(url) {
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
let query = new URL(url).pathname.split('/');
|
|
163
|
+
let id = query.pop();
|
|
164
|
+
let artist = await this.requestData(`/attists/${id}`)
|
|
165
|
+
let name = artistdata[0].attributes.name
|
|
166
|
+
|
|
167
|
+
const limitedTracks = this.options.artistLimit
|
|
168
|
+
? artist.data[0].relationships.tracks.data.slice(0, this.options.artist * 100)
|
|
169
|
+
: artist.data[0].relationships.tracks.data;
|
|
170
|
+
|
|
171
|
+
let tracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)));
|
|
172
|
+
return this.buildResponse('PLAYLIST_LOADED', tracks, name);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
return this.buildResponse(
|
|
175
|
+
'LOAD_FAILED',
|
|
176
|
+
[],
|
|
177
|
+
undefined,
|
|
178
|
+
e.body?.error.message ?? e.message,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async buildUnresolved(track) {
|
|
191
|
+
if (!track) throw new ReferenceError('The Apple track object was not provided');
|
|
192
|
+
|
|
193
|
+
return new PoruTrack({
|
|
194
|
+
track: '',
|
|
195
|
+
info: {
|
|
196
|
+
sourceName: 'Apple Music',
|
|
197
|
+
identifier: track.id,
|
|
198
|
+
isSeekable: true,
|
|
199
|
+
author: track.attributes.artistName ? track.attributes.artistName : 'Unknown',
|
|
200
|
+
length: track.attributes.durationInMillis,
|
|
201
|
+
isStream: false,
|
|
202
|
+
title: track.attributes.name,
|
|
203
|
+
uri: track.attributes.url,
|
|
204
|
+
image: track.attributes.artwork.url.replace("{w}", this.options.imageWeight).replace("{h}", this.options.imageHeight)
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
compareValue(value) {
|
|
210
|
+
return typeof value !== 'undefined' ? value !== null : typeof value !== 'undefined';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
buildResponse(loadType, tracks, playlistName, exceptionMsg) {
|
|
214
|
+
return Object.assign(
|
|
215
|
+
{
|
|
216
|
+
loadType,
|
|
217
|
+
tracks,
|
|
218
|
+
playlistInfo: playlistName ? { name: playlistName } : {},
|
|
219
|
+
},
|
|
220
|
+
exceptionMsg ? { exception: { message: exceptionMsg, severity: 'COMMON' } } : {},
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
}
|
|
243
|
+
module.exports = AppleMusic
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
const apple = new AppleMusic("",{apple:{}})
|
|
247
|
+
|
|
248
|
+
apple.resolve("https://music.apple.com/us/playlist/bollywood-hits/pl.d60caf02fcce4d7e9788fe01243b7c2c")
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
const fetch = (...args) => import('node-fetch').then(({
|
|
2
|
+
default: fetch
|
|
3
|
+
}) => fetch(...args));
|
|
4
|
+
let REGEX = /^(?:https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?(track|album|playlist|artist)\/(\d+)/
|
|
5
|
+
|
|
6
|
+
const PoruTrack = require("../guild/PoruTrack")
|
|
7
|
+
|
|
8
|
+
class Deezer {
|
|
9
|
+
constructor(manager, options) {
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
this.manager = manager;
|
|
13
|
+
this.baseURL = 'https://api.deezer.com';
|
|
14
|
+
this.options = {
|
|
15
|
+
playlistLimit: options.deezer.playlistLimit || null,
|
|
16
|
+
albumLimit: options.deezer.albumLimit || null,
|
|
17
|
+
artistLimit: options.deezer.artistLimit || null
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
check(url) {
|
|
24
|
+
return REGEX.test(url);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async requestData(endpoint) {
|
|
28
|
+
const req = await fetch(`${this.baseURL}/${endpoint}`, {
|
|
29
|
+
});
|
|
30
|
+
const data = await req.json();
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async resolve(url) {
|
|
36
|
+
const [, type, id] = REGEX.exec(url) ?? [];
|
|
37
|
+
switch (type) {
|
|
38
|
+
case 'playlist': {
|
|
39
|
+
return this.fetchPlaylist(id);
|
|
40
|
+
}
|
|
41
|
+
case 'track': {
|
|
42
|
+
return this.fetchTrack(id);
|
|
43
|
+
}
|
|
44
|
+
case 'album': {
|
|
45
|
+
return this.fetchAlbum(id);
|
|
46
|
+
}
|
|
47
|
+
case 'artist': {
|
|
48
|
+
return this.fetchArtist(id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async fetchPlaylist(id) {
|
|
59
|
+
try {
|
|
60
|
+
const playlist = await this.requestData(`/playlist/${id}`);
|
|
61
|
+
|
|
62
|
+
const limitedTracks = this.options.playlistLimit
|
|
63
|
+
? playlist.track.data.slice(0, this.options.playlistLimit * 100)
|
|
64
|
+
: playlist.track.data;
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
const unresolvedPlaylistTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)));
|
|
68
|
+
return this.buildResponse('PLAYLIST_LOADED', unresolvedPlaylistTracks, playlist.name);
|
|
69
|
+
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return this.buildResponse(
|
|
72
|
+
'LOAD_FAILED',
|
|
73
|
+
[],
|
|
74
|
+
undefined,
|
|
75
|
+
e.body?.error.message ?? e.message,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async fetchAlbum(id) {
|
|
81
|
+
try {
|
|
82
|
+
const album = await this.requestData(`/album/${id}`);
|
|
83
|
+
|
|
84
|
+
const limitedTracks = this.options.albumLimit
|
|
85
|
+
? album.track.data.slice(0, this.options.albumLimit * 100)
|
|
86
|
+
: album.track.data;
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
const unresolvedAlbumTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)));
|
|
90
|
+
|
|
91
|
+
return this.buildResponse('PLAYLIST_LOADED', unresolvedAlbumTracks, album.name);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return this.buildResponse(
|
|
94
|
+
'LOAD_FAILED',
|
|
95
|
+
[],
|
|
96
|
+
undefined,
|
|
97
|
+
e.body?.error.message ?? e.message,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async fetchTrack(id) {
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const track = await this.requestData(`/track/${id}`)
|
|
108
|
+
|
|
109
|
+
const unresolvedTrack = await Promise.all(this.buildUnresolved(track));
|
|
110
|
+
return this.buildResponse('TRACK_LOADED', [unresolvedTrack]);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
return this.buildResponse(
|
|
113
|
+
'LOAD_FAILED',
|
|
114
|
+
[],
|
|
115
|
+
undefined,
|
|
116
|
+
e.body?.error.message ?? e.message,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async fetchArtist(id) {
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const artist = await this.requestData(`/artist/${id}/top`);
|
|
127
|
+
await this.fetchArtistTracks(artist)
|
|
128
|
+
|
|
129
|
+
const limitedTracks = this.options.artistLimit
|
|
130
|
+
? artist.data.slice(0, this.options.artistLimit * 100)
|
|
131
|
+
: artist.data;
|
|
132
|
+
|
|
133
|
+
const unresolvedArtistTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)
|
|
134
|
+
));
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
return this.buildResponse('PLAYLIST_LOADED', unresolvedArtistTracks, artist.name);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
return this.buildResponse(
|
|
141
|
+
'LOAD_FAILED',
|
|
142
|
+
[],
|
|
143
|
+
undefined,
|
|
144
|
+
e.body?.error.message ?? e.message,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async fetchArtistTracks(deezerArtist) {
|
|
152
|
+
let nextPage = deezerArtist.next;
|
|
153
|
+
let pageLoaded = 1;
|
|
154
|
+
while (nextPage) {
|
|
155
|
+
if (!nextPage) break;
|
|
156
|
+
const req = await fetch(nextPage)
|
|
157
|
+
const json = await req.json()
|
|
158
|
+
|
|
159
|
+
deezerArtist.data.push(...json.data);
|
|
160
|
+
|
|
161
|
+
nextPage = json.next;
|
|
162
|
+
pageLoaded++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async fetch(query) {
|
|
167
|
+
if (this.check(query)) return this.resolve(query);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
if (this.check(query)) return this.resolve(query)
|
|
171
|
+
let tracks = await this.requestData(`/search?q="${query}"`)
|
|
172
|
+
|
|
173
|
+
const unresolvedTrack = await this.buildUnresolved(tracks.data[0]);
|
|
174
|
+
return this.buildResponse('TRACK_LOADED', [unresolvedTrack]);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return this.buildResponse(
|
|
177
|
+
'LOAD_FAILED',
|
|
178
|
+
[],
|
|
179
|
+
undefined,
|
|
180
|
+
e.body?.error.message ?? e.message,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
async buildUnresolved(track) {
|
|
189
|
+
if (!track) throw new ReferenceError('The Deezer track object was not provided');
|
|
190
|
+
|
|
191
|
+
return new PoruTrack({
|
|
192
|
+
track: '',
|
|
193
|
+
info: {
|
|
194
|
+
sourceName: 'deezer',
|
|
195
|
+
identifier: track.id,
|
|
196
|
+
isSeekable: true,
|
|
197
|
+
author: track.artist ? track.artist.name : 'Unknown',
|
|
198
|
+
length: track.duration,
|
|
199
|
+
isStream: false,
|
|
200
|
+
title: track.title,
|
|
201
|
+
uri: track.link,
|
|
202
|
+
image: track.album.cover_medium
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
compareValue(value) {
|
|
209
|
+
return typeof value !== 'undefined' ? value !== null : typeof value !== 'undefined';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
buildResponse(loadType, tracks, playlistName, exceptionMsg) {
|
|
213
|
+
return Object.assign(
|
|
214
|
+
{
|
|
215
|
+
loadType,
|
|
216
|
+
tracks,
|
|
217
|
+
playlistInfo: playlistName ? { name: playlistName } : {},
|
|
218
|
+
},
|
|
219
|
+
exceptionMsg ? { exception: { message: exceptionMsg, severity: 'COMMON' } } : {},
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = Deezer;
|
|
229
|
+
|
|
230
|
+
let deezer = new Deezer("", { deezer: { playlistLimit: 10 } })
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
deezer.resolve("https://www.deezer.com/en/playlist/4404579662")
|