poru 1.1.9 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poru",
3
- "version": "1.1.9",
3
+ "version": "1.2.2",
4
4
  "description": "A stable and powefull lavalink client with so many features",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/Node.js CHANGED
@@ -1,16 +1,15 @@
1
1
  const WebSocket = require("ws");
2
- const config = require("./config.json")
2
+ const config = require("./config")
3
3
 
4
4
  class Node {
5
5
  constructor(manager,options,node) {
6
-
7
6
  this.manager = manager
8
7
  this.name= options.name || null;
9
8
  this.host = options.host || "localhost"
10
9
  this.port = options.port || 2333
11
- this.url = `${options.secure ? 'wss' : 'ws'}://${options.host}:${options.port}`;
12
10
  this.password = options.password ||"youshallnotpass"
13
11
  this.secure = options.secure || false;
12
+ this.url = `${this.secure ? 'wss' : 'ws'}://${this.host}:${this.port}/`;
14
13
  this.ws = null;
15
14
  this.reconnectTime = node.reconnectTime || 5000;
16
15
  this.resumeKey = node.resumeKey || null;
@@ -37,132 +36,130 @@ class Node {
37
36
  };
38
37
 
39
38
 
40
- }
41
-
42
- connect() {
43
- if (this.ws) this.ws.close();
44
- const headers = {
45
- Authorization: this.password,
46
- "Num-Shards": this.manager.shards || 1,
47
- "User-Id": this.manager.user,
48
- "Client-Name": config.client
49
- };
50
- if (this.resumeKey) headers["Resume-Key"] = this.resumeKey;
51
- this.ws = new WebSocket(`ws${this.secure ? "s" : ""}:${this.host}:${this.port}/`, { headers });
52
- this.ws.on("open",this.#open.bind(this));
53
- this.ws.on("error", this.#error.bind(this));
54
- this.ws.on("message", this.#message.bind(this));
55
- this.ws.on("close", this.#close.bind(this));
56
- }
57
-
58
-
59
- #open(){
60
- if (this.reconnectAttempt) {
61
- clearTimeout(this.reconnectAttempt);
62
- delete this.reconnectAttempt;
63
- }
64
-
65
- if (this.resumeKey){
66
- this.send({
67
- op: "configureResuming",
68
- key: (this.resumeKey).toString(),
69
- timeout: this.resumeTimeout
70
- });
71
- this.manager.emit("debug",this.name,`[Web Socket] Resuming configured on Lavalink`)
72
- }
73
-
74
- this.manager.emit("nodeConnect", this);
75
- this.isConnected = true;
76
- this.manager.emit('debug', this.name, `[Web Socket] Connection ready ${this.url}`);
77
-
78
- }
79
-
80
- #message(payload) {
81
- if (Array.isArray(payload)) payload = Buffer.concat(payload);
82
- else if (payload instanceof ArrayBuffer) payload = Buffer.from(payload);
39
+ }
83
40
 
84
- const packet = JSON.parse(payload);
85
- if (packet.op && packet.op === "stats") {
86
- this.stats = { ...packet };
87
- delete this.stats.op;
41
+ connect() {
42
+ if (this.ws) this.ws.close();
43
+ const headers = {
44
+ Authorization: this.password,
45
+ "Num-Shards": this.manager.shards || 1,
46
+ "User-Id": this.manager.user,
47
+ "Client-Name": config.clientName
48
+ };
49
+ if (this.resumeKey) headers["Resume-Key"] = this.resumeKey;
50
+ this.ws = new WebSocket(this.url, { headers });
51
+ this.ws.on("open",this.#open.bind(this));
52
+ this.ws.on("error", this.#error.bind(this));
53
+ this.ws.on("message", this.#message.bind(this));
54
+ this.ws.on("close", this.#close.bind(this));
88
55
  }
89
- const player = this.manager.players.get(packet.guildId);
90
- if (packet.guildId && player) player.emit(packet.op, packet);
91
56
 
92
- packet.node = this;
93
- this.manager.emit("debug",this.name,`[Web Socket] Lavalink Node Update : ${packet.op} `)
94
- this.manager.emit("raw", packet);
95
- }
96
57
 
97
- #close(event) {
98
- this.manager.emit("nodeDisconnect", event, this);
99
- this.manager.emit("debug",this.name,`[Web Socket] Connection with Lavalink closed with Error code : ${event||"Unkwon code"}`)
100
- if (event !== 1000){
58
+ #open(){
59
+ if (this.reconnectAttempt) {
60
+ clearTimeout(this.reconnectAttempt);
61
+ delete this.reconnectAttempt;
62
+ }
63
+
64
+ if (this.resumeKey){
65
+ this.send({
66
+ op: "configureResuming",
67
+ key: (this.resumeKey).toString(),
68
+ timeout: this.resumeTimeout
69
+ });
70
+ this.manager.emit("debug",this.name,`[Web Socket] Resuming configured on Lavalink`)
71
+ }
72
+
73
+ this.manager.emit("nodeConnect", this);
74
+ this.isConnected = true;
75
+ this.manager.emit('debug', this.name, `[Web Socket] Connection ready ${this.url}`);
101
76
 
102
- return this.reconnect();
103
77
  }
104
- }
105
78
 
79
+ #message(payload) {
80
+ if (Array.isArray(payload)) payload = Buffer.concat(payload);
81
+ else if (payload instanceof ArrayBuffer) payload = Buffer.from(payload);
106
82
 
107
- #error(event) {
108
- if (!event) return "Unknown event";
83
+ const packet = JSON.parse(payload);
84
+ if(!packet.op) return;
85
+
86
+ if (packet.op && packet.op === "stats") {
87
+ this.stats = { ...packet };
88
+ delete this.stats.op;
89
+ }
90
+ const player = this.manager.players.get(packet.guildId);
91
+ if (packet.guildId && player) player.emit(packet.op, packet);
92
+
93
+ packet.node = this;
94
+ this.manager.emit("debug",this.name,`[Web Socket] Lavalink Node Update : ${packet.op} `)
95
+ this.manager.emit("raw", packet);
96
+ }
109
97
 
110
- this.manager.emit("debug",this.name,`[Web Socket] Connection for Lavalink node has error code: ${event.code}`)
111
- this.manager.emit("nodeError", this, event);
112
- return this.reconnect();
113
- }
98
+ #close(event) {
99
+ this.manager.emit("nodeDisconnect", event, this);
100
+ this.manager.emit("debug",this.name,`[Web Socket] Connection with Lavalink closed with Error code : ${event||"Unknown code"}`)
101
+ if (event !== 1000){
102
+
103
+ return this.reconnect();
104
+ }
105
+ }
114
106
 
115
- destroy(){
116
- if(!this.isConnected) return;
117
107
 
118
- const players = this.manager.players.filter(p => p.node == this);
119
- if (players.size) players.forEach(p => p.destroy());
120
- this.ws.close(1000, "destroy");
121
- this.ws.removeAllListeners();
122
- this.ws = null;
123
- this.reconnect = 1;
124
- this.destroyed = true;
125
- this.manager.nodes.delete(this.host)
126
- this.manager.emit("nodeDestroy", this);
108
+ #error(event) {
109
+ if (!event) return "Unknown event";
127
110
 
111
+ this.manager.emit("debug",this.name,`[Web Socket] Connection for Lavalink node has error code: ${event.code}`)
112
+ this.manager.emit("nodeError", this, event);
113
+ return this.reconnect();
114
+ }
128
115
 
129
- }
116
+ destroy(){
117
+ if(!this.isConnected) return;
130
118
 
131
- reconnect() {
132
- this.reconnectAttempt = setTimeout(() => {
133
- this.isConnected = false;
119
+ const players = this.manager.players.filter(p => p.node == this);
120
+ if (players.size) players.forEach(p => p.destroy());
121
+ this.ws.close(1000, "destroy");
134
122
  this.ws.removeAllListeners();
135
123
  this.ws = null;
136
- this.manager.emit("nodeReconnect", this);
137
- this.connect();
138
- }, this.reconnectTime);
139
- }
140
-
141
- send(payload) {
142
- this.ws.send(JSON.stringify(payload), (error) => {
143
- if (error) return error;
144
- return null;
145
- });
146
- }
124
+ this.reconnect = 1;
125
+ this.destroyed = true;
126
+ this.manager.nodes.delete(this.host)
127
+ this.manager.emit("nodeDestroy", this);
147
128
 
148
129
 
149
-
150
- get penalties(){
151
- let penalties = 0;
152
- if (!this.isConnected) return penalties;
153
- penalties += this.stats.players;
154
- penalties += Math.round(Math.pow(1.05, 100 * this.stats.cpu.systemLoad) * 10 - 10);
155
- if (this.stats.frameStats) {
156
- penalties += this.stats.frameStats.deficit;
157
- penalties += this.stats.frameStats.nulled * 2;
158
130
  }
159
- return penalties;
160
- }
161
131
 
132
+ reconnect() {
133
+ this.reconnectAttempt = setTimeout(() => {
134
+ this.isConnected = false;
135
+ this.ws.removeAllListeners();
136
+ this.ws = null;
137
+ this.manager.emit("nodeReconnect", this);
138
+ this.connect();
139
+ }, this.reconnectTime);
140
+ }
162
141
 
163
- }
164
-
142
+ send(payload) {
143
+ const data = JSON.stringify(payload);
144
+ this.ws.send(data, (error) => {
145
+ if (error) return error;
146
+ return null;
147
+ });
148
+ this.manager.emit("raw", data, this.name)
149
+ }
165
150
 
151
+ get penalties(){
152
+ let penalties = 0;
153
+ if (!this.isConnected) return penalties;
154
+ penalties += this.stats.players;
155
+ penalties += Math.round(Math.pow(1.05, 100 * this.stats.cpu.systemLoad) * 10 - 10);
156
+ if (this.stats.frameStats) {
157
+ penalties += this.stats.frameStats.deficit;
158
+ penalties += this.stats.frameStats.nulled * 2;
159
+ }
160
+ return penalties;
161
+ }
162
+ }
166
163
 
167
- module.exports = Node;
164
+ module.exports = Node;
168
165
 
package/src/Player.js CHANGED
@@ -50,6 +50,7 @@ class Player extends EventEmitter {
50
50
  this.manager.emit("playerUpdate", this, packet);
51
51
  });
52
52
  }
53
+
53
54
  async play() {
54
55
 
55
56
  if (!this.queue.length) {
@@ -72,7 +73,7 @@ class Player extends EventEmitter {
72
73
  }
73
74
 
74
75
 
75
- stop() {
76
+ stop() {
76
77
 
77
78
  this.position = 0;
78
79
  this.isConnectd = false
@@ -182,9 +183,6 @@ class Player extends EventEmitter {
182
183
  return this;
183
184
  }
184
185
 
185
-
186
-
187
-
188
186
  async disconnect() {
189
187
  if (this.voiceChannel === null) return null;
190
188
  this.pause(true);
@@ -221,7 +219,7 @@ class Player extends EventEmitter {
221
219
 
222
220
  let response = await this.manager.resolve(data);
223
221
 
224
- if (!response || !response.tracks || ["LOAD_FAILED", "NO_MATCHES"].includes(response.type)) return this.stop();
222
+ if (!response || !response.tracks || ["LOAD_FAILED", "NO_MATCHES"].includes(response.loadType)) return this.stop();
225
223
 
226
224
  let track = response.tracks[Math.floor(Math.random() * Math.floor(response.tracks.length))];
227
225
 
@@ -306,7 +304,6 @@ class Player extends EventEmitter {
306
304
  return events[data.type] || events.default;
307
305
  }
308
306
 
309
-
310
307
  }
311
308
 
312
309
  module.exports = Player;
package/src/Poru.js CHANGED
@@ -3,29 +3,30 @@ const { fetch } = require('undici');
3
3
  const Player = require("./Player");
4
4
  const Node = require("./Node");
5
5
  const Response = require("./guild/Response");
6
- const config = require("./config.json")
7
6
  const Spotify = require("./platform/Spotify")
8
7
  const Apple = require("./platform/Apple")
9
-
8
+ const Deezer = require("./platform/Deezer")
10
9
  class Poru extends EventEmitter {
11
- constructor(client, nodes, options = {}) {
12
- super();
13
- if (!client) throw new Error("[Poru Error] you did't provide a valid client");
14
- if (!nodes) throw new Error("[Poru Error] you did't provide a lavalink nodes");
15
- if (!options) throw new Error('[Poru Error] options must be provided!');
16
- this.client = client;
17
- this._nodes = nodes;
18
- this.nodes = new Map();
19
- this.players = new Map();
20
- this.voiceStates = new Map();
21
- this.voiceServers = new Map();
22
- this.user = null;
23
- this.options = options;
24
- this.shards = options.shards || 1;
25
- this.sendData = null;
26
- }
27
-
28
- //create a node and connect it with lavalink
10
+ constructor(client, nodes, options = {}) {
11
+ super();
12
+ if (!client) throw new Error("[Poru Error] you did't provide a valid client");
13
+ if (!nodes) throw new Error("[Poru Error] you did't provide a lavalink nodes");
14
+ if (!options) throw new Error("[Poru Error] options must be provided!")
15
+ this.client = client;
16
+ this._nodes = nodes;
17
+ this.nodes = new Map();
18
+ this.players = new Map();
19
+ this.voiceStates = new Map();
20
+ this.voiceServers = new Map();
21
+ this.user = null;
22
+ this.options = options
23
+ this.shards = options.shards || 1;
24
+ this.sendData = null;
25
+ }
26
+
27
+
28
+
29
+ //create a node and connect it with lavalink
29
30
  addNode(options) {
30
31
  const node = new Node(this, options, this.options);
31
32
  if (options.name) {
@@ -72,27 +73,33 @@ class Poru extends EventEmitter {
72
73
  const guild = client.guilds.cache.get(data.d.guild_id);
73
74
  if (guild) guild.shard.send(data);
74
75
  }
76
+
75
77
  client.on("raw", async packet => {
76
78
  await this.packetUpdate(packet);
77
79
  })
78
-
80
+
79
81
  this._nodes.forEach((node) => this.addNode(node));
80
82
 
81
83
 
82
- if (this.options.spotify && this.options.spotify.clientID && this.options.spotify.clientSecret) {
83
- this.spotify = new Spotify(this, {
84
- clientID: this.options.clientID,
85
- clientSecret: this.options.clientSecret
86
- })
84
+ if (this.options.spotify && this.options.spotify.clientID && this.options.spotify.clientSecret) {
85
+ this.spotify = new Spotify(this, this.options)
86
+ }
87
+ if (this.options.apple) {
88
+ if (!this.options.apple.playlistLimit) {
89
+ throw new Error("[Poru Apple Music] playlistLimit must be provided")
90
+ }
91
+ this.apple = new Apple(this, this.options)
92
+ }
93
+ if (this.options.deezer) {
94
+ if (!this.options.deezer.playlistLimit) {
95
+ throw new Error("[Poru Deezer Music] playlistLimit must be provided")
96
+
97
+ }
98
+ this.deezer = new Deezer(this, this.options)
99
+
87
100
  }
88
- if(this.options.apple){
89
- if(!this.options.apple.playlistLimit){
90
- throw new Error("[Poru Apple Music] playlistLimit must be provided")
91
- }
92
- this.apple = new Apple(this,this.options)
93
- }
94
101
  console.log(`Thanks for using Poru`)
95
- }
102
+ }
96
103
 
97
104
 
98
105
  setServersUpdate(data) {
@@ -110,100 +117,93 @@ if(this.options.apple){
110
117
  });
111
118
 
112
119
  return true;
113
-
114
- this.sendData({
115
- op: 4,
116
- d: {
117
- guild_id: data.guild.id || data.guild,
118
- channel_id: data.voiceChannel.id || data.voiceChannel,
119
- self_mute: data.selfMute || false,
120
- self_deaf: data.selfDeaf || true,
121
- },
122
- });
123
- return this.#Player(data);
124
- }
125
-
126
- init(client) {
127
- this.user = client.user.id;
128
- this.sendData = data => {
129
- const guild = client.guilds.cache.get(data.d.guild_id);
130
- if (guild) guild.shard.send(data);
131
- };
132
- client.on('raw', async packet => {
133
- await this.#packetUpdate(packet);
134
- });
135
-
136
- this._nodes.forEach(node => this.addNode(node));
137
-
138
- if (this.options.spotify && this.options.spotify.clientID && this.options.spotify.clientSecret) {
139
- this.spotify = new Spotify(this, {
140
- clientID: this.options.clientID,
141
- clientSecret: this.options.clientSecret,
142
- playlistLimit: this.options.playlistLimit,
143
- albumLimit: this.options.albumLimit,
144
- artistLimit: this.options.artistLimit,
145
- searchMarket: this.options.searchMarket,
146
- });
147
120
  }
148
- this.voiceServers.delete(data.guild_id);
149
- this.voiceStates.delete(data.guild_id);
150
- }
151
121
 
152
- #packetUpdate(packet) {
153
- if (!['VOICE_STATE_UPDATE', 'VOICE_SERVER_UPDATE'].includes(packet.t)) return;
154
- const player = this.players.get(packet.d.guild_id);
155
- if (!player) return;
122
+ setStateUpdate(data) {
123
+ if (data.user_id !== this.user) return;
124
+ if (data.channel_id) {
125
+ const guild = data.guild_id;
156
126
 
157
- if (packet.t === 'VOICE_SERVER_UPDATE') {
158
- this.setServersUpdate(packet.d);
127
+ this.voiceStates.set(data.guild_id, data);
128
+ const server = this.voiceServers.get(guild);
129
+ const state = this.voiceStates.get(guild);
130
+ if (!server) return false;
131
+ const player = this.players.get(guild);
132
+ if (!player) return false;
133
+
134
+ player.connect({
135
+ sessionId: state ? state.session_id : player.voiceUpdateState.sessionId,
136
+ event: server,
137
+ });
138
+
139
+ return true;
140
+ }
141
+ this.voiceServers.delete(data.guild_id);
142
+ this.voiceStates.delete(data.guild_id);
159
143
  }
160
- if (packet.t === 'VOICE_STATE_UPDATE') {
161
- this.setStateUpdate(packet.d);
144
+
145
+ packetUpdate(packet) {
146
+ if (!['VOICE_STATE_UPDATE', 'VOICE_SERVER_UPDATE'].includes(packet.t)) return;
147
+ const player = this.players.get(packet.d.guild_id);
148
+ if (!player) return;
149
+
150
+ if (packet.t === "VOICE_SERVER_UPDATE") {
151
+ this.setServersUpdate(packet.d);
152
+ }
153
+ if (packet.t === "VOICE_STATE_UPDATE") {
154
+ this.setStateUpdate(packet.d);
155
+ }
162
156
  }
163
- }
164
-
165
- get leastUsedNodes() {
166
- return [...this.nodes.values()]
167
- .filter(node => node.isConnected)
168
- .sort((a, b) => {
169
- const aLoad = a.stats.cpu ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100 : 0;
170
- const bLoad = b.stats.cpu ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 : 0;
171
- return aLoad - bLoad;
172
- });
173
- }
174
-
175
- #Player(data) {
176
- const guild = data.guild.id || data.guild;
177
- const Nodes = this.nodes.get(guild);
178
- if (Nodes) return Nodes;
179
- if (this.leastUsedNodes.length === 0) throw new Error('[Poru Error] No nodes are avaliable');
180
- const node = this.nodes.get(this.leastUsedNodes[0].name || this.leastUsedNodes[0].host);
181
- if (!node) throw new Error('[Poru Error] No nodes are avalible');
182
-
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
- }
157
+
158
+
159
+
160
+ get leastUsedNodes() {
161
+ return [...this.nodes.values()]
162
+ .filter((node) => node.isConnected)
163
+ .sort((a, b) => {
164
+ const aLoad = a.stats.cpu ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100 : 0;
165
+ const bLoad = b.stats.cpu ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 : 0;
166
+ return aLoad - bLoad;
167
+ });
168
+ }
169
+
170
+ #Player(data) {
171
+ const guild = data.guild.id || data.guild;
172
+ const Nodes = this.nodes.get(guild);
173
+ if (Nodes) return Nodes;
174
+ if (this.leastUsedNodes.length === 0) throw new Error("[Poru Error] No nodes are avaliable");
175
+ const node = this.nodes.get(this.leastUsedNodes[0].name
176
+ || this.leastUsedNodes[0].host);
177
+ if (!node) throw new Error("[Poru Error] No nodes are avalible");
178
+
179
+ // eslint-disable-next-line new-cap
180
+ const player = new Player(this, node, data);
181
+ this.players.set(guild, player);
182
+ player.connect()
183
+ return player;
184
+ }
185
+
186
+
187
+
189
188
 
190
189
  async resolve(track, source) {
191
-
190
+
192
191
  const node = this.leastUsedNodes[0];
193
192
  if (!node) throw new Error("No nodes are available.");
194
- if(this.spotify && this.spotify.check(track)){
195
- return await this.spotify.resolve(track);
196
- }
197
-
198
- if(this.apple && this.apple.check(track)){
193
+ if (this.spotify && this.spotify.check(track)) {
194
+ return await this.spotify.resolve(track);
195
+ }else if (this.apple && this.apple.check(track)) {
199
196
  return await this.apple.resolve(track);
200
- }
201
-
197
+ }else if (this.deezer && this.deezer.check(track)) {
198
+ return await this.deezer.resolve(track);
199
+ }
200
+
201
+
202
202
 
203
203
  const regex = /^https?:\/\//;
204
204
  if (!regex.test(track)) {
205
205
  // eslint-disable-next-line no-param-reassign
206
- track = `${source || "yt"}search:${track}`;
206
+ track = `${source || "ytsearch"}:${track}`;
207
207
  }
208
208
  const result = await this.#fetch(node, "loadtracks", `identifier=${encodeURIComponent(track)}`);
209
209
 
@@ -219,40 +219,23 @@ if(this.options.apple){
219
219
  if (result.status === 500) return null;
220
220
  return result;
221
221
  }
222
- const regex = /^https?:\/\//;
223
- if (!regex.test(track)) {
224
- // eslint-disable-next-line no-param-reassign
225
- track = `${source || 'yt'}search:${track}`;
222
+
223
+ #fetch(node, endpoint, param) {
224
+ return fetch(`http${node.secure ? "s" : ""}://${node.host}:${node.port}/${endpoint}?${param}`, {
225
+ headers: {
226
+ Authorization: node.password,
227
+
228
+ },
229
+ })
230
+ .then((r) => r.json())
231
+ .catch((e) => {
232
+ throw new Error(`[Poru Error] Failed to fetch from the lavalink.\n error: ${e}`);
233
+ });
234
+ }
235
+
236
+ get(guildId) {
237
+ return this.players.get(guildId);
226
238
  }
227
- const result = await this.#fetch(node, 'loadtracks', `identifier=${encodeURIComponent(track)}`);
228
-
229
- if (!result) throw new Error('[Poru Error] No tracks found.');
230
- return new Response(result);
231
- }
232
-
233
- async decodeTrack(track) {
234
- const node = this.leastUsedNodes[0];
235
- if (!node) throw new Error('No nodes are available.');
236
- const result = await this.#fetch(node, 'decodetrack', `track=${track}`);
237
- if (result.status === 500) return null;
238
- return result;
239
- }
240
-
241
- #fetch(node, endpoint, param) {
242
- return fetch(`http${node.secure ? 's' : ''}://${node.host}:${node.port}/${endpoint}?${param}`, {
243
- headers: {
244
- Authorization: node.password,
245
- },
246
- })
247
- .then(r => r.json())
248
- .catch(e => {
249
- throw new Error(`[Poru Error] Failed to fetch from the lavalink.\n error: ${e}`);
250
- });
251
- }
252
-
253
- get(guildId) {
254
- return this.players.get(guildId);
255
- }
256
239
  }
257
240
 
258
- module.exports = Poru;
241
+ module.exports = Poru
package/src/config.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ clientName: "Poru"
3
+ }
@@ -0,0 +1,61 @@
1
+ const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2
+ class DeezerTrack {
3
+ constructor(data) {
4
+ this.track = data.track
5
+ this.info = {
6
+ identifier: null,
7
+ isSeekable: data.info.isSeekable,
8
+ author: data.info.author,
9
+ length: data.info.length,
10
+ isStream: data.info.isStream,
11
+ sourceName: data.info.sourceName,
12
+ title: data.info.title,
13
+ uri: data.info.uri,
14
+ image: data.image || null
15
+
16
+ }
17
+ }
18
+
19
+ async resolve(manager) {
20
+
21
+ const query = [this.info.author, this.info.title].filter((x) => !!x).join(' - ');
22
+
23
+
24
+ const result = await manager.resolve(query);
25
+ if (!result || !result.tracks.length) return;
26
+
27
+ if (this.info.author) {
28
+ const author = [this.info.author, `${this.info.author} - Topic`];
29
+ const officialAudio = result.tracks.find(
30
+ (track) =>
31
+ author.some((name) => new RegExp(`^${escapeRegExp(name)}$`, 'i').test(track.info.author)) ||
32
+ new RegExp(`^${escapeRegExp(this.info.title)}$`, 'i').test(track.info.title),
33
+ );
34
+ if (officialAudio) {
35
+ this.info.identifier = officialAudio.info.identifier
36
+ this.track = officialAudio.track;
37
+ this.length = officialAudio.info.length
38
+ return this
39
+ }
40
+ }
41
+ if (this.info.length) {
42
+ const sameDuration = result.tracks.find(
43
+ (track) =>
44
+ track.info.length >= (this.info.length ? this.length : 0) - 2000 &&
45
+ track.info.length <= (this.info.length ? this.length : 0) + 2000,
46
+ );
47
+ if (sameDuration) {
48
+ this.info.identifier = sameDuration.info.identifier
49
+ this.track = sameDuration.track;
50
+ this.length = officialAudio.info.length
51
+ return this
52
+ }
53
+ }
54
+ this.info.identifier = result.tracks[0].info.identifier
55
+ this.track = result.tracks[0].track;
56
+ this.length = officialAudio.info.length
57
+ return this
58
+ }
59
+ }
60
+
61
+ module.exports = DeezerTrack;
@@ -1,4 +1,4 @@
1
- const { fetch } = require('undici');
1
+ const {fetch} = require("undici")
2
2
  const Track = require("../guild/Track")
3
3
  const cheerio = require("cheerio")
4
4
 
@@ -8,9 +8,10 @@ class Apple {
8
8
  this.baseURL = /(?:https:\/\/music\.apple\.com\/)(?:.+)?(artist|album|music-video|playlist)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)/;
9
9
  this.applePattern = /(?:https:\/\/music\.apple\.com\/)(?:\w{2}\/)?(track|album|playlist)/g;
10
10
  this.REGEX = /(?:https:\/\/music\.apple\.com\/)(?:\w{2}\/)?(track|album|playlist)/g;
11
- this.playlistLimit = options.playlistLimit || null;
11
+ this.playlistLimit = options.apple.playlistLimit || null;
12
12
  }
13
13
 
14
+
14
15
  check(url) {
15
16
  return this.baseURL.test(url);
16
17
  }
@@ -232,4 +233,5 @@ class Apple {
232
233
 
233
234
 
234
235
  }
236
+
235
237
  module.exports = Apple
@@ -0,0 +1,201 @@
1
+ const { fetch } = require('undici');
2
+ const DeezerTrack = require("../guild/DeezerTrack")
3
+
4
+ class Deezer {
5
+ constructor(manager, options) {
6
+
7
+
8
+ this.manager = manager;
9
+ this.baseURL = 'https://api.deezer.com';
10
+ this.REGEX = /^(?:https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?(track|album|playlist|artist)\/(\d+)/
11
+ this.playlistLimit = options.deezer.playlistLimit || null;
12
+
13
+ }
14
+
15
+
16
+ check(url) {
17
+ return this.REGEX.test(url);
18
+ }
19
+
20
+ async requestData(endpoint) {
21
+ const req = await fetch(`${this.baseURL}/${endpoint}`, {
22
+ });
23
+ const data = await req.json();
24
+ return data;
25
+ }
26
+
27
+
28
+ async resolve(url) {
29
+ const [, type, id] = this.REGEX.exec(url) ?? [];
30
+ switch (type) {
31
+ case 'playlist': {
32
+ return this.fetchPlaylist(id);
33
+ }
34
+ case 'track': {
35
+ return this.fetchTrack(id);
36
+ }
37
+ case 'album': {
38
+ return this.fetchAlbum(id);
39
+ }
40
+ case 'artist': {
41
+ return this.fetchArtist(id);
42
+ }
43
+
44
+ }
45
+
46
+
47
+
48
+ }
49
+
50
+
51
+ async fetchPlaylist(id) {
52
+ try {
53
+ const playlist = await this.requestData(`/playlist/${id}`);
54
+ const unresolvedPlaylistTracks = await Promise.all(playlist.tracks.data.map(x => this.buildUnresolved(x)));
55
+ return this.buildResponse('PLAYLIST_LOADED', unresolvedPlaylistTracks, playlist.name);
56
+
57
+ } catch (e) {
58
+ return this.buildResponse(
59
+ e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
60
+ [],
61
+ undefined,
62
+ e.body?.error.message ?? e.message,
63
+ );
64
+
65
+ }
66
+ }
67
+
68
+ async fetchAlbum(id) {
69
+ try {
70
+ const album = await this.requestData(`/album/${id}`)
71
+
72
+ const unresolvedAlbumTracks = await Promise.all(playlist.tracks.data.map(x => this.buildUnresolved(x)));
73
+
74
+ return this.buildResponse('PLAYLIST_LOADED', unresolvedAlbumTracks, album.name);
75
+ } catch (e) {
76
+ return this.buildResponse(
77
+ e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
78
+ [],
79
+ undefined,
80
+ e.body?.error.message ?? e.message,
81
+ );
82
+
83
+ }
84
+ }
85
+
86
+ async fetchTrack(id) {
87
+
88
+ try {
89
+ const track = await this.requestData(`/track/${id}`)
90
+
91
+ const unresolvedTrack = await Promise.all(this.buildUnresolved(track));
92
+ return this.buildResponse('TRACK_LOADED', [unresolvedTrack]);
93
+ } catch (e) {
94
+ return this.buildResponse(
95
+ e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
96
+ [],
97
+ undefined,
98
+ e.body?.error.message ?? e.message,
99
+ );
100
+
101
+ }
102
+ }
103
+
104
+ async fetchArtist(id) {
105
+
106
+ try {
107
+ const artist = await this.requestData(`/artist/${id}/top`);
108
+ await this.fetchArtistTracks(artist)
109
+ const unresolvedArtistTracks = await Promise.all(artist.data.map(x => this.buildUnresolved(x)
110
+ ));
111
+
112
+
113
+
114
+ return this.buildResponse('PLAYLIST_LOADED', unresolvedArtistTracks, artist.name);
115
+ } catch (e) {
116
+ return this.buildResponse(
117
+ e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
118
+ [],
119
+ undefined,
120
+ e.body?.error.message ?? e.message,
121
+ );
122
+
123
+ }
124
+ }
125
+
126
+ async fetchArtistTracks(deezerArtist) {
127
+ let nextPage = deezerArtist.next;
128
+ let pageLoaded = 1;
129
+ while (nextPage) {
130
+ if (!nextPage) break;
131
+ const req = await fetch(nextPage)
132
+ const json = await req.json()
133
+
134
+ deezerArtist.data.push(...json.data);
135
+
136
+ nextPage = json.next;
137
+ pageLoaded++;
138
+ }
139
+ }
140
+
141
+ async fetch(query) {
142
+ try {
143
+ if (this.check(query)) return this.resolve(query)
144
+ let tracks = await this.requestData(`/search?q="${query}"`)
145
+
146
+ const unresolvedTrack = await Promise.all(this.buildUnresolved(tracks.data[0]));
147
+ return this.buildResponse('TRACK_LOADED', [unresolvedTrack]);
148
+ } catch (e) {
149
+ return this.buildResponse(
150
+ e.body?.error.message === 'invalid id' ? 'NO_MATCHES' : 'LOAD_FAILED',
151
+ [],
152
+ undefined,
153
+ e.body?.error.message ?? e.message,
154
+ );
155
+
156
+
157
+ }
158
+ }
159
+
160
+
161
+ async buildUnresolved(track) {
162
+ if (!track) throw new ReferenceError('The Deezer track object was not provided');
163
+
164
+ return new DeezerTrack({
165
+ track: '',
166
+ info: {
167
+ sourceName: 'deezer',
168
+ identifier: track.id,
169
+ isSeekable: true,
170
+ author: track.artist ? track.artist.name : 'Unknown',
171
+ length: track.duration,
172
+ isStream: false,
173
+ title: track.title,
174
+ uri: track.link,
175
+ image: track.album.cover_medium
176
+ },
177
+ });
178
+ }
179
+
180
+
181
+ compareValue(value) {
182
+ return typeof value !== 'undefined' ? value !== null : typeof value !== 'undefined';
183
+ }
184
+
185
+ buildResponse(loadType, tracks, playlistName, exceptionMsg) {
186
+ return Object.assign(
187
+ {
188
+ loadType,
189
+ tracks,
190
+ playlistInfo: playlistName ? { name: playlistName } : {},
191
+ },
192
+ exceptionMsg ? { exception: { message: exceptionMsg, severity: 'COMMON' } } : {},
193
+ );
194
+ }
195
+
196
+
197
+
198
+
199
+ }
200
+
201
+ module.exports = Deezer;
@@ -78,9 +78,7 @@ class Spotify {
78
78
  return this.fetchArtist(id);
79
79
  }
80
80
 
81
- default: {
82
- return this.manager.resolve(url);
83
- }
81
+
84
82
  }
85
83
  }
86
84
 
@@ -90,7 +88,7 @@ class Spotify {
90
88
  await this.fetchPlaylistTracks(playlist);
91
89
 
92
90
  const limitedTracks = this.playlistLimit
93
- ? playlist.tracks.items.slice(0, this.playlistLimit * 50)
91
+ ? playlist.tracks.items.slice(0, this.playlistLimit * 100)
94
92
  : playlist.tracks.items;
95
93
 
96
94
  const unresolvedPlaylistTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x.track)));
@@ -110,7 +108,7 @@ class Spotify {
110
108
  try {
111
109
  const album = await this.requestData(`/albums/${id}`);
112
110
 
113
- const limitedTracks = this.albumLimit ? album.tracks.items.slice(0, this.albumLimit * 50) : album.tracks.items;
111
+ const limitedTracks = this.albumLimit ? album.tracks.items.slice(0, this.albumLimit * 100) : album.tracks.items;
114
112
 
115
113
  const unresolvedPlaylistTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)));
116
114
  return this.buildResponse('PLAYLIST_LOADED', unresolvedPlaylistTracks, album.name);
@@ -130,7 +128,7 @@ class Spotify {
130
128
 
131
129
  const data = await this.requestData(`/artists/${id}/top-tracks?market=${this.searchMarket ?? 'US'}`);
132
130
 
133
- const limitedTracks = this.artistLimit ? data.tracks.slice(0, this.artistLimit * 50) : data.tracks;
131
+ const limitedTracks = this.artistLimit ? data.tracks.slice(0, this.artistLimit * 100) : data.tracks;
134
132
 
135
133
  const unresolvedPlaylistTracks = await Promise.all(limitedTracks.map(x => this.buildUnresolved(x)));
136
134
 
@@ -1,4 +1,4 @@
1
- import { EventEmitter } from "ws";
1
+ import { EventEmitter, WebSocket } from "ws";
2
2
 
3
3
  declare module'Poru' {
4
4
 
@@ -56,48 +56,45 @@ export class Node {
56
56
  constructor(manager:typeof EventEmitter|any,options:object,node:object)
57
57
 
58
58
  public manager: EventEmitter
59
- public name: String
60
- public host: String
61
- public port : Number
62
- public url : String
63
- public password: String
64
- public secure: Boolean
65
- public ws = any
66
- public reconnectTime : Number
67
- public resumeKey:String|Number;
68
- public resumeTimeout =Number
69
- public reconnectAttempt:Number;
70
- public reconnects:Number;
71
- public isConnected:Boolean;
72
- public destroyed:Boolean;
59
+ public name: string
60
+ public host: string
61
+ public port : number
62
+ public url : string
63
+ public password: string
64
+ public secure: boolean
65
+ public ws: WebSocket
66
+ public reconnectTime : number
67
+ public resumeKey:string|number;
68
+ public resumeTimeout: number
69
+ public reconnectAttempt:number;
70
+ public reconnects:number;
71
+ public isConnected:boolean;
72
+ public destroyed:boolean;
73
73
  public stats:Object
74
74
 
75
75
 
76
76
  connect():void
77
77
 
78
- destroy()
78
+ destroy(): void
79
79
 
80
- reconnect()
80
+ reconnect(): void
81
81
 
82
- send(playload:any)
82
+ send(playload:Object): void | Object | String
83
83
 
84
84
  get penalties():Number
85
85
 
86
-
87
-
88
86
  }
89
87
 
90
88
 
91
89
  export class Player extends EventEmitter {
92
- constructor(manager:EventEmitter,node:Map|Object, options:Object)
93
-
90
+ constructor(manager:EventEmitter,node:Map<any, any>|Object, options:Object)
94
91
 
95
92
 
96
93
  public manager:EventEmitter
97
94
 
98
- public queue:Array |any
95
+ public queue:Array<any> |any
99
96
 
100
- public node: Map|Object|Any;
97
+ public node: Map<any, any>|Object|Any;
101
98
 
102
99
  public filters: any
103
100
 
@@ -107,15 +104,15 @@ export class Player extends EventEmitter {
107
104
 
108
105
  public textChannel:any;
109
106
 
110
- public isConnectd = Boolean;
107
+ public isConnected: boolean;
111
108
 
112
- public isPlaying:Boolean;
109
+ public isPlaying:boolean;
113
110
 
114
- public isPause:Boolean;
111
+ public isPause:boolean;
115
112
 
116
- public trackRepeat:Boolean;
113
+ public trackRepeat:boolean;
117
114
 
118
- public queueRepeat:Boolean;
115
+ public queueRepeat:boolean;
119
116
 
120
117
  public loop:Number;
121
118
 
@@ -127,9 +124,7 @@ export class Player extends EventEmitter {
127
124
 
128
125
  public previousTrack:Object;
129
126
 
130
- public voiceUpdateState = any;
131
-
132
-
127
+ public voiceUpdateState:any;
133
128
 
134
129
 
135
130
  play()
@@ -158,24 +153,8 @@ export class Player extends EventEmitter {
158
153
 
159
154
  disconnect()
160
155
 
161
-
162
156
  destroy()
163
157
 
164
158
  autoplay(toggle:Boolean)
165
159
 
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
160
  }
package/src/config.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "client" :"Poru"
3
- }