discord.js-selfv13 13.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of discord.js-selfv13 might be problematic. Click here for more details.
- package/.dccache +1 -0
- package/LICENSE +190 -0
- package/README.md +87 -0
- package/browser.js +9 -0
- package/deploy/deploy-key.enc +0 -0
- package/deploy/deploy.sh +90 -0
- package/deploy/test.sh +34 -0
- package/docs/README.md +1 -0
- package/docs/examples/attachments.md +163 -0
- package/docs/examples/avatars.js +29 -0
- package/docs/examples/embed.js +38 -0
- package/docs/examples/greeting.js +30 -0
- package/docs/examples/moderation.md +145 -0
- package/docs/examples/ping.js +29 -0
- package/docs/examples/webhook.js +12 -0
- package/docs/general/faq.md +23 -0
- package/docs/general/updating.md +181 -0
- package/docs/general/welcome.md +95 -0
- package/docs/index.yml +30 -0
- package/docs/logo.svg +19 -0
- package/docs/topics/voice.md +113 -0
- package/docs/topics/web.md +38 -0
- package/package.json +147 -0
- package/src/client/Client.js +564 -0
- package/src/client/ClientDataManager.js +150 -0
- package/src/client/ClientDataResolver.js +376 -0
- package/src/client/ClientManager.js +74 -0
- package/src/client/WebhookClient.js +118 -0
- package/src/client/actions/Action.js +23 -0
- package/src/client/actions/ActionsManager.js +40 -0
- package/src/client/actions/ChannelCreate.js +11 -0
- package/src/client/actions/ChannelDelete.js +30 -0
- package/src/client/actions/ChannelUpdate.js +74 -0
- package/src/client/actions/GuildBanRemove.js +13 -0
- package/src/client/actions/GuildChannelsPositionUpdate.js +19 -0
- package/src/client/actions/GuildDelete.js +57 -0
- package/src/client/actions/GuildEmojiCreate.js +17 -0
- package/src/client/actions/GuildEmojiDelete.js +18 -0
- package/src/client/actions/GuildEmojiUpdate.js +17 -0
- package/src/client/actions/GuildEmojisUpdate.js +38 -0
- package/src/client/actions/GuildMemberGet.js +10 -0
- package/src/client/actions/GuildMemberRemove.js +41 -0
- package/src/client/actions/GuildRoleCreate.js +26 -0
- package/src/client/actions/GuildRoleDelete.js +42 -0
- package/src/client/actions/GuildRoleUpdate.js +41 -0
- package/src/client/actions/GuildRolesPositionUpdate.js +19 -0
- package/src/client/actions/GuildSync.js +29 -0
- package/src/client/actions/GuildUpdate.js +34 -0
- package/src/client/actions/MessageCreate.js +53 -0
- package/src/client/actions/MessageDelete.js +35 -0
- package/src/client/actions/MessageDeleteBulk.js +26 -0
- package/src/client/actions/MessageReactionAdd.js +37 -0
- package/src/client/actions/MessageReactionRemove.js +37 -0
- package/src/client/actions/MessageReactionRemoveAll.js +25 -0
- package/src/client/actions/MessageUpdate.js +40 -0
- package/src/client/actions/Ready.js +1 -0
- package/src/client/actions/Ready.js.bak +65 -0
- package/src/client/actions/UserGet.js +11 -0
- package/src/client/actions/UserNoteUpdate.js +30 -0
- package/src/client/actions/UserUpdate.js +33 -0
- package/src/client/rest/APIRequest.js +56 -0
- package/src/client/rest/DiscordAPIError.js +60 -0
- package/src/client/rest/RESTManager.js +58 -0
- package/src/client/rest/RESTMethods.js +1006 -0
- package/src/client/rest/RequestHandlers/Burst.js +90 -0
- package/src/client/rest/RequestHandlers/RequestHandler.js +54 -0
- package/src/client/rest/RequestHandlers/Sequential.js +132 -0
- package/src/client/rest/UserAgentManager.js +25 -0
- package/src/client/voice/ClientVoiceManager.js +81 -0
- package/src/client/voice/VoiceBroadcast.js +366 -0
- package/src/client/voice/VoiceConnection.js +530 -0
- package/src/client/voice/VoiceUDPClient.js +127 -0
- package/src/client/voice/VoiceWebSocket.js +246 -0
- package/src/client/voice/dispatcher/StreamDispatcher.js +331 -0
- package/src/client/voice/opus/BaseOpusEngine.js +60 -0
- package/src/client/voice/opus/NodeOpusEngine.js +40 -0
- package/src/client/voice/opus/OpusEngineList.js +28 -0
- package/src/client/voice/opus/OpusScriptEngine.js +45 -0
- package/src/client/voice/player/AudioPlayer.js +170 -0
- package/src/client/voice/receiver/VoiceReadable.js +17 -0
- package/src/client/voice/receiver/VoiceReceiver.js +219 -0
- package/src/client/voice/util/SecretKey.js +16 -0
- package/src/client/voice/util/Secretbox.js +33 -0
- package/src/client/voice/util/VolumeInterface.js +86 -0
- package/src/client/websocket/WebSocketConnection.js +506 -0
- package/src/client/websocket/WebSocketManager.js +90 -0
- package/src/client/websocket/packets/WebSocketPacketManager.js +110 -0
- package/src/client/websocket/packets/handlers/AbstractHandler.js +11 -0
- package/src/client/websocket/packets/handlers/ChannelCreate.js +17 -0
- package/src/client/websocket/packets/handlers/ChannelDelete.js +20 -0
- package/src/client/websocket/packets/handlers/ChannelPinsUpdate.js +37 -0
- package/src/client/websocket/packets/handlers/ChannelUpdate.js +11 -0
- package/src/client/websocket/packets/handlers/GuildBanAdd.js +23 -0
- package/src/client/websocket/packets/handlers/GuildBanRemove.js +20 -0
- package/src/client/websocket/packets/handlers/GuildCreate.js +22 -0
- package/src/client/websocket/packets/handlers/GuildDelete.js +19 -0
- package/src/client/websocket/packets/handlers/GuildEmojisUpdate.js +11 -0
- package/src/client/websocket/packets/handlers/GuildIntegrationsUpdate.js +19 -0
- package/src/client/websocket/packets/handlers/GuildMemberAdd.js +17 -0
- package/src/client/websocket/packets/handlers/GuildMemberRemove.js +13 -0
- package/src/client/websocket/packets/handlers/GuildMemberUpdate.js +18 -0
- package/src/client/websocket/packets/handlers/GuildMembersChunk.js +33 -0
- package/src/client/websocket/packets/handlers/GuildRoleCreate.js +11 -0
- package/src/client/websocket/packets/handlers/GuildRoleDelete.js +11 -0
- package/src/client/websocket/packets/handlers/GuildRoleUpdate.js +11 -0
- package/src/client/websocket/packets/handlers/GuildSync.js +11 -0
- package/src/client/websocket/packets/handlers/GuildUpdate.js +11 -0
- package/src/client/websocket/packets/handlers/MessageCreate.js +19 -0
- package/src/client/websocket/packets/handlers/MessageDelete.js +19 -0
- package/src/client/websocket/packets/handlers/MessageDeleteBulk.js +17 -0
- package/src/client/websocket/packets/handlers/MessageReactionAdd.js +11 -0
- package/src/client/websocket/packets/handlers/MessageReactionRemove.js +11 -0
- package/src/client/websocket/packets/handlers/MessageReactionRemoveAll.js +11 -0
- package/src/client/websocket/packets/handlers/MessageUpdate.js +11 -0
- package/src/client/websocket/packets/handlers/PresenceUpdate.js +76 -0
- package/src/client/websocket/packets/handlers/Ready.js +83 -0
- package/src/client/websocket/packets/handlers/RelationshipAdd.js +19 -0
- package/src/client/websocket/packets/handlers/RelationshipRemove.js +19 -0
- package/src/client/websocket/packets/handlers/Resumed.js +28 -0
- package/src/client/websocket/packets/handlers/TypingStart.js +68 -0
- package/src/client/websocket/packets/handlers/UserGuildSettingsUpdate.js +21 -0
- package/src/client/websocket/packets/handlers/UserNoteUpdate.js +12 -0
- package/src/client/websocket/packets/handlers/UserSettingsUpdate.js +18 -0
- package/src/client/websocket/packets/handlers/UserUpdate.js +11 -0
- package/src/client/websocket/packets/handlers/VoiceServerUpdate.js +19 -0
- package/src/client/websocket/packets/handlers/VoiceStateUpdate.js +52 -0
- package/src/client/websocket/packets/handlers/WebhooksUpdate.js +19 -0
- package/src/index.js +66 -0
- package/src/sharding/Shard.js +282 -0
- package/src/sharding/ShardClientUtil.js +146 -0
- package/src/sharding/ShardingManager.js +220 -0
- package/src/structures/Attachment.js +75 -0
- package/src/structures/CategoryChannel.js +22 -0
- package/src/structures/Channel.js +78 -0
- package/src/structures/ClientUser.js +447 -0
- package/src/structures/ClientUserChannelOverride.js +30 -0
- package/src/structures/ClientUserGuildSettings.js +60 -0
- package/src/structures/ClientUserSettings.js +80 -0
- package/src/structures/DMChannel.js +76 -0
- package/src/structures/Emoji.js +256 -0
- package/src/structures/GroupDMChannel.js +246 -0
- package/src/structures/Guild.js +1461 -0
- package/src/structures/GuildAuditLogs.js +371 -0
- package/src/structures/GuildChannel.js +537 -0
- package/src/structures/GuildMember.js +613 -0
- package/src/structures/Invite.js +164 -0
- package/src/structures/Message.js +605 -0
- package/src/structures/MessageAttachment.js +68 -0
- package/src/structures/MessageCollector.js +100 -0
- package/src/structures/MessageEmbed.js +386 -0
- package/src/structures/MessageMentions.js +144 -0
- package/src/structures/MessageReaction.js +96 -0
- package/src/structures/NewsChannel.js +24 -0
- package/src/structures/OAuth2Application.js +148 -0
- package/src/structures/PartialGuild.js +51 -0
- package/src/structures/PartialGuildChannel.js +44 -0
- package/src/structures/PermissionOverwrites.js +69 -0
- package/src/structures/Presence.js +241 -0
- package/src/structures/ReactionCollector.js +85 -0
- package/src/structures/ReactionEmoji.js +49 -0
- package/src/structures/RichEmbed.js +295 -0
- package/src/structures/Role.js +376 -0
- package/src/structures/StoreChannel.js +25 -0
- package/src/structures/TextChannel.js +154 -0
- package/src/structures/User.js +329 -0
- package/src/structures/UserConnection.js +48 -0
- package/src/structures/UserProfile.js +62 -0
- package/src/structures/VoiceChannel.js +146 -0
- package/src/structures/VoiceRegion.js +50 -0
- package/src/structures/Webhook.js +304 -0
- package/src/structures/interfaces/Collector.js +179 -0
- package/src/structures/interfaces/TextBasedChannel.js +635 -0
- package/src/structures/shared/resolvePermissions.js +26 -0
- package/src/util/Collection.js +532 -0
- package/src/util/Constants.js +845 -0
- package/src/util/Permissions.js +306 -0
- package/src/util/Snowflake.js +82 -0
- package/src/util/Util.js +221 -0
- package/test/random.js +207 -0
- package/test/shard.js +31 -0
- package/test/sharder.js +7 -0
- package/test/voice.js +78 -0
- package/test/webpack.html +31 -0
- package/tsconfig.json +13 -0
- package/tslint.json +62 -0
- package/typings/discord.js-test.ts +69 -0
- package/typings/index.d.ts +2190 -0
- package/webpack.config.js +62 -0
@@ -0,0 +1,246 @@
|
|
1
|
+
const Constants = require('../../util/Constants');
|
2
|
+
const SecretKey = require('./util/SecretKey');
|
3
|
+
const EventEmitter = require('events').EventEmitter;
|
4
|
+
|
5
|
+
let WebSocket;
|
6
|
+
try {
|
7
|
+
WebSocket = require('@discordjs/uws');
|
8
|
+
} catch (err) {
|
9
|
+
WebSocket = require('ws');
|
10
|
+
}
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Represents a Voice Connection's WebSocket.
|
14
|
+
* @extends {EventEmitter}
|
15
|
+
* @private
|
16
|
+
*/
|
17
|
+
class VoiceWebSocket extends EventEmitter {
|
18
|
+
constructor(voiceConnection) {
|
19
|
+
super();
|
20
|
+
|
21
|
+
/**
|
22
|
+
* The client of this voice WebSocket
|
23
|
+
* @type {Client}
|
24
|
+
*/
|
25
|
+
this.client = voiceConnection.voiceManager.client;
|
26
|
+
|
27
|
+
/**
|
28
|
+
* The Voice Connection that this WebSocket serves
|
29
|
+
* @type {VoiceConnection}
|
30
|
+
*/
|
31
|
+
this.voiceConnection = voiceConnection;
|
32
|
+
|
33
|
+
/**
|
34
|
+
* How many connection attempts have been made
|
35
|
+
* @type {number}
|
36
|
+
*/
|
37
|
+
this.attempts = 0;
|
38
|
+
|
39
|
+
this.connect();
|
40
|
+
this.dead = false;
|
41
|
+
this.voiceConnection.on('closing', this.shutdown.bind(this));
|
42
|
+
}
|
43
|
+
|
44
|
+
shutdown() {
|
45
|
+
this.dead = true;
|
46
|
+
this.reset();
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Resets the current WebSocket.
|
51
|
+
*/
|
52
|
+
reset() {
|
53
|
+
if (this.ws) {
|
54
|
+
if (this.ws.readyState !== WebSocket.CLOSED) this.ws.close();
|
55
|
+
this.ws = null;
|
56
|
+
}
|
57
|
+
this.clearHeartbeat();
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Starts connecting to the Voice WebSocket Server.
|
62
|
+
*/
|
63
|
+
connect() {
|
64
|
+
if (this.dead) return;
|
65
|
+
if (this.ws) this.reset();
|
66
|
+
if (this.attempts >= 5) {
|
67
|
+
this.emit('debug', new Error(`Too many connection attempts (${this.attempts}).`));
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
71
|
+
this.attempts++;
|
72
|
+
|
73
|
+
/**
|
74
|
+
* The actual WebSocket used to connect to the Voice WebSocket Server.
|
75
|
+
* @type {WebSocket}
|
76
|
+
*/
|
77
|
+
this.ws = new WebSocket(`wss://${this.voiceConnection.authentication.endpoint}`);
|
78
|
+
this.ws.onopen = this.onOpen.bind(this);
|
79
|
+
this.ws.onmessage = this.onMessage.bind(this);
|
80
|
+
this.ws.onclose = this.onClose.bind(this);
|
81
|
+
this.ws.onerror = this.onError.bind(this);
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Sends data to the WebSocket if it is open.
|
86
|
+
* @param {string} data The data to send to the WebSocket
|
87
|
+
* @returns {Promise<string>}
|
88
|
+
*/
|
89
|
+
send(data) {
|
90
|
+
return new Promise((resolve, reject) => {
|
91
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
92
|
+
throw new Error(`Voice websocket not open to send ${data}.`);
|
93
|
+
}
|
94
|
+
this.ws.send(data, null, error => {
|
95
|
+
if (error) reject(error); else resolve(data);
|
96
|
+
});
|
97
|
+
});
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* JSON.stringify's a packet and then sends it to the WebSocket Server.
|
102
|
+
* @param {Object} packet The packet to send
|
103
|
+
* @returns {Promise<string>}
|
104
|
+
*/
|
105
|
+
sendPacket(packet) {
|
106
|
+
try {
|
107
|
+
packet = JSON.stringify(packet);
|
108
|
+
} catch (error) {
|
109
|
+
return Promise.reject(error);
|
110
|
+
}
|
111
|
+
return this.send(packet);
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Called whenever the WebSocket opens.
|
116
|
+
*/
|
117
|
+
onOpen() {
|
118
|
+
this.sendPacket({
|
119
|
+
op: Constants.OPCodes.DISPATCH,
|
120
|
+
d: {
|
121
|
+
server_id: this.voiceConnection.channel.guild.id,
|
122
|
+
user_id: this.client.user.id,
|
123
|
+
token: this.voiceConnection.authentication.token,
|
124
|
+
session_id: this.voiceConnection.authentication.sessionID,
|
125
|
+
},
|
126
|
+
}).catch(() => {
|
127
|
+
this.emit('error', new Error('Tried to send join packet, but the WebSocket is not open.'));
|
128
|
+
});
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Called whenever a message is received from the WebSocket.
|
133
|
+
* @param {MessageEvent} event The message event that was received
|
134
|
+
* @returns {void}
|
135
|
+
*/
|
136
|
+
onMessage(event) {
|
137
|
+
try {
|
138
|
+
return this.onPacket(JSON.parse(event.data));
|
139
|
+
} catch (error) {
|
140
|
+
return this.onError(error);
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Called whenever the connection to the WebSocket server is lost.
|
146
|
+
*/
|
147
|
+
onClose() {
|
148
|
+
if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000);
|
149
|
+
}
|
150
|
+
|
151
|
+
/**
|
152
|
+
* Called whenever an error occurs with the WebSocket.
|
153
|
+
* @param {Error} error The error that occurred
|
154
|
+
*/
|
155
|
+
onError(error) {
|
156
|
+
this.emit('error', error);
|
157
|
+
}
|
158
|
+
|
159
|
+
/**
|
160
|
+
* Called whenever a valid packet is received from the WebSocket.
|
161
|
+
* @param {Object} packet The received packet
|
162
|
+
*/
|
163
|
+
onPacket(packet) {
|
164
|
+
switch (packet.op) {
|
165
|
+
case Constants.VoiceOPCodes.READY:
|
166
|
+
this.setHeartbeat(packet.d.heartbeat_interval);
|
167
|
+
/**
|
168
|
+
* Emitted once the voice WebSocket receives the ready packet.
|
169
|
+
* @param {Object} packet The received packet
|
170
|
+
* @event VoiceWebSocket#ready
|
171
|
+
*/
|
172
|
+
this.emit('ready', packet.d);
|
173
|
+
break;
|
174
|
+
case Constants.VoiceOPCodes.SESSION_DESCRIPTION:
|
175
|
+
/**
|
176
|
+
* Emitted once the Voice Websocket receives a description of this voice session.
|
177
|
+
* @param {string} encryptionMode The type of encryption being used
|
178
|
+
* @param {SecretKey} secretKey The secret key used for encryption
|
179
|
+
* @event VoiceWebSocket#sessionDescription
|
180
|
+
*/
|
181
|
+
this.emit('sessionDescription', packet.d.mode, new SecretKey(packet.d.secret_key));
|
182
|
+
break;
|
183
|
+
case Constants.VoiceOPCodes.SPEAKING:
|
184
|
+
/**
|
185
|
+
* Emitted whenever a speaking packet is received.
|
186
|
+
* @param {Object} data
|
187
|
+
* @event VoiceWebSocket#speaking
|
188
|
+
*/
|
189
|
+
this.emit('speaking', packet.d);
|
190
|
+
break;
|
191
|
+
default:
|
192
|
+
/**
|
193
|
+
* Emitted when an unhandled packet is received.
|
194
|
+
* @param {Object} packet
|
195
|
+
* @event VoiceWebSocket#unknownPacket
|
196
|
+
*/
|
197
|
+
this.emit('unknownPacket', packet);
|
198
|
+
break;
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
/**
|
203
|
+
* Sets an interval at which to send a heartbeat packet to the WebSocket.
|
204
|
+
* @param {number} interval The interval at which to send a heartbeat packet
|
205
|
+
*/
|
206
|
+
setHeartbeat(interval) {
|
207
|
+
if (!interval || isNaN(interval)) {
|
208
|
+
this.onError(new Error('Tried to set voice heartbeat but no valid interval was specified.'));
|
209
|
+
return;
|
210
|
+
}
|
211
|
+
if (this.heartbeatInterval) {
|
212
|
+
/**
|
213
|
+
* Emitted whenver the voice WebSocket encounters a non-fatal error.
|
214
|
+
* @param {string} warn The warning
|
215
|
+
* @event VoiceWebSocket#warn
|
216
|
+
*/
|
217
|
+
this.emit('warn', 'A voice heartbeat interval is being overwritten');
|
218
|
+
clearInterval(this.heartbeatInterval);
|
219
|
+
}
|
220
|
+
this.heartbeatInterval = this.client.setInterval(this.sendHeartbeat.bind(this), interval);
|
221
|
+
}
|
222
|
+
|
223
|
+
/**
|
224
|
+
* Clears a heartbeat interval, if one exists.
|
225
|
+
*/
|
226
|
+
clearHeartbeat() {
|
227
|
+
if (!this.heartbeatInterval) {
|
228
|
+
this.emit('warn', 'Tried to clear a heartbeat interval that does not exist');
|
229
|
+
return;
|
230
|
+
}
|
231
|
+
clearInterval(this.heartbeatInterval);
|
232
|
+
this.heartbeatInterval = null;
|
233
|
+
}
|
234
|
+
|
235
|
+
/**
|
236
|
+
* Sends a heartbeat packet.
|
237
|
+
*/
|
238
|
+
sendHeartbeat() {
|
239
|
+
this.sendPacket({ op: Constants.VoiceOPCodes.HEARTBEAT, d: null }).catch(() => {
|
240
|
+
this.emit('warn', 'Tried to send heartbeat, but connection is not open');
|
241
|
+
this.clearHeartbeat();
|
242
|
+
});
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
module.exports = VoiceWebSocket;
|
@@ -0,0 +1,331 @@
|
|
1
|
+
const VolumeInterface = require('../util/VolumeInterface');
|
2
|
+
const VoiceBroadcast = require('../VoiceBroadcast');
|
3
|
+
const Constants = require('../../../util/Constants');
|
4
|
+
|
5
|
+
const secretbox = require('../util/Secretbox');
|
6
|
+
|
7
|
+
const nonce = Buffer.alloc(24);
|
8
|
+
nonce.fill(0);
|
9
|
+
|
10
|
+
/**
|
11
|
+
* The class that sends voice packet data to the voice connection.
|
12
|
+
* ```js
|
13
|
+
* // Obtained using:
|
14
|
+
* voiceChannel.join().then(connection => {
|
15
|
+
* // You can play a file or a stream here:
|
16
|
+
* const dispatcher = connection.playFile('./file.mp3');
|
17
|
+
* });
|
18
|
+
* ```
|
19
|
+
* @implements {VolumeInterface}
|
20
|
+
*/
|
21
|
+
class StreamDispatcher extends VolumeInterface {
|
22
|
+
constructor(player, stream, streamOptions) {
|
23
|
+
super(streamOptions);
|
24
|
+
/**
|
25
|
+
* The Audio Player that controls this dispatcher
|
26
|
+
* @type {AudioPlayer}
|
27
|
+
*/
|
28
|
+
this.player = player;
|
29
|
+
/**
|
30
|
+
* The stream that the dispatcher plays
|
31
|
+
* @type {ReadableStream|VoiceBroadcast}
|
32
|
+
*/
|
33
|
+
this.stream = stream;
|
34
|
+
if (!(this.stream instanceof VoiceBroadcast)) this.startStreaming();
|
35
|
+
this.streamOptions = streamOptions;
|
36
|
+
|
37
|
+
const data = this.streamingData;
|
38
|
+
data.length = 20;
|
39
|
+
data.missed = 0;
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Whether playing is paused
|
43
|
+
* @type {boolean}
|
44
|
+
*/
|
45
|
+
this.paused = false;
|
46
|
+
/**
|
47
|
+
* Whether this dispatcher has been destroyed
|
48
|
+
* @type {boolean}
|
49
|
+
*/
|
50
|
+
this.destroyed = false;
|
51
|
+
|
52
|
+
this._opus = streamOptions.opus;
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* How many passes the dispatcher should take when sending packets to reduce packet loss. Values over 5
|
57
|
+
* aren't recommended, as it means you are using 5x more bandwidth. You _can_ edit this at runtime
|
58
|
+
* @type {number}
|
59
|
+
* @readonly
|
60
|
+
*/
|
61
|
+
get passes() {
|
62
|
+
return this.streamOptions.passes || 1;
|
63
|
+
}
|
64
|
+
|
65
|
+
set passes(n) {
|
66
|
+
this.streamOptions.passes = n;
|
67
|
+
}
|
68
|
+
|
69
|
+
get streamingData() {
|
70
|
+
return this.player.streamingData;
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* How long the stream dispatcher has been "speaking" for
|
75
|
+
* @type {number}
|
76
|
+
* @readonly
|
77
|
+
*/
|
78
|
+
get time() {
|
79
|
+
return this.streamingData.count * (this.streamingData.length || 0);
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* The total time, taking into account pauses and skips, that the dispatcher has been streaming for
|
84
|
+
* @type {number}
|
85
|
+
* @readonly
|
86
|
+
*/
|
87
|
+
get totalStreamTime() {
|
88
|
+
return this.time + this.streamingData.pausedTime;
|
89
|
+
}
|
90
|
+
|
91
|
+
/**
|
92
|
+
* Stops sending voice packets to the voice connection (stream may still progress however).
|
93
|
+
*/
|
94
|
+
pause() { this.setPaused(true); }
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Resumes sending voice packets to the voice connection (may be further on in the stream than when paused).
|
98
|
+
*/
|
99
|
+
resume() { this.setPaused(false); }
|
100
|
+
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Stops the current stream permanently and emits an `end` event.
|
104
|
+
* @param {string} [reason='user'] An optional reason for stopping the dispatcher
|
105
|
+
*/
|
106
|
+
end(reason = 'user') {
|
107
|
+
this.destroy('end', reason);
|
108
|
+
}
|
109
|
+
|
110
|
+
setSpeaking(value) {
|
111
|
+
if (this.speaking === value) return;
|
112
|
+
if (this.player.voiceConnection.status !== Constants.VoiceStatus.CONNECTED) return;
|
113
|
+
this.speaking = value;
|
114
|
+
/**
|
115
|
+
* Emitted when the dispatcher starts/stops speaking.
|
116
|
+
* @event StreamDispatcher#speaking
|
117
|
+
* @param {boolean} value Whether or not the dispatcher is speaking
|
118
|
+
*/
|
119
|
+
this.emit('speaking', value);
|
120
|
+
}
|
121
|
+
|
122
|
+
|
123
|
+
/**
|
124
|
+
* Set the bitrate of the current Opus encoder.
|
125
|
+
* @param {number} bitrate New bitrate, in kbps
|
126
|
+
* If set to 'auto', the voice channel's bitrate will be used
|
127
|
+
*/
|
128
|
+
setBitrate(bitrate) {
|
129
|
+
this.player.setBitrate(bitrate);
|
130
|
+
}
|
131
|
+
|
132
|
+
sendBuffer(buffer, sequence, timestamp, opusPacket) {
|
133
|
+
opusPacket = opusPacket || this.player.opusEncoder.encode(buffer);
|
134
|
+
const packet = this.createPacket(sequence, timestamp, opusPacket);
|
135
|
+
this.sendPacket(packet);
|
136
|
+
}
|
137
|
+
|
138
|
+
sendPacket(packet) {
|
139
|
+
let repeats = this.passes;
|
140
|
+
/**
|
141
|
+
* Emitted whenever the dispatcher has debug information.
|
142
|
+
* @event StreamDispatcher#debug
|
143
|
+
* @param {string} info The debug info
|
144
|
+
*/
|
145
|
+
this.setSpeaking(true);
|
146
|
+
while (repeats-- && this.player.voiceConnection.sockets.udp) {
|
147
|
+
this.player.voiceConnection.sockets.udp.send(packet)
|
148
|
+
.catch(e => {
|
149
|
+
this.setSpeaking(false);
|
150
|
+
this.emit('debug', `Failed to send a packet ${e}`);
|
151
|
+
});
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
createPacket(sequence, timestamp, buffer) {
|
156
|
+
const packetBuffer = Buffer.alloc(buffer.length + 28);
|
157
|
+
packetBuffer.fill(0);
|
158
|
+
packetBuffer[0] = 0x80;
|
159
|
+
packetBuffer[1] = 0x78;
|
160
|
+
|
161
|
+
packetBuffer.writeUIntBE(sequence, 2, 2);
|
162
|
+
packetBuffer.writeUIntBE(timestamp, 4, 4);
|
163
|
+
packetBuffer.writeUIntBE(this.player.voiceConnection.authentication.ssrc, 8, 4);
|
164
|
+
|
165
|
+
packetBuffer.copy(nonce, 0, 0, 12);
|
166
|
+
buffer = secretbox.methods.close(buffer, nonce, this.player.voiceConnection.authentication.secretKey.key);
|
167
|
+
for (let i = 0; i < buffer.length; i++) packetBuffer[i + 12] = buffer[i];
|
168
|
+
|
169
|
+
return packetBuffer;
|
170
|
+
}
|
171
|
+
|
172
|
+
processPacket(packet) {
|
173
|
+
try {
|
174
|
+
if (this.destroyed || !this.player.voiceConnection.authentication.secretKey) {
|
175
|
+
this.setSpeaking(false);
|
176
|
+
return;
|
177
|
+
}
|
178
|
+
|
179
|
+
const data = this.streamingData;
|
180
|
+
|
181
|
+
if (this.paused) {
|
182
|
+
this.setSpeaking(false);
|
183
|
+
data.pausedTime = data.length * 10;
|
184
|
+
return;
|
185
|
+
}
|
186
|
+
|
187
|
+
if (!packet) {
|
188
|
+
data.missed++;
|
189
|
+
data.pausedTime += data.length * 10;
|
190
|
+
return;
|
191
|
+
}
|
192
|
+
|
193
|
+
this.started();
|
194
|
+
this.missed = 0;
|
195
|
+
|
196
|
+
this.stepStreamingData();
|
197
|
+
this.sendBuffer(null, data.sequence, data.timestamp, packet);
|
198
|
+
} catch (e) {
|
199
|
+
this.destroy('error', e);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
process() {
|
204
|
+
try {
|
205
|
+
if (this.destroyed) {
|
206
|
+
this.setSpeaking(false);
|
207
|
+
return;
|
208
|
+
}
|
209
|
+
|
210
|
+
const data = this.streamingData;
|
211
|
+
|
212
|
+
if (data.missed >= 5) {
|
213
|
+
this.destroy('end', 'Stream is not generating quickly enough.');
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
|
217
|
+
if (this.paused) {
|
218
|
+
this.setSpeaking(false);
|
219
|
+
// Old code?
|
220
|
+
// data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
|
221
|
+
data.pausedTime += data.length * 10;
|
222
|
+
this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10);
|
223
|
+
return;
|
224
|
+
}
|
225
|
+
|
226
|
+
this.started();
|
227
|
+
|
228
|
+
const buffer = this.readStreamBuffer();
|
229
|
+
if (!buffer) {
|
230
|
+
data.missed++;
|
231
|
+
data.pausedTime += data.length * 10;
|
232
|
+
this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), data.length * 10);
|
233
|
+
return;
|
234
|
+
}
|
235
|
+
|
236
|
+
data.missed = 0;
|
237
|
+
|
238
|
+
this.stepStreamingData();
|
239
|
+
|
240
|
+
if (this._opus) {
|
241
|
+
this.sendBuffer(null, data.sequence, data.timestamp, buffer);
|
242
|
+
} else {
|
243
|
+
this.sendBuffer(buffer, data.sequence, data.timestamp);
|
244
|
+
}
|
245
|
+
|
246
|
+
const nextTime = data.length + (data.startTime + data.pausedTime + (data.count * data.length) - Date.now());
|
247
|
+
this.player.voiceConnection.voiceManager.client.setTimeout(() => this.process(), nextTime);
|
248
|
+
} catch (e) {
|
249
|
+
this.destroy('error', e);
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
readStreamBuffer() {
|
254
|
+
const data = this.streamingData;
|
255
|
+
const bufferLength = (this._opus ? 80 : 1920) * data.channels;
|
256
|
+
let buffer = this.stream.read(bufferLength);
|
257
|
+
if (this._opus) return buffer;
|
258
|
+
if (!buffer) return null;
|
259
|
+
|
260
|
+
if (buffer.length !== bufferLength) {
|
261
|
+
const newBuffer = Buffer.alloc(bufferLength).fill(0);
|
262
|
+
buffer.copy(newBuffer);
|
263
|
+
buffer = newBuffer;
|
264
|
+
}
|
265
|
+
|
266
|
+
buffer = this.applyVolume(buffer);
|
267
|
+
return buffer;
|
268
|
+
}
|
269
|
+
|
270
|
+
started() {
|
271
|
+
const data = this.streamingData;
|
272
|
+
|
273
|
+
if (!data.startTime) {
|
274
|
+
/**
|
275
|
+
* Emitted once the dispatcher starts streaming.
|
276
|
+
* @event StreamDispatcher#start
|
277
|
+
*/
|
278
|
+
this.emit('start');
|
279
|
+
data.startTime = Date.now();
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
stepStreamingData() {
|
284
|
+
const data = this.streamingData;
|
285
|
+
data.count++;
|
286
|
+
data.sequence = data.sequence < 65535 ? data.sequence + 1 : 0;
|
287
|
+
data.timestamp = (data.timestamp + 960) < 4294967295 ? data.timestamp + 960 : 0;
|
288
|
+
}
|
289
|
+
|
290
|
+
destroy(type, reason) {
|
291
|
+
if (this.destroyed) return;
|
292
|
+
this.destroyed = true;
|
293
|
+
this.setSpeaking(false);
|
294
|
+
this.emit(type, reason);
|
295
|
+
/**
|
296
|
+
* Emitted once the dispatcher ends.
|
297
|
+
* @param {string} [reason] The reason the dispatcher ended
|
298
|
+
* @event StreamDispatcher#end
|
299
|
+
*/
|
300
|
+
if (type !== 'end') this.emit('end', `destroyed due to ${type} - ${reason}`);
|
301
|
+
}
|
302
|
+
|
303
|
+
startStreaming() {
|
304
|
+
if (!this.stream) {
|
305
|
+
/**
|
306
|
+
* Emitted if the dispatcher encounters an error.
|
307
|
+
* @event StreamDispatcher#error
|
308
|
+
* @param {string} error The error message
|
309
|
+
*/
|
310
|
+
this.emit('error', 'No stream');
|
311
|
+
return;
|
312
|
+
}
|
313
|
+
|
314
|
+
this.stream.on('end', err => this.destroy('end', err || 'stream'));
|
315
|
+
this.stream.on('error', err => this.destroy('error', err));
|
316
|
+
|
317
|
+
const data = this.streamingData;
|
318
|
+
data.length = 20;
|
319
|
+
data.missed = 0;
|
320
|
+
|
321
|
+
this.stream.once('readable', () => {
|
322
|
+
data.startTime = null;
|
323
|
+
data.count = 0;
|
324
|
+
this.process();
|
325
|
+
});
|
326
|
+
}
|
327
|
+
|
328
|
+
setPaused(paused) { this.setSpeaking(!(this.paused = paused)); }
|
329
|
+
}
|
330
|
+
|
331
|
+
module.exports = StreamDispatcher;
|
@@ -0,0 +1,60 @@
|
|
1
|
+
/**
|
2
|
+
* The base opus encoding engine.
|
3
|
+
* @private
|
4
|
+
*/
|
5
|
+
class BaseOpus {
|
6
|
+
/**
|
7
|
+
* @param {Object} [options] The options to apply to the Opus engine
|
8
|
+
* @param {number} [options.bitrate=48] The desired bitrate (kbps)
|
9
|
+
* @param {boolean} [options.fec=false] Whether to enable forward error correction
|
10
|
+
* @param {number} [options.plp=0] The expected packet loss percentage
|
11
|
+
*/
|
12
|
+
constructor({ bitrate = 48, fec = false, plp = 0 } = {}) {
|
13
|
+
this.ctl = {
|
14
|
+
BITRATE: 4002,
|
15
|
+
FEC: 4012,
|
16
|
+
PLP: 4014,
|
17
|
+
};
|
18
|
+
|
19
|
+
this.samplingRate = 48000;
|
20
|
+
this.channels = 2;
|
21
|
+
|
22
|
+
/**
|
23
|
+
* The desired bitrate (kbps)
|
24
|
+
* @type {number}
|
25
|
+
*/
|
26
|
+
this.bitrate = bitrate;
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Miscellaneous Opus options
|
30
|
+
* @type {Object}
|
31
|
+
*/
|
32
|
+
this.options = { fec, plp };
|
33
|
+
}
|
34
|
+
|
35
|
+
init() {
|
36
|
+
try {
|
37
|
+
this.setBitrate(this.bitrate);
|
38
|
+
|
39
|
+
// Set FEC (forward error correction)
|
40
|
+
if (this.options.fec) this.setFEC(this.options.fec);
|
41
|
+
|
42
|
+
// Set PLP (expected packet loss percentage)
|
43
|
+
if (this.options.plp) this.setPLP(this.options.plp);
|
44
|
+
} catch (err) {
|
45
|
+
// Opus engine likely has no support for libopus CTL
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
encode(buffer) {
|
50
|
+
return buffer;
|
51
|
+
}
|
52
|
+
|
53
|
+
decode(buffer) {
|
54
|
+
return buffer;
|
55
|
+
}
|
56
|
+
|
57
|
+
destroy() {} // eslint-disable-line no-empty-function
|
58
|
+
}
|
59
|
+
|
60
|
+
module.exports = BaseOpus;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
const OpusEngine = require('./BaseOpusEngine');
|
2
|
+
|
3
|
+
let opus;
|
4
|
+
|
5
|
+
class NodeOpusEngine extends OpusEngine {
|
6
|
+
constructor(player) {
|
7
|
+
super(player);
|
8
|
+
try {
|
9
|
+
opus = require('node-opus');
|
10
|
+
} catch (err) {
|
11
|
+
throw err;
|
12
|
+
}
|
13
|
+
this.encoder = new opus.OpusEncoder(this.samplingRate, this.channels);
|
14
|
+
super.init();
|
15
|
+
}
|
16
|
+
|
17
|
+
setBitrate(bitrate) {
|
18
|
+
this.encoder.applyEncoderCTL(this.ctl.BITRATE, Math.min(128, Math.max(16, bitrate)) * 1000);
|
19
|
+
}
|
20
|
+
|
21
|
+
setFEC(enabled) {
|
22
|
+
this.encoder.applyEncoderCTL(this.ctl.FEC, enabled ? 1 : 0);
|
23
|
+
}
|
24
|
+
|
25
|
+
setPLP(percent) {
|
26
|
+
this.encoder.applyEncoderCTL(this.ctl.PLP, Math.min(100, Math.max(0, percent * 100)));
|
27
|
+
}
|
28
|
+
|
29
|
+
encode(buffer) {
|
30
|
+
super.encode(buffer);
|
31
|
+
return this.encoder.encode(buffer, 1920);
|
32
|
+
}
|
33
|
+
|
34
|
+
decode(buffer) {
|
35
|
+
super.decode(buffer);
|
36
|
+
return this.encoder.decode(buffer, 1920);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
module.exports = NodeOpusEngine;
|
@@ -0,0 +1,28 @@
|
|
1
|
+
const list = [
|
2
|
+
require('./NodeOpusEngine'),
|
3
|
+
require('./OpusScriptEngine'),
|
4
|
+
];
|
5
|
+
|
6
|
+
function fetch(Encoder, engineOptions) {
|
7
|
+
try {
|
8
|
+
return new Encoder(engineOptions);
|
9
|
+
} catch (err) {
|
10
|
+
if (err.message.includes('Cannot find module')) return null;
|
11
|
+
|
12
|
+
// The Opus engine exists, but another error occurred.
|
13
|
+
throw err;
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
exports.add = encoder => {
|
18
|
+
list.push(encoder);
|
19
|
+
};
|
20
|
+
|
21
|
+
exports.fetch = engineOptions => {
|
22
|
+
for (const encoder of list) {
|
23
|
+
const fetched = fetch(encoder, engineOptions);
|
24
|
+
if (fetched) return fetched;
|
25
|
+
}
|
26
|
+
|
27
|
+
throw new Error('Couldn\'t find an Opus engine.');
|
28
|
+
};
|