djs-selfbot-v13 3.1.6 → 3.1.7

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 (148) hide show
  1. package/README.md +31 -16
  2. package/package.json +15 -8
  3. package/src/client/BaseClient.js +3 -2
  4. package/src/client/Client.js +539 -187
  5. package/src/client/actions/Action.js +13 -18
  6. package/src/client/actions/ActionsManager.js +1 -7
  7. package/src/client/actions/AutoModerationActionExecution.js +0 -1
  8. package/src/client/actions/AutoModerationRuleCreate.js +0 -1
  9. package/src/client/actions/AutoModerationRuleDelete.js +0 -1
  10. package/src/client/actions/AutoModerationRuleUpdate.js +0 -1
  11. package/src/client/actions/InteractionCreate.js +115 -0
  12. package/src/client/actions/MessageCreate.js +4 -0
  13. package/src/client/actions/PresenceUpdate.js +16 -17
  14. package/src/client/websocket/WebSocketManager.js +31 -11
  15. package/src/client/websocket/WebSocketShard.js +38 -39
  16. package/src/client/websocket/handlers/APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE.js +23 -0
  17. package/src/client/websocket/handlers/CALL_CREATE.js +3 -3
  18. package/src/client/websocket/handlers/CALL_DELETE.js +2 -2
  19. package/src/client/websocket/handlers/CALL_UPDATE.js +2 -2
  20. package/src/client/websocket/handlers/CHANNEL_RECIPIENT_ADD.js +13 -16
  21. package/src/client/websocket/handlers/CHANNEL_RECIPIENT_REMOVE.js +11 -11
  22. package/src/client/websocket/handlers/GUILD_APPLICATION_COMMANDS_UPDATE.js +11 -0
  23. package/src/client/websocket/handlers/GUILD_CREATE.js +0 -7
  24. package/src/client/websocket/handlers/GUILD_MEMBER_LIST_UPDATE.js +55 -0
  25. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUNDS_UPDATE.js +0 -0
  26. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_CREATE.js +0 -0
  27. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_DELETE.js +0 -0
  28. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_UPDATE.js +0 -0
  29. package/src/client/websocket/handlers/INTERACTION_CREATE.js +16 -0
  30. package/src/client/websocket/handlers/INTERACTION_FAILURE.js +18 -0
  31. package/src/client/websocket/handlers/INTERACTION_MODAL_CREATE.js +0 -1
  32. package/src/client/websocket/handlers/INTERACTION_SUCCESS.js +30 -0
  33. package/src/client/websocket/handlers/MESSAGE_ACK.js +16 -0
  34. package/src/client/websocket/handlers/READY.js +137 -47
  35. package/src/client/websocket/handlers/RELATIONSHIP_ADD.js +5 -7
  36. package/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js +4 -6
  37. package/src/client/websocket/handlers/RELATIONSHIP_UPDATE.js +9 -32
  38. package/src/client/websocket/handlers/SOUNDBOARD_SOUNDS.js +0 -0
  39. package/src/client/websocket/handlers/USER_GUILD_SETTINGS_UPDATE.js +8 -2
  40. package/src/client/websocket/handlers/USER_NOTE_UPDATE.js +1 -1
  41. package/src/client/websocket/handlers/USER_SETTINGS_UPDATE.js +5 -1
  42. package/src/client/websocket/handlers/VOICE_CHANNEL_EFFECT_SEND.js +0 -0
  43. package/src/client/websocket/handlers/index.js +20 -15
  44. package/src/errors/Messages.js +69 -24
  45. package/src/index.js +43 -12
  46. package/src/managers/ApplicationCommandManager.js +12 -9
  47. package/src/managers/ApplicationCommandPermissionsManager.js +11 -3
  48. package/src/managers/ChannelManager.js +4 -2
  49. package/src/managers/ClientUserSettingManager.js +279 -161
  50. package/src/managers/DeveloperPortalManager.js +104 -0
  51. package/src/managers/GuildApplicationCommandManager.js +28 -0
  52. package/src/managers/GuildBanManager.js +1 -1
  53. package/src/managers/GuildChannelManager.js +0 -2
  54. package/src/managers/GuildFolderManager.js +24 -0
  55. package/src/managers/GuildForumThreadManager.js +28 -22
  56. package/src/managers/GuildMemberManager.js +216 -40
  57. package/src/managers/GuildSettingManager.js +15 -22
  58. package/src/managers/MessageManager.js +44 -42
  59. package/src/managers/PermissionOverwriteManager.js +1 -1
  60. package/src/managers/ReactionUserManager.js +5 -5
  61. package/src/managers/RelationshipManager.js +74 -81
  62. package/src/managers/SessionManager.js +57 -0
  63. package/src/managers/ThreadManager.js +45 -12
  64. package/src/managers/ThreadMemberManager.js +1 -1
  65. package/src/managers/UserManager.js +10 -6
  66. package/src/rest/APIRequest.js +20 -42
  67. package/src/rest/CaptchaSolver.js +132 -0
  68. package/src/rest/DiscordAPIError.js +16 -17
  69. package/src/rest/RESTManager.js +21 -1
  70. package/src/rest/RequestHandler.js +21 -35
  71. package/src/structures/ApplicationCommand.js +456 -19
  72. package/src/structures/ApplicationRoleConnectionMetadata.js +0 -3
  73. package/src/structures/AutoModerationRule.js +5 -5
  74. package/src/structures/AutocompleteInteraction.js +0 -1
  75. package/src/structures/BaseGuildTextChannel.js +12 -10
  76. package/src/structures/BaseGuildVoiceChannel.js +18 -16
  77. package/src/structures/{CallState.js → Call.js} +12 -17
  78. package/src/structures/CategoryChannel.js +0 -2
  79. package/src/structures/Channel.js +3 -2
  80. package/src/structures/ClientApplication.js +204 -0
  81. package/src/structures/ClientPresence.js +8 -12
  82. package/src/structures/ClientUser.js +336 -117
  83. package/src/structures/ContextMenuInteraction.js +1 -1
  84. package/src/structures/DMChannel.js +92 -29
  85. package/src/structures/DeveloperPortalApplication.js +520 -0
  86. package/src/structures/ForumChannel.js +10 -0
  87. package/src/structures/Guild.js +271 -135
  88. package/src/structures/GuildAuditLogs.js +5 -0
  89. package/src/structures/GuildChannel.js +2 -16
  90. package/src/structures/GuildFolder.js +75 -0
  91. package/src/structures/GuildMember.js +145 -27
  92. package/src/structures/Interaction.js +62 -1
  93. package/src/structures/InteractionResponse.js +114 -0
  94. package/src/structures/Invite.js +52 -35
  95. package/src/structures/Message.js +202 -222
  96. package/src/structures/MessageAttachment.js +0 -11
  97. package/src/structures/MessageButton.js +67 -1
  98. package/src/structures/MessageEmbed.js +1 -1
  99. package/src/structures/MessageMentions.js +2 -3
  100. package/src/structures/MessagePayload.js +46 -4
  101. package/src/structures/MessageReaction.js +1 -1
  102. package/src/structures/MessageSelectMenu.js +252 -1
  103. package/src/structures/Modal.js +180 -75
  104. package/src/structures/PartialGroupDMChannel.js +433 -0
  105. package/src/structures/Presence.js +2 -2
  106. package/src/structures/RichPresence.js +34 -14
  107. package/src/structures/Role.js +2 -18
  108. package/src/structures/SelectMenuInteraction.js +151 -2
  109. package/src/structures/Session.js +81 -0
  110. package/src/structures/Team.js +49 -0
  111. package/src/structures/TextInputComponent.js +70 -0
  112. package/src/structures/ThreadChannel.js +19 -0
  113. package/src/structures/User.js +345 -117
  114. package/src/structures/UserContextMenuInteraction.js +2 -2
  115. package/src/structures/VoiceState.js +39 -74
  116. package/src/structures/WebEmbed.js +52 -38
  117. package/src/structures/Webhook.js +11 -17
  118. package/src/structures/interfaces/Application.js +23 -146
  119. package/src/structures/interfaces/TextBasedChannel.js +256 -411
  120. package/src/util/ApplicationFlags.js +1 -1
  121. package/src/util/Constants.js +284 -106
  122. package/src/util/Formatters.js +2 -16
  123. package/src/util/LimitedCollection.js +1 -1
  124. package/src/util/Options.js +68 -48
  125. package/src/util/Permissions.js +0 -5
  126. package/src/util/PurchasedFlags.js +0 -2
  127. package/src/util/RemoteAuth.js +356 -221
  128. package/src/util/Sweepers.js +1 -1
  129. package/src/util/Util.js +36 -76
  130. package/src/util/Voice.js +1456 -0
  131. package/src/util/arRPC/index.js +229 -0
  132. package/src/util/arRPC/process/detectable.json +1 -0
  133. package/src/util/arRPC/process/index.js +102 -0
  134. package/src/util/arRPC/process/native/index.js +5 -0
  135. package/src/util/arRPC/process/native/linux.js +37 -0
  136. package/src/util/arRPC/process/native/win32.js +25 -0
  137. package/src/util/arRPC/transports/ipc.js +281 -0
  138. package/src/util/arRPC/transports/websocket.js +128 -0
  139. package/typings/enums.d.ts +73 -18
  140. package/typings/index.d.ts +1249 -897
  141. package/typings/rawDataTypes.d.ts +9 -68
  142. package/src/client/websocket/handlers/USER_REQUIRED_ACTION_UPDATE.js +0 -78
  143. package/src/client/websocket/handlers/VOICE_CHANNEL_STATUS_UPDATE.js +0 -12
  144. package/src/managers/UserNoteManager.js +0 -53
  145. package/src/structures/GroupDMChannel.js +0 -387
  146. package/src/util/AttachmentFlags.js +0 -38
  147. package/src/util/InviteFlags.js +0 -29
  148. package/src/util/RoleFlags.js +0 -37
@@ -1,14 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  /* eslint-disable import/order */
4
+ const InteractionManager = require('../../managers/InteractionManager');
4
5
  const MessageCollector = require('../MessageCollector');
5
6
  const MessagePayload = require('../MessagePayload');
6
- const { InteractionTypes, ApplicationCommandOptionTypes, Events } = require('../../util/Constants');
7
- const { Error } = require('../../errors');
8
7
  const SnowflakeUtil = require('../../util/SnowflakeUtil');
9
- const { setTimeout } = require('node:timers');
8
+ const { Collection } = require('@discordjs/collection');
9
+ const { InteractionTypes, MaxBulkDeletableMessageAge } = require('../../util/Constants');
10
+ const { TypeError, Error } = require('../../errors');
11
+ const InteractionCollector = require('../InteractionCollector');
12
+ const { lazy, getAttachments, uploadFile } = require('../../util/Util');
13
+ const Message = lazy(() => require('../Message').Message);
10
14
  const { s } = require('@sapphire/shapeshift');
11
- const Util = require('../../util/Util');
12
15
  const validateName = stringName =>
13
16
  s.string
14
17
  .lengthGreaterThanOrEqual(1)
@@ -29,6 +32,12 @@ class TextBasedChannel {
29
32
  */
30
33
  this.messages = new MessageManager(this);
31
34
 
35
+ /**
36
+ * A manager of the interactions sent to this channel
37
+ * @type {InteractionManager}
38
+ */
39
+ this.interactions = new InteractionManager(this);
40
+
32
41
  /**
33
42
  * The channel's last message id, if one was sent
34
43
  * @type {?Snowflake}
@@ -67,7 +76,7 @@ class TextBasedChannel {
67
76
  * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
68
77
  * @property {string} [nonce=''] The nonce for the message
69
78
  * @property {string} [content=''] The content for the message
70
- * @property {Array<(MessageEmbed|APIEmbed)>} [embeds] The embeds for the message
79
+ * @property {Array<(MessageEmbed|APIEmbed|WebEmbed)>} [embeds] The embeds for the message
71
80
  * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
72
81
  * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
73
82
  * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
@@ -75,6 +84,7 @@ class TextBasedChannel {
75
84
  * @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
76
85
  * Action rows containing interactive components for the message (buttons, select menus)
77
86
  * @property {MessageAttachment[]} [attachments] Attachments to send in the message
87
+ * @property {boolean} [usingNewAttachmentAPI] Whether to use the new attachment API (`channels/:id/attachments`)
78
88
  */
79
89
 
80
90
  /**
@@ -158,195 +168,39 @@ class TextBasedChannel {
158
168
  let messagePayload;
159
169
 
160
170
  if (options instanceof MessagePayload) {
161
- messagePayload = options.resolveData();
171
+ messagePayload = await options.resolveData();
162
172
  } else {
163
- messagePayload = MessagePayload.create(this, options).resolveData();
173
+ messagePayload = await MessagePayload.create(this, options).resolveData();
164
174
  }
165
175
 
166
- const { data, files } = await messagePayload.resolveFiles();
167
- // New API
168
- const attachments = await Util.getUploadURL(this.client, this.id, files);
169
- const requestPromises = attachments.map(async attachment => {
170
- await Util.uploadFile(files[attachment.id].file, attachment.upload_url);
171
- return {
172
- id: attachment.id,
173
- filename: files[attachment.id].name,
174
- uploaded_filename: attachment.upload_filename,
175
- description: files[attachment.id].description,
176
- duration_secs: files[attachment.id].duration_secs,
177
- waveform: files[attachment.id].waveform,
178
- };
179
- });
180
- const attachmentsData = await Promise.all(requestPromises);
181
- attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
182
- data.attachments = attachmentsData;
183
- // Empty Files
184
- const d = await this.client.api.channels[this.id].messages.post({ data });
185
-
186
- return this.messages.cache.get(d.id) ?? this.messages._add(d);
187
- }
176
+ let { data, files } = await messagePayload.resolveFiles();
188
177
 
189
- searchInteraction() {
190
- // Support Slash / ContextMenu
191
- // API https://canary.discord.com/api/v9/guilds/:id/application-command-index // Guild
192
- // https://canary.discord.com/api/v9/channels/:id/application-command-index // DM Channel
193
- // Updated: 07/01/2023
194
- return this.client.api[this.guild ? 'guilds' : 'channels'][this.guild?.id || this.id][
195
- 'application-command-index'
196
- ].get();
197
- }
198
-
199
- async sendSlash(botOrApplicationId, commandNameString, ...args) {
200
- // Parse commandName /role add user
201
- const cmd = commandNameString.trim().split(' ');
202
- // Ex: role add user => [role, add, user]
203
- // Parse: name, subGr, sub
204
- const commandName = validateName(cmd[0]);
205
- // Parse: role
206
- const sub = cmd.slice(1);
207
- // Parse: [add, user]
208
- for (let i = 0; i < sub.length; i++) {
209
- if (sub.length > 2) {
210
- throw new Error('INVALID_COMMAND_NAME', cmd);
211
- }
212
- validateName(sub[i]);
178
+ if (typeof options == 'object' && typeof options.usingNewAttachmentAPI !== 'boolean') {
179
+ options.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
213
180
  }
214
- // Search all
215
- const data = await this.searchInteraction();
216
- // Find command...
217
- const filterCommand = data.application_commands.filter(obj =>
218
- // Filter: name | name_default
219
- [obj.name, obj.name_default].includes(commandName),
220
- );
221
- // Filter Bot
222
- botOrApplicationId = this.client.users.resolveId(botOrApplicationId);
223
- const application = data.applications.find(
224
- obj => obj.id == botOrApplicationId || obj.bot?.id == botOrApplicationId,
225
- );
226
- // Find Command with application
227
- const command = filterCommand.find(command => command.application_id == application.id);
228
181
 
229
- args = args.flat(2);
230
- let optionFormat = [];
231
- let attachments = [];
232
- let optionsMaxdepth, subGroup, subCommand;
233
- if (sub.length == 2) {
234
- // Subcommand Group > Subcommand
235
- // Find Sub group
236
- subGroup = command.options.find(
237
- obj =>
238
- obj.type == ApplicationCommandOptionTypes.SUB_COMMAND_GROUP && [obj.name, obj.name_default].includes(sub[0]),
239
- );
240
- if (!subGroup) throw new Error('SLASH_COMMAND_SUB_COMMAND_GROUP_INVALID', sub[0]);
241
- // Find Sub
242
- subCommand = subGroup.options.find(
243
- obj => obj.type == ApplicationCommandOptionTypes.SUB_COMMAND && [obj.name, obj.name_default].includes(sub[1]),
244
- );
245
- if (!subCommand) throw new Error('SLASH_COMMAND_SUB_COMMAND_INVALID', sub[1]);
246
- // Options
247
- optionsMaxdepth = subCommand.options;
248
- } else if (sub.length == 1) {
249
- // Subcommand
250
- subCommand = command.options.find(
251
- obj => obj.type == ApplicationCommandOptionTypes.SUB_COMMAND && [obj.name, obj.name_default].includes(sub[0]),
252
- );
253
- if (!subCommand) throw new Error('SLASH_COMMAND_SUB_COMMAND_INVALID', sub[0]);
254
- // Options
255
- optionsMaxdepth = subCommand.options;
256
- } else {
257
- optionsMaxdepth = command.options;
258
- }
259
- const valueRequired = optionsMaxdepth?.filter(o => o.required).length || 0;
260
- for (let i = 0; i < Math.min(args.length, optionsMaxdepth?.length || 0); i++) {
261
- const optionInput = optionsMaxdepth[i];
262
- const value = args[i];
263
- const parseData = await parseOption(
264
- this.client,
265
- optionInput,
266
- value,
267
- optionFormat,
268
- attachments,
269
- command,
270
- application.id,
271
- this.guild?.id,
272
- this.id,
273
- subGroup,
274
- subCommand,
275
- );
276
- optionFormat = parseData.optionFormat;
277
- attachments = parseData.attachments;
278
- }
279
- if (valueRequired > args.length) {
280
- throw new Error('SLASH_COMMAND_REQUIRED_OPTIONS_MISSING', valueRequired, optionFormat.length);
281
- }
282
- // Post
283
- let postData;
284
- if (subGroup) {
285
- postData = [
286
- {
287
- type: ApplicationCommandOptionTypes.SUB_COMMAND_GROUP,
288
- name: subGroup.name,
289
- options: [
290
- {
291
- type: ApplicationCommandOptionTypes.SUB_COMMAND,
292
- name: subCommand.name,
293
- options: optionFormat,
294
- },
295
- ],
296
- },
297
- ];
298
- } else if (subCommand) {
299
- postData = [
300
- {
301
- type: ApplicationCommandOptionTypes.SUB_COMMAND,
302
- name: subCommand.name,
303
- options: optionFormat,
304
- },
305
- ];
306
- } else {
307
- postData = optionFormat;
182
+ if (options?.usingNewAttachmentAPI === true) {
183
+ const attachments = await getAttachments(this.client, this.id, ...files);
184
+ const requestPromises = attachments.map(async attachment => {
185
+ await uploadFile(files[attachment.id].file, attachment.upload_url);
186
+ return {
187
+ id: attachment.id,
188
+ filename: files[attachment.id].name,
189
+ uploaded_filename: attachment.upload_filename,
190
+ description: files[attachment.id].description,
191
+ duration_secs: files[attachment.id].duration_secs,
192
+ waveform: files[attachment.id].waveform,
193
+ };
194
+ });
195
+ const attachmentsData = await Promise.all(requestPromises);
196
+ attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
197
+ data.attachments = attachmentsData;
198
+ files = [];
308
199
  }
309
- const nonce = SnowflakeUtil.generate();
310
- const body = createPostData(
311
- this.client,
312
- false,
313
- application.id,
314
- nonce,
315
- this.guild?.id,
316
- Boolean(command.guild_id),
317
- this.id,
318
- command.version,
319
- command.id,
320
- command.name_default || command.name,
321
- command.type,
322
- postData,
323
- attachments,
324
- );
325
- this.client.api.interactions.post({
326
- data: body,
327
- usePayloadJSON: true,
328
- });
329
- return new Promise((resolve, reject) => {
330
- const timeoutMs = 5_000;
331
- // Waiting for MsgCreate / ModalCreate
332
- const handler = data => {
333
- if (data.nonce !== nonce) return;
334
- clearTimeout(timeout);
335
- this.client.removeListener(Events.MESSAGE_CREATE, handler);
336
- this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
337
- this.client.decrementMaxListeners();
338
- resolve(data);
339
- };
340
- const timeout = setTimeout(() => {
341
- this.client.removeListener(Events.MESSAGE_CREATE, handler);
342
- this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
343
- this.client.decrementMaxListeners();
344
- reject(new Error('INTERACTION_FAILED'));
345
- }, timeoutMs).unref();
346
- this.client.incrementMaxListeners();
347
- this.client.on(Events.MESSAGE_CREATE, handler);
348
- this.client.on(Events.INTERACTION_MODAL_CREATE, handler);
349
- });
200
+
201
+ const d = await this.client.api.channels[this.id].messages.post({ data, files });
202
+
203
+ return this.messages.cache.get(d.id) ?? this.messages._add(d);
350
204
  }
351
205
 
352
206
  /**
@@ -407,6 +261,101 @@ class TextBasedChannel {
407
261
  });
408
262
  }
409
263
 
264
+ /**
265
+ * Creates a component interaction collector.
266
+ * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
267
+ * @returns {InteractionCollector}
268
+ * @example
269
+ * // Create a button interaction collector
270
+ * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
271
+ * const collector = channel.createMessageComponentCollector({ filter, time: 15_000 });
272
+ * collector.on('collect', i => console.log(`Collected ${i.customId}`));
273
+ * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
274
+ */
275
+ createMessageComponentCollector(options = {}) {
276
+ return new InteractionCollector(this.client, {
277
+ ...options,
278
+ interactionType: InteractionTypes.MESSAGE_COMPONENT,
279
+ channel: this,
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Collects a single component interaction that passes the filter.
285
+ * The Promise will reject if the time expires.
286
+ * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
287
+ * @returns {Promise<MessageComponentInteraction>}
288
+ * @example
289
+ * // Collect a message component interaction
290
+ * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
291
+ * channel.awaitMessageComponent({ filter, time: 15_000 })
292
+ * .then(interaction => console.log(`${interaction.customId} was clicked!`))
293
+ * .catch(console.error);
294
+ */
295
+ awaitMessageComponent(options = {}) {
296
+ const _options = { ...options, max: 1 };
297
+ return new Promise((resolve, reject) => {
298
+ const collector = this.createMessageComponentCollector(_options);
299
+ collector.once('end', (interactions, reason) => {
300
+ const interaction = interactions.first();
301
+ if (interaction) resolve(interaction);
302
+ else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
303
+ });
304
+ });
305
+ }
306
+
307
+ /**
308
+ * Bulk deletes given messages that are newer than two weeks.
309
+ * @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages
310
+ * Messages or number of messages to delete
311
+ * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
312
+ * @returns {Promise<Collection<Snowflake, Message|undefined>>} Returns the deleted messages
313
+ * @example
314
+ * // Bulk delete messages
315
+ * channel.bulkDelete(5)
316
+ * .then(messages => console.log(`Bulk deleted ${messages.size} messages`))
317
+ * .catch(console.error);
318
+ */
319
+ async bulkDelete(messages, filterOld = false) {
320
+ if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
321
+ if (Array.isArray(messages) || messages instanceof Collection) {
322
+ let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m);
323
+ if (filterOld) {
324
+ messageIds = messageIds.filter(id => Date.now() - SnowflakeUtil.timestampFrom(id) < MaxBulkDeletableMessageAge);
325
+ }
326
+ if (messageIds.length === 0) return new Collection();
327
+ if (messageIds.length === 1) {
328
+ await this.client.api.channels(this.id).messages(messageIds[0]).delete();
329
+ const message = this.client.actions.MessageDelete.getMessage(
330
+ {
331
+ message_id: messageIds[0],
332
+ },
333
+ this,
334
+ );
335
+ return message ? new Collection([[message.id, message]]) : new Collection();
336
+ }
337
+ await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIds } });
338
+ return messageIds.reduce(
339
+ (col, id) =>
340
+ col.set(
341
+ id,
342
+ this.client.actions.MessageDeleteBulk.getMessage(
343
+ {
344
+ message_id: id,
345
+ },
346
+ this,
347
+ ),
348
+ ),
349
+ new Collection(),
350
+ );
351
+ }
352
+ if (!isNaN(messages)) {
353
+ const msgs = await this.messages.fetch({ limit: messages });
354
+ return this.bulkDelete(msgs, filterOld);
355
+ }
356
+ throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
357
+ }
358
+
410
359
  /**
411
360
  * Fetches all webhooks for the channel.
412
361
  * @returns {Promise<Collection<Snowflake, Webhook>>}
@@ -465,21 +414,139 @@ class TextBasedChannel {
465
414
  return this.edit({ nsfw }, reason);
466
415
  }
467
416
 
417
+ /**
418
+ * Search Slash Command (return raw data)
419
+ * @param {Snowflake} applicationId Application ID
420
+ * @param {?ApplicationCommandType} type Command Type
421
+ * @returns {Object}
422
+ */
423
+ searchInteraction(applicationId, type = 'CHAT_INPUT') {
424
+ switch (type) {
425
+ case 'USER':
426
+ case 2:
427
+ type = 2;
428
+ break;
429
+ case 'MESSAGE':
430
+ case 3:
431
+ type = 3;
432
+ break;
433
+ default:
434
+ type = 1;
435
+ break;
436
+ }
437
+ return this.client.api.channels[this.id]['application-commands'].search.get({
438
+ query: {
439
+ type,
440
+ application_id: applicationId,
441
+ },
442
+ });
443
+ }
444
+
445
+ /**
446
+ * Send Slash to this channel
447
+ * @param {UserResolvable} bot Bot user (BotID, not applicationID)
448
+ * @param {string} commandString Command name (and sub / group formats)
449
+ * @param {...?any|any[]} args Command arguments
450
+ * @returns {Promise<InteractionResponse>}
451
+ * @example
452
+ * // Send a basic slash
453
+ * channel.sendSlash('botid', 'ping')
454
+ * .then(console.log)
455
+ * .catch(console.error);
456
+ * @example
457
+ * // Send a remote file
458
+ * channel.sendSlash('botid', 'emoji upload', 'https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048', 'test')
459
+ * .then(console.log)
460
+ * .catch(console.error);
461
+ * @see {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/SlashCommand.md}
462
+ */
463
+ async sendSlash(bot, commandString, ...args) {
464
+ const perms =
465
+ this.type != 'DM'
466
+ ? this.permissionsFor(this.client.user).toArray()
467
+ : ['USE_APPLICATION_COMMANDS', `${this.recipient.relationships == 'BLOCKED' ? '' : 'SEND_MESSAGES'}`];
468
+ if (!perms.includes('SEND_MESSAGES')) {
469
+ throw new Error(
470
+ 'INTERACTION_SEND_FAILURE',
471
+ `Cannot send Slash to ${this.toString()} ${
472
+ this.recipient ? 'because bot has been blocked' : 'due to missing SEND_MESSAGES permission'
473
+ }`,
474
+ );
475
+ }
476
+ if (!perms.includes('USE_APPLICATION_COMMANDS')) {
477
+ throw new Error(
478
+ 'INTERACTION_SEND_FAILURE',
479
+ `Cannot send Slash to ${this.toString()} due to missing USE_APPLICATION_COMMANDS permission`,
480
+ );
481
+ }
482
+ args = args.flat(2);
483
+ const cmd = commandString.trim().split(' ');
484
+ // Validate CommandName
485
+ const commandName = validateName(cmd[0]);
486
+ const sub = cmd.slice(1);
487
+ for (let i = 0; i < sub.length; i++) {
488
+ if (sub.length > 2) {
489
+ throw new Error('INVALID_COMMAND_NAME', cmd);
490
+ }
491
+ validateName(sub[i]);
492
+ }
493
+ if (!bot) throw new Error('MUST_SPECIFY_BOT');
494
+ const botId = this.client.users.resolveId(bot);
495
+ const user = await this.client.users.fetch(botId).catch(() => {});
496
+ if (!user || !user.bot || !user.application) {
497
+ throw new Error('botId is not a bot or does not have an application slash command');
498
+ }
499
+ if (user._partial) await user.getProfile().catch(() => {});
500
+ if (!commandName || typeof commandName !== 'string') throw new Error('Command name is required');
501
+ const data = await this.searchInteraction(user.application?.id ?? user.id, 'CHAT_INPUT');
502
+ for (const command of data.application_commands) {
503
+ if (user.id == command.application_id || user.application.id == command.application_id) {
504
+ user.application?.commands?._add(command, true);
505
+ }
506
+ }
507
+ // Remove
508
+ const commandTarget = user.application?.commands?.cache.find(
509
+ c => c.name === commandName && c.type === 'CHAT_INPUT',
510
+ );
511
+ if (!commandTarget) {
512
+ throw new Error(
513
+ 'INTERACTION_SEND_FAILURE',
514
+ `SlashCommand ${commandName} is not found (With search)\nDebug:\n+ botId: ${botId} (ApplicationId: ${
515
+ user.application?.id
516
+ })\n+ args: ${args.join(' | ') || null}`,
517
+ );
518
+ }
519
+ return commandTarget.sendSlashCommand(
520
+ new (Message())(this.client, {
521
+ channel_id: this.id,
522
+ guild_id: this.guild?.id || null,
523
+ author: this.client.user,
524
+ content: '',
525
+ id: this.client.user.id,
526
+ }),
527
+ sub && sub.length > 0 ? sub : [],
528
+ args && args.length ? args : [],
529
+ );
530
+ }
531
+
468
532
  static applyToClass(structure, full = false, ignore = []) {
469
533
  const props = ['send'];
470
534
  if (full) {
471
535
  props.push(
472
- 'sendSlash',
473
- 'searchInteraction',
474
536
  'lastMessage',
475
537
  'lastPinAt',
538
+ 'bulkDelete',
476
539
  'sendTyping',
477
540
  'createMessageCollector',
478
541
  'awaitMessages',
542
+ 'createMessageComponentCollector',
543
+ 'awaitMessageComponent',
479
544
  'fetchWebhooks',
480
545
  'createWebhook',
481
546
  'setRateLimitPerUser',
482
547
  'setNSFW',
548
+ 'sendSlash',
549
+ 'searchInteraction',
483
550
  );
484
551
  }
485
552
  for (const prop of props) {
@@ -497,225 +564,3 @@ module.exports = TextBasedChannel;
497
564
 
498
565
  // Fixes Circular
499
566
  const MessageManager = require('../../managers/MessageManager');
500
-
501
- // Utils
502
- function parseChoices(parent, list_choices, value) {
503
- if (value !== undefined) {
504
- if (Array.isArray(list_choices) && list_choices.length) {
505
- const choice = list_choices.find(c => [c.name, c.value].includes(value));
506
- if (choice) {
507
- return choice.value;
508
- } else {
509
- throw new Error('INVALID_SLASH_COMMAND_CHOICES', parent, value);
510
- }
511
- } else {
512
- return value;
513
- }
514
- } else {
515
- return undefined;
516
- }
517
- }
518
-
519
- async function addDataFromAttachment(value, client, channelId, attachments) {
520
- value = await MessagePayload.resolveFile(value);
521
- if (!value?.file) {
522
- throw new TypeError('The attachment data must be a BufferResolvable or Stream or FileOptions of MessageAttachment');
523
- }
524
- const data = await Util.getUploadURL(client, channelId, [value]);
525
- await Util.uploadFile(value.file, data[0].upload_url);
526
- const id = attachments.length;
527
- attachments.push({
528
- id,
529
- filename: value.name,
530
- uploaded_filename: data[0].upload_filename,
531
- });
532
- return {
533
- id,
534
- attachments,
535
- };
536
- }
537
-
538
- async function parseOption(
539
- client,
540
- optionCommand,
541
- value,
542
- optionFormat,
543
- attachments,
544
- command,
545
- applicationId,
546
- guildId,
547
- channelId,
548
- subGroup,
549
- subCommand,
550
- ) {
551
- const data = {
552
- type: optionCommand.type,
553
- name: optionCommand.name,
554
- };
555
- if (value !== undefined) {
556
- switch (optionCommand.type) {
557
- case ApplicationCommandOptionTypes.BOOLEAN:
558
- case 'BOOLEAN': {
559
- data.value = Boolean(value);
560
- break;
561
- }
562
- case ApplicationCommandOptionTypes.INTEGER:
563
- case 'INTEGER': {
564
- data.value = Number(value);
565
- break;
566
- }
567
- case ApplicationCommandOptionTypes.ATTACHMENT:
568
- case 'ATTACHMENT': {
569
- const parseData = await addDataFromAttachment(value, client, channelId, attachments);
570
- data.value = parseData.id;
571
- attachments = parseData.attachments;
572
- break;
573
- }
574
- case ApplicationCommandOptionTypes.SUB_COMMAND_GROUP:
575
- case 'SUB_COMMAND_GROUP': {
576
- break;
577
- }
578
- default: {
579
- value = parseChoices(optionCommand.name, optionCommand.choices, value);
580
- if (optionCommand.autocomplete) {
581
- const nonce = SnowflakeUtil.generate();
582
- // Post
583
- let postData;
584
- if (subGroup) {
585
- postData = [
586
- {
587
- type: ApplicationCommandOptionTypes.SUB_COMMAND_GROUP,
588
- name: subGroup.name,
589
- options: [
590
- {
591
- type: ApplicationCommandOptionTypes.SUB_COMMAND,
592
- name: subCommand.name,
593
- options: [
594
- {
595
- type: optionCommand.type,
596
- name: optionCommand.name,
597
- value,
598
- focused: true,
599
- },
600
- ],
601
- },
602
- ],
603
- },
604
- ];
605
- } else if (subCommand) {
606
- postData = [
607
- {
608
- type: ApplicationCommandOptionTypes.SUB_COMMAND,
609
- name: subCommand.name,
610
- options: [
611
- {
612
- type: optionCommand.type,
613
- name: optionCommand.name,
614
- value,
615
- focused: true,
616
- },
617
- ],
618
- },
619
- ];
620
- } else {
621
- postData = [
622
- {
623
- type: optionCommand.type,
624
- name: optionCommand.name,
625
- value,
626
- focused: true,
627
- },
628
- ];
629
- }
630
- const body = createPostData(
631
- client,
632
- true,
633
- applicationId,
634
- nonce,
635
- guildId,
636
- Boolean(command.guild_id),
637
- channelId,
638
- command.version,
639
- command.id,
640
- command.name_default || command.name,
641
- command.type,
642
- postData,
643
- [],
644
- );
645
- await client.api.interactions.post({
646
- data: body,
647
- });
648
- data.value = await awaitAutocomplete(client, nonce, value);
649
- } else {
650
- data.value = value;
651
- }
652
- }
653
- }
654
- optionFormat.push(data);
655
- }
656
- return {
657
- optionFormat,
658
- attachments,
659
- };
660
- }
661
-
662
- function awaitAutocomplete(client, nonce, defaultValue) {
663
- return new Promise(resolve => {
664
- const handler = data => {
665
- if (data.t !== 'APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE') return;
666
- if (data.d?.nonce !== nonce) return;
667
- clearTimeout(timeout);
668
- client.removeListener(Events.UNHANDLED_PACKET, handler);
669
- client.decrementMaxListeners();
670
- if (data.d.choices.length >= 1) {
671
- resolve(data.d.choices[0].value);
672
- } else {
673
- resolve(defaultValue);
674
- }
675
- };
676
- const timeout = setTimeout(() => {
677
- client.removeListener(Events.UNHANDLED_PACKET, handler);
678
- client.decrementMaxListeners();
679
- resolve(defaultValue);
680
- }, 5_000).unref();
681
- client.incrementMaxListeners();
682
- client.on(Events.UNHANDLED_PACKET, handler);
683
- });
684
- }
685
-
686
- function createPostData(
687
- client,
688
- isAutocomplete = false,
689
- applicationId,
690
- nonce,
691
- guildId,
692
- isGuildCommand,
693
- channelId,
694
- commandVersion,
695
- commandId,
696
- commandName,
697
- commandType,
698
- postData,
699
- attachments = [],
700
- ) {
701
- const data = {
702
- type: isAutocomplete ? InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE : InteractionTypes.APPLICATION_COMMAND,
703
- application_id: applicationId,
704
- guild_id: guildId,
705
- channel_id: channelId,
706
- session_id: client.sessionId,
707
- data: {
708
- version: commandVersion,
709
- id: commandId,
710
- name: commandName,
711
- type: commandType,
712
- options: postData,
713
- attachments: attachments,
714
- },
715
- nonce,
716
- };
717
- if (isGuildCommand) {
718
- data.data.guild_id = guildId;
719
- }
720
- return data;
721
- }