poru 2.0.0 → 3.0.0
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/README.md +59 -55
- package/index.js +12 -16
- package/package.json +5 -2
- package/src/Node.js +30 -8
- package/src/Player.js +88 -62
- package/src/Poru.js +140 -90
- package/src/config.js +1 -0
- package/src/guild/Filter.js +6 -8
- package/src/guild/PoruTrack.js +1 -1
- package/src/guild/Track.js +1 -1
- package/src/platform/AppleMusic.js +13 -11
- package/src/platform/Deezer.js +6 -7
- package/src/platform/Spotify.js +32 -12
- package/typings/index.d.ts +2 -3
package/README.md
CHANGED
|
@@ -50,72 +50,76 @@ To use you need a configured [Lavalink](https://github.com/Frederikam/Lavalink)
|
|
|
50
50
|
## Example usage basic bot
|
|
51
51
|
|
|
52
52
|
```javascript
|
|
53
|
-
// main file
|
|
54
|
-
// Require both libraries
|
|
55
53
|
const { Client } = require('discord.js');
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const { Poru } = require('poru');
|
|
55
|
+
const nodes = [
|
|
56
|
+
{
|
|
57
|
+
id: "main_node",
|
|
58
|
+
hostname:"localhost",
|
|
59
|
+
port: 8080,
|
|
60
|
+
password: "iloveyou3000"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
|
|
59
64
|
const client = new Client();
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
host: 'localhost',
|
|
65
|
-
password: 'youshallnotpass',
|
|
66
|
-
port: 2333,
|
|
67
|
-
secure: false,
|
|
68
|
-
},
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
// Define if you want to integrate spotify
|
|
72
|
-
const spotifyOptions = {
|
|
73
|
-
clientID: 'Your Client ID', // You'll find this on https://developers.spotify.com/dashboard/
|
|
74
|
-
clientSecret: 'Your Client Secret', // You'll find this on https://developers.spotify.com/dashboard/
|
|
75
|
-
playlistLimit: 10, // The amount of pages to load when a playlist is searched with each page having 50 tracks.
|
|
76
|
-
albumLimit: 5, // The amount of pages to load when a album is searched with each page having 50 tracks.
|
|
77
|
-
artistLimit: 5, // The amount of pages to load when a artist is searched with each page having 50 tracks.
|
|
78
|
-
searchMarket: 'IN', // The market from where the query should be searched from. Mainly this should contain your country.
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// Assign Manager to the client variable
|
|
82
|
-
client.poru = new Poru(client, nodes);
|
|
83
|
-
|
|
84
|
-
// Emitted whenever a node connects
|
|
85
|
-
client.poru.on('nodeConnect', node => {
|
|
86
|
-
console.log(`Node "${node.name}" connected.`);
|
|
87
|
-
});
|
|
66
|
+
client.poru = new Poru(client,nodes,PoruOptions)
|
|
67
|
+
|
|
68
|
+
|
|
88
69
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
70
|
+
client.poru.on('trackStart', (player, track) => {
|
|
71
|
+
|
|
72
|
+
player.textChannel.send(`Now playing \`${track.title}\``);
|
|
92
73
|
});
|
|
93
74
|
|
|
94
|
-
|
|
95
|
-
client.
|
|
75
|
+
|
|
76
|
+
client.on('ready', () => {
|
|
77
|
+
console.log('Ready!');
|
|
96
78
|
client.poru.init(client);
|
|
97
|
-
console.log(`Logged in as ${client.user.tag}`);
|
|
98
79
|
});
|
|
99
80
|
|
|
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
81
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
82
|
+
client.on('interactionCreate', async interaction => {
|
|
83
|
+
if (!interaction.isCommand()) return;
|
|
84
|
+
if (!interaction.member.voice.channel) return interaction.reply({ content: `Please connect with voice channel `, ephemeral: true });
|
|
85
|
+
|
|
86
|
+
const track = interaction.options.getString('track');
|
|
87
|
+
|
|
88
|
+
const res = await client.poru.resolve(track);
|
|
89
|
+
|
|
90
|
+
if (res.loadType === "LOAD_FAILED") {
|
|
91
|
+
return interaction.reply(`Failed to load track`);
|
|
92
|
+
} else if (res.loadType === "NO_MATCHES") {
|
|
93
|
+
return interaction.reply('No source found!');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//create connection with discord voice channnel
|
|
97
|
+
const player = client.poru.createConnection({
|
|
98
|
+
guildId: interaction.guild.id,
|
|
99
|
+
voiceChannel: interaction.member.voice.channelId,
|
|
100
|
+
textChannel: interaction.channel,
|
|
101
|
+
deaf: true
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if (res.loadType === 'PLAYLIST_LOADED') {
|
|
106
|
+
for (const track of res.tracks) {
|
|
107
|
+
trackk.info.requester = interaction.user;
|
|
108
|
+
player.queue.add(track);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interaction.reply(`${res.playlistInfo.name} has been loaded with ${res.tracks.length}`);
|
|
112
|
+
} else {
|
|
113
|
+
const track = res.tracks[0];
|
|
114
|
+
track.info.requester = interaction.user;
|
|
115
|
+
player.queue.add(track);
|
|
116
|
+
interacton.reply(`Queued Track \n \`${track.title}\``);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!player.isPlaying && player.isConnected) player.play();
|
|
115
120
|
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
```
|
|
121
|
+
|
|
122
|
+
client.login('TOKEN');```
|
|
119
123
|
|
|
120
124
|
## Need Help?
|
|
121
125
|
|
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.0",
|
|
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/Node.js
CHANGED
|
@@ -55,6 +55,17 @@ class Node {
|
|
|
55
55
|
this.ws.on("close", this.#close.bind(this));
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
disconnect(){
|
|
59
|
+
if(!this.isConnected) return;
|
|
60
|
+
|
|
61
|
+
this.ws?.removeAllListeners();
|
|
62
|
+
this.ws?.close();
|
|
63
|
+
this.ws = null;
|
|
64
|
+
this.isConnected = false;
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
|
|
59
70
|
#open(){
|
|
60
71
|
if (this.reconnectAttempt) {
|
|
@@ -74,35 +85,46 @@ class Node {
|
|
|
74
85
|
this.manager.emit("nodeConnect", this);
|
|
75
86
|
this.isConnected = true;
|
|
76
87
|
this.manager.emit('debug', this.name, `[Web Socket] Connection ready ${this.url}`);
|
|
88
|
+
|
|
89
|
+
if(config.autoResume){
|
|
90
|
+
|
|
91
|
+
for (const player of this.manager.players.values()) {
|
|
92
|
+
if (player.node === this) {
|
|
93
|
+
player.restart();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
}
|
|
77
103
|
|
|
78
104
|
}
|
|
79
105
|
|
|
80
106
|
#message(payload) {
|
|
81
|
-
if (Array.isArray(payload)) payload = Buffer.concat(payload);
|
|
82
|
-
else if (payload instanceof ArrayBuffer) payload = Buffer.from(payload);
|
|
83
107
|
|
|
84
108
|
const packet = JSON.parse(payload);
|
|
85
109
|
if(!packet.op) return;
|
|
86
110
|
|
|
87
111
|
if (packet.op && packet.op === "stats") {
|
|
88
112
|
this.stats = { ...packet };
|
|
89
|
-
delete this.stats.op;
|
|
90
113
|
}
|
|
91
114
|
const player = this.manager.players.get(packet.guildId);
|
|
92
115
|
if (packet.guildId && player) player.emit(packet.op, packet);
|
|
93
|
-
|
|
94
|
-
packet.node = this;
|
|
116
|
+
packet.node = this;
|
|
95
117
|
this.manager.emit("debug",this.name,`[Web Socket] Lavalink Node Update : ${packet.op} `)
|
|
96
118
|
this.manager.emit("raw", packet);
|
|
97
119
|
}
|
|
98
120
|
|
|
99
121
|
#close(event) {
|
|
100
|
-
this.
|
|
122
|
+
this.disconnect();
|
|
123
|
+
this.manager.emit("nodeDisconnect",this,event);
|
|
101
124
|
this.manager.emit("debug",this.name,`[Web Socket] Connection with Lavalink closed with Error code : ${event||"Unknown code"}`)
|
|
102
125
|
if (event !== 1000){
|
|
103
126
|
|
|
104
|
-
|
|
105
|
-
}
|
|
127
|
+
}
|
|
106
128
|
}
|
|
107
129
|
|
|
108
130
|
|
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,19 +11,23 @@ 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
|
|
|
26
|
-
this.
|
|
30
|
+
this.isPaused = false;
|
|
27
31
|
|
|
28
32
|
this.trackRepeat = false;
|
|
29
33
|
|
|
@@ -45,28 +49,30 @@ 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() {
|
|
58
|
+
async play(options = {}) {
|
|
55
59
|
|
|
56
60
|
if (!this.queue.length) {
|
|
57
|
-
return
|
|
61
|
+
return null;
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
|
|
64
|
+
this.currentTrack = this.queue.shift()
|
|
65
|
+
|
|
66
|
+
if (!this.currentTrack.track) {
|
|
67
|
+
this.currentTrack = await this.currentTrack.resolve(this.manager);
|
|
62
68
|
}
|
|
69
|
+
|
|
63
70
|
this.isPlaying = true;
|
|
64
|
-
this.timestamp = Date.now();
|
|
65
71
|
this.node.send({
|
|
66
72
|
op: "play",
|
|
67
|
-
guildId: this.
|
|
73
|
+
guildId: this.guildId,
|
|
68
74
|
track: this.currentTrack.track,
|
|
69
|
-
|
|
75
|
+
noReplace: options.noReplace || true,
|
|
70
76
|
});
|
|
71
77
|
this.position = 0;
|
|
72
78
|
return this;
|
|
@@ -76,11 +82,11 @@ class Player extends EventEmitter {
|
|
|
76
82
|
stop() {
|
|
77
83
|
|
|
78
84
|
this.position = 0;
|
|
79
|
-
this.
|
|
85
|
+
this.isConnected = false
|
|
80
86
|
this.isPlaying = false;
|
|
81
87
|
this.node.send({
|
|
82
88
|
op: "stop",
|
|
83
|
-
guildId: this.
|
|
89
|
+
guildId: this.guildId
|
|
84
90
|
});
|
|
85
91
|
return this;
|
|
86
92
|
}
|
|
@@ -90,11 +96,11 @@ class Player extends EventEmitter {
|
|
|
90
96
|
|
|
91
97
|
this.node.send({
|
|
92
98
|
op: "pause",
|
|
93
|
-
guildId: this.
|
|
99
|
+
guildId: this.guildId,
|
|
94
100
|
pause,
|
|
95
101
|
});
|
|
96
102
|
this.isPlaying = !pause;
|
|
97
|
-
this.
|
|
103
|
+
this.isPaused = pause;
|
|
98
104
|
|
|
99
105
|
return this;
|
|
100
106
|
}
|
|
@@ -104,97 +110,100 @@ class Player extends EventEmitter {
|
|
|
104
110
|
this.position = position;
|
|
105
111
|
this.node.send({
|
|
106
112
|
op: "seek",
|
|
107
|
-
guildId: this.
|
|
113
|
+
guildId: this.guildId,
|
|
108
114
|
position,
|
|
109
115
|
});
|
|
110
116
|
return this;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
|
|
119
|
+
setVolume(volume) {
|
|
114
120
|
if (Number.isNaN(volume)) throw new RangeError("Volume level must be a number.");
|
|
115
|
-
|
|
121
|
+
this.volume = volume;
|
|
116
122
|
this.node.send({
|
|
117
123
|
op: "volume",
|
|
118
|
-
guildId: this.
|
|
124
|
+
guildId: this.guildId,
|
|
119
125
|
volume: this.volume,
|
|
120
126
|
});
|
|
121
127
|
return this;
|
|
122
128
|
|
|
123
129
|
}
|
|
124
130
|
|
|
125
|
-
|
|
131
|
+
TrackRepeat() {
|
|
126
132
|
this.loop = 1;
|
|
127
133
|
this.trackRepeat = true;
|
|
128
134
|
this.queueRepeat = false;
|
|
129
135
|
return this;
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
|
|
138
|
+
|
|
139
|
+
QueueRepeat() {
|
|
133
140
|
this.loop = 2;
|
|
134
141
|
this.queueRepeat = true;
|
|
135
142
|
this.trackRepeat = false;
|
|
136
143
|
return this;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
|
|
146
|
+
DisableRepeat() {
|
|
140
147
|
this.loop = 0;
|
|
141
148
|
this.trackRepeat = false;
|
|
142
149
|
this.queueRepeat = false;
|
|
143
|
-
|
|
144
|
-
|
|
145
150
|
return this;
|
|
146
151
|
}
|
|
147
152
|
|
|
148
|
-
|
|
153
|
+
setTextChannel(channel) {
|
|
149
154
|
if (typeof channel !== "string") throw new RangeError("Channel must be a string.");
|
|
150
155
|
this.textChannel = channel;
|
|
151
156
|
return this;
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
|
|
159
|
+
setVoiceChannel(channel) {
|
|
155
160
|
if (typeof channel !== "string") throw new RangeError("Channel must be a string.");
|
|
156
161
|
this.voiceChannel = channel;
|
|
157
162
|
return this;
|
|
158
163
|
}
|
|
159
164
|
|
|
160
|
-
|
|
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) {
|
|
161
174
|
if (data) {
|
|
162
175
|
this.voiceUpdateState = data;
|
|
163
176
|
this.node.send({
|
|
164
177
|
op: "voiceUpdate",
|
|
165
|
-
guildId: this.
|
|
178
|
+
guildId: this.guildId,
|
|
166
179
|
...data,
|
|
167
180
|
});
|
|
168
181
|
}
|
|
169
182
|
return this;
|
|
183
|
+
|
|
170
184
|
}
|
|
171
185
|
|
|
172
|
-
|
|
186
|
+
reconnect() {
|
|
173
187
|
if (this.voiceChannel === null) return null;
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
d: {
|
|
177
|
-
guild_id: this.guild,
|
|
188
|
+
this.send({
|
|
189
|
+
guild_id: this.guildId,
|
|
178
190
|
channel_id: this.voiceChannel,
|
|
179
191
|
self_mute: false,
|
|
180
192
|
self_deaf: false,
|
|
181
|
-
}
|
|
182
|
-
|
|
193
|
+
})
|
|
194
|
+
|
|
183
195
|
return this;
|
|
184
196
|
}
|
|
185
197
|
|
|
186
|
-
|
|
198
|
+
disconnect() {
|
|
187
199
|
if (this.voiceChannel === null) return null;
|
|
188
200
|
this.pause(true);
|
|
189
|
-
this.
|
|
190
|
-
this.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
self_mute: false,
|
|
196
|
-
self_deaf: false,
|
|
197
|
-
},
|
|
201
|
+
this.isConnected = false;
|
|
202
|
+
this.send({
|
|
203
|
+
guild_id: this.guildId,
|
|
204
|
+
channel_id: null,
|
|
205
|
+
self_mute: false,
|
|
206
|
+
self_deaf: false,
|
|
198
207
|
});
|
|
199
208
|
this.voiceChannel = null;
|
|
200
209
|
return this;
|
|
@@ -204,10 +213,28 @@ class Player extends EventEmitter {
|
|
|
204
213
|
this.disconnect();
|
|
205
214
|
this.node.send({
|
|
206
215
|
op: "destroy",
|
|
207
|
-
guildId: this.
|
|
216
|
+
guildId: this.guildId,
|
|
208
217
|
});
|
|
209
218
|
this.manager.emit("playerDestroy", this);
|
|
210
|
-
this.manager.players.delete(this.
|
|
219
|
+
this.manager.players.delete(this.guildId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
restart() {
|
|
223
|
+
this.filters.updateFilters();
|
|
224
|
+
if (this.currentTrack) {
|
|
225
|
+
|
|
226
|
+
this.isPlaying = true;
|
|
227
|
+
this.node.send({
|
|
228
|
+
op: "play",
|
|
229
|
+
startTime: this.position,
|
|
230
|
+
noReplace: true,
|
|
231
|
+
guildId: this.guildId,
|
|
232
|
+
track: this.currentTrack.track,
|
|
233
|
+
pause: this.isPaused
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
}
|
|
211
238
|
}
|
|
212
239
|
|
|
213
240
|
async autoplay(toggle = false) {
|
|
@@ -217,7 +244,7 @@ class Player extends EventEmitter {
|
|
|
217
244
|
if (!this.previousTrack) return this.stop();
|
|
218
245
|
let data = `https://www.youtube.com/watch?v=${this.previousTrack.info.identifier}&list=RD${this.previousTrack.info.identifier}`;
|
|
219
246
|
|
|
220
|
-
let response = await this.manager.resolve(data);
|
|
247
|
+
let response = await this.manager.resolve(data,this.manager.options.defaultPlatform || "ytsearch");
|
|
221
248
|
|
|
222
249
|
if (!response || !response.tracks || ["LOAD_FAILED", "NO_MATCHES"].includes(response.loadType)) return this.stop();
|
|
223
250
|
|
|
@@ -236,6 +263,11 @@ class Player extends EventEmitter {
|
|
|
236
263
|
|
|
237
264
|
}
|
|
238
265
|
|
|
266
|
+
send(data) {
|
|
267
|
+
|
|
268
|
+
this.manager.sendData({ op: 4, d: data });
|
|
269
|
+
}
|
|
270
|
+
|
|
239
271
|
|
|
240
272
|
lavalinkEvent(data) {
|
|
241
273
|
const events = {
|
|
@@ -277,22 +309,16 @@ class Player extends EventEmitter {
|
|
|
277
309
|
},
|
|
278
310
|
TrackExceptionEvent() {
|
|
279
311
|
this.queue.shift();
|
|
280
|
-
/**
|
|
281
|
-
* Fire up when there's an error while playing the track
|
|
282
|
-
* @event trackError
|
|
283
|
-
*/
|
|
284
312
|
this.manager.emit("trackError", this, this.track, data);
|
|
285
313
|
},
|
|
286
314
|
WebSocketClosedEvent() {
|
|
287
315
|
if ([4015, 4009].includes(data.code)) {
|
|
288
|
-
this.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
self_deaf: this.options.selfDeaf || false,
|
|
295
|
-
},
|
|
316
|
+
this.send({
|
|
317
|
+
guild_id: data.guildId,
|
|
318
|
+
channel_id: this.voiceChannel.id || this.voiceChannel,
|
|
319
|
+
self_mute: this.options.mute || false,
|
|
320
|
+
self_deaf: this.options.deaf || false,
|
|
321
|
+
|
|
296
322
|
});
|
|
297
323
|
}
|
|
298
324
|
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 did'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;
|
|
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);
|
|
85
111
|
|
|
86
112
|
|
|
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
113
|
|
|
101
|
-
}
|
|
102
|
-
this.deezer = new Deezer(this, this.options)
|
|
103
|
-
|
|
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/config.js
CHANGED
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;
|
|
@@ -69,14 +68,14 @@ class Filters {
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
setFilters(options) {
|
|
72
|
-
this.player.filters = new Filters(options);
|
|
71
|
+
this.player.filters = new Filters(this.player,options);
|
|
73
72
|
this.updateFilters();
|
|
74
73
|
return this;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
clearFilters() {
|
|
78
|
-
this.player.filters = new Filters();
|
|
79
|
-
this.node.send({
|
|
77
|
+
this.player.filters = new Filters(this.player);
|
|
78
|
+
this.player.node.send({
|
|
80
79
|
op: "filters",
|
|
81
80
|
guildId: this.player.guild
|
|
82
81
|
});
|
|
@@ -131,7 +130,7 @@ class Filters {
|
|
|
131
130
|
if (!this.player) return;
|
|
132
131
|
this.bassboost = !!val;
|
|
133
132
|
this.bassboost = val / 100;
|
|
134
|
-
this.
|
|
133
|
+
this.setEqualizer(
|
|
135
134
|
val
|
|
136
135
|
? Array(6)
|
|
137
136
|
.fill(0.22)
|
|
@@ -141,11 +140,10 @@ class Filters {
|
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
updateFilters(){
|
|
144
|
-
const {
|
|
143
|
+
const {equalizer, karaoke, timescale, tremolo, vibrato, rotation, distortion, channelMix, lowPass } = this;
|
|
145
144
|
this.node.send({
|
|
146
145
|
op:"filters",
|
|
147
|
-
guildId: this.player.
|
|
148
|
-
volume,
|
|
146
|
+
guildId: this.player.guildId,
|
|
149
147
|
equalizer,
|
|
150
148
|
karaoke,
|
|
151
149
|
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;
|
|
@@ -109,14 +107,13 @@ class AppleMusic {
|
|
|
109
107
|
let query = new URL(url).pathname.split('/');
|
|
110
108
|
let id = query.pop();
|
|
111
109
|
let playlist = await this.requestData(`/playlists/${id}`)
|
|
112
|
-
|
|
110
|
+
let name = playlist.data[0].attributes.name
|
|
113
111
|
|
|
114
112
|
const limitedTracks = this.options.playlistLimit
|
|
115
113
|
? playlist.data[0].relationships.tracks.data.slice(0, this.options.playlistLimit * 100)
|
|
116
114
|
: playlist.data[0].relationships.tracks.data;
|
|
117
115
|
|
|
118
116
|
let tracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)))
|
|
119
|
-
|
|
120
117
|
return this.buildResponse('PLAYLIST_LOADED', tracks, name);
|
|
121
118
|
} catch (e) {
|
|
122
119
|
return this.buildResponse(
|
|
@@ -242,3 +239,8 @@ class AppleMusic {
|
|
|
242
239
|
|
|
243
240
|
}
|
|
244
241
|
module.exports = AppleMusic
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
const apple = new AppleMusic("",{apple:{}})
|
|
245
|
+
|
|
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,6 +1,5 @@
|
|
|
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
|
|
|
@@ -11,21 +10,43 @@ class Spotify {
|
|
|
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
|
}
|
package/typings/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ 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:{})
|
|
@@ -108,7 +107,7 @@ export class Player extends EventEmitter {
|
|
|
108
107
|
|
|
109
108
|
public isPlaying:boolean;
|
|
110
109
|
|
|
111
|
-
public
|
|
110
|
+
public isPaused:boolean;
|
|
112
111
|
|
|
113
112
|
public trackRepeat:boolean;
|
|
114
113
|
|
|
@@ -157,4 +156,4 @@ export class Player extends EventEmitter {
|
|
|
157
156
|
|
|
158
157
|
autoplay(toggle:Boolean)
|
|
159
158
|
|
|
160
|
-
}
|
|
159
|
+
}
|