poru 2.0.2 → 3.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/.github/FUNDING.yml +13 -0
- package/README.md +66 -53
- package/index.js +12 -16
- package/package.json +5 -2
- package/src/Player.js +84 -73
- package/src/Poru.js +139 -89
- package/src/guild/Filter.js +37 -17
- package/src/guild/PoruTrack.js +1 -1
- package/src/guild/Track.js +1 -1
- package/src/platform/AppleMusic.js +8 -10
- package/src/platform/Deezer.js +6 -7
- package/src/platform/Spotify.js +34 -14
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# These are supported funding model platforms
|
|
2
|
+
|
|
3
|
+
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
4
|
+
patreon: # Replace with a single Patreon username
|
|
5
|
+
open_collective: # Replace with a single Open Collective username
|
|
6
|
+
ko_fi: parasdev
|
|
7
|
+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
+
liberapay: # Replace with a single Liberapay username
|
|
10
|
+
issuehunt: # Replace with a single IssueHunt username
|
|
11
|
+
otechie: # Replace with a single Otechie username
|
|
12
|
+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
13
|
+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
package/README.md
CHANGED
|
@@ -50,71 +50,84 @@ To use you need a configured [Lavalink](https://github.com/Frederikam/Lavalink)
|
|
|
50
50
|
## Example usage basic bot
|
|
51
51
|
|
|
52
52
|
```javascript
|
|
53
|
-
|
|
54
|
-
// Require both libraries
|
|
55
|
-
const { Client } = require('discord.js');
|
|
53
|
+
const { Client, GatewayIntentBits } = require('discord.js');
|
|
56
54
|
const { Poru } = require('poru');
|
|
57
|
-
|
|
58
|
-
// Initiate both main classes
|
|
59
|
-
const client = new Client();
|
|
60
|
-
|
|
61
|
-
// Define some options for the node
|
|
62
55
|
const nodes = [
|
|
63
56
|
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
port:
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Emitted whenever a node connects
|
|
85
|
-
client.poru.on('nodeConnect', node => {
|
|
86
|
-
console.log(`Node "${node.name}" connected.`);
|
|
57
|
+
id: "main_node",
|
|
58
|
+
hostname: "localhost",
|
|
59
|
+
port: 8080,
|
|
60
|
+
password: "iloveyou3000"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
const PoruOptions = {
|
|
64
|
+
reconnectTime: 0,
|
|
65
|
+
resumeKey: 'MyPlayers',
|
|
66
|
+
resumeTimeout: 60,
|
|
67
|
+
defaultPlatform: "ytsearch"
|
|
68
|
+
}
|
|
69
|
+
const client = new Client({
|
|
70
|
+
intents: [
|
|
71
|
+
GatewayIntentBits.Guilds,
|
|
72
|
+
GatewayIntentBits.GuildMessages,
|
|
73
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
74
|
+
GatewayIntentBits.MessageContent
|
|
75
|
+
]
|
|
87
76
|
});
|
|
77
|
+
client.poru = new Poru(client, nodes, PoruOptions)
|
|
88
78
|
|
|
89
|
-
|
|
90
|
-
client.
|
|
91
|
-
|
|
79
|
+
client.poru.on('trackStart', (player, track) => {
|
|
80
|
+
const channel = client.channels.cache.get(player.textChannel);
|
|
81
|
+
return channel.send(`Now playing \`${track.title}\``);
|
|
92
82
|
});
|
|
93
83
|
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
client.on('ready', () => {
|
|
85
|
+
console.log('Ready!');
|
|
96
86
|
client.poru.init(client);
|
|
97
|
-
console.log(`Logged in as ${client.user.tag}`);
|
|
98
87
|
});
|
|
99
88
|
|
|
100
|
-
// this event used to make connections upto date with lavalink
|
|
101
|
-
client.on('raw', async d => await client.poru.packetUpdate(d));
|
|
102
|
-
|
|
103
|
-
// Finally login at the END of your code
|
|
104
|
-
client.login('your bot token here');
|
|
105
|
-
```
|
|
106
89
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
90
|
+
client.on('interactionCreate', async (interaction) => {
|
|
91
|
+
if (!interaction.isChatInputCommand()) return;
|
|
92
|
+
if (!interaction.member.voice.channel) return interaction.reply({ content: `Please connect with voice channel `, ephemeral: true });
|
|
93
|
+
|
|
94
|
+
const track = interaction.options.getString('track');
|
|
95
|
+
|
|
96
|
+
const res = await client.poru.resolve(track);
|
|
97
|
+
|
|
98
|
+
if (res.loadType === "LOAD_FAILED") {
|
|
99
|
+
return interaction.reply('Failed to load track.');
|
|
100
|
+
} else if (res.loadType === "NO_MATCHES") {
|
|
101
|
+
return interaction.reply('No source found!');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//create connection with discord voice channnel
|
|
105
|
+
const player = client.poru.createConnection({
|
|
106
|
+
guildId: interaction.guild.id,
|
|
107
|
+
voiceChannel: interaction.member.voice.channelId,
|
|
108
|
+
textChannel: interaction.channel.id,
|
|
109
|
+
selfDeaf: true
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if (res.loadType === 'PLAYLIST_LOADED') {
|
|
114
|
+
for (const track of res.tracks) {
|
|
115
|
+
track.info.requester = interaction.user;
|
|
116
|
+
player.queue.add(track);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interaction.reply(`${res.playlistInfo.name} has been loaded with ${res.tracks.length}`);
|
|
120
|
+
} else {
|
|
121
|
+
const track = res.tracks[0];
|
|
122
|
+
track.info.requester = interaction.user;
|
|
123
|
+
player.queue.add(track);
|
|
124
|
+
interacton.reply(`Queued Track \n \`${track.title}\``);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!player.isPlaying && player.isConnected) player.play();
|
|
115
128
|
});
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
|
|
130
|
+
client.login('TOKEN');
|
|
118
131
|
```
|
|
119
132
|
|
|
120
133
|
## Need Help?
|
package/index.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
+
|
|
1
2
|
module.exports = {
|
|
2
3
|
Node: require("./src/Node"),
|
|
3
4
|
Player: require("./src/Player"),
|
|
4
|
-
Poru: require("./src/Poru")
|
|
5
|
+
Poru: require("./src/Poru"),
|
|
6
|
+
Spotify : require("./src/platform/Spotify"),
|
|
7
|
+
Deezer : require("./src/platform/Deezer"),
|
|
8
|
+
AppleMusic : require("./src/platform/AppleMusic"),
|
|
9
|
+
Queue : require("./src/guild/Queue"),
|
|
10
|
+
Filter : require("./src/guild/Filter"),
|
|
11
|
+
Track: require("./src/guild/Track"),
|
|
12
|
+
PoruTrack : require("./src/guild/PoruTrack")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
5
16
|
}
|
|
6
|
-
process.on('unhandledRejection', error => {
|
|
7
|
-
if (error.code === '10008' || error.code === '10062') return;
|
|
8
|
-
console.log(error.stack);
|
|
9
|
-
});
|
|
10
|
-
process.on('uncaughtException', (err, origin) => {
|
|
11
|
-
console.log(err, origin);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
process.on('uncaughtExceptionMonitor', (err, origin) => {
|
|
15
|
-
console.log(err, origin);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
process.on('multipleResolves', (type, promise, reason) => {
|
|
19
|
-
console.log(type, promise, reason);
|
|
20
|
-
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poru",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "A stable and powefull lavalink client with so many features",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,9 +17,12 @@
|
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/parasop/poru#readme",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"
|
|
20
|
+
"undici": "^5.7.0",
|
|
21
21
|
"ws": "^8.8.0"
|
|
22
22
|
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.9.0"
|
|
25
|
+
},
|
|
23
26
|
"keywords": [
|
|
24
27
|
"music",
|
|
25
28
|
"lavalink",
|
package/src/Player.js
CHANGED
|
@@ -2,7 +2,7 @@ const { EventEmitter } = require("events");
|
|
|
2
2
|
const Queue = require("./guild/Queue");
|
|
3
3
|
const Filters = require("./guild/Filter")
|
|
4
4
|
class Player extends EventEmitter {
|
|
5
|
-
constructor(manager,node, options) {
|
|
5
|
+
constructor(manager, node, options) {
|
|
6
6
|
super();
|
|
7
7
|
|
|
8
8
|
this.manager = manager;
|
|
@@ -11,15 +11,19 @@ class Player extends EventEmitter {
|
|
|
11
11
|
|
|
12
12
|
this.node = node;
|
|
13
13
|
|
|
14
|
+
this.options = options;
|
|
15
|
+
|
|
14
16
|
this.filters = new Filters(this, this.node)
|
|
15
17
|
|
|
16
|
-
this.
|
|
18
|
+
this.guildId = options.guildId;
|
|
17
19
|
|
|
18
20
|
this.voiceChannel = options.voiceChannel.id || options.voiceChannel;
|
|
19
21
|
|
|
20
22
|
this.textChannel = options.textChannel || null;
|
|
21
23
|
|
|
22
|
-
this.
|
|
24
|
+
this.shardId = options.shardId || 1;
|
|
25
|
+
|
|
26
|
+
this.isConnected = false;
|
|
23
27
|
|
|
24
28
|
this.isPlaying = false;
|
|
25
29
|
|
|
@@ -37,7 +41,7 @@ class Player extends EventEmitter {
|
|
|
37
41
|
|
|
38
42
|
this.currentTrack = {};
|
|
39
43
|
|
|
40
|
-
this.previousTrack =
|
|
44
|
+
this.previousTrack = null;
|
|
41
45
|
|
|
42
46
|
this.voiceUpdateState = null;
|
|
43
47
|
|
|
@@ -45,31 +49,31 @@ class Player extends EventEmitter {
|
|
|
45
49
|
|
|
46
50
|
this.on("event", (data) => (this.lavalinkEvent(data).bind(this))());
|
|
47
51
|
this.on("playerUpdate", (packet) => {
|
|
48
|
-
this.
|
|
52
|
+
this.isConnected = packet.state.connected,
|
|
49
53
|
this.position = packet.state.position
|
|
50
54
|
this.manager.emit("playerUpdate", this, packet);
|
|
51
55
|
});
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
async play(options={}) {
|
|
58
|
+
async play(options = {}) {
|
|
55
59
|
|
|
56
60
|
if (!this.queue.length) {
|
|
57
61
|
return null;
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
this.currentTrack = this.queue.shift()
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
this.currentTrack = this.queue.shift()
|
|
65
|
+
|
|
66
|
+
if (!this.currentTrack.track) {
|
|
67
|
+
this.currentTrack = await this.currentTrack.resolve(this.manager);
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
this.isPlaying = true;
|
|
67
71
|
this.node.send({
|
|
68
72
|
op: "play",
|
|
69
|
-
guildId: this.
|
|
73
|
+
guildId: this.guildId,
|
|
70
74
|
track: this.currentTrack.track,
|
|
71
|
-
noReplace:options.noReplace || true,
|
|
72
|
-
|
|
75
|
+
noReplace: options.noReplace || true,
|
|
76
|
+
});
|
|
73
77
|
this.position = 0;
|
|
74
78
|
return this;
|
|
75
79
|
}
|
|
@@ -78,11 +82,11 @@ class Player extends EventEmitter {
|
|
|
78
82
|
stop() {
|
|
79
83
|
|
|
80
84
|
this.position = 0;
|
|
81
|
-
this.
|
|
85
|
+
this.isConnected = false
|
|
82
86
|
this.isPlaying = false;
|
|
83
87
|
this.node.send({
|
|
84
88
|
op: "stop",
|
|
85
|
-
guildId: this.
|
|
89
|
+
guildId: this.guildId
|
|
86
90
|
});
|
|
87
91
|
return this;
|
|
88
92
|
}
|
|
@@ -92,7 +96,7 @@ class Player extends EventEmitter {
|
|
|
92
96
|
|
|
93
97
|
this.node.send({
|
|
94
98
|
op: "pause",
|
|
95
|
-
guildId: this.
|
|
99
|
+
guildId: this.guildId,
|
|
96
100
|
pause,
|
|
97
101
|
});
|
|
98
102
|
this.isPlaying = !pause;
|
|
@@ -106,25 +110,25 @@ class Player extends EventEmitter {
|
|
|
106
110
|
this.position = position;
|
|
107
111
|
this.node.send({
|
|
108
112
|
op: "seek",
|
|
109
|
-
guildId: this.
|
|
113
|
+
guildId: this.guildId,
|
|
110
114
|
position,
|
|
111
115
|
});
|
|
112
116
|
return this;
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
|
|
119
|
+
setVolume(volume) {
|
|
116
120
|
if (Number.isNaN(volume)) throw new RangeError("Volume level must be a number.");
|
|
117
|
-
|
|
121
|
+
this.volume = volume;
|
|
118
122
|
this.node.send({
|
|
119
123
|
op: "volume",
|
|
120
|
-
guildId: this.
|
|
124
|
+
guildId: this.guildId,
|
|
121
125
|
volume: this.volume,
|
|
122
126
|
});
|
|
123
127
|
return this;
|
|
124
128
|
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
|
|
131
|
+
TrackRepeat() {
|
|
128
132
|
this.loop = 1;
|
|
129
133
|
this.trackRepeat = true;
|
|
130
134
|
this.queueRepeat = false;
|
|
@@ -132,70 +136,74 @@ class Player extends EventEmitter {
|
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
|
|
135
|
-
|
|
139
|
+
QueueRepeat() {
|
|
136
140
|
this.loop = 2;
|
|
137
141
|
this.queueRepeat = true;
|
|
138
142
|
this.trackRepeat = false;
|
|
139
143
|
return this;
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
|
|
146
|
+
DisableRepeat() {
|
|
143
147
|
this.loop = 0;
|
|
144
148
|
this.trackRepeat = false;
|
|
145
149
|
this.queueRepeat = false;
|
|
146
|
-
|
|
150
|
+
return this;
|
|
147
151
|
}
|
|
148
152
|
|
|
149
|
-
|
|
153
|
+
setTextChannel(channel) {
|
|
150
154
|
if (typeof channel !== "string") throw new RangeError("Channel must be a string.");
|
|
151
155
|
this.textChannel = channel;
|
|
152
156
|
return this;
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
|
|
159
|
+
setVoiceChannel(channel) {
|
|
156
160
|
if (typeof channel !== "string") throw new RangeError("Channel must be a string.");
|
|
157
161
|
this.voiceChannel = channel;
|
|
158
162
|
return this;
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
|
|
165
|
+
connect(options) {
|
|
166
|
+
|
|
167
|
+
let { guildId, voiceChannel, deaf, mute } = options;
|
|
168
|
+
this.send({ guild_id: guildId, channel_id: voiceChannel, self_deaf: deaf || true, self_mute: mute || false }, true);
|
|
169
|
+
this.isConnected = true;
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
updateSession(data) {
|
|
162
174
|
if (data) {
|
|
163
175
|
this.voiceUpdateState = data;
|
|
164
176
|
this.node.send({
|
|
165
177
|
op: "voiceUpdate",
|
|
166
|
-
guildId: this.
|
|
178
|
+
guildId: this.guildId,
|
|
167
179
|
...data,
|
|
168
180
|
});
|
|
169
181
|
}
|
|
170
182
|
return this;
|
|
183
|
+
|
|
171
184
|
}
|
|
172
185
|
|
|
173
|
-
|
|
186
|
+
reconnect() {
|
|
174
187
|
if (this.voiceChannel === null) return null;
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
d: {
|
|
178
|
-
guild_id: this.guild,
|
|
188
|
+
this.send({
|
|
189
|
+
guild_id: this.guildId,
|
|
179
190
|
channel_id: this.voiceChannel,
|
|
180
191
|
self_mute: false,
|
|
181
192
|
self_deaf: false,
|
|
182
|
-
}
|
|
183
|
-
|
|
193
|
+
})
|
|
194
|
+
|
|
184
195
|
return this;
|
|
185
196
|
}
|
|
186
197
|
|
|
187
|
-
|
|
198
|
+
disconnect() {
|
|
188
199
|
if (this.voiceChannel === null) return null;
|
|
189
200
|
this.pause(true);
|
|
190
|
-
this.
|
|
191
|
-
this.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
self_mute: false,
|
|
197
|
-
self_deaf: false,
|
|
198
|
-
},
|
|
201
|
+
this.isConnected = false;
|
|
202
|
+
this.send({
|
|
203
|
+
guild_id: this.guildId,
|
|
204
|
+
channel_id: null,
|
|
205
|
+
self_mute: false,
|
|
206
|
+
self_deaf: false,
|
|
199
207
|
});
|
|
200
208
|
this.voiceChannel = null;
|
|
201
209
|
return this;
|
|
@@ -205,26 +213,26 @@ class Player extends EventEmitter {
|
|
|
205
213
|
this.disconnect();
|
|
206
214
|
this.node.send({
|
|
207
215
|
op: "destroy",
|
|
208
|
-
guildId: this.
|
|
216
|
+
guildId: this.guildId,
|
|
209
217
|
});
|
|
210
218
|
this.manager.emit("playerDestroy", this);
|
|
211
|
-
this.manager.players.delete(this.
|
|
219
|
+
this.manager.players.delete(this.guildId);
|
|
212
220
|
}
|
|
213
221
|
|
|
214
|
-
restart(){
|
|
222
|
+
restart() {
|
|
215
223
|
this.filters.updateFilters();
|
|
216
|
-
if(this.currentTrack){
|
|
224
|
+
if (this.currentTrack) {
|
|
217
225
|
|
|
218
226
|
this.isPlaying = true;
|
|
219
227
|
this.node.send({
|
|
220
228
|
op: "play",
|
|
221
229
|
startTime: this.position,
|
|
222
|
-
noReplace:true,
|
|
223
|
-
guildId: this.
|
|
230
|
+
noReplace: true,
|
|
231
|
+
guildId: this.guildId,
|
|
224
232
|
track: this.currentTrack.track,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
233
|
+
pause: this.isPaused
|
|
234
|
+
});
|
|
235
|
+
|
|
228
236
|
|
|
229
237
|
}
|
|
230
238
|
}
|
|
@@ -236,7 +244,7 @@ class Player extends EventEmitter {
|
|
|
236
244
|
if (!this.previousTrack) return this.stop();
|
|
237
245
|
let data = `https://www.youtube.com/watch?v=${this.previousTrack.info.identifier}&list=RD${this.previousTrack.info.identifier}`;
|
|
238
246
|
|
|
239
|
-
let response = await this.manager.resolve(data);
|
|
247
|
+
let response = await this.manager.resolve(data,this.manager.options.defaultPlatform || "ytsearch");
|
|
240
248
|
|
|
241
249
|
if (!response || !response.tracks || ["LOAD_FAILED", "NO_MATCHES"].includes(response.loadType)) return this.stop();
|
|
242
250
|
|
|
@@ -255,12 +263,17 @@ class Player extends EventEmitter {
|
|
|
255
263
|
|
|
256
264
|
}
|
|
257
265
|
|
|
266
|
+
send(data) {
|
|
267
|
+
|
|
268
|
+
this.manager.sendData({ op: 4, d: data });
|
|
269
|
+
}
|
|
270
|
+
|
|
258
271
|
|
|
259
272
|
lavalinkEvent(data) {
|
|
260
273
|
const events = {
|
|
261
274
|
TrackStartEvent() {
|
|
262
275
|
this.isPlaying = true;
|
|
263
|
-
this.
|
|
276
|
+
this.isPaused = false;
|
|
264
277
|
this.manager.emit("trackStart", this, this.currentTrack, data);
|
|
265
278
|
},
|
|
266
279
|
// eslint-disable-next-line consistent-return
|
|
@@ -282,36 +295,34 @@ class Player extends EventEmitter {
|
|
|
282
295
|
}
|
|
283
296
|
|
|
284
297
|
if (this.queue.length === 0) {
|
|
285
|
-
|
|
298
|
+
this.manager.emit("queueEnd",this, this.track, data);
|
|
299
|
+
return this.destroy();
|
|
300
|
+
|
|
286
301
|
} else if (this.queue.length > 0) {
|
|
287
302
|
this.manager.emit("trackEnd", this, this.currentTrack, data)
|
|
288
303
|
return this.play();
|
|
289
304
|
}
|
|
290
|
-
this.manager.emit("queueEnd", this,
|
|
305
|
+
this.manager.emit("queueEnd", this, this.currentTrack, data);
|
|
306
|
+
this.destroy();
|
|
291
307
|
|
|
292
308
|
},
|
|
293
309
|
TrackStuckEvent() {
|
|
294
|
-
this.
|
|
295
|
-
|
|
310
|
+
this.manager.emit("trackError", this,this.currentTrack, data);
|
|
311
|
+
this.stop();
|
|
312
|
+
|
|
296
313
|
},
|
|
297
314
|
TrackExceptionEvent() {
|
|
298
|
-
this.queue.shift();
|
|
299
|
-
/**
|
|
300
|
-
* Fire up when there's an error while playing the track
|
|
301
|
-
* @event trackError
|
|
302
|
-
*/
|
|
303
315
|
this.manager.emit("trackError", this, this.track, data);
|
|
316
|
+
this.stop();
|
|
304
317
|
},
|
|
305
318
|
WebSocketClosedEvent() {
|
|
306
319
|
if ([4015, 4009].includes(data.code)) {
|
|
307
|
-
this.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
self_deaf: this.options.selfDeaf || false,
|
|
314
|
-
},
|
|
320
|
+
this.send({
|
|
321
|
+
guild_id: data.guildId,
|
|
322
|
+
channel_id: this.voiceChannel.id || this.voiceChannel,
|
|
323
|
+
self_mute: this.options.mute || false,
|
|
324
|
+
self_deaf: this.options.deaf || false,
|
|
325
|
+
|
|
315
326
|
});
|
|
316
327
|
}
|
|
317
328
|
this.manager.emit("socketClosed", this, data);
|
package/src/Poru.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const { EventEmitter } = require("events");
|
|
2
|
-
const fetch = (
|
|
3
|
-
default: fetch
|
|
4
|
-
}) => fetch(...args));
|
|
2
|
+
const { fetch } = require("undici")
|
|
5
3
|
const config = require("./config")
|
|
6
4
|
const Player = require("./Player");
|
|
7
5
|
const Node = require("./Node");
|
|
@@ -12,23 +10,49 @@ const Deezer = require("./platform/Deezer")
|
|
|
12
10
|
class Poru extends EventEmitter {
|
|
13
11
|
constructor(client, nodes, options = {}) {
|
|
14
12
|
super();
|
|
15
|
-
if (!client) throw new Error("[Poru Error]
|
|
16
|
-
if (!nodes) throw new Error("[Poru Error]
|
|
17
|
-
|
|
13
|
+
if (!client) throw new Error("[Poru Error] You didn't provide a valid client");
|
|
14
|
+
if (!nodes) throw new Error("[Poru Error] You didn't provide a lavalink node");
|
|
15
|
+
|
|
18
16
|
this.client = client;
|
|
19
17
|
this._nodes = nodes;
|
|
20
18
|
this.nodes = new Map();
|
|
21
19
|
this.players = new Map();
|
|
22
20
|
this.voiceStates = new Map();
|
|
23
21
|
this.voiceServers = new Map();
|
|
22
|
+
this.isReady = false;
|
|
24
23
|
this.user = null;
|
|
25
24
|
this.options = options
|
|
26
25
|
this.shards = options.shards || 1;
|
|
27
26
|
this.sendData = null;
|
|
28
27
|
this.version = config.version
|
|
28
|
+
this.spotify = new Spotify(this, this.options);
|
|
29
|
+
this.apple = new AppleMusic(this, this.options)
|
|
30
|
+
this.apple.requestToken();
|
|
31
|
+
this.deezer = new Deezer(this, this.options)
|
|
32
|
+
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
|
|
36
|
+
init(client) {
|
|
37
|
+
|
|
38
|
+
if (this.isReady) return this;
|
|
39
|
+
|
|
40
|
+
this.user = client.user.id;
|
|
41
|
+
this.sendData = (data) => {
|
|
42
|
+
|
|
43
|
+
const guild = client.guilds.cache.get(data.d.guild_id);
|
|
44
|
+
if (guild) guild.shard.send(data);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
client.on("raw", async packet => {
|
|
48
|
+
await this.packetUpdate(packet);
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
this._nodes.forEach((node) => this.addNode(node));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
32
56
|
|
|
33
57
|
//create a node and connect it with lavalink
|
|
34
58
|
addNode(options) {
|
|
@@ -45,66 +69,89 @@ class Poru extends EventEmitter {
|
|
|
45
69
|
|
|
46
70
|
//remove node and destroy web socket connection
|
|
47
71
|
removeNode(identifier) {
|
|
72
|
+
if (!identifier) throw new Error(`[Poru Error] Provide identifier as a parameter of removeNode`)
|
|
48
73
|
const node = this.nodes.get(identifier);
|
|
49
74
|
if (!node) return;
|
|
50
75
|
node.destroy();
|
|
51
76
|
this.nodes.delete(identifier)
|
|
52
77
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
guild_id: data.guild.id || data.guild,
|
|
63
|
-
channel_id: data.voiceChannel.id || data.voiceChannel,
|
|
64
|
-
self_mute: data.selfMute || false,
|
|
65
|
-
self_deaf: data.selfDeaf || true,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
return this.#Player(data);
|
|
78
|
+
|
|
79
|
+
get leastUsedNodes() {
|
|
80
|
+
return [...this.nodes.values()]
|
|
81
|
+
.filter((node) => node.isConnected)
|
|
82
|
+
.sort((a, b) => {
|
|
83
|
+
const aLoad = a.stats.cpu ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100 : 0;
|
|
84
|
+
const bLoad = b.stats.cpu ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 : 0;
|
|
85
|
+
return aLoad - bLoad;
|
|
86
|
+
});
|
|
69
87
|
}
|
|
70
88
|
|
|
71
89
|
|
|
90
|
+
getNode(identifier = "best") {
|
|
91
|
+
if (!this.nodes.size) throw new Error(`No nodes avaliable currently`)
|
|
92
|
+
if (identifier === "best") return this.leastUsedNodes();
|
|
72
93
|
|
|
73
|
-
|
|
94
|
+
const node = this.nodes.get(indetifier);
|
|
95
|
+
if (!node) throw new Error('The node identifier you provided is not found');
|
|
96
|
+
if (!node.isConnected) node.connect();
|
|
97
|
+
return node;
|
|
98
|
+
}
|
|
74
99
|
|
|
75
|
-
this.user = client.user.id;
|
|
76
|
-
this.sendData = (data) => {
|
|
77
|
-
const guild = client.guilds.cache.get(data.d.guild_id);
|
|
78
|
-
if (guild) guild.shard.send(data);
|
|
79
|
-
}
|
|
80
|
-
client.on("raw", async packet => {
|
|
81
|
-
await this.packetUpdate(packet);
|
|
82
|
-
})
|
|
83
100
|
|
|
84
|
-
|
|
101
|
+
createConnection(options) {
|
|
102
|
+
this.checkConnection(options)
|
|
103
|
+
const player = this.players.get(options.guildId);
|
|
104
|
+
if (player) return player;
|
|
85
105
|
|
|
106
|
+
if (this.leastUsedNodes.length === 0) throw new Error("[Poru Error] No nodes are avaliable");
|
|
107
|
+
const node = this.nodes.get(this.leastUsedNodes[0].name || this.leastUsedNodes[0].host);
|
|
108
|
+
if (!node) throw new Error("[Poru Error] No nodes are avalible");
|
|
109
|
+
|
|
110
|
+
return this.#createPlayer(node, options);
|
|
86
111
|
|
|
87
|
-
if (this.options.spotify && this.options.spotify.clientID && this.options.spotify.clientSecret) {
|
|
88
|
-
this.spotify = new Spotify(this, this.options)
|
|
89
|
-
}
|
|
90
|
-
if (this.options.apple) {
|
|
91
|
-
if (!this.options.apple.playlistLimit) {
|
|
92
|
-
throw new Error("[Poru Apple Music] playlistLimit must be provided")
|
|
93
|
-
}
|
|
94
|
-
this.apple = new AppleMusic(this, this.options)
|
|
95
|
-
this.apple.requestToken();
|
|
96
|
-
}
|
|
97
|
-
if (this.options.deezer) {
|
|
98
|
-
if (!this.options.deezer.playlistLimit) {
|
|
99
|
-
throw new Error("[Poru Deezer Music] playlistLimit must be provided")
|
|
100
112
|
|
|
101
|
-
}
|
|
102
|
-
this.deezer = new Deezer(this, this.options)
|
|
103
113
|
|
|
104
|
-
}
|
|
105
|
-
console.log(`Thanks for using Poru`)
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
removeConnection(guildId) {
|
|
117
|
+
this.players.get(guildId)?.destroy();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
checkConnection(options) {
|
|
122
|
+
|
|
123
|
+
let { guildId, voiceChannel, textChannel, shardId } = options;
|
|
124
|
+
if (!guildId) throw new Error(`[Poru Connection] you have to Provide guildId`)
|
|
125
|
+
if (!voiceChannel) throw new Error(`[Poru Connection] you have to Provide voiceChannel`)
|
|
126
|
+
if (!textChannel) throw new Error(`[Poru Connection] you have to Provide texteChannel`);
|
|
127
|
+
// if(shardId == null) throw new Error(`[Poru Connection] You must have to Provide shardId`);
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if (typeof guildId !== "string") throw new Error(`[Poru Connection] guildId must be provided as a string`);
|
|
131
|
+
if (typeof voiceChannel !== "string") throw new Error(`[Poru Connection] voiceChannel must be provided as a string`);
|
|
132
|
+
if (typeof textChannel !== "string") throw new Error(`[Poru Connection] textChannel must be provided as a string`);
|
|
133
|
+
// if(typeof shardId !=="number") throw new Error(`[Poru Connection] shardId must be provided as a number`);
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
#createPlayer(node, options) {
|
|
141
|
+
|
|
142
|
+
if (this.players.has(options.guildId)) return this.players.get(options.guildId);
|
|
143
|
+
|
|
144
|
+
const player = new Player(this, node, options);
|
|
145
|
+
this.players.set(options.guildId, player);
|
|
146
|
+
player.connect(options)
|
|
147
|
+
return player;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
108
155
|
|
|
109
156
|
setServersUpdate(data) {
|
|
110
157
|
let guild = data.guild_id
|
|
@@ -115,7 +162,7 @@ class Poru extends EventEmitter {
|
|
|
115
162
|
const player = this.players.get(guild);
|
|
116
163
|
if (!player) return false;
|
|
117
164
|
|
|
118
|
-
player.
|
|
165
|
+
player.updateSession({
|
|
119
166
|
sessionId: state ? state.session_id : player.voiceUpdateState.sessionId,
|
|
120
167
|
event: server,
|
|
121
168
|
});
|
|
@@ -134,8 +181,7 @@ class Poru extends EventEmitter {
|
|
|
134
181
|
if (!server) return false;
|
|
135
182
|
const player = this.players.get(guild);
|
|
136
183
|
if (!player) return false;
|
|
137
|
-
|
|
138
|
-
player.connect({
|
|
184
|
+
player.updateSession({
|
|
139
185
|
sessionId: state ? state.session_id : player.voiceUpdateState.sessionId,
|
|
140
186
|
event: server,
|
|
141
187
|
});
|
|
@@ -161,61 +207,65 @@ class Poru extends EventEmitter {
|
|
|
161
207
|
|
|
162
208
|
|
|
163
209
|
|
|
164
|
-
get leastUsedNodes() {
|
|
165
|
-
return [...this.nodes.values()]
|
|
166
|
-
.filter((node) => node.isConnected)
|
|
167
|
-
.sort((a, b) => {
|
|
168
|
-
const aLoad = a.stats.cpu ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100 : 0;
|
|
169
|
-
const bLoad = b.stats.cpu ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 : 0;
|
|
170
|
-
return aLoad - bLoad;
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
210
|
|
|
174
|
-
#Player(data) {
|
|
175
|
-
const guild = data.guild.id || data.guild;
|
|
176
|
-
const Nodes = this.nodes.get(guild);
|
|
177
|
-
if (Nodes) return Nodes;
|
|
178
|
-
if (this.leastUsedNodes.length === 0) throw new Error("[Poru Error] No nodes are avaliable");
|
|
179
|
-
const node = this.nodes.get(this.leastUsedNodes[0].name
|
|
180
|
-
|| this.leastUsedNodes[0].host);
|
|
181
|
-
if (!node) throw new Error("[Poru Error] No nodes are avalible");
|
|
182
211
|
|
|
183
|
-
// eslint-disable-next-line new-cap
|
|
184
|
-
const player = new Player(this, node, data);
|
|
185
|
-
this.players.set(guild, player);
|
|
186
|
-
player.connect()
|
|
187
|
-
return player;
|
|
188
|
-
}
|
|
189
212
|
|
|
213
|
+
async resolve(query, source) {
|
|
190
214
|
|
|
215
|
+
const node = this.leastUsedNodes[0];
|
|
216
|
+
if (!node) throw new Error("No nodes are available.");
|
|
217
|
+
const regex = /^https?:\/\//;
|
|
191
218
|
|
|
219
|
+
if (regex.test(query)) {
|
|
220
|
+
return this.fetchURL(node, query, source)
|
|
221
|
+
} else {
|
|
222
|
+
return this.fetchTrack(node, query, source)
|
|
223
|
+
}
|
|
192
224
|
|
|
193
|
-
|
|
225
|
+
}
|
|
194
226
|
|
|
195
|
-
|
|
196
|
-
if (!node) throw new Error("No nodes are available.");
|
|
227
|
+
async fetchURL(node, track, source) {
|
|
197
228
|
|
|
198
|
-
if (this.spotify
|
|
229
|
+
if (this.spotify.check(track)) {
|
|
199
230
|
return await this.spotify.resolve(track);
|
|
200
|
-
} else if (this.apple
|
|
231
|
+
} else if (this.apple.check(track)) {
|
|
201
232
|
return await this.apple.resolve(track);
|
|
202
|
-
} else if (this.deezer
|
|
233
|
+
} else if (this.deezer.check(track)) {
|
|
203
234
|
return await this.deezer.resolve(track);
|
|
235
|
+
} else {
|
|
236
|
+
const result = await this.#fetch(node, "loadtracks", `identifier=${encodeURIComponent(track)}`);
|
|
237
|
+
if (!result) throw new Error("[Poru Error] No tracks found.");
|
|
238
|
+
return new Response(result);
|
|
204
239
|
}
|
|
240
|
+
}
|
|
205
241
|
|
|
206
242
|
|
|
243
|
+
async fetchTrack(node, query, source) {
|
|
244
|
+
switch (source) {
|
|
245
|
+
|
|
246
|
+
case "spotify": {
|
|
247
|
+
return this.spotify.fetch(query)
|
|
248
|
+
}
|
|
249
|
+
case "applemusic": {
|
|
250
|
+
return this.apple.fetch(query)
|
|
251
|
+
}
|
|
252
|
+
case "deezer": {
|
|
253
|
+
return this.deezer.fetch(query);
|
|
254
|
+
}
|
|
255
|
+
default:
|
|
256
|
+
{
|
|
257
|
+
let track = `${source || "ytsearch"}:${query}`;
|
|
258
|
+
const result = await this.#fetch(node, "loadtracks", `identifier=${encodeURIComponent(track)}`);
|
|
259
|
+
if (!result) throw new Error("[Poru Error] No tracks found.");
|
|
260
|
+
return new Response(result);
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
}
|
|
207
264
|
|
|
208
|
-
const regex = /^https?:\/\//;
|
|
209
|
-
if (!regex.test(track)) {
|
|
210
|
-
// eslint-disable-next-line no-param-reassign
|
|
211
|
-
track = `${source || "ytsearch"}:${track}`;
|
|
212
265
|
}
|
|
213
|
-
const result = await this.#fetch(node, "loadtracks", `identifier=${encodeURIComponent(track)}`);
|
|
214
266
|
|
|
215
|
-
if (!result) throw new Error("[Poru Error] No tracks found.");
|
|
216
|
-
return new Response(result);
|
|
217
|
-
}
|
|
218
267
|
|
|
268
|
+
}
|
|
219
269
|
|
|
220
270
|
async decodeTrack(track) {
|
|
221
271
|
const node = this.leastUsedNodes[0];
|
package/src/guild/Filter.js
CHANGED
|
@@ -5,7 +5,6 @@ class Filters {
|
|
|
5
5
|
this.bassboost = options.bassboost || null;
|
|
6
6
|
this.player = player;
|
|
7
7
|
this.node = player.node
|
|
8
|
-
this.volume = player.volume ?? 1.0 ;
|
|
9
8
|
this.equalizer = options.equalizer || [];
|
|
10
9
|
this.karaoke = options.karaoke || null;
|
|
11
10
|
this.timescale = options.timescale || null;
|
|
@@ -18,8 +17,36 @@ class Filters {
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
setEqualizer(
|
|
22
|
-
|
|
20
|
+
setEqualizer(band,gain){
|
|
21
|
+
this.band = band || this.band;
|
|
22
|
+
this.gain = gain || this.gain;
|
|
23
|
+
|
|
24
|
+
this.equalizer = [
|
|
25
|
+
{
|
|
26
|
+
band: this.band,
|
|
27
|
+
gain: this.gain,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
band: this.band,
|
|
31
|
+
gain: this.gain,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
band: this.band,
|
|
35
|
+
gain: this.gain,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
band: this.band,
|
|
39
|
+
gain: this.gain,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
band: this.band,
|
|
43
|
+
gain: this.gain,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
band: this.band,
|
|
47
|
+
gain: this.gain,
|
|
48
|
+
},
|
|
49
|
+
]
|
|
23
50
|
this.updateFilters();
|
|
24
51
|
return this;
|
|
25
52
|
}
|
|
@@ -69,16 +96,16 @@ class Filters {
|
|
|
69
96
|
}
|
|
70
97
|
|
|
71
98
|
setFilters(options) {
|
|
72
|
-
this.player.filters = new Filters(options);
|
|
99
|
+
this.player.filters = new Filters(this.player,options);
|
|
73
100
|
this.updateFilters();
|
|
74
101
|
return this;
|
|
75
102
|
}
|
|
76
103
|
|
|
77
104
|
clearFilters() {
|
|
78
|
-
this.player.filters = new Filters();
|
|
79
|
-
this.node.send({
|
|
105
|
+
this.player.filters = new Filters(this.player);
|
|
106
|
+
this.player.node.send({
|
|
80
107
|
op: "filters",
|
|
81
|
-
guildId: this.player.
|
|
108
|
+
guildId: this.player.guildId
|
|
82
109
|
});
|
|
83
110
|
return this;
|
|
84
111
|
}
|
|
@@ -131,21 +158,14 @@ class Filters {
|
|
|
131
158
|
if (!this.player) return;
|
|
132
159
|
this.bassboost = !!val;
|
|
133
160
|
this.bassboost = val / 100;
|
|
134
|
-
this.
|
|
135
|
-
val
|
|
136
|
-
? Array(6)
|
|
137
|
-
.fill(0.22)
|
|
138
|
-
.map((x, i) => ({ band: i, gain: x * val }))
|
|
139
|
-
: []
|
|
140
|
-
);
|
|
161
|
+
this.setEqualizer(1,0.90);
|
|
141
162
|
}
|
|
142
163
|
|
|
143
164
|
updateFilters(){
|
|
144
|
-
const {
|
|
165
|
+
const {equalizer, karaoke, timescale, tremolo, vibrato, rotation, distortion, channelMix, lowPass } = this;
|
|
145
166
|
this.node.send({
|
|
146
167
|
op:"filters",
|
|
147
|
-
guildId: this.player.
|
|
148
|
-
volume,
|
|
168
|
+
guildId: this.player.guildId,
|
|
149
169
|
equalizer,
|
|
150
170
|
karaoke,
|
|
151
171
|
timescale,
|
package/src/guild/PoruTrack.js
CHANGED
|
@@ -21,7 +21,7 @@ class PoruTrack {
|
|
|
21
21
|
const query = [this.info.author, this.info.title].filter((x) => !!x).join(' - ');
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
const result = await manager.resolve(query);
|
|
24
|
+
const result = await manager.resolve(query,manager.options.defaultPlatform || "ytsearch");
|
|
25
25
|
if (!result || !result.tracks.length) return;
|
|
26
26
|
|
|
27
27
|
if (this.info.author) {
|
package/src/guild/Track.js
CHANGED
|
@@ -21,7 +21,7 @@ class Track {
|
|
|
21
21
|
const query = [this.info.author, this.info.title].filter((x) => !!x).join(' - ');
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
const result = await manager.resolve(query);
|
|
24
|
+
const result = await manager.resolve(query,manager.options.defaultPlatform || "ytsearch");
|
|
25
25
|
if (!result || !result.tracks.length) return;
|
|
26
26
|
|
|
27
27
|
if (this.info.author) {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const fetch = (
|
|
2
|
-
default: fetch
|
|
3
|
-
}) => fetch(...args));
|
|
1
|
+
const {fetch} = require("undici")
|
|
4
2
|
const PoruTrack = require("../guild/PoruTrack")
|
|
5
3
|
let baseURL = /(?:https:\/\/music\.apple\.com\/)(?:.+)?(artist|album|music-video|playlist)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)/;
|
|
6
4
|
|
|
@@ -8,12 +6,12 @@ class AppleMusic {
|
|
|
8
6
|
constructor(manager, options) {
|
|
9
7
|
this.manager = manager;
|
|
10
8
|
this.options = {
|
|
11
|
-
playlistLimit: options.
|
|
12
|
-
albumLimit: options.
|
|
13
|
-
artistLimit: options.
|
|
14
|
-
searchMarket: options.
|
|
15
|
-
imageHeight: options.
|
|
16
|
-
imageWeight: options.
|
|
9
|
+
playlistLimit: options.playlistLimit || null,
|
|
10
|
+
albumLimit: options.albumLimit || null,
|
|
11
|
+
artistLimit: options.artistLimit || null,
|
|
12
|
+
searchMarket: options.searchMarket || "us",
|
|
13
|
+
imageHeight: options.imageHeight || 500,
|
|
14
|
+
imageWeight: options.imageWeight || 500,
|
|
17
15
|
}
|
|
18
16
|
this.url = `https://amp-api.music.apple.com/v1/catalog/${this.options.searchMarket}`
|
|
19
17
|
this.token = null;
|
|
@@ -245,4 +243,4 @@ module.exports = AppleMusic
|
|
|
245
243
|
|
|
246
244
|
const apple = new AppleMusic("",{apple:{}})
|
|
247
245
|
|
|
248
|
-
apple.resolve("https://music.apple.com/us/playlist/bollywood-hits/pl.d60caf02fcce4d7e9788fe01243b7c2c")
|
|
246
|
+
apple.resolve("https://music.apple.com/us/playlist/bollywood-hits/pl.d60caf02fcce4d7e9788fe01243b7c2c")
|
package/src/platform/Deezer.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
const fetch = (
|
|
2
|
-
|
|
3
|
-
}) => fetch(...args));
|
|
1
|
+
const {fetch} = require("undici")
|
|
2
|
+
|
|
4
3
|
let REGEX = /^(?:https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?(track|album|playlist|artist)\/(\d+)/
|
|
5
4
|
|
|
6
5
|
const PoruTrack = require("../guild/PoruTrack")
|
|
@@ -12,9 +11,9 @@ class Deezer {
|
|
|
12
11
|
this.manager = manager;
|
|
13
12
|
this.baseURL = 'https://api.deezer.com';
|
|
14
13
|
this.options = {
|
|
15
|
-
playlistLimit: options.
|
|
16
|
-
albumLimit: options.
|
|
17
|
-
artistLimit: options.
|
|
14
|
+
playlistLimit: options.playlistLimit || null,
|
|
15
|
+
albumLimit: options.albumLimit || null,
|
|
16
|
+
artistLimit: options.artistLimit || null
|
|
18
17
|
|
|
19
18
|
}
|
|
20
19
|
}
|
|
@@ -230,4 +229,4 @@ module.exports = Deezer;
|
|
|
230
229
|
let deezer = new Deezer("", { deezer: { playlistLimit: 10 } })
|
|
231
230
|
|
|
232
231
|
|
|
233
|
-
deezer.resolve("https://www.deezer.com/en/playlist/4404579662")
|
|
232
|
+
deezer.resolve("https://www.deezer.com/en/playlist/4404579662")
|
package/src/platform/Spotify.js
CHANGED
|
@@ -1,31 +1,52 @@
|
|
|
1
|
-
const fetch = (
|
|
2
|
-
|
|
3
|
-
}) => fetch(...args));
|
|
1
|
+
const {fetch} = require("undici")
|
|
2
|
+
|
|
4
3
|
let spotifyPattern =
|
|
5
4
|
/^(?:https:\/\/open\.spotify\.com\/(?:user\/[A-Za-z0-9]+\/)?|spotify:)(album|playlist|track|artist)(?:[/:])([A-Za-z0-9]+).*$/;
|
|
6
5
|
|
|
7
|
-
const
|
|
6
|
+
const PoruTrack = require("../guild/PoruTrack")
|
|
8
7
|
|
|
9
8
|
class Spotify {
|
|
10
9
|
constructor(manager) {
|
|
11
10
|
this.manager = manager;
|
|
12
11
|
this.baseURL = 'https://api.spotify.com/v1';
|
|
13
12
|
this.options ={
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
searchMarket : manager.options.spotify.searchMarket
|
|
13
|
+
|
|
14
|
+
playlistLimit : manager.options.playlistLimit,
|
|
15
|
+
albumLimit : manager.options.albumLimit,
|
|
16
|
+
artistLimit : manager.options.artistLimit,
|
|
17
|
+
searchMarket : manager.options.searchMarket
|
|
20
18
|
}
|
|
21
19
|
this.authorization = Buffer.from(`${this.options.clientID}:${this.options.clientSecret}`).toString('base64');
|
|
22
20
|
this.interval = 0;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
check(url) {
|
|
24
|
+
|
|
26
25
|
return spotifyPattern.test(url);
|
|
27
26
|
}
|
|
28
27
|
|
|
28
|
+
|
|
29
|
+
async requestToken() {
|
|
30
|
+
try{
|
|
31
|
+
const data = await fetch('https://open.spotify.com/get_access_token?reason=transport&productType=embed', {
|
|
32
|
+
headers: {
|
|
33
|
+
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36'
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const body = await data.json();
|
|
38
|
+
this.token = `Bearer ${body.accessToken}`;
|
|
39
|
+
this.interval = body.accessTokenExpirationTimestampMs * 1000;
|
|
40
|
+
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (e.status === 400) {
|
|
43
|
+
throw new Error('Invalid Spotify client.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
29
50
|
async requestToken() {
|
|
30
51
|
|
|
31
52
|
try {
|
|
@@ -47,7 +68,7 @@ class Spotify {
|
|
|
47
68
|
}
|
|
48
69
|
}
|
|
49
70
|
}
|
|
50
|
-
|
|
71
|
+
*/
|
|
51
72
|
async renew() {
|
|
52
73
|
if (Date.now() >= this.interval) {
|
|
53
74
|
await this.requestToken();
|
|
@@ -65,7 +86,7 @@ class Spotify {
|
|
|
65
86
|
}
|
|
66
87
|
|
|
67
88
|
async resolve(url) {
|
|
68
|
-
|
|
89
|
+
if (!this.token) await this.requestToken();
|
|
69
90
|
const [, type, id] = spotifyPattern.exec(url) ?? [];
|
|
70
91
|
|
|
71
92
|
switch (type) {
|
|
@@ -81,7 +102,6 @@ class Spotify {
|
|
|
81
102
|
case 'artist': {
|
|
82
103
|
return this.fetchArtist(id);
|
|
83
104
|
}
|
|
84
|
-
|
|
85
105
|
|
|
86
106
|
}
|
|
87
107
|
}
|
|
@@ -203,7 +223,7 @@ class Spotify {
|
|
|
203
223
|
async buildUnresolved(track) {
|
|
204
224
|
if (!track) throw new ReferenceError('The Spotify track object was not provided');
|
|
205
225
|
|
|
206
|
-
return new
|
|
226
|
+
return new PoruTrack({
|
|
207
227
|
track: '',
|
|
208
228
|
info: {
|
|
209
229
|
sourceName: 'spotify',
|