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,45 @@
|
|
1
|
+
const OpusEngine = require('./BaseOpusEngine');
|
2
|
+
|
3
|
+
let OpusScript;
|
4
|
+
|
5
|
+
class OpusScriptEngine extends OpusEngine {
|
6
|
+
constructor(player) {
|
7
|
+
super(player);
|
8
|
+
try {
|
9
|
+
OpusScript = require('opusscript');
|
10
|
+
} catch (err) {
|
11
|
+
throw err;
|
12
|
+
}
|
13
|
+
this.encoder = new OpusScript(this.samplingRate, this.channels);
|
14
|
+
super.init();
|
15
|
+
}
|
16
|
+
|
17
|
+
setBitrate(bitrate) {
|
18
|
+
this.encoder.encoderCTL(this.ctl.BITRATE, Math.min(128, Math.max(16, bitrate)) * 1000);
|
19
|
+
}
|
20
|
+
|
21
|
+
setFEC(enabled) {
|
22
|
+
this.encoder.encoderCTL(this.ctl.FEC, enabled ? 1 : 0);
|
23
|
+
}
|
24
|
+
|
25
|
+
setPLP(percent) {
|
26
|
+
this.encoder.encoderCTL(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, 960);
|
32
|
+
}
|
33
|
+
|
34
|
+
decode(buffer) {
|
35
|
+
super.decode(buffer);
|
36
|
+
return this.encoder.decode(buffer);
|
37
|
+
}
|
38
|
+
|
39
|
+
destroy() {
|
40
|
+
super.destroy();
|
41
|
+
this.encoder.delete();
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
module.exports = OpusScriptEngine;
|
@@ -0,0 +1,170 @@
|
|
1
|
+
const EventEmitter = require('events').EventEmitter;
|
2
|
+
const Prism = require('prism-media');
|
3
|
+
const StreamDispatcher = require('../dispatcher/StreamDispatcher');
|
4
|
+
const Collection = require('../../../util/Collection');
|
5
|
+
const OpusEncoders = require('../opus/OpusEngineList');
|
6
|
+
|
7
|
+
const ffmpegArguments = [
|
8
|
+
'-analyzeduration', '0',
|
9
|
+
'-loglevel', '0',
|
10
|
+
'-f', 's16le',
|
11
|
+
'-ar', '48000',
|
12
|
+
'-ac', '2',
|
13
|
+
];
|
14
|
+
|
15
|
+
/**
|
16
|
+
* An Audio Player for a Voice Connection.
|
17
|
+
* @private
|
18
|
+
* @extends {EventEmitter}
|
19
|
+
*/
|
20
|
+
class AudioPlayer extends EventEmitter {
|
21
|
+
constructor(voiceConnection) {
|
22
|
+
super();
|
23
|
+
/**
|
24
|
+
* The voice connection that the player serves
|
25
|
+
* @type {VoiceConnection}
|
26
|
+
*/
|
27
|
+
this.voiceConnection = voiceConnection;
|
28
|
+
/**
|
29
|
+
* The prism transcoder that the player uses
|
30
|
+
* @type {Prism}
|
31
|
+
*/
|
32
|
+
this.prism = new Prism();
|
33
|
+
this.streams = new Collection();
|
34
|
+
this.currentStream = {};
|
35
|
+
this.streamingData = {
|
36
|
+
channels: 2,
|
37
|
+
count: 0,
|
38
|
+
sequence: 0,
|
39
|
+
timestamp: 0,
|
40
|
+
pausedTime: 0,
|
41
|
+
};
|
42
|
+
this.voiceConnection.once('closing', () => this.destroyCurrentStream());
|
43
|
+
}
|
44
|
+
|
45
|
+
/**
|
46
|
+
* The current transcoder
|
47
|
+
* @type {?Object}
|
48
|
+
* @readonly
|
49
|
+
*/
|
50
|
+
get transcoder() {
|
51
|
+
return this.currentStream.transcoder;
|
52
|
+
}
|
53
|
+
|
54
|
+
/**
|
55
|
+
* The current dispatcher
|
56
|
+
* @type {?StreamDispatcher}
|
57
|
+
* @readonly
|
58
|
+
*/
|
59
|
+
get dispatcher() {
|
60
|
+
return this.currentStream.dispatcher;
|
61
|
+
}
|
62
|
+
|
63
|
+
destroy() {
|
64
|
+
if (this.opusEncoder) this.opusEncoder.destroy();
|
65
|
+
this.opusEncoder = null;
|
66
|
+
}
|
67
|
+
|
68
|
+
destroyCurrentStream() {
|
69
|
+
const transcoder = this.transcoder;
|
70
|
+
const dispatcher = this.dispatcher;
|
71
|
+
if (transcoder) transcoder.kill();
|
72
|
+
if (dispatcher) {
|
73
|
+
const end = dispatcher.listeners('end')[0];
|
74
|
+
const error = dispatcher.listeners('error')[0];
|
75
|
+
if (end) dispatcher.removeListener('end', end);
|
76
|
+
if (error) dispatcher.removeListener('error', error);
|
77
|
+
dispatcher.destroy('end');
|
78
|
+
}
|
79
|
+
this.currentStream = {};
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Set the bitrate of the current Opus encoder.
|
84
|
+
* @param {number} value New bitrate, in kbps
|
85
|
+
* If set to 'auto', the voice channel's bitrate will be used
|
86
|
+
*/
|
87
|
+
setBitrate(value) {
|
88
|
+
if (!value) return;
|
89
|
+
if (!this.opusEncoder) return;
|
90
|
+
const bitrate = value === 'auto' ? this.voiceConnection.channel.bitrate : value;
|
91
|
+
this.opusEncoder.setBitrate(bitrate);
|
92
|
+
}
|
93
|
+
|
94
|
+
playUnknownStream(stream, options = {}) {
|
95
|
+
this.destroy();
|
96
|
+
this.opusEncoder = OpusEncoders.fetch(options);
|
97
|
+
const transcoder = this.prism.transcode({
|
98
|
+
type: 'ffmpeg',
|
99
|
+
media: stream,
|
100
|
+
ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek || 0)]),
|
101
|
+
});
|
102
|
+
this.destroyCurrentStream();
|
103
|
+
this.currentStream = {
|
104
|
+
transcoder: transcoder,
|
105
|
+
output: transcoder.output,
|
106
|
+
input: stream,
|
107
|
+
};
|
108
|
+
transcoder.on('error', e => {
|
109
|
+
this.destroyCurrentStream();
|
110
|
+
if (this.listenerCount('error') > 0) this.emit('error', e);
|
111
|
+
this.emit('warn', `prism transcoder error - ${e}`);
|
112
|
+
});
|
113
|
+
return this.playPCMStream(transcoder.output, options, true);
|
114
|
+
}
|
115
|
+
|
116
|
+
playPCMStream(stream, options = {}, fromUnknown = false) {
|
117
|
+
this.destroy();
|
118
|
+
this.opusEncoder = OpusEncoders.fetch(options);
|
119
|
+
this.setBitrate(options.bitrate);
|
120
|
+
const dispatcher = this.createDispatcher(stream, options);
|
121
|
+
if (fromUnknown) {
|
122
|
+
this.currentStream.dispatcher = dispatcher;
|
123
|
+
} else {
|
124
|
+
this.destroyCurrentStream();
|
125
|
+
this.currentStream = {
|
126
|
+
dispatcher,
|
127
|
+
input: stream,
|
128
|
+
output: stream,
|
129
|
+
};
|
130
|
+
}
|
131
|
+
return dispatcher;
|
132
|
+
}
|
133
|
+
|
134
|
+
playOpusStream(stream, options = {}) {
|
135
|
+
options.opus = true;
|
136
|
+
this.destroyCurrentStream();
|
137
|
+
const dispatcher = this.createDispatcher(stream, options);
|
138
|
+
this.currentStream = {
|
139
|
+
dispatcher,
|
140
|
+
input: stream,
|
141
|
+
output: stream,
|
142
|
+
};
|
143
|
+
return dispatcher;
|
144
|
+
}
|
145
|
+
|
146
|
+
playBroadcast(broadcast, options) {
|
147
|
+
this.destroyCurrentStream();
|
148
|
+
const dispatcher = this.createDispatcher(broadcast, options);
|
149
|
+
this.currentStream = {
|
150
|
+
dispatcher,
|
151
|
+
broadcast,
|
152
|
+
input: broadcast,
|
153
|
+
output: broadcast,
|
154
|
+
};
|
155
|
+
broadcast.registerDispatcher(dispatcher);
|
156
|
+
return dispatcher;
|
157
|
+
}
|
158
|
+
|
159
|
+
createDispatcher(stream, { seek = 0, volume = 1, passes = 1, opus } = {}) {
|
160
|
+
const options = { seek, volume, passes, opus };
|
161
|
+
|
162
|
+
const dispatcher = new StreamDispatcher(this, stream, options);
|
163
|
+
dispatcher.on('end', () => this.destroyCurrentStream());
|
164
|
+
dispatcher.on('error', () => this.destroyCurrentStream());
|
165
|
+
dispatcher.on('speaking', value => this.voiceConnection.setSpeaking(value));
|
166
|
+
return dispatcher;
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
module.exports = AudioPlayer;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
const Readable = require('stream').Readable;
|
2
|
+
|
3
|
+
class VoiceReadable extends Readable {
|
4
|
+
constructor() {
|
5
|
+
super();
|
6
|
+
this._packets = [];
|
7
|
+
this.open = true;
|
8
|
+
}
|
9
|
+
|
10
|
+
_read() {} // eslint-disable-line no-empty-function
|
11
|
+
|
12
|
+
_push(d) {
|
13
|
+
if (this.open) this.push(d);
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
module.exports = VoiceReadable;
|
@@ -0,0 +1,219 @@
|
|
1
|
+
const EventEmitter = require('events').EventEmitter;
|
2
|
+
const secretbox = require('../util/Secretbox');
|
3
|
+
const Readable = require('./VoiceReadable');
|
4
|
+
const OpusEncoders = require('../opus/OpusEngineList');
|
5
|
+
|
6
|
+
const nonce = Buffer.alloc(24);
|
7
|
+
nonce.fill(0);
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Receives voice data from a voice connection.
|
11
|
+
* ```js
|
12
|
+
* // Obtained using:
|
13
|
+
* voiceChannel.join()
|
14
|
+
* .then(connection => {
|
15
|
+
* const receiver = connection.createReceiver();
|
16
|
+
* });
|
17
|
+
* ```
|
18
|
+
* @extends {EventEmitter}
|
19
|
+
*/
|
20
|
+
class VoiceReceiver extends EventEmitter {
|
21
|
+
constructor(connection) {
|
22
|
+
super();
|
23
|
+
/*
|
24
|
+
Need a queue because we don't get the ssrc of the user speaking until after the first few packets,
|
25
|
+
so we queue up unknown SSRCs until they become known, then empty the queue
|
26
|
+
*/
|
27
|
+
this.queues = new Map();
|
28
|
+
this.pcmStreams = new Map();
|
29
|
+
this.opusStreams = new Map();
|
30
|
+
this.opusEncoders = new Map();
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Whether or not this receiver has been destroyed
|
34
|
+
* @type {boolean}
|
35
|
+
*/
|
36
|
+
this.destroyed = false;
|
37
|
+
|
38
|
+
/**
|
39
|
+
* The VoiceConnection that instantiated this
|
40
|
+
* @type {VoiceConnection}
|
41
|
+
*/
|
42
|
+
this.voiceConnection = connection;
|
43
|
+
|
44
|
+
this._listener = msg => {
|
45
|
+
const ssrc = +msg.readUInt32BE(8).toString(10);
|
46
|
+
const user = this.voiceConnection.ssrcMap.get(ssrc);
|
47
|
+
if (!user) {
|
48
|
+
if (!this.queues.has(ssrc)) this.queues.set(ssrc, []);
|
49
|
+
this.queues.get(ssrc).push(msg);
|
50
|
+
} else {
|
51
|
+
if (this.queues.get(ssrc)) {
|
52
|
+
this.queues.get(ssrc).push(msg);
|
53
|
+
this.queues.get(ssrc).map(m => this.handlePacket(m, user));
|
54
|
+
this.queues.delete(ssrc);
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
this.handlePacket(msg, user);
|
58
|
+
}
|
59
|
+
};
|
60
|
+
this.voiceConnection.sockets.udp.socket.on('message', this._listener);
|
61
|
+
}
|
62
|
+
|
63
|
+
/**
|
64
|
+
* If this VoiceReceiver has been destroyed, running `recreate()` will recreate the listener.
|
65
|
+
* This avoids you having to create a new receiver.
|
66
|
+
* <info>Any streams that you had prior to destroying the receiver will not be recreated.</info>
|
67
|
+
*/
|
68
|
+
recreate() {
|
69
|
+
if (!this.destroyed) return;
|
70
|
+
this.voiceConnection.sockets.udp.socket.on('message', this._listener);
|
71
|
+
this.destroyed = false;
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Destroy this VoiceReceiver, also ending any streams that it may be controlling.
|
76
|
+
*/
|
77
|
+
destroy() {
|
78
|
+
this.voiceConnection.sockets.udp.socket.removeListener('message', this._listener);
|
79
|
+
for (const [id, stream] of this.pcmStreams) {
|
80
|
+
stream._push(null);
|
81
|
+
this.pcmStreams.delete(id);
|
82
|
+
}
|
83
|
+
for (const [id, stream] of this.opusStreams) {
|
84
|
+
stream._push(null);
|
85
|
+
this.opusStreams.delete(id);
|
86
|
+
}
|
87
|
+
for (const [id, encoder] of this.opusEncoders) {
|
88
|
+
encoder.destroy();
|
89
|
+
this.opusEncoders.delete(id);
|
90
|
+
}
|
91
|
+
this.destroyed = true;
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Invoked when a user stops speaking.
|
96
|
+
* @param {User} user The user that stopped speaking
|
97
|
+
* @private
|
98
|
+
*/
|
99
|
+
stoppedSpeaking(user) {
|
100
|
+
const opusStream = this.opusStreams.get(user.id);
|
101
|
+
const pcmStream = this.pcmStreams.get(user.id);
|
102
|
+
const opusEncoder = this.opusEncoders.get(user.id);
|
103
|
+
if (opusStream) {
|
104
|
+
opusStream.push(null);
|
105
|
+
opusStream.open = false;
|
106
|
+
this.opusStreams.delete(user.id);
|
107
|
+
}
|
108
|
+
if (pcmStream) {
|
109
|
+
pcmStream.push(null);
|
110
|
+
pcmStream.open = false;
|
111
|
+
this.pcmStreams.delete(user.id);
|
112
|
+
}
|
113
|
+
if (opusEncoder) {
|
114
|
+
opusEncoder.destroy();
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Creates a readable stream for a user that provides opus data while the user is speaking. When the user
|
120
|
+
* stops speaking, the stream is destroyed.
|
121
|
+
* @param {UserResolvable} user The user to create the stream for
|
122
|
+
* @returns {ReadableStream}
|
123
|
+
*/
|
124
|
+
createOpusStream(user) {
|
125
|
+
user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user);
|
126
|
+
if (!user) throw new Error('Couldn\'t resolve the user to create Opus stream.');
|
127
|
+
if (this.opusStreams.get(user.id)) throw new Error('There is already an existing stream for that user.');
|
128
|
+
const stream = new Readable();
|
129
|
+
this.opusStreams.set(user.id, stream);
|
130
|
+
return stream;
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* Creates a readable stream for a user that provides PCM data while the user is speaking. When the user
|
135
|
+
* stops speaking, the stream is destroyed. The stream is 32-bit signed stereo PCM at 48KHz.
|
136
|
+
* @param {UserResolvable} user The user to create the stream for
|
137
|
+
* @returns {ReadableStream}
|
138
|
+
*/
|
139
|
+
createPCMStream(user) {
|
140
|
+
user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user);
|
141
|
+
if (!user) throw new Error('Couldn\'t resolve the user to create PCM stream.');
|
142
|
+
if (this.pcmStreams.get(user.id)) throw new Error('There is already an existing stream for that user.');
|
143
|
+
const stream = new Readable();
|
144
|
+
this.pcmStreams.set(user.id, stream);
|
145
|
+
return stream;
|
146
|
+
}
|
147
|
+
|
148
|
+
handlePacket(msg, user) {
|
149
|
+
msg.copy(nonce, 0, 0, 12);
|
150
|
+
let data = secretbox.methods.open(msg.slice(12), nonce, this.voiceConnection.authentication.secretKey.key);
|
151
|
+
if (!data) {
|
152
|
+
/**
|
153
|
+
* Emitted whenever a voice packet experiences a problem.
|
154
|
+
* @event VoiceReceiver#warn
|
155
|
+
* @param {string} reason The reason for the warning. If it happened because the voice packet could not be
|
156
|
+
* decrypted, this would be `decrypt`. If it happened because the voice packet could not be decoded into
|
157
|
+
* PCM, this would be `decode`
|
158
|
+
* @param {string} message The warning message
|
159
|
+
*/
|
160
|
+
this.emit('warn', 'decrypt', 'Failed to decrypt voice packet');
|
161
|
+
return;
|
162
|
+
}
|
163
|
+
data = Buffer.from(data);
|
164
|
+
|
165
|
+
// Strip RTP Header Extensions (one-byte only)
|
166
|
+
if (data[0] === 0xBE && data[1] === 0xDE && data.length > 4) {
|
167
|
+
const headerExtensionLength = data.readUInt16BE(2);
|
168
|
+
let offset = 4;
|
169
|
+
for (let i = 0; i < headerExtensionLength; i++) {
|
170
|
+
const byte = data[offset];
|
171
|
+
offset++;
|
172
|
+
if (byte === 0) {
|
173
|
+
continue;
|
174
|
+
}
|
175
|
+
offset += 1 + (0b1111 & (byte >> 4));
|
176
|
+
}
|
177
|
+
while (data[offset] === 0) {
|
178
|
+
offset++;
|
179
|
+
}
|
180
|
+
data = data.slice(offset);
|
181
|
+
}
|
182
|
+
|
183
|
+
if (this.opusStreams.get(user.id)) this.opusStreams.get(user.id)._push(data);
|
184
|
+
/**
|
185
|
+
* Emitted whenever voice data is received from the voice connection. This is _always_ emitted (unlike PCM).
|
186
|
+
* @event VoiceReceiver#opus
|
187
|
+
* @param {User} user The user that is sending the buffer (is speaking)
|
188
|
+
* @param {Buffer} buffer The opus buffer
|
189
|
+
*/
|
190
|
+
this.emit('opus', user, data);
|
191
|
+
if (this.listenerCount('pcm') > 0 || this.pcmStreams.size > 0) {
|
192
|
+
if (!this.opusEncoders.get(user.id)) this.opusEncoders.set(user.id, OpusEncoders.fetch());
|
193
|
+
const { pcm, error } = VoiceReceiver._tryDecode(this.opusEncoders.get(user.id), data);
|
194
|
+
if (error) {
|
195
|
+
this.emit('warn', 'decode', `Failed to decode packet voice to PCM because: ${error.message}`);
|
196
|
+
return;
|
197
|
+
}
|
198
|
+
if (this.pcmStreams.get(user.id)) this.pcmStreams.get(user.id)._push(pcm);
|
199
|
+
/**
|
200
|
+
* Emits decoded voice data when it's received. For performance reasons, the decoding will only
|
201
|
+
* happen if there is at least one `pcm` listener on this receiver.
|
202
|
+
* @event VoiceReceiver#pcm
|
203
|
+
* @param {User} user The user that is sending the buffer (is speaking)
|
204
|
+
* @param {Buffer} buffer The decoded buffer
|
205
|
+
*/
|
206
|
+
this.emit('pcm', user, pcm);
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
static _tryDecode(encoder, data) {
|
211
|
+
try {
|
212
|
+
return { pcm: encoder.decode(data) };
|
213
|
+
} catch (error) {
|
214
|
+
return { error };
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
module.exports = VoiceReceiver;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/**
|
2
|
+
* Represents a Secret Key used in encryption over voice.
|
3
|
+
* @private
|
4
|
+
*/
|
5
|
+
class SecretKey {
|
6
|
+
constructor(key) {
|
7
|
+
/**
|
8
|
+
* The key used for encryption
|
9
|
+
* @type {Uint8Array}
|
10
|
+
*/
|
11
|
+
this.key = new Uint8Array(new ArrayBuffer(key.length));
|
12
|
+
for (const index in key) this.key[index] = key[index];
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
module.exports = SecretKey;
|
@@ -0,0 +1,33 @@
|
|
1
|
+
const libs = {
|
2
|
+
sodium: sodium => ({
|
3
|
+
open: sodium.api.crypto_secretbox_open_easy,
|
4
|
+
close: sodium.api.crypto_secretbox_easy,
|
5
|
+
}),
|
6
|
+
'libsodium-wrappers': sodium => ({
|
7
|
+
open: sodium.crypto_secretbox_open_easy,
|
8
|
+
close: sodium.crypto_secretbox_easy,
|
9
|
+
}),
|
10
|
+
tweetnacl: tweetnacl => ({
|
11
|
+
open: tweetnacl.secretbox.open,
|
12
|
+
close: tweetnacl.secretbox,
|
13
|
+
}),
|
14
|
+
};
|
15
|
+
|
16
|
+
exports.methods = {};
|
17
|
+
|
18
|
+
for (const libName of Object.keys(libs)) {
|
19
|
+
try {
|
20
|
+
const lib = require(libName);
|
21
|
+
if (libName === 'libsodium-wrappers' && lib.ready) {
|
22
|
+
lib.ready.then(() => {
|
23
|
+
exports.methods = libs[libName](lib);
|
24
|
+
}).catch(() => {
|
25
|
+
const tweetnacl = require('tweetnacl');
|
26
|
+
exports.methods = libs.tweetnacl(tweetnacl);
|
27
|
+
}).catch(() => undefined);
|
28
|
+
} else {
|
29
|
+
exports.methods = libs[libName](lib);
|
30
|
+
}
|
31
|
+
break;
|
32
|
+
} catch (err) {} // eslint-disable-line no-empty
|
33
|
+
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
const EventEmitter = require('events');
|
2
|
+
|
3
|
+
/**
|
4
|
+
* An interface class for volume transformation.
|
5
|
+
* @extends {EventEmitter}
|
6
|
+
*/
|
7
|
+
class VolumeInterface extends EventEmitter {
|
8
|
+
constructor({ volume = 1 } = {}) {
|
9
|
+
super();
|
10
|
+
this.setVolume(volume);
|
11
|
+
}
|
12
|
+
|
13
|
+
/**
|
14
|
+
* The current volume of the broadcast
|
15
|
+
* @readonly
|
16
|
+
* @type {number}
|
17
|
+
*/
|
18
|
+
get volume() {
|
19
|
+
return this._volume;
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* The current volume of the broadcast in decibels
|
24
|
+
* @readonly
|
25
|
+
* @type {number}
|
26
|
+
*/
|
27
|
+
get volumeDecibels() {
|
28
|
+
return Math.log10(this._volume) * 20;
|
29
|
+
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* The current volume of the broadcast from a logarithmic scale
|
33
|
+
* @readonly
|
34
|
+
* @type {number}
|
35
|
+
*/
|
36
|
+
get volumeLogarithmic() {
|
37
|
+
return Math.pow(this._volume, 1 / 1.660964);
|
38
|
+
}
|
39
|
+
|
40
|
+
applyVolume(buffer, volume) {
|
41
|
+
volume = volume || this._volume;
|
42
|
+
if (volume === 1) return buffer;
|
43
|
+
|
44
|
+
const out = Buffer.alloc(buffer.length);
|
45
|
+
for (let i = 0; i < buffer.length; i += 2) {
|
46
|
+
if (i >= buffer.length - 1) break;
|
47
|
+
const uint = Math.min(32767, Math.max(-32767, Math.floor(volume * buffer.readInt16LE(i))));
|
48
|
+
out.writeInt16LE(uint, i);
|
49
|
+
}
|
50
|
+
|
51
|
+
return out;
|
52
|
+
}
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double.
|
56
|
+
* @param {number} volume The volume that you want to set
|
57
|
+
*/
|
58
|
+
setVolume(volume) {
|
59
|
+
/**
|
60
|
+
* Emitted when the volume of this interface changes.
|
61
|
+
* @event VolumeInterface#volumeChange
|
62
|
+
* @param {number} oldVolume The old volume of this interface
|
63
|
+
* @param {number} newVolume The new volume of this interface
|
64
|
+
*/
|
65
|
+
this.emit('volumeChange', this._volume, volume);
|
66
|
+
this._volume = volume;
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Set the volume in decibels.
|
71
|
+
* @param {number} db The decibels
|
72
|
+
*/
|
73
|
+
setVolumeDecibels(db) {
|
74
|
+
this.setVolume(Math.pow(10, db / 20));
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Set the volume so that a perceived value of 0.5 is half the perceived volume etc.
|
79
|
+
* @param {number} value The value for the volume
|
80
|
+
*/
|
81
|
+
setVolumeLogarithmic(value) {
|
82
|
+
this.setVolume(Math.pow(value, 1.660964));
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
module.exports = VolumeInterface;
|