diff --git a/packages/discord.js/src/structures/Message.js b/packages/discord.js/src/structures/Message.js index 9ee5ebc49a0e..dd5a4b6ff12e 100644 --- a/packages/discord.js/src/structures/Message.js +++ b/packages/discord.js/src/structures/Message.js @@ -22,7 +22,7 @@ const { Sticker } = require('./Sticker'); const { DiscordjsError, ErrorCodes } = require('../errors'); const ReactionManager = require('../managers/ReactionManager'); const { createComponent } = require('../util/Components'); -const { NonSystemMessageTypes } = require('../util/Constants'); +const { NonSystemMessageTypes, MaxBulkDeletableMessageAge } = require('../util/Constants'); const MessageFlagsBitField = require('../util/MessageFlagsBitField'); const PermissionsBitField = require('../util/PermissionsBitField'); const { cleanContent, resolvePartialEmoji } = require('../util/Util'); @@ -611,6 +611,25 @@ class Message extends Base { ); } + /** + * Whether the message is bulk deletable by the client user + * @type {boolean} + * @readonly + * @example + * // Filter for bulk deletable messages + * channel.bulkDelete(messages.filter(message => message.bulkDeletable)); + */ + get bulkDeletable() { + const permissions = this.channel?.permissionsFor(this.client.user); + return ( + (this.inGuild() && + Date.now() - this.createdTimestamp < MaxBulkDeletableMessageAge && + this.deletable && + permissions?.has(PermissionFlagsBits.ManageMessages, false)) ?? + false + ); + } + /** * Whether the message is pinnable by the client user * @type {boolean} diff --git a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js index 905b5bac5f92..b769f4d4af88 100644 --- a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js +++ b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -4,6 +4,7 @@ const { Collection } = require('@discordjs/collection'); const { DiscordSnowflake } = require('@sapphire/snowflake'); const { InteractionType, Routes } = require('discord-api-types/v10'); const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../../errors'); +const { MaxBulkDeletableMessageAge } = require('../../util/Constants'); const InteractionCollector = require('../InteractionCollector'); const MessageCollector = require('../MessageCollector'); const MessagePayload = require('../MessagePayload'); @@ -294,7 +295,9 @@ class TextBasedChannel { if (Array.isArray(messages) || messages instanceof Collection) { let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m); if (filterOld) { - messageIds = messageIds.filter(id => Date.now() - DiscordSnowflake.timestampFrom(id) < 1_209_600_000); + messageIds = messageIds.filter( + id => Date.now() - DiscordSnowflake.timestampFrom(id) < MaxBulkDeletableMessageAge, + ); } if (messageIds.length === 0) return new Collection(); if (messageIds.length === 1) { diff --git a/packages/discord.js/src/util/Constants.js b/packages/discord.js/src/util/Constants.js index a2d98c1788af..3f06c3fd50d1 100644 --- a/packages/discord.js/src/util/Constants.js +++ b/packages/discord.js/src/util/Constants.js @@ -2,6 +2,12 @@ const { ChannelType, MessageType, ComponentType } = require('discord-api-types/v10'); +/** + * Max bulk deletable message age + * @typedef {number} MaxBulkDeletableMessageAge + */ +exports.MaxBulkDeletableMessageAge = 1_209_600_000; + /** * The name of an item to be swept in Sweepers * * `applicationCommands` - both global and guild commands @@ -132,6 +138,7 @@ exports.SelectMenuTypes = [ /** * @typedef {Object} Constants Constants that can be used in an enum or object-like way. + * @property {number} MaxBulkDeletableMessageAge Max bulk deletable message age * @property {SweeperKey[]} SweeperKeys The possible names of items that can be swept in sweepers * @property {NonSystemMessageTypes} NonSystemMessageTypes The types of messages that are not deemed a system type * @property {TextBasedChannelTypes} TextBasedChannelTypes The types of channels that are text-based diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index a334f2bf6ae3..437db959f4d5 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1774,6 +1774,7 @@ export class Message extends Base { public applicationId: Snowflake | null; public attachments: Collection; public author: User; + public get bulkDeletable(): boolean; public get channel(): If; public channelId: Snowflake; public get cleanContent(): string; @@ -3294,6 +3295,7 @@ export type NonSystemMessageType = | MessageType.ContextMenuCommand; export const Constants: { + MaxBulkDeletableMessageAge: 1_209_600_000; SweeperKeys: SweeperKey[]; NonSystemMessageTypes: NonSystemMessageType[]; TextBasedChannelTypes: TextBasedChannelTypes[];