poru 1.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/LICENSE +21 -0
- package/README.md +50 -0
- package/index.js +20 -0
- package/package.json +23 -0
- package/src/Node.js +137 -0
- package/src/Player.js +317 -0
- package/src/Poru.js +194 -0
- package/src/config.json +3 -0
- package/src/guild/Filter.js +168 -0
- package/src/guild/Queue.js +50 -0
- package/src/guild/Response.js +11 -0
- package/src/guild/Track.js +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 parasop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://cdn.discordapp.com/attachments/732987654165233744/987656504373026816/20220618_000923_0000.png" />
|
|
3
|
+
</p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
[](https://discord.gg/Zmmc47Nrh8)
|
|
6
|
+
[](https://www.npmjs.com/package/poru)
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://nodei.co/npm/poru/"><img src="https://nodei.co/npm/poru.png?downloads=true&downloadRank=true&stars=true"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
## Table of contents
|
|
20
|
+
|
|
21
|
+
- [Documentation](https://gorillas-team.github.io/Gorilink/)
|
|
22
|
+
- [Installation](#installation)
|
|
23
|
+
- [About](#about)
|
|
24
|
+
- [Example](#example-usage-basic-bot)
|
|
25
|
+
|
|
26
|
+
# Installation
|
|
27
|
+
```
|
|
28
|
+
// Using npm
|
|
29
|
+
npm install poru
|
|
30
|
+
|
|
31
|
+
// Using yarn
|
|
32
|
+
yarn add poru
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
# About
|
|
36
|
+
To use you need a configured [Lavalink](https://github.com/Frederikam/Lavalink) instance.
|
|
37
|
+
|
|
38
|
+
- Stable client
|
|
39
|
+
- 100% Compatible with Lavalink
|
|
40
|
+
- Object-oriented
|
|
41
|
+
- 100% Customizable
|
|
42
|
+
- Easy to setup
|
|
43
|
+
|
|
44
|
+
## Example usage basic bot
|
|
45
|
+
```javascript
|
|
46
|
+
// adding soon
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Need Help?
|
|
50
|
+
If you do not understand something in the documentation or have any questions, please join our [My disocrd server ](https://discord.gg/Zmmc47Nrh8)
|
package/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
Node: require("./src/Node"),
|
|
3
|
+
Player: require("./src/Player"),
|
|
4
|
+
Poru: require("./src/Poru")
|
|
5
|
+
}
|
|
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
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "poru",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A stable and powefull lavalink client with so many features",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/parasop/poru.git"
|
|
12
|
+
},
|
|
13
|
+
"author": "PARAS",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/parasop/poru/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/parasop/poru#readme",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"node-fetch": "^3.2.6",
|
|
21
|
+
"ws": "^8.8.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/Node.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const WebSocket = require("ws");
|
|
2
|
+
const config = require("./config.json")
|
|
3
|
+
|
|
4
|
+
class Node {
|
|
5
|
+
constructor(manager, options = { }) {
|
|
6
|
+
|
|
7
|
+
this.manager = manager
|
|
8
|
+
this.name= options.name || null;
|
|
9
|
+
this.host = options.host || "localhost"
|
|
10
|
+
this.port = options.port || 2333
|
|
11
|
+
this.url = `${options.secure ? 'wss' : 'ws'}://${options.host}:${options.port}`;
|
|
12
|
+
this.password = options.password ||"youshallnotpass"
|
|
13
|
+
this.secure = options.secure || false;
|
|
14
|
+
this.ws = null;
|
|
15
|
+
this.reconnect = options.reconnect || 10000;
|
|
16
|
+
this.reconnectTime = 50000;
|
|
17
|
+
this.resumeKey = options.resumeKey || null;
|
|
18
|
+
this._resumeTimeout = options.resumeTimeout || 60
|
|
19
|
+
this.queue = [];
|
|
20
|
+
this.isConnected = false;
|
|
21
|
+
this.stats = {
|
|
22
|
+
players: 0,
|
|
23
|
+
playingPlayers: 0,
|
|
24
|
+
uptime: 0,
|
|
25
|
+
memory: {
|
|
26
|
+
free: 0,
|
|
27
|
+
used: 0,
|
|
28
|
+
allocated: 0,
|
|
29
|
+
reservable: 0,
|
|
30
|
+
},
|
|
31
|
+
cpu: {
|
|
32
|
+
cores: 0,
|
|
33
|
+
systemLoad: 0,
|
|
34
|
+
lavalinkLoad: 0,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
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.client
|
|
48
|
+
};
|
|
49
|
+
if (this.resumeKey) headers["Resume-Key"] = this.resumeKey;
|
|
50
|
+
this.ws = new WebSocket(`ws${this.secure ? "s" : ""}:${this.host}:${this.port}/`, { 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));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
open(){
|
|
59
|
+
if (this.reconnect) {
|
|
60
|
+
clearTimeout(this.reconnect);
|
|
61
|
+
delete this.reconnect;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.queue =[];
|
|
65
|
+
if (this.resumeKey) this.send({ op: "configureResuming", key: (this.resumeKey).toString(), timeout: this._resumeTimeout });
|
|
66
|
+
this.manager.emit("nodeConnect", this);
|
|
67
|
+
this.isConnected = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
message(payload) {
|
|
71
|
+
// eslint-disable-next-line no-param-reassign
|
|
72
|
+
if (Array.isArray(payload)) payload = Buffer.concat(payload);
|
|
73
|
+
// eslint-disable-next-line no-param-reassign
|
|
74
|
+
else if (payload instanceof ArrayBuffer) payload = Buffer.from(payload);
|
|
75
|
+
|
|
76
|
+
const packet = JSON.parse(payload);
|
|
77
|
+
if (packet.op && packet.op === "stats") {
|
|
78
|
+
this.stats = { ...packet };
|
|
79
|
+
delete this.stats.op;
|
|
80
|
+
}
|
|
81
|
+
const player = this.manager.players.get(packet.guildId);
|
|
82
|
+
if (packet.guildId && player) player.emit(packet.op, packet);
|
|
83
|
+
|
|
84
|
+
packet.node = this;
|
|
85
|
+
/**
|
|
86
|
+
* Fire up when raw packets / or sending raw data
|
|
87
|
+
*/
|
|
88
|
+
this.manager.emit("raw", packet);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
close(event) {
|
|
92
|
+
// if (!event) return "Unknown event";
|
|
93
|
+
/**
|
|
94
|
+
* Fire up when node disconnect
|
|
95
|
+
* @event nodeClosed
|
|
96
|
+
*/
|
|
97
|
+
this.manager.emit("nodeClose", event, this);
|
|
98
|
+
if (event !== 1000) return this.reconnect();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
error(event) {
|
|
103
|
+
if (!event) return "Unknown event";
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Fire up when node return an error
|
|
107
|
+
* @event nodeError
|
|
108
|
+
*/
|
|
109
|
+
this.manager.emit("nodeError", event, this);
|
|
110
|
+
return this.reconnect();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
reconnect() {
|
|
114
|
+
this.reconnect = setTimeout(() => {
|
|
115
|
+
this.connected = false;
|
|
116
|
+
this.ws.removeAllListeners();
|
|
117
|
+
this.ws = null;
|
|
118
|
+
this.manager.emit("nodeReconnect", this);
|
|
119
|
+
this.connect();
|
|
120
|
+
}, this.reconnectTime);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
send(payload) {
|
|
124
|
+
this.ws.send(JSON.stringify(payload), (error) => {
|
|
125
|
+
if (error) return error;
|
|
126
|
+
return null;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
module.exports = Node;
|
|
137
|
+
|
package/src/Player.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
const { EventEmitter } = require("events");
|
|
2
|
+
const Queue = require("./guild/Queue");
|
|
3
|
+
const Filters = require("./guild/Filter")
|
|
4
|
+
class Player extends EventEmitter {
|
|
5
|
+
constructor(node, options, manager) {
|
|
6
|
+
super();
|
|
7
|
+
|
|
8
|
+
this.manager = manager;
|
|
9
|
+
this.queue = new Queue()
|
|
10
|
+
this.node = node;
|
|
11
|
+
this.filters = new Filters(this,this.node)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
this.guild = options.guild.id || options.guild;
|
|
15
|
+
|
|
16
|
+
this.voiceChannel = options.voiceChannel.id || options.voiceChannel;
|
|
17
|
+
|
|
18
|
+
this.textChannel = options.textChannel || null;
|
|
19
|
+
|
|
20
|
+
this.state = { volume: 100, equalizer: [] };
|
|
21
|
+
|
|
22
|
+
this.isConnectd = false;
|
|
23
|
+
|
|
24
|
+
this.trackRepeat = false;
|
|
25
|
+
|
|
26
|
+
this.queueRepeat = false;
|
|
27
|
+
|
|
28
|
+
this.loop = 0;
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
this.playing = false;
|
|
32
|
+
|
|
33
|
+
this.timestamp = Date.now();
|
|
34
|
+
|
|
35
|
+
this.position = 0;
|
|
36
|
+
|
|
37
|
+
this.isPaused = false
|
|
38
|
+
|
|
39
|
+
// this.tracks = {}
|
|
40
|
+
|
|
41
|
+
this.currentTrack = {};
|
|
42
|
+
|
|
43
|
+
this.previousTrack = {};
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
this.voiceUpdateState = null;
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
this.on("event", (data) => (this.lavalinkEvent(data).bind(this))());
|
|
54
|
+
this.on("event",(data)=> this.manager.emit("debug",data))
|
|
55
|
+
this.on("playerUpdate", (packet) => {
|
|
56
|
+
this.isConnectd = packet.state.connected,
|
|
57
|
+
this.position = packet.state.position
|
|
58
|
+
this.state = {
|
|
59
|
+
volume: this.state.volume,
|
|
60
|
+
equalizer: this.state.equalizer,
|
|
61
|
+
...packet.state,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async play() {
|
|
66
|
+
|
|
67
|
+
if(!this.queue.length){
|
|
68
|
+
return nulll;
|
|
69
|
+
}
|
|
70
|
+
this.currentTrack = this.queue.shift();
|
|
71
|
+
this.playing = true;
|
|
72
|
+
this.timestamp = Date.now();
|
|
73
|
+
this.node.send({
|
|
74
|
+
op: "play",
|
|
75
|
+
guildId: this.guild,
|
|
76
|
+
track: this.currentTrack.track,
|
|
77
|
+
volume: this.volume || 100,
|
|
78
|
+
});
|
|
79
|
+
this.position =0;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async stop() {
|
|
85
|
+
|
|
86
|
+
this.position = 0;
|
|
87
|
+
this.isConnectd = false
|
|
88
|
+
this.playing =false;
|
|
89
|
+
this.node.send({
|
|
90
|
+
op: "stop",
|
|
91
|
+
guildId: this.guild
|
|
92
|
+
});
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pause(pause = true) {
|
|
97
|
+
if (typeof pause !== "boolean") throw new RangeError("Pause function must be pass with boolean value.");
|
|
98
|
+
if (this.paused || !this.queue.size) return this;
|
|
99
|
+
this.playing = !pause;
|
|
100
|
+
this.paused = pause;
|
|
101
|
+
this.node.send({
|
|
102
|
+
op: "pause",
|
|
103
|
+
guildId: this.guild,
|
|
104
|
+
pause,
|
|
105
|
+
});
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async seekTo(position) {
|
|
110
|
+
if (Number.isNaN(position)) throw new RangeError("[Poru Error] Position must be a number.");
|
|
111
|
+
this.position = position;
|
|
112
|
+
this.node.send({
|
|
113
|
+
op: "seek",
|
|
114
|
+
guildId: this.guild,
|
|
115
|
+
position,
|
|
116
|
+
});
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async setVolume(volume) {
|
|
121
|
+
if (Number.isNaN(volume)) throw new RangeError("Volume level must be a number.");
|
|
122
|
+
this.volume = volume;
|
|
123
|
+
this.node.send({
|
|
124
|
+
op: "volume",
|
|
125
|
+
guildId: this.guild,
|
|
126
|
+
volume: this.volume,
|
|
127
|
+
});
|
|
128
|
+
return this;
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async TrackRepeat() {
|
|
133
|
+
this.loop = 1;
|
|
134
|
+
this.trackRepeat = true;
|
|
135
|
+
this.queueRepeat = false;
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async QueueRepeat() {
|
|
140
|
+
this.loop = 2;
|
|
141
|
+
this.queueRepeat = true;
|
|
142
|
+
this.trackRepeat= false;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async DisableRepeat() {
|
|
147
|
+
this.loop = 0;
|
|
148
|
+
this.trackRepeat =false;
|
|
149
|
+
this.queueRepeat = false;
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async setTextChannel(channel) {
|
|
156
|
+
if (typeof channel !== "string") throw new RangeError("Channel must be a string.");
|
|
157
|
+
this.textChannel = channel;
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async setVoiceChannel(channel) {
|
|
162
|
+
if (typeof channel !== "string") throw new RangeError("Channel must be a string.");
|
|
163
|
+
this.voiceChannel = channel;
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async connect(data) {
|
|
168
|
+
this.voiceUpdateState = data;
|
|
169
|
+
this.node.send({
|
|
170
|
+
op: "voiceUpdate",
|
|
171
|
+
guildId: this.guild,
|
|
172
|
+
...data,
|
|
173
|
+
});
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async reconnect() {
|
|
178
|
+
if (this.voiceChannel === null) return null;
|
|
179
|
+
this.node.send({
|
|
180
|
+
op: 4,
|
|
181
|
+
d: {
|
|
182
|
+
guild_id: this.guild,
|
|
183
|
+
channel_id: this.voiceChannel,
|
|
184
|
+
self_mute: false,
|
|
185
|
+
self_deaf: false,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async disconnect() {
|
|
195
|
+
if (this.voiceChannel === null) return null;
|
|
196
|
+
this.pause(true);
|
|
197
|
+
this.isConnectd = false;
|
|
198
|
+
this.manager.sendWS({
|
|
199
|
+
op: 4,
|
|
200
|
+
d: {
|
|
201
|
+
guild_id: this.guild,
|
|
202
|
+
channel_id: null,
|
|
203
|
+
self_mute: false,
|
|
204
|
+
self_deaf: false,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
this.voiceChannel = null;
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
destroy() {
|
|
212
|
+
this.disconnect();
|
|
213
|
+
this.node.send({
|
|
214
|
+
op: "destroy",
|
|
215
|
+
guildId: this.guild,
|
|
216
|
+
});
|
|
217
|
+
this.manager.emit("playerDestroy", this);
|
|
218
|
+
this.manager.players.delete(this.guild);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async autoplay(toggle =false){
|
|
222
|
+
|
|
223
|
+
if(!toggle) return null;
|
|
224
|
+
try{
|
|
225
|
+
if(!this.previousTrack) return this.stop();
|
|
226
|
+
let data = `https://www.youtube.com/watch?v=${this.previousTrack.identifier}&list=RD${this.previousTrack.identifier}`;
|
|
227
|
+
|
|
228
|
+
let response = await this.manager.search(data);
|
|
229
|
+
|
|
230
|
+
if (!response || !response.tracks || ["LOAD_FAILED", "NO_MATCHES"].includes(response.type)) return this.stop();
|
|
231
|
+
|
|
232
|
+
let track = response.tracks[Math.floor(Math.random() * Math.floor(response.tracks.length))];
|
|
233
|
+
|
|
234
|
+
this.queue.push(track);
|
|
235
|
+
|
|
236
|
+
this.play();
|
|
237
|
+
|
|
238
|
+
return this;
|
|
239
|
+
|
|
240
|
+
}catch(e){
|
|
241
|
+
console.log(`[Poru Autoplay] error : ${e}`)
|
|
242
|
+
return this.stop();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
lavalinkEvent(data) {
|
|
249
|
+
const events = {
|
|
250
|
+
TrackStartEvent() {
|
|
251
|
+
this.playing =true;
|
|
252
|
+
this.paused =false;
|
|
253
|
+
this.manager.emit("trackStart", this, this.currentTrack);
|
|
254
|
+
},
|
|
255
|
+
// eslint-disable-next-line consistent-return
|
|
256
|
+
TrackEndEvent() {
|
|
257
|
+
|
|
258
|
+
this.previousTrack = this.currentTrack;
|
|
259
|
+
|
|
260
|
+
if (this.currentTrack && this.loop === 1) {
|
|
261
|
+
|
|
262
|
+
this.queue.unshift(this.previousTrack)
|
|
263
|
+
return this.play();
|
|
264
|
+
} else if (this.currentTrack && this.loop === 2) {
|
|
265
|
+
|
|
266
|
+
this.queue.push(this.previousTrack)
|
|
267
|
+
return this.play();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if(this.queue.length ===0){
|
|
271
|
+
return this.manager.emit("queueEnd", this, this.track,data);
|
|
272
|
+
// this.destroy();
|
|
273
|
+
} else if (this.queue.length>0) {
|
|
274
|
+
|
|
275
|
+
return this.play();
|
|
276
|
+
}
|
|
277
|
+
this.manager.emit("queueEnd", this, this.track,data);
|
|
278
|
+
|
|
279
|
+
},
|
|
280
|
+
TrackStuckEvent() {
|
|
281
|
+
this.queue.shift();
|
|
282
|
+
this.manager.emit("trackError", this, this.track, data);
|
|
283
|
+
},
|
|
284
|
+
TrackExceptionEvent() {
|
|
285
|
+
this.queue.shift();
|
|
286
|
+
/**
|
|
287
|
+
* Fire up when there's an error while playing the track
|
|
288
|
+
* @event trackError
|
|
289
|
+
*/
|
|
290
|
+
this.manager.emit("trackError", this, this.track, data);
|
|
291
|
+
},
|
|
292
|
+
WebSocketClosedEvent() {
|
|
293
|
+
if ([4015, 4009].includes(data.code)) {
|
|
294
|
+
this.manager.sendWS({
|
|
295
|
+
op: 4,
|
|
296
|
+
d: {
|
|
297
|
+
guild_id: data.guildId,
|
|
298
|
+
channel_id: this.voiceChannel.id || this.voiceChannel,
|
|
299
|
+
self_mute: this.options.selfMute || false,
|
|
300
|
+
self_deaf: this.options.selfDeaf || false,
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
this.manager.emit("socketClosed", this, data);
|
|
305
|
+
},
|
|
306
|
+
default() {
|
|
307
|
+
throw new Error(`An unknown event: ${data}`);
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
return events[data.type] || events.default;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
module.exports = Player;
|
|
317
|
+
|
package/src/Poru.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
const { EventEmitter } = require("events");
|
|
2
|
+
const fetch = (...args) => import('node-fetch').then(({
|
|
3
|
+
default: fetch
|
|
4
|
+
}) => fetch(...args));const Player = require("./Player");
|
|
5
|
+
const Node = require("./Node");
|
|
6
|
+
const Response = require("./guild/Response");
|
|
7
|
+
|
|
8
|
+
class Poru extends EventEmitter {
|
|
9
|
+
constructor(client, nodes, options = {}) {
|
|
10
|
+
super();
|
|
11
|
+
if (!client) throw new Error("[Poru Error] you did't provide a valid client");
|
|
12
|
+
if (!nodes) throw new Error("[Poru Error] you did't provide a lavalink nodes");
|
|
13
|
+
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.player = options.player || Player;
|
|
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.shards = options.shards || 1;
|
|
23
|
+
this.sendWS = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
//create a node and connect it with lavalink
|
|
29
|
+
createNode(options) {
|
|
30
|
+
const node = new Node(this, options);
|
|
31
|
+
if (options.name) {
|
|
32
|
+
this.nodes.set(options.name || options.host, node);
|
|
33
|
+
node.connect();
|
|
34
|
+
return node;
|
|
35
|
+
}
|
|
36
|
+
this.nodes.set(options.host, node);
|
|
37
|
+
node.connect();
|
|
38
|
+
return node;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//create connection with discord voice channel
|
|
42
|
+
createConnection(data = {}, options = {}) {
|
|
43
|
+
const player = this.players.get(data.guild.id || data.guild);
|
|
44
|
+
if (player){
|
|
45
|
+
return player;
|
|
46
|
+
}
|
|
47
|
+
this.sendWS({
|
|
48
|
+
op: 4,
|
|
49
|
+
d: {
|
|
50
|
+
guild_id: data.guild.id || data.guild,
|
|
51
|
+
channel_id: data.voiceChannel.id || data.voiceChannel,
|
|
52
|
+
self_mute: options.selfMute || false,
|
|
53
|
+
self_deaf: options.selfDeaf || true,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
return this.Player(data);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
init(client) {
|
|
60
|
+
|
|
61
|
+
this.user =client.user.id;
|
|
62
|
+
this.sendWS = (data) => {
|
|
63
|
+
const guild = client.guilds.cache.get(data.d.guild_id);
|
|
64
|
+
if (guild) guild.shard.send(data);
|
|
65
|
+
}
|
|
66
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
67
|
+
this._nodes.forEach((node) => this.createNode(node));
|
|
68
|
+
console.log(`Thanks for using Poru`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
voiceServersUpdate(data) {
|
|
72
|
+
this.voiceServers.set(data.guild_id, data);
|
|
73
|
+
return this.connectionProcess(data.guild_id);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
voiceStateUpdate(data) {
|
|
77
|
+
if (data.user_id !== this.user) return;
|
|
78
|
+
if (data.channel_id) {
|
|
79
|
+
this.voiceStates.set(data.guild_id, data);
|
|
80
|
+
// eslint-disable-next-line consistent-return
|
|
81
|
+
return this.connectionProcess(data.guild_id);
|
|
82
|
+
}
|
|
83
|
+
this.voiceServers.delete(data.guild_id);
|
|
84
|
+
this.voiceStates.delete(data.guild_id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
packetUpdate(packet) {
|
|
88
|
+
if (packet.t === "VOICE_SERVER_UPDATE"){
|
|
89
|
+
this.voiceServersUpdate(packet.d);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (packet.t === "VOICE_STATE_UPDATE"){
|
|
93
|
+
this.voiceStateUpdate(packet.d);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
connectionProcess(guildId) {
|
|
98
|
+
const server = this.voiceServers.get(guildId);
|
|
99
|
+
const state = this.voiceStates.get(guildId);
|
|
100
|
+
if (!server) return false;
|
|
101
|
+
const player = this.players.get(guildId);
|
|
102
|
+
if (!player) return false;
|
|
103
|
+
|
|
104
|
+
player.connect({
|
|
105
|
+
sessionId: state ? state.session_id : player.voiceUpdateState.sessionId,
|
|
106
|
+
event: server,
|
|
107
|
+
});
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
get leastUsedNodes() {
|
|
113
|
+
return [...this.nodes.values()]
|
|
114
|
+
.filter((node) => node.isConnected)
|
|
115
|
+
.sort((a, b) => {
|
|
116
|
+
const aLoad = a.stats.cpu ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100 : 0;
|
|
117
|
+
const bLoad = b.stats.cpu ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 : 0;
|
|
118
|
+
return aLoad - bLoad;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
Player(data) {
|
|
123
|
+
const guild = data.guild.id || data.guild;
|
|
124
|
+
const Nodes = this.nodes.get(guild);
|
|
125
|
+
if (Nodes) return Nodes;
|
|
126
|
+
if (this.leastUsedNodes.length === 0) throw new Error("No nodes are avaliable");
|
|
127
|
+
const node = this.nodes.get(this.leastUsedNodes[0].name
|
|
128
|
+
|| this.leastUsedNodes[0].host);
|
|
129
|
+
if (!node) throw new Error("No nodes are avalible");
|
|
130
|
+
|
|
131
|
+
// eslint-disable-next-line new-cap
|
|
132
|
+
const player = new this.player(node, data, this);
|
|
133
|
+
this.players.set(guild, player);
|
|
134
|
+
|
|
135
|
+
return player;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async search(track, source) {
|
|
139
|
+
const node = this.leastUsedNodes[0];
|
|
140
|
+
if (!node) throw new Error("No nodes are available.");
|
|
141
|
+
const regex = /^https?:\/\//;
|
|
142
|
+
if (!regex.test(track)) {
|
|
143
|
+
// eslint-disable-next-line no-param-reassign
|
|
144
|
+
track = `${source || "yt"}search:${track}`;
|
|
145
|
+
}
|
|
146
|
+
const result = await this.request(node, "loadtracks", `identifier=${encodeURIComponent(track)}`);
|
|
147
|
+
// this.emit("error", result);
|
|
148
|
+
if (!result) throw new Error("No tracks found.");
|
|
149
|
+
return new Response(result);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async resolve(track, source) {
|
|
154
|
+
const node = this.leastUsedNodes[0];
|
|
155
|
+
if (!node) throw new Error("No nodes are available.");
|
|
156
|
+
const regex = /^https?:\/\//;
|
|
157
|
+
if (!regex.test(track)) {
|
|
158
|
+
// eslint-disable-next-line no-param-reassign
|
|
159
|
+
track = `${source || "yt"}search:${track}`;
|
|
160
|
+
}
|
|
161
|
+
const result = await this.request(node, "loadtracks", `identifier=${encodeURIComponent(track)}`);
|
|
162
|
+
// this.emit("error", result);
|
|
163
|
+
if (!result) throw new Error("No tracks found.");
|
|
164
|
+
return new Response(result);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
async decodeTrack(track) {
|
|
169
|
+
const node = this.leastUsedNodes[0];
|
|
170
|
+
if (!node) throw new Error("No nodes are available.");
|
|
171
|
+
const result = await this.request(node, "decodetrack", `track=${track}`);
|
|
172
|
+
this.emit("error", result);
|
|
173
|
+
if (result.status === 500) return null;
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
request(node, endpoint, param) {
|
|
178
|
+
return fetch(`http${node.secure ? "s" : ""}://${node.host}:${node.port}/${endpoint}?${param}`, {
|
|
179
|
+
headers: {
|
|
180
|
+
Authorization: node.password,
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
.then((r) => r.json())
|
|
184
|
+
.catch((e) => {
|
|
185
|
+
throw new Error(`[Poru Error] Failed to request to the lavalink.\n error: ${e}`);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
get(guildId) {
|
|
190
|
+
return this.players.get(guildId);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = Poru
|
package/src/config.json
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
class Filters {
|
|
2
|
+
|
|
3
|
+
constructor(player,options = {}) {
|
|
4
|
+
this._8d =options._8d|| null;
|
|
5
|
+
this.bassboost = options.bassboost || null;
|
|
6
|
+
this.player = player;
|
|
7
|
+
this.node = player.node
|
|
8
|
+
this.volume = player.volume ?? 1.0 ;
|
|
9
|
+
this.equalizer = options.equalizer || [];
|
|
10
|
+
this.karaoke = options.karaoke || null;
|
|
11
|
+
this.timescale = options.timescale || null;
|
|
12
|
+
this.tremolo = options.tremolo || null;
|
|
13
|
+
this.vibrato = options.vibrato || null;
|
|
14
|
+
this.rotation = options.rotation || null;
|
|
15
|
+
this.distortion = options.distortion || null;
|
|
16
|
+
this.channelMix = options.channelMix || null;
|
|
17
|
+
this.lowPass = options.lowPass || null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
setEqualizer(bands){
|
|
22
|
+
this.equalizer = bands;
|
|
23
|
+
this.updateFilters();
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
setKaraoke(karaoke) {
|
|
27
|
+
this.karaoke = karaoke|| null;
|
|
28
|
+
this.updateFilters();
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
setTimescale(timescale) {
|
|
32
|
+
this.updateFilters();
|
|
33
|
+
this.timescale = timescale || null;
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
setTremolo(tremolo) {
|
|
37
|
+
this.tremolo = tremolo || null;
|
|
38
|
+
this.updateFilters();
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setVibrato(vibrato){
|
|
43
|
+
this.vibrato = vibrato || null;
|
|
44
|
+
this.updateFilters();
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setRotation(rotation) {
|
|
49
|
+
this.rotation = rotation || null;
|
|
50
|
+
this.updateFilters();
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
setDistortion(distortion) {
|
|
54
|
+
this.distortion = distortion || null;
|
|
55
|
+
this.updateFilters();
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setChannelMix(mix) {
|
|
60
|
+
this.channelMix = mix || null;
|
|
61
|
+
this.updateFilters();
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setLowPass(pass) {
|
|
66
|
+
this.lowPass = pass || null;
|
|
67
|
+
this.updateFilters();
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setFilters(options) {
|
|
72
|
+
this.player.filters = new Filters(options);
|
|
73
|
+
this.updateFilters();
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
clearFilters() {
|
|
78
|
+
this.player.filters = new Filters();
|
|
79
|
+
this.node.send({
|
|
80
|
+
op: "filters",
|
|
81
|
+
guildId: this.player.guild
|
|
82
|
+
});
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
setNightcore(val) {
|
|
86
|
+
if (!this.player) return;
|
|
87
|
+
this.setTimescale(val ? { rate: 1.5 } : null);
|
|
88
|
+
this.nightcore = val;
|
|
89
|
+
if (val) {
|
|
90
|
+
this.doubleTime = false;
|
|
91
|
+
this.vaporwave = false;
|
|
92
|
+
}
|
|
93
|
+
return this
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setSlowmode(val) {
|
|
97
|
+
if (!this.player) return;
|
|
98
|
+
this.slowmode = val;
|
|
99
|
+
if (val) {
|
|
100
|
+
}
|
|
101
|
+
this.setFilters(
|
|
102
|
+
val
|
|
103
|
+
? {
|
|
104
|
+
timescale: {
|
|
105
|
+
speed: 0.5,
|
|
106
|
+
pitch: 1.0,
|
|
107
|
+
rate: 0.8,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
: this.clearFilters()
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setVaporwave(val) {
|
|
115
|
+
if (!this.player) return;
|
|
116
|
+
this.vaporwave = val;
|
|
117
|
+
if (val) {
|
|
118
|
+
this.doubleTime = false;
|
|
119
|
+
this.nightcore = false;
|
|
120
|
+
}
|
|
121
|
+
this.setTimescale(val ? { pitch: 0.5 } : null);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
set8D(val) {
|
|
125
|
+
if (!this.player) return;
|
|
126
|
+
this._8d = val;
|
|
127
|
+
this.setRotation(val ? { rotationHz: 0.2 } : null);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setBassboost(val) {
|
|
131
|
+
if (!this.player) return;
|
|
132
|
+
this.bassboost = !!val;
|
|
133
|
+
this.bassboost = val / 100;
|
|
134
|
+
this.player.setEqualizer(
|
|
135
|
+
val
|
|
136
|
+
? Array(6)
|
|
137
|
+
.fill(0.22)
|
|
138
|
+
.map((x, i) => ({ band: i, gain: x * val }))
|
|
139
|
+
: []
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
updateFilters(){
|
|
144
|
+
console.log(this.node.send)
|
|
145
|
+
const { volume, equalizer, karaoke, timescale, tremolo, vibrato, rotation, distortion, channelMix, lowPass } = this;
|
|
146
|
+
this.node.send({
|
|
147
|
+
op:"filters",
|
|
148
|
+
guildId: this.player.guild,
|
|
149
|
+
volume,
|
|
150
|
+
equalizer,
|
|
151
|
+
karaoke,
|
|
152
|
+
timescale,
|
|
153
|
+
tremolo,
|
|
154
|
+
vibrato,
|
|
155
|
+
rotation,
|
|
156
|
+
distortion,
|
|
157
|
+
channelMix,
|
|
158
|
+
lowPass
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
module.exports = Filters;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class Queue extends Array{
|
|
2
|
+
constructor() {
|
|
3
|
+
super(...arguments);
|
|
4
|
+
}
|
|
5
|
+
get duration() {
|
|
6
|
+
return this.reduce((acc, val) => acc + val.info.length, 0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get totalSize() {
|
|
10
|
+
return this.length + (this.current ? 1 : 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get size() {
|
|
14
|
+
return this.length
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get empty() {
|
|
18
|
+
return this.length / 2 < 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
first() {
|
|
22
|
+
return this ? this[0] : 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
add(track) {
|
|
26
|
+
this.push(track);
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
removeFirst() {
|
|
31
|
+
return this.shift();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
remove(index) {
|
|
35
|
+
return this.splice(index, 1)[0];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
clear() {
|
|
39
|
+
return this.splice(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
shuffle() {
|
|
43
|
+
for (let i = this.length - 1; i > 0; i -= 1) {
|
|
44
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
45
|
+
[this[i], this[j]] = [this[j], this[i]];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = Queue;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Track {
|
|
2
|
+
constructor(data) {
|
|
3
|
+
this.uri = data.info.uri;
|
|
4
|
+
this.title = data.info.title;
|
|
5
|
+
this.author = data.info.author;
|
|
6
|
+
this.duration = data.info.length;
|
|
7
|
+
this.identifier = data.info.identifier;
|
|
8
|
+
this.isStream = data.info.isStream;
|
|
9
|
+
this.isSeekable = data.info.isSeekable;
|
|
10
|
+
this.track = data.track;
|
|
11
|
+
this.thumbnail = `https://i.ytimg.com/vi/${data.info.identifier}/maxresdefault.jpg` || null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = Track;
|