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 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 { Poru } = require('poru');
57
-
58
- // Initiate both main classes
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
- // Define some options for the node
62
- const nodes = [
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
- // Emitted whenever a node encountered an error
90
- client.poru.on('nodeError', (node, error) => {
91
- console.log(`Node "${node.name}" encountered an error`);
70
+ client.poru.on('trackStart', (player, track) => {
71
+
72
+ player.textChannel.send(`Now playing \`${track.title}\``);
92
73
  });
93
74
 
94
- // Listen for when the client becomes ready
95
- client.once('ready', () => {
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
- ```javascript
108
- // creating player
109
- const player = await client.poru.createConnection({
110
- guild: message.guild.id,
111
- voiceChannel: message.member.voice.channel.id,
112
- textChannel: message.channel,
113
- selfDeaf: true,
114
- selfMute: false,
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
- // Getting tracks
117
- const resolve = await client.poru.resolve('Ignite', 'yt');
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": "2.0.0",
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
- "node-fetch": "^3.2.6",
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.manager.emit("nodeDisconnect", event, 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
- return this.reconnect();
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.guild = options.guild.id || options.guild;
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.isConnectd = false;
24
+ this.shardId = options.shardId || 1;
25
+
26
+ this.isConnected = false;
23
27
 
24
28
  this.isPlaying = false;
25
29
 
26
- this.isPause = false;
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.isConnectd = packet.state.connected,
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 nulll;
61
+ return null;
58
62
  }
59
- this.currentTrack = this.queue.shift();
60
- if(!this.currentTrack.track){
61
- this.currentTrack = await this.currentTrack.resolve(this.manager);
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.guild,
73
+ guildId: this.guildId,
68
74
  track: this.currentTrack.track,
69
- volume: this.volume || 100,
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.isConnectd = false
85
+ this.isConnected = false
80
86
  this.isPlaying = false;
81
87
  this.node.send({
82
88
  op: "stop",
83
- guildId: this.guild
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.guild,
99
+ guildId: this.guildId,
94
100
  pause,
95
101
  });
96
102
  this.isPlaying = !pause;
97
- this.isPause = pause;
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.guild,
113
+ guildId: this.guildId,
108
114
  position,
109
115
  });
110
116
  return this;
111
117
  }
112
118
 
113
- async setVolume(volume) {
119
+ setVolume(volume) {
114
120
  if (Number.isNaN(volume)) throw new RangeError("Volume level must be a number.");
115
- this.volume = volume;
121
+ this.volume = volume;
116
122
  this.node.send({
117
123
  op: "volume",
118
- guildId: this.guild,
124
+ guildId: this.guildId,
119
125
  volume: this.volume,
120
126
  });
121
127
  return this;
122
128
 
123
129
  }
124
130
 
125
- async TrackRepeat() {
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
- async QueueRepeat() {
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
- async DisableRepeat() {
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
- async setTextChannel(channel) {
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
- async setVoiceChannel(channel) {
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
- async connect(data) {
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.guild,
178
+ guildId: this.guildId,
166
179
  ...data,
167
180
  });
168
181
  }
169
182
  return this;
183
+
170
184
  }
171
185
 
172
- async reconnect() {
186
+ reconnect() {
173
187
  if (this.voiceChannel === null) return null;
174
- this.node.send({
175
- op: 4,
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
- async disconnect() {
198
+ disconnect() {
187
199
  if (this.voiceChannel === null) return null;
188
200
  this.pause(true);
189
- this.isConnectd = false;
190
- this.manager.sendData({
191
- op: 4,
192
- d: {
193
- guild_id: this.guild,
194
- channel_id: null,
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.guild,
216
+ guildId: this.guildId,
208
217
  });
209
218
  this.manager.emit("playerDestroy", this);
210
- this.manager.players.delete(this.guild);
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.manager.sendData({
289
- op: 4,
290
- d: {
291
- guild_id: data.guildId,
292
- channel_id: this.voiceChannel.id || this.voiceChannel,
293
- self_mute: this.options.selfMute || false,
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 = (...args) => import('node-fetch').then(({
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] you did't provide a valid client");
16
- if (!nodes) throw new Error("[Poru Error] you did't provide a lavalink nodes");
17
- if (!options) throw new Error("[Poru Error] options must be provided!")
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
- //create connection with discord voice channel
54
- createConnection(data = {}) {
55
- const player = this.players.get(data.guild.id || data.guild);
56
- if (player) {
57
- return player;
58
- }
59
- this.sendData({
60
- op: 4,
61
- d: {
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
- init(client) {
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
- this._nodes.forEach((node) => this.addNode(node));
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.connect({
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
- async resolve(track,source) {
225
+ }
194
226
 
195
- const node = this.leastUsedNodes[0];
196
- if (!node) throw new Error("No nodes are available.");
227
+ async fetchURL(node, track, source) {
197
228
 
198
- if (this.spotify && this.spotify.check(track)) {
229
+ if (this.spotify.check(track)) {
199
230
  return await this.spotify.resolve(track);
200
- }else if (this.apple && this.apple.check(track)) {
231
+ } else if (this.apple.check(track)) {
201
232
  return await this.apple.resolve(track);
202
- }else if (this.deezer && this.deezer.check(track)) {
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
@@ -1,5 +1,6 @@
1
1
  module.exports = {
2
2
  clientName: "Poru",
3
+ autoResume : true,
3
4
  version :"2.0",
4
5
  OPCodes: {
5
6
  CONFIGURE_RESUMING : 'configureResuming',
@@ -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.player.setEqualizer(
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 { volume, equalizer, karaoke, timescale, tremolo, vibrato, rotation, distortion, channelMix, lowPass } = this;
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.guild,
148
- volume,
146
+ guildId: this.player.guildId,
149
147
  equalizer,
150
148
  karaoke,
151
149
  timescale,
@@ -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) {
@@ -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 = (...args) => import('node-fetch').then(({
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.apple.playlistLimit || null,
12
- albumLimit: options.apple.albumLimit || null,
13
- artistLimit: options.apple.artistLimit || null,
14
- searchMarket: options.apple.searchMarket || "us",
15
- imageHeight: options.apple.imageHeight || 500,
16
- imageWeight: options.apple.imageWeight || 500,
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
- let name = playlist.data.attributes.name
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")
@@ -1,6 +1,5 @@
1
- const fetch = (...args) => import('node-fetch').then(({
2
- default: fetch
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.deezer.playlistLimit || null,
16
- albumLimit: options.deezer.albumLimit || null,
17
- artistLimit: options.deezer.artistLimit || null
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")
@@ -1,6 +1,5 @@
1
- const fetch = (...args) => import('node-fetch').then(({
2
- default: fetch
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
- 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
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
- if (!this.token) await this.requestToken();
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
  }
@@ -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 isPause:boolean;
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
+ }