discord-sb.js 1.2.2 → 1.3.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.
Files changed (44) hide show
  1. package/package.json +13 -12
  2. package/src/client/BaseClient.js +24 -0
  3. package/src/client/actions/Action.js +39 -22
  4. package/src/client/actions/InviteCreate.js +1 -1
  5. package/src/client/actions/InviteDelete.js +1 -1
  6. package/src/client/actions/MessageCreate.js +6 -6
  7. package/src/client/actions/MessageDelete.js +3 -1
  8. package/src/client/actions/MessageDeleteBulk.js +6 -9
  9. package/src/client/actions/MessageReactionAdd.js +12 -9
  10. package/src/client/actions/MessageReactionRemove.js +3 -14
  11. package/src/client/actions/MessageReactionRemoveAll.js +8 -5
  12. package/src/client/actions/MessageReactionRemoveEmoji.js +3 -1
  13. package/src/client/actions/MessageUpdate.js +3 -1
  14. package/src/client/actions/PresenceUpdate.js +8 -3
  15. package/src/client/actions/ThreadListSync.js +6 -5
  16. package/src/client/actions/ThreadMembersUpdate.js +10 -6
  17. package/src/client/actions/TypingStart.js +3 -1
  18. package/src/client/actions/VoiceStateUpdate.js +20 -7
  19. package/src/client/voice/ClientVoiceManager.js +18 -7
  20. package/src/client/voice/StreamEventRouter.js +60 -0
  21. package/src/client/voice/VoiceConnection.js +77 -159
  22. package/src/client/voice/networking/VoiceUDPClient.js +25 -11
  23. package/src/client/voice/networking/VoiceWebSocket.js +84 -22
  24. package/src/client/websocket/DispatchTable.js +7 -0
  25. package/src/client/websocket/GatewaySendScheduler.js +107 -0
  26. package/src/client/websocket/WebSocketManager.js +24 -16
  27. package/src/client/websocket/WebSocketShard.js +161 -51
  28. package/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js +8 -2
  29. package/src/client/websocket/handlers/READY.js +30 -42
  30. package/src/client/websocket/handlers/USER_REQUIRED_ACTION_UPDATE.js +13 -9
  31. package/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js +7 -1
  32. package/src/managers/MessageManager.js +27 -23
  33. package/src/managers/QuestManager.js +7 -9
  34. package/src/rest/APIRequest.js +40 -43
  35. package/src/rest/APIRouter.js +1 -1
  36. package/src/rest/RESTManager.js +69 -2
  37. package/src/rest/RateLimitCoordinator.js +156 -0
  38. package/src/rest/RequestHandler.js +162 -91
  39. package/src/structures/GroupDMChannel.js +18 -0
  40. package/src/structures/interfaces/Collector.js +2 -1
  41. package/src/util/FastQueue.js +93 -0
  42. package/src/util/ListenerUtil.js +12 -0
  43. package/src/util/Options.js +2 -2
  44. package/typings/index.d.ts +1 -1
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "discord-sb.js",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "An unofficial discord.js fork for creating selfbots",
5
5
  "main": "./src/index.js",
6
6
  "types": "./typings/index.d.ts",
7
7
  "scripts": {
8
8
  "all": "bun run build && bun publish",
9
9
  "test": "bun run lint:all && bun run docs:test && bun run test:typescript",
10
+ "test:unit": "bun test",
10
11
  "fix:all": "bun run lint:fix && bun run lint:typings:fix && bun run format",
11
12
  "test:typescript": "tsc --noEmit && tsd",
12
13
  "lint": "eslint .",
@@ -51,35 +52,35 @@
51
52
  },
52
53
  "homepage": "https://github.com/sqlu/discord-sb.js#readme",
53
54
  "dependencies": {
54
- "@discordjs/builders": "^1.6.3",
55
+ "@discordjs/builders": "^1.13.1",
55
56
  "@discordjs/collection": "^2.1.1",
56
57
  "@sapphire/async-queue": "^1.5.5",
57
58
  "@sapphire/shapeshift": "^4.0.0",
58
59
  "debug": "^4.4.3",
59
- "discord-api-types": "^0.38.15",
60
+ "discord-api-types": "^0.38.38",
60
61
  "otplib": "^12.0.1",
61
62
  "prism-media": "^1.3.5",
62
63
  "qrcode": "^1.5.4",
63
- "werift-rtp": "^0.8.4"
64
+ "werift-rtp": "^0.8.8"
64
65
  },
65
66
  "engines": {
66
67
  "bun": ">=1.3.5"
67
68
  },
68
69
  "devDependencies": {
69
70
  "@discordjs/docgen": "^0.11.1",
70
- "@types/bun": "^1.2.19",
71
+ "@types/bun": "^1.3.8",
71
72
  "@types/debug": "^4.1.12",
72
- "@types/node": "^22.10.7",
73
+ "@types/node": "^22.19.10",
73
74
  "dtslint": "^4.2.1",
74
- "eslint": "^8.39.0",
75
- "eslint-config-prettier": "^8.8.0",
76
- "eslint-plugin-import": "^2.27.5",
77
- "eslint-plugin-prettier": "^4.2.1",
78
- "patch-package": "^8.0.0",
75
+ "eslint": "^8.57.1",
76
+ "eslint-config-prettier": "^8.10.2",
77
+ "eslint-plugin-import": "^2.32.0",
78
+ "eslint-plugin-prettier": "^4.2.5",
79
+ "patch-package": "^8.0.1",
79
80
  "prettier": "^2.8.8",
80
81
  "tsd": "^0.32.0",
81
82
  "tslint": "^6.1.3",
82
- "typescript": "^5.5.4"
83
+ "typescript": "^5.9.3"
83
84
  },
84
85
  "packageManager": "bun@1.3.5"
85
86
  }
@@ -14,6 +14,10 @@ class BaseClient extends EventEmitter {
14
14
  constructor(options = {}) {
15
15
  super({ captureRejections: true });
16
16
 
17
+ this._listenerCountsFast = new Map();
18
+ this.on('newListener', this._onNewListener.bind(this));
19
+ this.on('removeListener', this._onRemoveListener.bind(this));
20
+
17
21
  if (options.intents) {
18
22
  process.emitWarning('Intents is not available.', 'DeprecationWarning');
19
23
  }
@@ -32,6 +36,26 @@ class BaseClient extends EventEmitter {
32
36
  this.rest = new RESTManager(this);
33
37
  }
34
38
 
39
+ _onNewListener(event) {
40
+ if (event === 'newListener' || event === 'removeListener') return;
41
+ const current = this._listenerCountsFast.get(event) ?? 0;
42
+ this._listenerCountsFast.set(event, current + 1);
43
+ }
44
+
45
+ _onRemoveListener(event) {
46
+ if (event === 'newListener' || event === 'removeListener') return;
47
+ const current = this._listenerCountsFast.get(event) ?? 0;
48
+ if (current <= 1) {
49
+ this._listenerCountsFast.delete(event);
50
+ } else {
51
+ this._listenerCountsFast.set(event, current - 1);
52
+ }
53
+ }
54
+
55
+ hasListenerFast(event) {
56
+ return (this._listenerCountsFast.get(event) ?? 0) > 0;
57
+ }
58
+
35
59
  /**
36
60
  * API shortcut
37
61
  * @type {Object}
@@ -17,6 +17,7 @@ that WebSocket events don't clash with REST methods.
17
17
  class GenericAction {
18
18
  constructor(client) {
19
19
  this.client = client;
20
+ this._partials = new Set(client.options.partials);
20
21
  }
21
22
 
22
23
  handle(data) {
@@ -25,45 +26,55 @@ class GenericAction {
25
26
 
26
27
  getPayload(data, manager, id, partialType, cache) {
27
28
  const existing = manager.cache.get(id);
28
- if (!existing && this.client.options.partials.includes(partialType)) {
29
+ if (!existing && this._partials.has(partialType)) {
29
30
  return manager._add(data, cache);
30
31
  }
31
32
  return existing;
32
33
  }
33
34
 
34
35
  getChannel(data) {
35
- const payloadData = {};
36
+ const injected = data[this.client.actions.injectedChannel];
37
+ if (injected) return injected;
38
+
36
39
  const id = data.channel_id ?? data.id;
40
+ const existing = this.client.channels.cache.get(id);
41
+ if (existing) return existing;
37
42
 
38
- if (!('recipients' in data)) {
43
+ let recipients;
44
+ if (!('recipients' in data) && this.client.user) {
39
45
  // Try to resolve the recipient, but do not add the client user.
40
46
  const recipient = data.author ?? data.user ?? { id: data.user_id };
41
- if (recipient.id !== this.client.user.id) payloadData.recipients = [recipient];
47
+ if (recipient.id !== this.client.user.id) recipients = [recipient];
42
48
  }
43
49
 
44
- if (id !== undefined) payloadData.id = id;
50
+ if (id === data.id && !recipients) {
51
+ return this.getPayload(data, this.client.channels, id, PartialTypes.CHANNEL);
52
+ }
45
53
 
46
- return (
47
- data[this.client.actions.injectedChannel] ??
48
- this.getPayload({ ...data, ...payloadData }, this.client.channels, id, PartialTypes.CHANNEL)
49
- );
54
+ const payload = { ...data, id };
55
+ if (recipients) payload.recipients = recipients;
56
+
57
+ return this.getPayload(payload, this.client.channels, id, PartialTypes.CHANNEL);
50
58
  }
51
59
 
52
60
  getMessage(data, channel, cache) {
61
+ const injected = data[this.client.actions.injectedMessage];
62
+ if (injected) return injected;
63
+
53
64
  const id = data.message_id ?? data.id;
54
- return (
55
- data[this.client.actions.injectedMessage] ??
56
- this.getPayload(
57
- {
58
- id,
59
- channel_id: channel.id,
60
- guild_id: data.guild_id ?? channel.guild?.id,
61
- },
62
- channel.messages,
65
+ const existing = channel.messages.cache.get(id);
66
+ if (existing) return existing;
67
+
68
+ return this.getPayload(
69
+ {
63
70
  id,
64
- PartialTypes.MESSAGE,
65
- cache,
66
- )
71
+ channel_id: channel.id,
72
+ guild_id: data.guild_id ?? channel.guild?.id,
73
+ },
74
+ channel.messages,
75
+ id,
76
+ PartialTypes.MESSAGE,
77
+ cache,
67
78
  );
68
79
  }
69
80
 
@@ -86,8 +97,14 @@ class GenericAction {
86
97
  }
87
98
 
88
99
  getUser(data) {
100
+ const injected = data[this.client.actions.injectedUser];
101
+ if (injected) return injected;
102
+
89
103
  const id = data.user_id;
90
- return data[this.client.actions.injectedUser] ?? this.getPayload({ id }, this.client.users, id, PartialTypes.USER);
104
+ const existing = this.client.users.cache.get(id);
105
+ if (existing) return existing;
106
+
107
+ return this.getPayload({ id }, this.client.users, id, PartialTypes.USER);
91
108
  }
92
109
 
93
110
  getUserFromMember(data) {
@@ -10,7 +10,7 @@ class InviteCreateAction extends Action {
10
10
  const guild = client.guilds.cache.get(data.guild_id);
11
11
  if (!channel) return false;
12
12
 
13
- const inviteData = Object.assign(data, { channel, guild });
13
+ const inviteData = Object.assign({}, data, { channel, guild });
14
14
  const invite = guild.invites._add(inviteData);
15
15
 
16
16
  /**
@@ -11,7 +11,7 @@ class InviteDeleteAction extends Action {
11
11
  const guild = client.guilds.cache.get(data.guild_id);
12
12
  if (!channel) return false;
13
13
 
14
- const inviteData = Object.assign(data, { channel, guild });
14
+ const inviteData = Object.assign({}, data, { channel, guild });
15
15
  const invite = new Invite(client, inviteData);
16
16
  guild.invites.cache.delete(invite.code);
17
17
 
@@ -3,17 +3,16 @@
3
3
  const process = require('node:process');
4
4
  const Action = require('./Action');
5
5
  const { Events } = require('../../util/Constants');
6
+ const { hasListener } = require('../../util/ListenerUtil');
6
7
 
7
8
  let deprecationEmitted = false;
8
9
 
9
10
  class MessageCreateAction extends Action {
10
11
  handle(data) {
11
12
  const client = this.client;
12
- const channel = this.getChannel({
13
- id: data.channel_id,
14
- author: data.author,
15
- ...('guild_id' in data && { guild_id: data.guild_id }),
16
- });
13
+ const channelData = { id: data.channel_id, author: data.author };
14
+ if ('guild_id' in data) channelData.guild_id = data.guild_id;
15
+ const channel = this.getChannel(channelData);
17
16
  if (channel) {
18
17
  if (!channel.isText()) return {};
19
18
 
@@ -35,7 +34,8 @@ class MessageCreateAction extends Action {
35
34
  * @param {Message} message The created message
36
35
  * @deprecated Use {@link Client#event:messageCreate} instead
37
36
  */
38
- if (client.emit('message', message) && !deprecationEmitted) {
37
+ const hasMessageListener = hasListener(client, 'message');
38
+ if (hasMessageListener && client.emit('message', message) && !deprecationEmitted) {
39
39
  deprecationEmitted = true;
40
40
  process.emitWarning('The message event is deprecated. Use messageCreate instead', 'DeprecationWarning');
41
41
  }
@@ -7,7 +7,9 @@ const { Events } = require('../../util/Constants');
7
7
  class MessageDeleteAction extends Action {
8
8
  handle(data) {
9
9
  const client = this.client;
10
- const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
10
+ const channelData = { id: data.channel_id };
11
+ if ('guild_id' in data) channelData.guild_id = data.guild_id;
12
+ const channel = this.getChannel(channelData);
11
13
  let message;
12
14
  if (channel) {
13
15
  if (!channel.isText()) return {};
@@ -14,20 +14,17 @@ class MessageDeleteBulkAction extends Action {
14
14
  if (!channel.isText()) return {};
15
15
 
16
16
  const ids = data.ids;
17
+ const { cache } = channel.messages;
18
+ const channelId = channel.id;
19
+ const guildId = data.guild_id;
17
20
  const messages = new Collection();
18
21
  for (const id of ids) {
19
- const message = this.getMessage(
20
- {
21
- id,
22
- guild_id: data.guild_id,
23
- },
24
- channel,
25
- false,
26
- );
22
+ const message =
23
+ cache.get(id) ?? this.getMessage({ id, channel_id: channelId, guild_id: guildId }, channel, false);
27
24
  if (message) {
28
25
  deletedMessages.add(message);
29
26
  messages.set(message.id, message);
30
- channel.messages.cache.delete(id);
27
+ cache.delete(id);
31
28
  }
32
29
  }
33
30
 
@@ -22,12 +22,9 @@ class MessageReactionAdd extends Action {
22
22
  const user = this.getUserFromMember(data);
23
23
  if (!user) return false;
24
24
 
25
- // Verify channel
26
- const channel = this.getChannel({
27
- id: data.channel_id,
28
- user_id: data.user_id,
29
- ...('guild_id' in data && { guild_id: data.guild_id }),
30
- });
25
+ const channelData = { id: data.channel_id, user_id: data.user_id };
26
+ if ('guild_id' in data) channelData.guild_id = data.guild_id;
27
+ const channel = this.getChannel(channelData);
31
28
  if (!channel || !channel.isText()) return false;
32
29
 
33
30
  // Verify message
@@ -37,12 +34,18 @@ class MessageReactionAdd extends Action {
37
34
  // Verify reaction
38
35
  const includePartial = this.client.options.partials.includes(PartialTypes.REACTION);
39
36
  if (message.partial && !includePartial) return false;
40
- const reaction = message.reactions._add({
37
+ const reactionData = {
41
38
  emoji: data.emoji,
42
39
  count: message.partial ? null : 0,
43
40
  me: user.id === this.client.user.id,
44
- ...data,
45
- });
41
+ };
42
+ if ('count' in data) reactionData.count = data.count;
43
+ if ('me' in data) reactionData.me = data.me;
44
+ if ('me_burst' in data) reactionData.me_burst = data.me_burst;
45
+ if ('burst_colors' in data) reactionData.burst_colors = data.burst_colors;
46
+ if ('count_details' in data) reactionData.count_details = data.count_details;
47
+
48
+ const reaction = message.reactions._add(reactionData);
46
49
  if (!reaction) return false;
47
50
  reaction._add(user, data.burst);
48
51
  if (fromStructure) return { message, reaction, user };
@@ -3,14 +3,6 @@
3
3
  const Action = require('./Action');
4
4
  const { Events } = require('../../util/Constants');
5
5
 
6
- /*
7
- { user_id: 'id',
8
- message_id: 'id',
9
- emoji: { name: '�', id: null },
10
- channel_id: 'id',
11
- guild_id: 'id' }
12
- */
13
-
14
6
  class MessageReactionRemove extends Action {
15
7
  handle(data) {
16
8
  if (!data.emoji) return false;
@@ -18,12 +10,9 @@ class MessageReactionRemove extends Action {
18
10
  const user = this.getUser(data);
19
11
  if (!user) return false;
20
12
 
21
- // Verify channel
22
- const channel = this.getChannel({
23
- id: data.channel_id,
24
- user_id: data.user_id,
25
- ...('guild_id' in data && { guild_id: data.guild_id }),
26
- });
13
+ const channelData = { id: data.channel_id, user_id: data.user_id };
14
+ if ('guild_id' in data) channelData.guild_id = data.guild_id;
15
+ const channel = this.getChannel(channelData);
27
16
  if (!channel || !channel.isText()) return false;
28
17
 
29
18
  // Verify message
@@ -2,22 +2,25 @@
2
2
 
3
3
  const Action = require('./Action');
4
4
  const { Events } = require('../../util/Constants');
5
+ const { hasListener } = require('../../util/ListenerUtil');
5
6
 
6
7
  class MessageReactionRemoveAll extends Action {
7
8
  handle(data) {
8
- // Verify channel
9
- const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
9
+ const hasReactionListener = hasListener(this.client, Events.MESSAGE_REACTION_REMOVE_ALL);
10
+ const channelData = { id: data.channel_id };
11
+ if ('guild_id' in data) channelData.guild_id = data.guild_id;
12
+ const channel = this.getChannel(channelData);
10
13
  if (!channel || !channel.isText()) return false;
11
14
 
12
15
  // Verify message
13
16
  const message = this.getMessage(data, channel);
14
17
  if (!message) return false;
15
18
 
16
- // Copy removed reactions to emit for the event.
17
- const removed = message.reactions.cache.clone();
19
+ // Copy removed reactions only when needed for the event payload.
20
+ const removed = hasReactionListener ? message.reactions.cache.clone() : null;
18
21
 
19
22
  message.reactions.cache.clear();
20
- this.client.emit(Events.MESSAGE_REACTION_REMOVE_ALL, message, removed);
23
+ if (hasReactionListener) this.client.emit(Events.MESSAGE_REACTION_REMOVE_ALL, message, removed);
21
24
 
22
25
  return { message };
23
26
  }
@@ -5,7 +5,9 @@ const { Events } = require('../../util/Constants');
5
5
 
6
6
  class MessageReactionRemoveEmoji extends Action {
7
7
  handle(data) {
8
- const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
8
+ const channelData = { id: data.channel_id };
9
+ if ('guild_id' in data) channelData.guild_id = data.guild_id;
10
+ const channel = this.getChannel(channelData);
9
11
  if (!channel || !channel.isText()) return false;
10
12
 
11
13
  const message = this.getMessage(data, channel);
@@ -4,7 +4,9 @@ const Action = require('./Action');
4
4
 
5
5
  class MessageUpdateAction extends Action {
6
6
  handle(data) {
7
- const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
7
+ const channelData = { id: data.channel_id };
8
+ if ('guild_id' in data) channelData.guild_id = data.guild_id;
9
+ const channel = this.getChannel(channelData);
8
10
  if (channel) {
9
11
  if (!channel.isText()) return {};
10
12
 
@@ -3,9 +3,12 @@
3
3
  const Action = require('./Action');
4
4
  const { Events } = require('../../util/Constants');
5
5
  const { PartialTypes } = require('../../util/Constants');
6
+ const { hasListener } = require('../../util/ListenerUtil');
6
7
 
7
8
  class PresenceUpdateAction extends Action {
8
9
  handle(data) {
10
+ const hasPresenceUpdateListener = hasListener(this.client, Events.PRESENCE_UPDATE);
11
+
9
12
  let user = this.client.users.cache.get(data.user.id);
10
13
  if (!user && data.user?.username) user = this.client.users._add(data.user);
11
14
  if (!user && ('username' in data.user || this.client.options.partials.includes(PartialTypes.USER))) {
@@ -31,11 +34,13 @@ class PresenceUpdateAction extends Action {
31
34
  }
32
35
  }
33
36
 
34
- const oldPresence = (guild || this.client).presences.cache.get(user.id)?._clone() ?? null;
37
+ const oldPresence = hasPresenceUpdateListener
38
+ ? (guild || this.client).presences.cache.get(user.id)?._clone() ?? null
39
+ : null;
35
40
 
36
- const newPresence = (guild || this.client).presences._add(Object.assign(data, { guild }));
41
+ const newPresence = (guild || this.client).presences._add(Object.assign({}, data, { guild }));
37
42
 
38
- if (this.client.listenerCount(Events.PRESENCE_UPDATE) && !newPresence.equals(oldPresence)) {
43
+ if (hasPresenceUpdateListener && !newPresence.equals(oldPresence)) {
39
44
  /**
40
45
  * Emitted whenever a guild member's presence (e.g. status, activity) is changed.
41
46
  * @event Client#presenceUpdate
@@ -22,10 +22,11 @@ class ThreadListSyncAction extends Action {
22
22
  }
23
23
  }
24
24
 
25
- const syncedThreads = data.threads.reduce((coll, rawThread) => {
25
+ const syncedThreads = new Collection();
26
+ for (const rawThread of data.threads) {
26
27
  const thread = client.channels._add(rawThread);
27
- return coll.set(thread.id, thread);
28
- }, new Collection());
28
+ syncedThreads.set(thread.id, thread);
29
+ }
29
30
 
30
31
  for (const rawMember of Object.values(data.members || {})) {
31
32
  // Discord sends the thread id as id in this object
@@ -48,11 +49,11 @@ class ThreadListSyncAction extends Action {
48
49
  }
49
50
 
50
51
  removeStale(channel) {
51
- channel.threads?.cache.forEach(thread => {
52
+ for (const thread of channel.threads?.cache?.values() ?? []) {
52
53
  if (!thread.archived) {
53
54
  this.client.channels._remove(thread.id);
54
55
  }
55
- });
56
+ }
56
57
  }
57
58
  }
58
59
 
@@ -2,22 +2,24 @@
2
2
 
3
3
  const Action = require('./Action');
4
4
  const { Events } = require('../../util/Constants');
5
+ const { hasListener } = require('../../util/ListenerUtil');
5
6
 
6
7
  class ThreadMembersUpdateAction extends Action {
7
8
  handle(data) {
8
9
  const client = this.client;
9
10
  const thread = client.channels.cache.get(data.id);
10
11
  if (thread) {
11
- const old = thread.members.cache.clone();
12
+ const hasThreadMembersListener = hasListener(client, Events.THREAD_MEMBERS_UPDATE);
13
+ const old = hasThreadMembersListener ? thread.members.cache.clone() : null;
12
14
  thread.memberCount = data.member_count;
13
15
 
14
- data.added_members?.forEach(rawMember => {
16
+ for (const rawMember of data.added_members ?? []) {
15
17
  thread.members._add(rawMember);
16
- });
18
+ }
17
19
 
18
- data.removed_member_ids?.forEach(memberId => {
20
+ for (const memberId of data.removed_member_ids ?? []) {
19
21
  thread.members.cache.delete(memberId);
20
- });
22
+ }
21
23
 
22
24
  /**
23
25
  * Emitted whenever members are added or removed from a thread. Requires `GUILD_MEMBERS` privileged intent
@@ -25,7 +27,9 @@ class ThreadMembersUpdateAction extends Action {
25
27
  * @param {Collection<Snowflake, ThreadMember>} oldMembers The members before the update
26
28
  * @param {Collection<Snowflake, ThreadMember>} newMembers The members after the update
27
29
  */
28
- client.emit(Events.THREAD_MEMBERS_UPDATE, old, thread.members.cache);
30
+ if (hasThreadMembersListener) {
31
+ client.emit(Events.THREAD_MEMBERS_UPDATE, old, thread.members.cache);
32
+ }
29
33
  }
30
34
  return {};
31
35
  }
@@ -6,7 +6,9 @@ const { Events } = require('../../util/Constants');
6
6
 
7
7
  class TypingStart extends Action {
8
8
  handle(data) {
9
- const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
9
+ const channelData = { id: data.channel_id };
10
+ if ('guild_id' in data) channelData.guild_id = data.guild_id;
11
+ const channel = this.getChannel(channelData);
10
12
  if (!channel) return;
11
13
 
12
14
  if (!channel.isText()) {
@@ -3,15 +3,19 @@
3
3
  const Action = require('./Action');
4
4
  const VoiceState = require('../../structures/VoiceState');
5
5
  const { Events } = require('../../util/Constants');
6
+ const { hasListener } = require('../../util/ListenerUtil');
6
7
 
7
8
  class VoiceStateUpdate extends Action {
8
9
  handle(data) {
9
10
  const client = this.client;
11
+ const hasVoiceStateListener = hasListener(client, Events.VOICE_STATE_UPDATE);
10
12
  const guild = client.guilds.cache.get(data.guild_id);
11
13
  if (guild) {
12
14
  // Update the state
13
- const oldState =
14
- guild.voiceStates.cache.get(data.user_id)?._clone() ?? new VoiceState(guild, { user_id: data.user_id });
15
+ const previous = guild.voiceStates.cache.get(data.user_id);
16
+ const oldState = hasVoiceStateListener
17
+ ? previous?._clone() ?? new VoiceState(guild, { user_id: data.user_id })
18
+ : null;
15
19
 
16
20
  const newState = guild.voiceStates._add(data);
17
21
 
@@ -29,19 +33,28 @@ class VoiceStateUpdate extends Action {
29
33
  * @param {VoiceState} oldState The voice state before the update
30
34
  * @param {VoiceState} newState The voice state after the update
31
35
  */
32
- client.emit(Events.VOICE_STATE_UPDATE, oldState, newState);
36
+ if (hasVoiceStateListener) {
37
+ client.emit(Events.VOICE_STATE_UPDATE, oldState, newState);
38
+ }
33
39
  } else {
34
40
  // Update the state
35
- const oldState =
36
- client.voiceStates.cache.get(data.user_id)?._clone() ?? new VoiceState({ client }, { user_id: data.user_id });
41
+ const previous = client.voiceStates.cache.get(data.user_id);
42
+ const oldState = hasVoiceStateListener
43
+ ? previous?._clone() ?? new VoiceState({ client }, { user_id: data.user_id })
44
+ : null;
37
45
 
38
46
  const newState = client.voiceStates._add(data);
39
47
 
40
- client.emit(Events.VOICE_STATE_UPDATE, oldState, newState);
48
+ if (hasVoiceStateListener) {
49
+ client.emit(Events.VOICE_STATE_UPDATE, oldState, newState);
50
+ }
41
51
  }
42
52
  // Emit event
43
53
  if (data.user_id === client.user?.id) {
44
- client.emit('debug', `[VOICE] received voice state update: ${JSON.stringify(data)}`);
54
+ const hasDebugListener = hasListener(client, Events.DEBUG);
55
+ if (hasDebugListener) {
56
+ client.emit(Events.DEBUG, `[VOICE] received voice state update: ${JSON.stringify(data)}`);
57
+ }
45
58
  client.voice.onVoiceStateUpdate(data);
46
59
  }
47
60
  }
@@ -3,6 +3,7 @@
3
3
  const VoiceConnection = require('./VoiceConnection');
4
4
  const { Error } = require('../../errors');
5
5
  const { Events } = require('../../util/Constants');
6
+ const { hasListener } = require('../../util/ListenerUtil');
6
7
 
7
8
  /**
8
9
  * Manages voice connections for the client
@@ -42,8 +43,7 @@ class ClientVoiceManager {
42
43
 
43
44
  onVoiceServer(payload) {
44
45
  const { guild_id, channel_id, token, endpoint } = payload;
45
- this.client.emit(
46
- 'debug',
46
+ this._debug(
47
47
  `[VOICE] voiceServer ${channel_id ? 'channel' : 'guild'}: ${
48
48
  channel_id || guild_id
49
49
  } token: ${token} endpoint: ${endpoint}`,
@@ -68,7 +68,7 @@ class ClientVoiceManager {
68
68
  }
69
69
  // Main lib
70
70
  const connection = this.connection;
71
- this.client.emit('debug', `[VOICE] connection? ${!!connection}, ${guild_id} ${session_id} ${channel_id}`);
71
+ this._debug(`[VOICE] connection? ${!!connection}, ${guild_id} ${session_id} ${channel_id}`);
72
72
  if (!connection) return;
73
73
  if (!channel_id) {
74
74
  connection._disconnect();
@@ -80,7 +80,7 @@ class ClientVoiceManager {
80
80
  connection.channel = channel;
81
81
  connection.setSessionId(session_id);
82
82
  } else {
83
- this.client.emit('debug', `[VOICE] disconnecting from guild ${guild_id} as channel ${channel_id} is uncached`);
83
+ this._debug(`[VOICE] disconnecting from guild ${guild_id} as channel ${channel_id} is uncached`);
84
84
  connection.disconnect();
85
85
  }
86
86
  }
@@ -117,9 +117,14 @@ class ClientVoiceManager {
117
117
  } else {
118
118
  connection = new VoiceConnection(this, channel);
119
119
  if (config?.videoCodec) connection.setVideoCodec(config.videoCodec);
120
- connection.on('debug', msg =>
121
- this.client.emit('debug', `[VOICE (${channel.guild?.id || channel.id}:${connection.status})]: ${msg}`),
122
- );
120
+ const forwardDebug = msg => {
121
+ if (hasListener(this.client, Events.DEBUG)) {
122
+ this.client.emit(Events.DEBUG, `[VOICE (${channel.guild?.id || channel.id}:${connection.status})]: ${msg}`);
123
+ }
124
+ };
125
+ forwardDebug.__voiceForwarder = true;
126
+ connection.on('debug', forwardDebug);
127
+ connection._voiceDebugForwarder = forwardDebug;
123
128
  connection.authenticate({
124
129
  self_mute: Boolean(config.selfMute),
125
130
  self_deaf: Boolean(config.selfDeaf),
@@ -146,6 +151,12 @@ class ClientVoiceManager {
146
151
  });
147
152
  });
148
153
  }
154
+
155
+ _debug(message) {
156
+ if (hasListener(this.client, Events.DEBUG)) {
157
+ this.client.emit(Events.DEBUG, message);
158
+ }
159
+ }
149
160
  }
150
161
 
151
162
  module.exports = ClientVoiceManager;