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/platform/Spotify.js
CHANGED
|
@@ -1,258 +1,252 @@
|
|
|
1
1
|
const fetch = (...args) => import('node-fetch').then(({
|
|
2
|
-
|
|
2
|
+
default: fetch
|
|
3
3
|
}) => fetch(...args));
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
this.manager = manager;
|
|
7
|
-
this.baseURL = "https://api.spotify.com/v1"
|
|
8
|
-
this.spotifyPattern = /^(?:https:\/\/open\.spotify\.com\/(?:user\/[A-Za-z0-9]+\/)?|spotify:)(album|playlist|track|artist)(?:[/:])([A-Za-z0-9]+).*$/
|
|
9
|
-
this.clientID = manager.options.spotify.clientID;
|
|
10
|
-
this.clientSecret = manager.options.spotify.clientSecret
|
|
11
|
-
this.authorization = Buffer
|
|
12
|
-
.from(`${this.clientID}:${this.clientSecret}`)
|
|
13
|
-
.toString("base64");
|
|
14
|
-
this.interval = 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
check(url) {
|
|
18
|
-
return this.spotifyPattern.test(url);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async requestToken() {
|
|
22
|
-
if (this.nextRequest) return;
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const data = await fetch("https://accounts.spotify.com/api/token?grant_type=client_credentials",{
|
|
26
|
-
method:"POST",
|
|
27
|
-
headers: {
|
|
28
|
-
Authorization: `Basic ${this.authorization}`,
|
|
29
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const body = await data.json();
|
|
34
|
-
|
|
35
|
-
this.token = `Bearer ${body.access_token}`;
|
|
36
|
-
this.interval = body.expires_in * 1000
|
|
37
|
-
} catch (e) {
|
|
38
|
-
if (e.status === 400) {
|
|
39
|
-
throw new Error("Invalid Spotify client.")
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async renew() {
|
|
45
|
-
if (Date.now() >= this.interval) {
|
|
46
|
-
await this.requestToken();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async requestData(endpoint) {
|
|
51
|
-
await this.renew();
|
|
52
|
-
|
|
53
|
-
const req = await fetch(`${this.baseURL}${/^\//.test(endpoint) ? endpoint : `/${endpoint}`}`, {
|
|
54
|
-
headers: { Authorization: this.token }
|
|
55
|
-
})
|
|
56
|
-
const data = await req.json()
|
|
57
|
-
return data
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
async resolve(url) {
|
|
62
|
-
if (!this.token) await this.requestToken()
|
|
63
|
-
const [, type, id] = await this.spotifyPattern.exec(url) ?? [];
|
|
64
|
-
|
|
65
|
-
switch (type) {
|
|
66
|
-
|
|
67
|
-
case "playlist":
|
|
68
|
-
{
|
|
69
|
-
return this.fetchPlaylist(id)
|
|
70
|
-
}
|
|
71
|
-
case "track":
|
|
72
|
-
{
|
|
73
|
-
return this.fetchTrack(id)
|
|
74
|
-
}
|
|
75
|
-
case "album":
|
|
76
|
-
{
|
|
77
|
-
return this.fetchAlbum(id)
|
|
78
|
-
}
|
|
79
|
-
case "artist":
|
|
80
|
-
{
|
|
81
|
-
return this.fetchArtist(id);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
default: {
|
|
85
|
-
return this.manager.resolve(url)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async fetchPlaylist(id) {
|
|
92
|
-
try {
|
|
93
|
-
const playlist = await this.requestData(`/playlists/${id}`)
|
|
94
|
-
await this.fetchPlaylistTracks(playlist);
|
|
95
|
-
const unresolvedPlaylistTracks = playlist.tracks.items.map(x => this.buildUnresolved(x.track));
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return this.buildResponse(
|
|
99
|
-
"PLAYLIST_LOADED",
|
|
100
|
-
(await Promise.all(unresolvedPlaylistTracks.map(x => x.then((a) => a.resolve())))).filter(Boolean),
|
|
101
|
-
playlist.name
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
} catch (e) {
|
|
105
|
-
return this.buildResponse(e.status === 404 ? "NO_MATCHES" : "LOAD_FAILED", [], undefined, e.body?.error.message ?? e.message);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async fetchAlbum(id) {
|
|
110
|
-
try{
|
|
111
|
-
const album = await this.requestData(`/albums/${id}`)
|
|
112
|
-
|
|
113
|
-
const unresolvedPlaylistTracks = album.tracks.map(x => this.buildUnresolved(x));
|
|
114
|
-
return this.buildResponse(
|
|
115
|
-
"PLAYLIST_LOADED",
|
|
116
|
-
(await Promise.all(unresolvedPlaylistTracks.map(x => x.then((a) => a.resolve())))).filter(Boolean),
|
|
117
|
-
album.name
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
}catch(e){
|
|
121
|
-
return this.buildResponse(e.body?.error.message === "invalid id" ? "NO_MATCHES" : "LOAD_FAILED", [], undefined, e.body?.error.message ?? e.message);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async fetchArtist(id) {
|
|
126
|
-
try{
|
|
127
|
-
const artist = await this.requestData(`/artists/${id}`)
|
|
128
|
-
|
|
129
|
-
const data = await this.requestData(`/artists/${id}/top-tracks?market=US`)
|
|
130
|
-
const unresolvedPlaylistTracks = data.tracks.map(x => this.buildUnresolved(x));
|
|
131
|
-
|
|
132
|
-
return this.buildResponse(
|
|
133
|
-
"PLAYLIST_LOADED",
|
|
134
|
-
(await Promise.all(unresolvedPlaylistTracks.map(x => x.then((a) => a.resolve())))).filter(Boolean),
|
|
135
|
-
artist.name
|
|
136
|
-
);
|
|
137
|
-
}catch(e){
|
|
138
|
-
return this.buildResponse(e.body?.error.message === "invalid id" ? "NO_MATCHES" : "LOAD_FAILED", [], undefined, e.body?.error.message ?? e.message);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async fetchTrack(id) {
|
|
144
|
-
try{
|
|
145
|
-
const data = await this.requestData(`/tracks/${id}`)
|
|
146
|
-
const unresolvedTrack = this.buildUnresolved(data);
|
|
147
|
-
|
|
148
|
-
return this.buildResponse(
|
|
149
|
-
"TRACK_LOADED",
|
|
150
|
-
[await unresolvedTrack.then((a) => a.resolve())]
|
|
151
|
-
);
|
|
152
|
-
}catch(e){
|
|
153
|
-
return this.buildResponse(e.body?.error.message === "invalid id" ? "NO_MATCHES" : "LOAD_FAILED", [], undefined, e.body?.error.message ?? e.message);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async fetchByWords(query) {
|
|
158
|
-
try{
|
|
159
|
-
const data = await this.requestData(`/search/?q="${query}"&type=artist,album,track`)
|
|
160
|
-
|
|
161
|
-
const unresolvedTrack = this.buildUnresolved(data.tracks.items[0]);
|
|
162
|
-
|
|
163
|
-
return this.buildResponse(
|
|
164
|
-
"TRACK_LOADED",
|
|
165
|
-
[await unresolvedTrack.then((a) => a.resolve())]
|
|
166
|
-
);
|
|
167
|
-
}catch(e){
|
|
168
|
-
return this.buildResponse(e.body?.error.message === "invalid id" ? "NO_MATCHES" : "LOAD_FAILED", [], undefined, e.body?.error.message ?? e.message);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async fetchPlaylistTracks(spotifyPlaylist) {
|
|
173
|
-
let nextPage = spotifyPlaylist.tracks.next;
|
|
174
|
-
let pageLoaded = 1;
|
|
175
|
-
while (nextPage) {
|
|
176
|
-
if (!nextPage) break;
|
|
177
|
-
const req = await fetch(nextPage, {
|
|
178
|
-
headers: { Authorization: this.token }
|
|
179
|
-
})
|
|
180
|
-
const body = await req.json()
|
|
181
|
-
if (body.error) break;
|
|
182
|
-
spotifyPlaylist.tracks.items.push(...body.items);
|
|
183
|
-
|
|
184
|
-
nextPage = body.next;
|
|
185
|
-
pageLoaded++;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
async buildUnresolved(track) {
|
|
192
|
-
if (!track) throw new ReferenceError("The Spotify track object was not provided");
|
|
193
|
-
// if (!track.artists) throw new ReferenceError("The track artists array was not provided");
|
|
194
|
-
if (!track.name) throw new ReferenceError("The track name was not provided");
|
|
195
|
-
if (!Array.isArray(track.artists)) throw new TypeError(`The track artists must be an array, received type ${typeof track.artists}`);
|
|
196
|
-
if (typeof track.name !== "string") throw new TypeError(`The track name must be a string, received type ${typeof track.name}`);
|
|
197
|
-
|
|
198
|
-
const _this = this;
|
|
199
|
-
return {
|
|
200
|
-
track: "",
|
|
201
|
-
info: {
|
|
202
|
-
sourceName: 'spotify',
|
|
203
|
-
identifier: track.id,
|
|
204
|
-
isSeekable: true,
|
|
205
|
-
author: track.artists[0] ? track.artists[0].name : 'Unknown',
|
|
206
|
-
length: track.duration_ms,
|
|
207
|
-
isStream: false,
|
|
208
|
-
title: track.name,
|
|
209
|
-
uri: `https://open.spotify.com/track/${track.id}`,
|
|
210
|
-
image: track.album?.images[0]?.url,
|
|
211
|
-
},
|
|
212
|
-
resolve() {
|
|
213
|
-
return _this.buildTrack(this)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async fetchMetaData(track) {
|
|
222
|
-
|
|
223
|
-
const fetch = await this.manager.resolve(`${track.info.title} ${track.info.author}`)
|
|
224
|
-
return fetch.tracks[0];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async buildTrack(unresolvedTrack) {
|
|
228
|
-
const lavaTrack = await this.fetchMetaData(unresolvedTrack);
|
|
229
|
-
if(lavaTrack){
|
|
230
|
-
unresolvedTrack.track = lavaTrack.track;
|
|
231
|
-
unresolvedTrack.info.identifier = lavaTrack.info.identifier
|
|
232
|
-
return unresolvedTrack
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
compareValue(value) {
|
|
238
|
-
return typeof value !== 'undefined' ? value !== null : typeof value !== 'undefined';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
buildResponse(loadType, tracks, playlistName, exceptionMsg) {
|
|
243
|
-
|
|
244
|
-
return Object.assign({
|
|
245
|
-
loadType,
|
|
246
|
-
tracks,
|
|
247
|
-
playlistInfo: playlistName ? { name: playlistName } : {}
|
|
248
|
-
}, exceptionMsg ? { exception: { message: exceptionMsg, severity: "COMMON" } } : {});
|
|
249
|
-
}
|
|
4
|
+
let spotifyPattern =
|
|
5
|
+
/^(?:https:\/\/open\.spotify\.com\/(?:user\/[A-Za-z0-9]+\/)?|spotify:)(album|playlist|track|artist)(?:[/:])([A-Za-z0-9]+).*$/;
|
|
250
6
|
|
|
7
|
+
const Track = require("../guild/Track")
|
|
251
8
|
|
|
9
|
+
class Spotify {
|
|
10
|
+
constructor(manager) {
|
|
11
|
+
this.manager = manager;
|
|
12
|
+
this.baseURL = 'https://api.spotify.com/v1';
|
|
13
|
+
this.options ={
|
|
14
|
+
clientID : manager.options.spotify.clientID,
|
|
15
|
+
clientSecret : manager.options.spotify.clientSecret,
|
|
16
|
+
playlistLimit : manager.options.spotify.playlistLimit,
|
|
17
|
+
albumLimit : manager.options.spotify.albumLimit,
|
|
18
|
+
artistLimit : manager.options.spotify.artistLimit,
|
|
19
|
+
searchMarket : manager.options.spotify.searchMarket
|
|
20
|
+
}
|
|
21
|
+
this.authorization = Buffer.from(`${this.options.clientID}:${this.options.clientSecret}`).toString('base64');
|
|
22
|
+
this.interval = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
check(url) {
|
|
26
|
+
return spotifyPattern.test(url);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async requestToken() {
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const data = await fetch('https://accounts.spotify.com/api/token?grant_type=client_credentials', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Basic ${this.authorization}`,
|
|
36
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const body = await data.json();
|
|
41
|
+
|
|
42
|
+
this.token = `Bearer ${body.access_token}`;
|
|
43
|
+
this.interval = body.expires_in * 1000;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
if (e.status === 400) {
|
|
46
|
+
throw new Error('Invalid Spotify client.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async renew() {
|
|
52
|
+
if (Date.now() >= this.interval) {
|
|
53
|
+
await this.requestToken();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async requestData(endpoint) {
|
|
58
|
+
await this.renew();
|
|
59
|
+
|
|
60
|
+
const req = await fetch(`${this.baseURL}${/^\//.test(endpoint) ? endpoint : `/${endpoint}`}`, {
|
|
61
|
+
headers: { Authorization: this.token },
|
|
62
|
+
});
|
|
63
|
+
const data = await req.json();
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async resolve(url) {
|
|
68
|
+
if (!this.token) await this.requestToken();
|
|
69
|
+
const [, type, id] = spotifyPattern.exec(url) ?? [];
|
|
70
|
+
|
|
71
|
+
switch (type) {
|
|
72
|
+
case 'playlist': {
|
|
73
|
+
return this.fetchPlaylist(id);
|
|
74
|
+
}
|
|
75
|
+
case 'track': {
|
|
76
|
+
return this.fetchTrack(id);
|
|
77
|
+
}
|
|
78
|
+
case 'album': {
|
|
79
|
+
return this.fetchAlbum(id);
|
|
80
|
+
}
|
|
81
|
+
case 'artist': {
|
|
82
|
+
return this.fetchArtist(id);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async fetchPlaylist(id) {
|
|
90
|
+
try {
|
|
91
|
+
const playlist = await this.requestData(`/playlists/${id}`);
|
|
92
|
+
await this.fetchPlaylistTracks(playlist);
|
|
93
|
+
|
|
94
|
+
const limitedTracks = this.options.playlistLimit
|
|
95
|
+
? playlist.tracks.items.slice(0, this.options.playlistLimit * 100)
|
|
96
|
+
: playlist.tracks.items;
|
|
97
|
+
|
|
98
|
+
const unresolvedPlaylistTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x.track)));
|
|
99
|
+
|
|
100
|
+
return this.buildResponse('PLAYLIST_LOADED', unresolvedPlaylistTracks, playlist.name);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return this.buildResponse(
|
|
103
|
+
e.status === 404 ? 'NO_MATCHES' : 'LOAD_FAILED',
|
|
104
|
+
[],
|
|
105
|
+
undefined,
|
|
106
|
+
e.body?.error.message ?? e.message,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async fetchAlbum(id) {
|
|
112
|
+
try {
|
|
113
|
+
const album = await this.requestData(`/albums/${id}`);
|
|
114
|
+
|
|
115
|
+
const limitedTracks = this.options.albumLimit ? album.tracks.items.slice(0, this.options.albumLimit * 100) : album.tracks.items;
|
|
116
|
+
|
|
117
|
+
const unresolvedPlaylistTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)));
|
|
118
|
+
return this.buildResponse('PLAYLIST_LOADED', unresolvedPlaylistTracks, album.name);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return this.buildResponse(
|
|
121
|
+
e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
|
|
122
|
+
[],
|
|
123
|
+
undefined,
|
|
124
|
+
e.body?.error.message ?? e.message,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async fetchArtist(id) {
|
|
130
|
+
try {
|
|
131
|
+
const artist = await this.requestData(`/artists/${id}`);
|
|
132
|
+
|
|
133
|
+
const data = await this.requestData(`/artists/${id}/top-tracks?market=${this.searchMarket ?? 'US'}`);
|
|
134
|
+
|
|
135
|
+
const limitedTracks = this.options.artistLimit ? data.tracks.slice(0, this.options.artistLimit * 100) : data.tracks;
|
|
136
|
+
|
|
137
|
+
const unresolvedPlaylistTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)));
|
|
138
|
+
|
|
139
|
+
return this.buildResponse('PLAYLIST_LOADED', unresolvedPlaylistTracks, artist.name);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
return this.buildResponse(
|
|
142
|
+
e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
|
|
143
|
+
[],
|
|
144
|
+
undefined,
|
|
145
|
+
e.body?.error.message ?? e.message,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async fetchTrack(id) {
|
|
151
|
+
try {
|
|
152
|
+
const data = await this.requestData(`/tracks/${id}`);
|
|
153
|
+
const unresolvedTrack = await this.buildUnresolved(data);
|
|
154
|
+
return this.buildResponse('TRACK_LOADED', [unresolvedTrack]);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
return this.buildResponse(
|
|
157
|
+
e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
|
|
158
|
+
[],
|
|
159
|
+
undefined,
|
|
160
|
+
e.body?.error.message ?? e.message,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async fetch(query) {
|
|
166
|
+
try {
|
|
167
|
+
if (this.check(query)) return this.resolve(query);
|
|
168
|
+
|
|
169
|
+
const data = await this.requestData(
|
|
170
|
+
`/search/?q="${query}"&type=artist,album,track&market=${this.options.searchMarket ?? 'US'}`,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const unresolvedTrack = await this.buildUnresolved(data.tracks.items[0]);
|
|
174
|
+
|
|
175
|
+
return this.buildResponse('TRACK_LOADED', [unresolvedTrack]);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
return this.buildResponse(
|
|
178
|
+
e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
|
|
179
|
+
[],
|
|
180
|
+
undefined,
|
|
181
|
+
e.body?.error.message ?? e.message,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async fetchPlaylistTracks(spotifyPlaylist) {
|
|
187
|
+
let nextPage = spotifyPlaylist.tracks.next;
|
|
188
|
+
let pageLoaded = 1;
|
|
189
|
+
while (nextPage) {
|
|
190
|
+
if (!nextPage) break;
|
|
191
|
+
const req = await fetch(nextPage, {
|
|
192
|
+
headers: { Authorization: this.token },
|
|
193
|
+
});
|
|
194
|
+
const body = await req.json();
|
|
195
|
+
if (body.error) break;
|
|
196
|
+
spotifyPlaylist.tracks.items.push(...body.items);
|
|
197
|
+
|
|
198
|
+
nextPage = body.next;
|
|
199
|
+
pageLoaded++;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async buildUnresolved(track) {
|
|
204
|
+
if (!track) throw new ReferenceError('The Spotify track object was not provided');
|
|
205
|
+
|
|
206
|
+
return new Track({
|
|
207
|
+
track: '',
|
|
208
|
+
info: {
|
|
209
|
+
sourceName: 'spotify',
|
|
210
|
+
identifier: track.id,
|
|
211
|
+
isSeekable: true,
|
|
212
|
+
author: track.artists[0] ? track.artists[0].name : 'Unknown',
|
|
213
|
+
length: track.duration_ms,
|
|
214
|
+
isStream: false,
|
|
215
|
+
title: track.name,
|
|
216
|
+
uri: `https://open.spotify.com/track/${track.id}`,
|
|
217
|
+
image: track.album?.images[0]?.url,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async fetchMetaData(track) {
|
|
223
|
+
const fetch = await this.manager.resolve(`${track.info.title} ${track.info.author}`);
|
|
224
|
+
return fetch.tracks[0];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async buildTrack(unresolvedTrack) {
|
|
228
|
+
const lavaTrack = await this.fetchMetaData(unresolvedTrack);
|
|
229
|
+
if (lavaTrack) {
|
|
230
|
+
unresolvedTrack.track = lavaTrack.track;
|
|
231
|
+
unresolvedTrack.info.identifier = lavaTrack.info.identifier;
|
|
232
|
+
return unresolvedTrack;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
compareValue(value) {
|
|
237
|
+
return typeof value !== 'undefined' ? value !== null : typeof value !== 'undefined';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
buildResponse(loadType, tracks, playlistName, exceptionMsg) {
|
|
241
|
+
return Object.assign(
|
|
242
|
+
{
|
|
243
|
+
loadType,
|
|
244
|
+
tracks,
|
|
245
|
+
playlistInfo: playlistName ? { name: playlistName } : {},
|
|
246
|
+
},
|
|
247
|
+
exceptionMsg ? { exception: { message: exceptionMsg, severity: 'COMMON' } } : {},
|
|
248
|
+
);
|
|
249
|
+
}
|
|
252
250
|
}
|
|
253
251
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
module.exports = Spotify
|
|
252
|
+
module.exports = Spotify;
|
package/typings/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { EventEmitter } from "ws";
|
|
1
|
+
import { EventEmitter, WebSocket } from "ws";
|
|
2
2
|
|
|
3
3
|
declare module'Poru' {
|
|
4
4
|
|
|
5
|
-
import { EventEmitter } from 'events';
|
|
6
5
|
|
|
7
6
|
export class Poru extends EventEmitter {
|
|
8
7
|
constructor(client: typeof EventEmitter| any,nodes:[],options:{})
|
|
@@ -56,48 +55,45 @@ export class Node {
|
|
|
56
55
|
constructor(manager:typeof EventEmitter|any,options:object,node:object)
|
|
57
56
|
|
|
58
57
|
public manager: EventEmitter
|
|
59
|
-
public name:
|
|
60
|
-
public host:
|
|
61
|
-
public port :
|
|
62
|
-
public url :
|
|
63
|
-
public password:
|
|
64
|
-
public secure:
|
|
65
|
-
public ws
|
|
66
|
-
public reconnectTime :
|
|
67
|
-
public resumeKey:
|
|
68
|
-
public resumeTimeout
|
|
69
|
-
public reconnectAttempt:
|
|
70
|
-
public reconnects:
|
|
71
|
-
public isConnected:
|
|
72
|
-
public destroyed:
|
|
58
|
+
public name: string
|
|
59
|
+
public host: string
|
|
60
|
+
public port : number
|
|
61
|
+
public url : string
|
|
62
|
+
public password: string
|
|
63
|
+
public secure: boolean
|
|
64
|
+
public ws: WebSocket
|
|
65
|
+
public reconnectTime : number
|
|
66
|
+
public resumeKey:string|number;
|
|
67
|
+
public resumeTimeout: number
|
|
68
|
+
public reconnectAttempt:number;
|
|
69
|
+
public reconnects:number;
|
|
70
|
+
public isConnected:boolean;
|
|
71
|
+
public destroyed:boolean;
|
|
73
72
|
public stats:Object
|
|
74
73
|
|
|
75
74
|
|
|
76
75
|
connect():void
|
|
77
76
|
|
|
78
|
-
destroy()
|
|
77
|
+
destroy(): void
|
|
79
78
|
|
|
80
|
-
reconnect()
|
|
79
|
+
reconnect(): void
|
|
81
80
|
|
|
82
|
-
send(playload:
|
|
81
|
+
send(playload:Object): void | Object | String
|
|
83
82
|
|
|
84
83
|
get penalties():Number
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
|
|
91
88
|
export class Player extends EventEmitter {
|
|
92
|
-
constructor(manager:EventEmitter,node:Map
|
|
93
|
-
|
|
89
|
+
constructor(manager:EventEmitter,node:Map<any, any>|Object, options:Object)
|
|
94
90
|
|
|
95
91
|
|
|
96
92
|
public manager:EventEmitter
|
|
97
93
|
|
|
98
|
-
public queue:Array |any
|
|
94
|
+
public queue:Array<any> |any
|
|
99
95
|
|
|
100
|
-
public node: Map
|
|
96
|
+
public node: Map<any, any>|Object|Any;
|
|
101
97
|
|
|
102
98
|
public filters: any
|
|
103
99
|
|
|
@@ -107,15 +103,15 @@ export class Player extends EventEmitter {
|
|
|
107
103
|
|
|
108
104
|
public textChannel:any;
|
|
109
105
|
|
|
110
|
-
public
|
|
106
|
+
public isConnected: boolean;
|
|
111
107
|
|
|
112
|
-
public isPlaying:
|
|
108
|
+
public isPlaying:boolean;
|
|
113
109
|
|
|
114
|
-
public
|
|
110
|
+
public isPaused:boolean;
|
|
115
111
|
|
|
116
|
-
public trackRepeat:
|
|
112
|
+
public trackRepeat:boolean;
|
|
117
113
|
|
|
118
|
-
public queueRepeat:
|
|
114
|
+
public queueRepeat:boolean;
|
|
119
115
|
|
|
120
116
|
public loop:Number;
|
|
121
117
|
|
|
@@ -127,9 +123,7 @@ export class Player extends EventEmitter {
|
|
|
127
123
|
|
|
128
124
|
public previousTrack:Object;
|
|
129
125
|
|
|
130
|
-
public voiceUpdateState
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
public voiceUpdateState:any;
|
|
133
127
|
|
|
134
128
|
|
|
135
129
|
play()
|
|
@@ -158,24 +152,8 @@ export class Player extends EventEmitter {
|
|
|
158
152
|
|
|
159
153
|
disconnect()
|
|
160
154
|
|
|
161
|
-
|
|
162
155
|
destroy()
|
|
163
156
|
|
|
164
157
|
autoplay(toggle:Boolean)
|
|
165
158
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
159
|
+
}
|
package/src/config.json
DELETED