rockcord 4.0.0-dev.5 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/package.json +15 -12
- package/src/client/Client.js +3 -3
- package/src/client/WebhookClient.js +1 -1
- package/src/client/actions/VoiceStateUpdate.js +2 -2
- package/src/client/voice/VoiceConnection.js +2 -2
- package/src/client/voice/dispatcher/AudioDispatcher.js +1 -1
- package/src/client/voice/dispatcher/BaseDispatcher.js +37 -12
- package/src/client/voice/dispatcher/VideoDispatcher.js +83 -0
- package/src/client/voice/networking/CongestionControl.js +84 -0
- package/src/client/voice/networking/DAVE.js +26 -1
- package/src/client/voice/networking/MediaRecovery.js +112 -9
- package/src/client/voice/player/MediaPlayer.js +18 -3
- package/src/client/voice/player/processing/AnnexBNalSplitter.js +0 -1
- package/src/client/voice/receiver/AudioJitter.js +7 -4
- package/src/client/voice/receiver/PacketHandler.js +10 -4
- package/src/client/voice/receiver/Receiver.js +5 -3
- package/src/client/voice/receiver/Recorder.js +44 -14
- package/src/client/voice/util/PlayInterface.js +2 -0
- package/src/client/voice/util/Secretbox.js +1 -1
- package/src/client/websocket/WebSocketShard.js +0 -1
- package/src/managers/ApplicationCommandManager.js +0 -2
- package/src/managers/ApplicationCommandPermissionsManager.js +0 -1
- package/src/managers/GuildChannelManager.js +2 -3
- package/src/managers/GuildMemberManager.js +0 -1
- package/src/managers/GuildTextThreadManager.js +1 -1
- package/src/managers/MessageManager.js +5 -5
- package/src/managers/RoleManager.js +1 -1
- package/src/rest/APIRequest.js +0 -1
- package/src/rest/APIRouter.js +2 -2
- package/src/rest/DiscordAPIError.js +1 -1
- package/src/rest/RequestHandler.js +3 -3
- package/src/sharding/ShardingManager.js +2 -2
- package/src/structures/ApplicationCommand.js +2 -7
- package/src/structures/BaseCommandInteraction.js +1 -2
- package/src/structures/BaseGuildTextChannel.js +1 -1
- package/src/structures/BaseGuildVoiceChannel.js +1 -1
- package/src/structures/ClientPresence.js +0 -1
- package/src/structures/ClientUser.js +13 -5
- package/src/structures/ContainerComponent.js +1 -1
- package/src/structures/DMChannel.js +1 -1
- package/src/structures/GroupDMChannel.js +4 -4
- package/src/structures/Guild.js +0 -2
- package/src/structures/GuildAuditLogs.js +4 -4
- package/src/structures/GuildScheduledEvent.js +0 -2
- package/src/structures/Interaction.js +1 -1
- package/src/structures/InteractionCollector.js +2 -2
- package/src/structures/InteractionWebhook.js +0 -1
- package/src/structures/Message.js +16 -17
- package/src/structures/MessageComponentInteraction.js +1 -1
- package/src/structures/MessageEmbed.js +0 -2
- package/src/structures/MessagePayload.js +2 -2
- package/src/structures/ModalSubmitInteraction.js +2 -2
- package/src/structures/ThreadChannel.js +3 -3
- package/src/structures/ThreadOnlyChannel.js +1 -1
- package/src/structures/WebEmbed.js +0 -1
- package/src/structures/interfaces/Application.js +2 -2
- package/src/structures/interfaces/Collector.js +1 -4
- package/src/structures/interfaces/TextBasedChannel.js +6 -6
- package/src/util/AttachmentFlags.js +0 -1
- package/src/util/Constants.js +0 -2
- package/src/util/Options.js +3 -3
- package/src/util/Util.js +1 -2
- package/typings/index.d.ts +108 -115
- package/typings/rawDataTypes.d.ts +5 -15
|
@@ -38,7 +38,20 @@ const FFMPEG_INPUT_PREFIX = [
|
|
|
38
38
|
'4294',
|
|
39
39
|
];
|
|
40
40
|
const FFMPEG_PCM_ARGUMENTS = ['-f', 's16le', '-ar', '48000', '-ac', '2'];
|
|
41
|
-
const FFMPEG_VP8_ARGUMENTS =
|
|
41
|
+
const FFMPEG_VP8_ARGUMENTS = options => [
|
|
42
|
+
'-f',
|
|
43
|
+
'ivf',
|
|
44
|
+
'-deadline',
|
|
45
|
+
'realtime',
|
|
46
|
+
'-c:v',
|
|
47
|
+
'libvpx',
|
|
48
|
+
'-g',
|
|
49
|
+
String(Math.max(1, Math.round(options?.fps || 30))),
|
|
50
|
+
'-lag-in-frames',
|
|
51
|
+
'0',
|
|
52
|
+
'-auto-alt-ref',
|
|
53
|
+
'0',
|
|
54
|
+
];
|
|
42
55
|
const FFMPEG_H264_ARGUMENTS = options => [
|
|
43
56
|
'-c:v',
|
|
44
57
|
'libx264',
|
|
@@ -209,7 +222,7 @@ class MediaPlayer extends EventEmitter {
|
|
|
209
222
|
|
|
210
223
|
// Get stream type
|
|
211
224
|
if (this.voiceConnection.videoCodec === 'VP8') {
|
|
212
|
-
args.push(...FFMPEG_VP8_ARGUMENTS);
|
|
225
|
+
args.push(...FFMPEG_VP8_ARGUMENTS(options));
|
|
213
226
|
// Remove '-speed', '5' bc bad quality
|
|
214
227
|
}
|
|
215
228
|
|
|
@@ -271,7 +284,6 @@ class MediaPlayer extends EventEmitter {
|
|
|
271
284
|
return dispatcher;
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
// eslint-disable-next-line no-unused-vars
|
|
275
287
|
playAnnexBVideo(stream, options, streams, type) {
|
|
276
288
|
this.destroyVideoDispatcher();
|
|
277
289
|
let videoStream;
|
|
@@ -310,6 +322,7 @@ class MediaPlayer extends EventEmitter {
|
|
|
310
322
|
streams,
|
|
311
323
|
options?.fps,
|
|
312
324
|
));
|
|
325
|
+
dispatcher.configureCongestion(options);
|
|
313
326
|
return dispatcher;
|
|
314
327
|
}
|
|
315
328
|
case 'H264': {
|
|
@@ -319,6 +332,7 @@ class MediaPlayer extends EventEmitter {
|
|
|
319
332
|
streams,
|
|
320
333
|
options?.fps,
|
|
321
334
|
));
|
|
335
|
+
dispatcher.configureCongestion(options);
|
|
322
336
|
return dispatcher;
|
|
323
337
|
}
|
|
324
338
|
case 'H265': {
|
|
@@ -328,6 +342,7 @@ class MediaPlayer extends EventEmitter {
|
|
|
328
342
|
streams,
|
|
329
343
|
options?.fps,
|
|
330
344
|
));
|
|
345
|
+
dispatcher.configureCongestion(options);
|
|
331
346
|
return dispatcher;
|
|
332
347
|
}
|
|
333
348
|
default: {
|
|
@@ -147,7 +147,6 @@ class AnnexBNalSplitter extends Transform {
|
|
|
147
147
|
const newData = Buffer.allocUnsafe(data.length);
|
|
148
148
|
let newLength = 0;
|
|
149
149
|
|
|
150
|
-
// eslint-disable-next-line no-constant-condition
|
|
151
150
|
while (true) {
|
|
152
151
|
const epbsPos = data.indexOf(epbPrefix);
|
|
153
152
|
if (epbsPos === -1) {
|
|
@@ -49,10 +49,13 @@ class AudioJitter {
|
|
|
49
49
|
|
|
50
50
|
_schedule() {
|
|
51
51
|
if (this.timer || !this.packets.size) return;
|
|
52
|
-
this.timer = setTimeout(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
this.timer = setTimeout(
|
|
53
|
+
() => {
|
|
54
|
+
this.timer = null;
|
|
55
|
+
this._play();
|
|
56
|
+
},
|
|
57
|
+
Math.max(0, this.next - performance.now()),
|
|
58
|
+
).unref();
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
_play() {
|
|
@@ -23,7 +23,7 @@ const UNPADDED_NONCE_LENGTH = 4;
|
|
|
23
23
|
const AUTH_TAG_LENGTH = 16;
|
|
24
24
|
|
|
25
25
|
class Readable extends require('stream').Readable {
|
|
26
|
-
_read() {}
|
|
26
|
+
_read() {}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -40,6 +40,7 @@ class PacketHandler extends EventEmitter {
|
|
|
40
40
|
this.speakingTimeouts = new Map();
|
|
41
41
|
this.videoFrames = new VideoFrames();
|
|
42
42
|
this.videoSequences = new Map();
|
|
43
|
+
this.videoPictures = new Map();
|
|
43
44
|
this.audioJitters = new Map();
|
|
44
45
|
this.recovery = this.connection._mediaRecovery = new MediaRecovery(this.connection);
|
|
45
46
|
}
|
|
@@ -74,9 +75,10 @@ class PacketHandler extends EventEmitter {
|
|
|
74
75
|
return stream;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
makeVideoStream(user, output) {
|
|
78
|
+
makeVideoStream(user, output, options = {}) {
|
|
78
79
|
if (this.videoStreams.has(user)) return this.videoStreams.get(user);
|
|
79
80
|
const stream = new Recorder(this, {
|
|
81
|
+
...options,
|
|
80
82
|
userId: user,
|
|
81
83
|
output,
|
|
82
84
|
});
|
|
@@ -251,7 +253,9 @@ class PacketHandler extends EventEmitter {
|
|
|
251
253
|
if (!frame) return;
|
|
252
254
|
this.receiver.emit('videoFrame', userStat, plain, codec);
|
|
253
255
|
if (streamInfo) {
|
|
254
|
-
const
|
|
256
|
+
const picture = this.videoPictures.get(ssrc) ?? 0;
|
|
257
|
+
const payloads = packetize(plain, codec, 1200, picture);
|
|
258
|
+
this.videoPictures.set(ssrc, (picture + 1) & 0x7fff);
|
|
255
259
|
let sequence = this.videoSequences.get(ssrc) ?? packet.header.sequenceNumber;
|
|
256
260
|
for (let i = 0; i < payloads.length; i++) {
|
|
257
261
|
streamInfo.feed(
|
|
@@ -307,7 +311,7 @@ class PacketHandler extends EventEmitter {
|
|
|
307
311
|
packet.header.sequenceNumber = packet.payload.readUInt16BE();
|
|
308
312
|
packet.header.payloadType = codec.payload_type;
|
|
309
313
|
packet.payload = packet.payload.subarray(2);
|
|
310
|
-
this.recovery.receive(packet);
|
|
314
|
+
this.recovery.receive(packet, true);
|
|
311
315
|
this.videoReceiver(userStat.primarySsrc, userStat, packet);
|
|
312
316
|
} else if (userStat.kind === 'video') {
|
|
313
317
|
this.recovery.receive(packet);
|
|
@@ -350,6 +354,7 @@ class PacketHandler extends EventEmitter {
|
|
|
350
354
|
this.audioJitters.delete(ssrc);
|
|
351
355
|
this.recovery.sources.delete(ssrc);
|
|
352
356
|
this.videoSequences.delete(ssrc);
|
|
357
|
+
this.videoPictures.delete(ssrc);
|
|
353
358
|
clearTimeout(this.speakingTimeouts.get(ssrc));
|
|
354
359
|
this.speakingTimeouts.delete(ssrc);
|
|
355
360
|
for (const key of this.videoFrames.frames.keys()) {
|
|
@@ -366,6 +371,7 @@ class PacketHandler extends EventEmitter {
|
|
|
366
371
|
this.videoFrames.frames.clear();
|
|
367
372
|
this.videoFrames.completed.clear();
|
|
368
373
|
this.videoSequences.clear();
|
|
374
|
+
this.videoPictures.clear();
|
|
369
375
|
for (const stream of this.streams.values()) {
|
|
370
376
|
stream.stream.destroy();
|
|
371
377
|
}
|
|
@@ -92,16 +92,18 @@ class VoiceReceiver extends EventEmitter {
|
|
|
92
92
|
/**
|
|
93
93
|
* Creates a new video receiving stream. If a stream already exists for a user, then that stream will be returned
|
|
94
94
|
* rather than generating a new one.
|
|
95
|
-
* Records received H264 video and Opus audio using FFmpeg.
|
|
95
|
+
* Records received H264, H265 or VP8 video and Opus audio using FFmpeg.
|
|
96
96
|
* Wait for `ready` before sending media and await `stop()` to finalize the output.
|
|
97
97
|
* @param {UserResolvable} user The user to start listening to.
|
|
98
98
|
* @param {WritableStream|string} output Output stream or file path to write the video stream to.
|
|
99
|
+
* @param {Object} [options] Recording options
|
|
100
|
+
* @param {VideoCodec} [options.codec='H264'] The codec sent by the recorded participant
|
|
99
101
|
* @returns {Recorder} The video stream for the specified user.
|
|
100
102
|
*/
|
|
101
|
-
createVideoStream(user, output) {
|
|
103
|
+
createVideoStream(user, output, options = {}) {
|
|
102
104
|
user = this.connection.client.users.resolve(user);
|
|
103
105
|
if (!user) throw new Error('VOICE_USER_MISSING');
|
|
104
|
-
const stream = this.packets.makeVideoStream(user.id, output);
|
|
106
|
+
const stream = this.packets.makeVideoStream(user.id, output, options);
|
|
105
107
|
this._updateVideoSubscriptions();
|
|
106
108
|
stream.once('closed', () => this._updateVideoSubscriptions());
|
|
107
109
|
return stream;
|
|
@@ -21,9 +21,12 @@ const { StreamOutput } = require('../util/Socket');
|
|
|
21
21
|
* @extends {EventEmitter}
|
|
22
22
|
*/
|
|
23
23
|
class Recorder extends EventEmitter {
|
|
24
|
-
constructor(receiver, { userId, portUdpH264, portUdpOpus, output } = {}) {
|
|
24
|
+
constructor(receiver, { userId, codec = 'H264', portUdpVideo, portUdpH264, portUdpOpus, output } = {}) {
|
|
25
25
|
super();
|
|
26
26
|
|
|
27
|
+
if (!['H264', 'H265', 'VP8'].includes(codec)) throw new RangeError('Unsupported recording codec');
|
|
28
|
+
this.codec = codec;
|
|
29
|
+
|
|
27
30
|
Object.defineProperty(this, 'receiver', { value: receiver });
|
|
28
31
|
|
|
29
32
|
/**
|
|
@@ -32,16 +35,15 @@ class Recorder extends EventEmitter {
|
|
|
32
35
|
*/
|
|
33
36
|
this.userId = userId;
|
|
34
37
|
|
|
35
|
-
this.
|
|
36
|
-
this.portUdpH265 = null;
|
|
38
|
+
this.portUdpVideo = (portUdpVideo ?? portUdpH264) || null;
|
|
37
39
|
this.portUdpOpus = portUdpOpus || null;
|
|
38
40
|
|
|
39
41
|
this.promise = null;
|
|
40
42
|
|
|
41
|
-
if (!
|
|
43
|
+
if (!this.portUdpVideo || !portUdpOpus) {
|
|
42
44
|
this.promise = (async () => {
|
|
43
|
-
const selected = new Set([this.
|
|
44
|
-
const missing = Number(!this.
|
|
45
|
+
const selected = new Set([this.portUdpVideo, this.portUdpOpus].filter(Boolean));
|
|
46
|
+
const missing = Number(!this.portUdpVideo) + Number(!this.portUdpOpus);
|
|
45
47
|
const ports = [];
|
|
46
48
|
for (let attempt = 0; ports.length < missing && attempt < 64; attempt++) {
|
|
47
49
|
const port = await randomPort('udp4');
|
|
@@ -51,7 +53,7 @@ class Recorder extends EventEmitter {
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
if (ports.length < missing) throw new Error('Could not allocate recording ports');
|
|
54
|
-
this.
|
|
56
|
+
this.portUdpVideo ??= ports.shift();
|
|
55
57
|
this.portUdpOpus ??= ports.shift();
|
|
56
58
|
})();
|
|
57
59
|
}
|
|
@@ -88,10 +90,25 @@ class Recorder extends EventEmitter {
|
|
|
88
90
|
if (this.destroyed) return;
|
|
89
91
|
// Keep stdin available for FFmpeg's graceful quit command. Reading SDP from
|
|
90
92
|
// stdin and closing it leaves no way to write the Matroska trailer on Windows.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
this.portUdpH264 = this.codec === 'H264' ? this.portUdpVideo : null;
|
|
94
|
+
this.portUdpH265 = this.codec === 'H265' ? this.portUdpVideo : null;
|
|
95
|
+
this.portUdpVP8 = this.codec === 'VP8' ? this.portUdpVideo : null;
|
|
96
|
+
const videoType = Util.getPayloadType(this.codec);
|
|
97
|
+
const opusType = Util.getPayloadType('opus');
|
|
98
|
+
const sdpData = [
|
|
99
|
+
'v=0',
|
|
100
|
+
'o=- 0 0 IN IP4 127.0.0.1',
|
|
101
|
+
's=Rockcord recording',
|
|
102
|
+
'c=IN IP4 127.0.0.1',
|
|
103
|
+
't=0 0',
|
|
104
|
+
`m=video ${this.portUdpVideo} RTP/AVP ${videoType}`,
|
|
105
|
+
`a=rtpmap:${videoType} ${this.codec}/90000`,
|
|
106
|
+
...(this.codec === 'H264' ? [`a=fmtp:${videoType} packetization-mode=1`] : []),
|
|
107
|
+
`m=audio ${this.portUdpOpus} RTP/AVP ${opusType}`,
|
|
108
|
+
`a=rtpmap:${opusType} opus/48000/2`,
|
|
109
|
+
`a=fmtp:${opusType} minptime=10;useinbandfec=1`,
|
|
93
110
|
'',
|
|
94
|
-
);
|
|
111
|
+
].join('\r\n');
|
|
95
112
|
this._directory = fs.mkdtempSync(path.join(os.tmpdir(), 'rockcord-recorder-'));
|
|
96
113
|
this._sdpPath = path.join(this._directory, 'input.sdp');
|
|
97
114
|
fs.writeFileSync(this._sdpPath, sdpData);
|
|
@@ -176,7 +193,7 @@ class Recorder extends EventEmitter {
|
|
|
176
193
|
probe.bind(port, '127.0.0.1', () => probe.close(() => resolve(false)));
|
|
177
194
|
});
|
|
178
195
|
for (let attempt = 0; attempt < 120 && !this.destroyed; attempt++) {
|
|
179
|
-
const ports = await Promise.all([bound(this.
|
|
196
|
+
const ports = await Promise.all([bound(this.portUdpVideo), bound(this.portUdpOpus)]);
|
|
180
197
|
if (ports.every(Boolean)) {
|
|
181
198
|
this.ready = true;
|
|
182
199
|
this.emit('ready', this);
|
|
@@ -206,15 +223,28 @@ class Recorder extends EventEmitter {
|
|
|
206
223
|
const message = payload.serialize();
|
|
207
224
|
// Get port from payloadType
|
|
208
225
|
let port;
|
|
226
|
+
let kind;
|
|
209
227
|
if (payload.header.payloadType === Util.getPayloadType('opus')) {
|
|
228
|
+
kind = 'audio';
|
|
210
229
|
port = this.portUdpOpus;
|
|
211
230
|
this._lastAudioHeader = payload.header;
|
|
212
|
-
} else if (payload.header.payloadType === Util.getPayloadType(
|
|
213
|
-
|
|
231
|
+
} else if (payload.header.payloadType === Util.getPayloadType(this.codec)) {
|
|
232
|
+
kind = 'video';
|
|
233
|
+
port = this.portUdpVideo;
|
|
214
234
|
this._lastVideoHeader = payload.header;
|
|
215
235
|
} else {
|
|
216
236
|
return;
|
|
217
237
|
}
|
|
238
|
+
// FFmpeg's RTP probation starts at sequence zero. Normalize each new local
|
|
239
|
+
// source to one so the first keyframe's leading fragment is not discarded.
|
|
240
|
+
// Keep subsequent gaps and wrap intact, and never mutate the caller's packet.
|
|
241
|
+
const sourceKey = `_${kind}SequenceSource`;
|
|
242
|
+
const offsetKey = `_${kind}SequenceOffset`;
|
|
243
|
+
if (this[sourceKey] !== payload.header.ssrc) {
|
|
244
|
+
this[sourceKey] = payload.header.ssrc;
|
|
245
|
+
this[offsetKey] = 1 - payload.header.sequenceNumber;
|
|
246
|
+
}
|
|
247
|
+
message.writeUInt16BE((payload.header.sequenceNumber + this[offsetKey]) & 65535, 2);
|
|
218
248
|
this.socket.send(message, 0, message.length, port, '127.0.0.1', callback);
|
|
219
249
|
}
|
|
220
250
|
|
|
@@ -227,7 +257,7 @@ class Recorder extends EventEmitter {
|
|
|
227
257
|
// RTCP BYE ends both local RTP inputs. Unlike an immediate quit, EOF lets
|
|
228
258
|
// FFmpeg drain its parser and muxer, preserving the final buffered frame.
|
|
229
259
|
for (const [port, ssrc] of [
|
|
230
|
-
[this.
|
|
260
|
+
[this.portUdpVideo, this._lastVideoHeader?.ssrc ?? 1],
|
|
231
261
|
[this.portUdpOpus, this._lastAudioHeader?.ssrc ?? 2],
|
|
232
262
|
]) {
|
|
233
263
|
// Include an empty, padded reason: FFmpeg's RTP demuxer requires at
|
|
@@ -91,6 +91,8 @@ class PlayInterface {
|
|
|
91
91
|
* Ex: ['-config1', 'value1', '-config2', 'value2']
|
|
92
92
|
* @property {number|'auto'} [bitrate=2000] The bitrate (quality) of the video in kbps.
|
|
93
93
|
* If set to 'auto', ffmpeg will automatically select
|
|
94
|
+
* @property {boolean|Object} [congestionControl=true] Enable loss-based AIMD video pacing.
|
|
95
|
+
* An object can set bitrate and minBitrate in kbps. Audio playback is unaffected.
|
|
94
96
|
*/
|
|
95
97
|
|
|
96
98
|
/**
|
|
@@ -56,7 +56,7 @@ async function importModule(name, usingImport = false) {
|
|
|
56
56
|
for (const libName of Object.keys(libs)) {
|
|
57
57
|
try {
|
|
58
58
|
const lib = await importModule(libName);
|
|
59
|
-
if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready;
|
|
59
|
+
if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready;
|
|
60
60
|
exports.methods = libs[libName](lib);
|
|
61
61
|
break;
|
|
62
62
|
} catch {} // eslint-disable-line no-empty
|
|
@@ -605,7 +605,6 @@ class WebSocketShard extends EventEmitter {
|
|
|
605
605
|
}
|
|
606
606
|
|
|
607
607
|
this.debug(
|
|
608
|
-
// eslint-disable-next-line max-len
|
|
609
608
|
`[WebSocket] Close Emitted: ${this.closeEmitted} | did not close properly, assuming a zombie connection.\nEmitting close and reconnecting again.`,
|
|
610
609
|
);
|
|
611
610
|
|
|
@@ -55,12 +55,10 @@ class ApplicationCommandManager extends CachedManager {
|
|
|
55
55
|
* @typedef {ApplicationCommand|Snowflake} ApplicationCommandResolvable
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
|
-
/* eslint-disable max-len */
|
|
59
58
|
/**
|
|
60
59
|
* Data that resolves to the data of an ApplicationCommand
|
|
61
60
|
* @typedef {ApplicationCommandData|APIApplicationCommand|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
|
|
62
61
|
*/
|
|
63
|
-
/* eslint-enable max-len */
|
|
64
62
|
|
|
65
63
|
/**
|
|
66
64
|
* Options used to fetch data from Discord
|
|
@@ -410,7 +410,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
|
|
410
410
|
|
|
411
411
|
module.exports = ApplicationCommandPermissionsManager;
|
|
412
412
|
|
|
413
|
-
/* eslint-disable max-len */
|
|
414
413
|
/**
|
|
415
414
|
* @external APIApplicationCommandPermissions
|
|
416
415
|
* @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure}
|
|
@@ -58,7 +58,7 @@ class GuildChannelManager extends CachedManager {
|
|
|
58
58
|
get channelCountWithoutThreads() {
|
|
59
59
|
return this.cache.reduce((acc, channel) => {
|
|
60
60
|
if (ThreadChannelTypes.includes(channel.type)) return acc;
|
|
61
|
-
return
|
|
61
|
+
return acc + 1;
|
|
62
62
|
}, 0);
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -155,7 +155,7 @@ class GuildChannelManager extends CachedManager {
|
|
|
155
155
|
) {
|
|
156
156
|
parent &&= this.client.channels.resolveId(parent);
|
|
157
157
|
permissionOverwrites &&= permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild));
|
|
158
|
-
const intType = typeof type === 'number' ? type : ChannelTypes[type] ?? ChannelTypes.GUILD_TEXT;
|
|
158
|
+
const intType = typeof type === 'number' ? type : (ChannelTypes[type] ?? ChannelTypes.GUILD_TEXT);
|
|
159
159
|
|
|
160
160
|
const videoMode = typeof videoQualityMode === 'number' ? videoQualityMode : VideoQualityModes[videoQualityMode];
|
|
161
161
|
|
|
@@ -167,7 +167,6 @@ class GuildChannelManager extends CachedManager {
|
|
|
167
167
|
if (intType === ChannelTypes.GUILD_STORE && !storeChannelDeprecationEmitted) {
|
|
168
168
|
storeChannelDeprecationEmitted = true;
|
|
169
169
|
process.emitWarning(
|
|
170
|
-
// eslint-disable-next-line max-len
|
|
171
170
|
'Creating store channels is deprecated by Discord and will stop working in March 2022. Check the docs for more info.',
|
|
172
171
|
'DeprecationWarning',
|
|
173
172
|
);
|
|
@@ -75,7 +75,7 @@ class GuildTextThreadManager extends ThreadManager {
|
|
|
75
75
|
if (!startMessageId) throw new TypeError('INVALID_TYPE', 'startMessage', 'MessageResolvable');
|
|
76
76
|
path = path.messages(startMessageId);
|
|
77
77
|
} else if (this.channel.type !== 'GUILD_NEWS') {
|
|
78
|
-
resolvedType = typeof type === 'string' ? ChannelTypes[type] : type ?? resolvedType;
|
|
78
|
+
resolvedType = typeof type === 'string' ? ChannelTypes[type] : (type ?? resolvedType);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
|
|
@@ -139,16 +139,17 @@ class MessageManager extends CachedManager {
|
|
|
139
139
|
|
|
140
140
|
const preserveAttachments =
|
|
141
141
|
(options instanceof MessagePayload ? options.options : options)?.attachments === undefined;
|
|
142
|
-
const { data, files } = await (
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
const { data, files } = await (
|
|
143
|
+
options instanceof MessagePayload
|
|
144
|
+
? options
|
|
145
|
+
: MessagePayload.create(message instanceof Message ? message : this, options)
|
|
145
146
|
)
|
|
146
147
|
.resolveData()
|
|
147
148
|
.resolveFiles();
|
|
148
149
|
|
|
149
150
|
if (files.length && preserveAttachments) {
|
|
150
151
|
const original =
|
|
151
|
-
message instanceof Message ? message : this.cache.get(messageId) ?? (await this.fetch(messageId));
|
|
152
|
+
message instanceof Message ? message : (this.cache.get(messageId) ?? (await this.fetch(messageId)));
|
|
152
153
|
data.attachments = [...original.attachments.values()].map(attachment => ({ id: attachment.id }));
|
|
153
154
|
}
|
|
154
155
|
data.attachments = await Util.resolveUploadedAttachments(this.client, this.channel.id, files, data.attachments);
|
|
@@ -221,7 +222,6 @@ class MessageManager extends CachedManager {
|
|
|
221
222
|
? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}`
|
|
222
223
|
: encodeURIComponent(emoji.name);
|
|
223
224
|
|
|
224
|
-
// eslint-disable-next-line newline-per-chained-call
|
|
225
225
|
await this.client.api
|
|
226
226
|
.channels(this.channel.id)
|
|
227
227
|
.messages(message)
|
|
@@ -72,7 +72,7 @@ class RoleManager extends CachedManager {
|
|
|
72
72
|
const data = await this.client.api.guilds(this.guild.id).roles.get();
|
|
73
73
|
const roles = new Collection();
|
|
74
74
|
for (const role of data) roles.set(role.id, this._add(role, cache));
|
|
75
|
-
return id ? roles.get(id) ?? null : roles;
|
|
75
|
+
return id ? (roles.get(id) ?? null) : roles;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
package/src/rest/APIRequest.js
CHANGED
package/src/rest/APIRouter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const noop = () => {};
|
|
3
|
+
const noop = () => {};
|
|
4
4
|
const methods = ['get', 'post', 'delete', 'patch', 'put'];
|
|
5
5
|
const reflectors = [
|
|
6
6
|
'toString',
|
|
@@ -43,7 +43,7 @@ function buildRoute(manager) {
|
|
|
43
43
|
return new Proxy(noop, handler);
|
|
44
44
|
},
|
|
45
45
|
apply(target, _, args) {
|
|
46
|
-
route.push(...args.filter(x => x != null));
|
|
46
|
+
route.push(...args.filter(x => x != null));
|
|
47
47
|
return new Proxy(noop, handler);
|
|
48
48
|
},
|
|
49
49
|
};
|
|
@@ -11,7 +11,7 @@ class DiscordAPIError extends Error {
|
|
|
11
11
|
super();
|
|
12
12
|
const flattened = this.constructor.flattenErrors(error.errors ?? error).join('\n');
|
|
13
13
|
this.name = 'DiscordAPIError';
|
|
14
|
-
this.message = error.message && flattened ? `${error.message}\n${flattened}` : error.message ?? flattened;
|
|
14
|
+
this.message = error.message && flattened ? `${error.message}\n${flattened}` : (error.message ?? flattened);
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The HTTP method used for the request
|
|
@@ -154,7 +154,7 @@ class RequestHandler {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// Decide before allocating a delay: a rejected limit must not leave an unused promise/timer.
|
|
157
|
-
await this.onRateLimit(request, limit, timeout, isGlobal);
|
|
157
|
+
await this.onRateLimit(request, limit, timeout, isGlobal);
|
|
158
158
|
|
|
159
159
|
if (isGlobal) {
|
|
160
160
|
// If this is the first task to reach the global timeout, set the global delay
|
|
@@ -168,7 +168,7 @@ class RequestHandler {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
// Wait for the timeout to expire in order to avoid an actual 429
|
|
171
|
-
await delayPromise;
|
|
171
|
+
await delayPromise;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// As the request goes out, update the global usage information
|
|
@@ -378,7 +378,7 @@ class RequestHandler {
|
|
|
378
378
|
rqToken : [REDACTED]`,
|
|
379
379
|
);
|
|
380
380
|
const solved = await this.manager.client.options.captchaSolver(data, request.fullUserAgent);
|
|
381
|
-
const captcha = typeof solved === 'string' ? solved : solved?.token ?? solved?.data;
|
|
381
|
+
const captcha = typeof solved === 'string' ? solved : (solved?.token ?? solved?.data);
|
|
382
382
|
if (typeof captcha !== 'string' || !captcha) throw new Error('Captcha solver must return a non-empty token');
|
|
383
383
|
this.manager.client.emit(
|
|
384
384
|
DEBUG,
|
|
@@ -218,7 +218,7 @@ class ShardingManager extends EventEmitter {
|
|
|
218
218
|
const shard = this.createShard(shardId);
|
|
219
219
|
promises.push(shard.spawn(timeout));
|
|
220
220
|
if (delay > 0 && this.shards.size !== this.shardList.length) promises.push(sleep(delay));
|
|
221
|
-
await Promise.all(promises);
|
|
221
|
+
await Promise.all(promises);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
return this.shards;
|
|
@@ -310,7 +310,7 @@ class ShardingManager extends EventEmitter {
|
|
|
310
310
|
for (const shard of this.shards.values()) {
|
|
311
311
|
const promises = [shard.respawn({ delay: respawnDelay, timeout })];
|
|
312
312
|
if (++s < this.shards.size && shardDelay > 0) promises.push(sleep(shardDelay));
|
|
313
|
-
await Promise.all(promises);
|
|
313
|
+
await Promise.all(promises);
|
|
314
314
|
}
|
|
315
315
|
return this.shards;
|
|
316
316
|
}
|
|
@@ -121,7 +121,6 @@ class ApplicationCommand extends Base {
|
|
|
121
121
|
this.options ??= [];
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
/* eslint-disable max-len */
|
|
125
124
|
if ('default_permission' in data) {
|
|
126
125
|
/**
|
|
127
126
|
* Whether the command is enabled by default when the app is added to a guild
|
|
@@ -130,7 +129,6 @@ class ApplicationCommand extends Base {
|
|
|
130
129
|
*/
|
|
131
130
|
this.defaultPermission = data.default_permission;
|
|
132
131
|
}
|
|
133
|
-
/* eslint-disable max-len */
|
|
134
132
|
|
|
135
133
|
if ('default_member_permissions' in data) {
|
|
136
134
|
/**
|
|
@@ -307,7 +305,6 @@ class ApplicationCommand extends Base {
|
|
|
307
305
|
return this.edit({ descriptionLocalizations });
|
|
308
306
|
}
|
|
309
307
|
|
|
310
|
-
/* eslint-disable max-len */
|
|
311
308
|
/**
|
|
312
309
|
* Edits the default permission of this ApplicationCommand
|
|
313
310
|
* @param {boolean} [defaultPermission=true] The default permission for this command
|
|
@@ -317,7 +314,6 @@ class ApplicationCommand extends Base {
|
|
|
317
314
|
setDefaultPermission(defaultPermission = true) {
|
|
318
315
|
return this.edit({ defaultPermission });
|
|
319
316
|
}
|
|
320
|
-
/* eslint-enable max-len */
|
|
321
317
|
|
|
322
318
|
/**
|
|
323
319
|
* Edits the default member permissions of this ApplicationCommand
|
|
@@ -568,9 +564,9 @@ class ApplicationCommand extends Base {
|
|
|
568
564
|
options: option.options?.map(o => this.transformOption(o, received)),
|
|
569
565
|
[channelTypesKey]: received
|
|
570
566
|
? option.channel_types?.map(type => ChannelTypes[type])
|
|
571
|
-
: option.channelTypes?.map(type => (typeof type === 'string' ? ChannelTypes[type] : type)) ??
|
|
567
|
+
: (option.channelTypes?.map(type => (typeof type === 'string' ? ChannelTypes[type] : type)) ??
|
|
572
568
|
// When transforming to API data, accept API data
|
|
573
|
-
option.channel_types,
|
|
569
|
+
option.channel_types),
|
|
574
570
|
[minValueKey]: option.minValue ?? option.min_value,
|
|
575
571
|
[maxValueKey]: option.maxValue ?? option.max_value,
|
|
576
572
|
[minLengthKey]: option.minLength ?? option.min_length,
|
|
@@ -581,7 +577,6 @@ class ApplicationCommand extends Base {
|
|
|
581
577
|
|
|
582
578
|
module.exports = ApplicationCommand;
|
|
583
579
|
|
|
584
|
-
/* eslint-disable max-len */
|
|
585
580
|
/**
|
|
586
581
|
* @external APIApplicationCommand
|
|
587
582
|
* @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure}
|
|
@@ -189,7 +189,7 @@ class BaseCommandInteraction extends Interaction {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
// These are here only for documentation purposes - they are implemented by InteractionResponses
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
deferReply() {}
|
|
194
194
|
reply() {}
|
|
195
195
|
fetchReply() {}
|
|
@@ -204,7 +204,6 @@ InteractionResponses.applyToClass(BaseCommandInteraction, ['deferUpdate', 'updat
|
|
|
204
204
|
|
|
205
205
|
module.exports = BaseCommandInteraction;
|
|
206
206
|
|
|
207
|
-
/* eslint-disable max-len */
|
|
208
207
|
/**
|
|
209
208
|
* @external APIInteractionDataResolved
|
|
210
209
|
* @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure}
|
|
@@ -173,7 +173,7 @@ class BaseGuildTextChannel extends GuildChannel {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
get lastMessage() {}
|
|
178
178
|
get lastPinAt() {}
|
|
179
179
|
send() {}
|
|
@@ -224,7 +224,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
get lastMessage() {}
|
|
229
229
|
send() {}
|
|
230
230
|
sendTyping() {}
|
|
@@ -70,7 +70,6 @@ class ClientPresence extends Presence {
|
|
|
70
70
|
|
|
71
71
|
module.exports = ClientPresence;
|
|
72
72
|
|
|
73
|
-
/* eslint-disable max-len */
|
|
74
73
|
/**
|
|
75
74
|
* @external APIPresence
|
|
76
75
|
* @see {@link https://discord.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields}
|
|
@@ -15,11 +15,19 @@ const Util = require('../util/Util');
|
|
|
15
15
|
*/
|
|
16
16
|
class ClientUser extends User {
|
|
17
17
|
#packageName = null;
|
|
18
|
-
#intervalSamsungPresence = setInterval(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
#intervalSamsungPresence = setInterval(
|
|
19
|
+
() => {
|
|
20
|
+
this.client.emit('debug', `[UPDATE] Samsung Presence: ${this.#packageName}`);
|
|
21
|
+
if (!this.#packageName) return;
|
|
22
|
+
this.setSamsungActivity(this.#packageName, 'UPDATE').catch(error => this.client.emit('debug', error));
|
|
23
|
+
},
|
|
24
|
+
1000 * 60 * 10,
|
|
25
|
+
).unref();
|
|
26
|
+
|
|
27
|
+
_stopSamsungPresence() {
|
|
28
|
+
clearInterval(this.#intervalSamsungPresence);
|
|
29
|
+
this.#packageName = null;
|
|
30
|
+
}
|
|
23
31
|
|
|
24
32
|
_patch(data) {
|
|
25
33
|
super._patch(data);
|
|
@@ -49,7 +49,7 @@ class ContainerComponent extends BaseMessageComponent {
|
|
|
49
49
|
get hexAccentColor() {
|
|
50
50
|
return typeof this.accentColor === 'number'
|
|
51
51
|
? `#${this.accentColor.toString(16).padStart(6, '0')}`
|
|
52
|
-
: this.accentColor ?? null;
|
|
52
|
+
: (this.accentColor ?? null);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|