diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e356e097dd96..69d82ecfc107 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -132,6 +132,8 @@ body: - DirectMessageTyping - MessageContent - GuildScheduledEvents + - AutoModerationConfiguration + - AutoModerationExecution multiple: true validations: required: true diff --git a/packages/discord.js/src/client/actions/ActionsManager.js b/packages/discord.js/src/client/actions/ActionsManager.js index 0ab0be1a0f58..bcb9f2744ed2 100644 --- a/packages/discord.js/src/client/actions/ActionsManager.js +++ b/packages/discord.js/src/client/actions/ActionsManager.js @@ -5,6 +5,10 @@ class ActionsManager { this.client = client; this.register(require('./ApplicationCommandPermissionsUpdate')); + this.register(require('./AutoModerationActionExecution')); + this.register(require('./AutoModerationRuleCreate')); + this.register(require('./AutoModerationRuleDelete')); + this.register(require('./AutoModerationRuleUpdate')); this.register(require('./ChannelCreate')); this.register(require('./ChannelDelete')); this.register(require('./ChannelUpdate')); diff --git a/packages/discord.js/src/client/actions/AutoModerationActionExecution.js b/packages/discord.js/src/client/actions/AutoModerationActionExecution.js new file mode 100644 index 000000000000..ad601167c41f --- /dev/null +++ b/packages/discord.js/src/client/actions/AutoModerationActionExecution.js @@ -0,0 +1,26 @@ +'use strict'; + +const Action = require('./Action'); +const AutoModerationActionExecution = require('../../structures/AutoModerationActionExecution'); +const Events = require('../../util/Events'); + +class AutoModerationActionExecutionAction extends Action { + handle(data) { + const { client } = this; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + /** + * Emitted whenever an auto moderation rule is triggered. + * This event requires the {@link PermissionFlagsBits.ManageGuild} permission. + * @event Client#autoModerationActionExecution + * @param {AutoModerationActionExecution} autoModerationActionExecution The data of the execution + */ + client.emit(Events.AutoModerationActionExecution, new AutoModerationActionExecution(data, guild)); + } + + return {}; + } +} + +module.exports = AutoModerationActionExecutionAction; diff --git a/packages/discord.js/src/client/actions/AutoModerationRuleCreate.js b/packages/discord.js/src/client/actions/AutoModerationRuleCreate.js new file mode 100644 index 000000000000..775b1d370cf5 --- /dev/null +++ b/packages/discord.js/src/client/actions/AutoModerationRuleCreate.js @@ -0,0 +1,27 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class AutoModerationRuleCreateAction extends Action { + handle(data) { + const { client } = this; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const autoModerationRule = guild.autoModerationRules._add(data); + + /** + * Emitted whenever an auto moderation rule is created. + * This event requires the {@link PermissionFlagsBits.ManageGuild} permission. + * @event Client#autoModerationRuleCreate + * @param {AutoModerationRule} autoModerationRule The created auto moderation rule + */ + client.emit(Events.AutoModerationRuleCreate, autoModerationRule); + } + + return {}; + } +} + +module.exports = AutoModerationRuleCreateAction; diff --git a/packages/discord.js/src/client/actions/AutoModerationRuleDelete.js b/packages/discord.js/src/client/actions/AutoModerationRuleDelete.js new file mode 100644 index 000000000000..641822c24b2a --- /dev/null +++ b/packages/discord.js/src/client/actions/AutoModerationRuleDelete.js @@ -0,0 +1,31 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class AutoModerationRuleDeleteAction extends Action { + handle(data) { + const { client } = this; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const autoModerationRule = guild.autoModerationRules.cache.get(data.id); + + if (autoModerationRule) { + guild.autoModerationRules.cache.delete(autoModerationRule.id); + + /** + * Emitted whenever an auto moderation rule is deleted. + * This event requires the {@link PermissionFlagsBits.ManageGuild} permission. + * @event Client#autoModerationRuleDelete + * @param {AutoModerationRule} autoModerationRule The deleted auto moderation rule + */ + client.emit(Events.AutoModerationRuleDelete, autoModerationRule); + } + } + + return {}; + } +} + +module.exports = AutoModerationRuleDeleteAction; diff --git a/packages/discord.js/src/client/actions/AutoModerationRuleUpdate.js b/packages/discord.js/src/client/actions/AutoModerationRuleUpdate.js new file mode 100644 index 000000000000..56e39568c5c7 --- /dev/null +++ b/packages/discord.js/src/client/actions/AutoModerationRuleUpdate.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class AutoModerationRuleUpdateAction extends Action { + handle(data) { + const { client } = this; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const oldAutoModerationRule = guild.autoModerationRules.cache.get(data.id)?._clone() ?? null; + const newAutoModerationRule = guild.autoModerationRules._add(data); + + /** + * Emitted whenever an auto moderation rule gets updated. + * This event requires the {@link PermissionFlagsBits.ManageGuild} permission. + * @event Client#autoModerationRuleUpdate + * @param {?AutoModerationRule} oldAutoModerationRule The auto moderation rule before the update + * @param {AutoModerationRule} newAutoModerationRule The auto moderation rule after the update + */ + client.emit(Events.AutoModerationRuleUpdate, oldAutoModerationRule, newAutoModerationRule); + } + + return {}; + } +} + +module.exports = AutoModerationRuleUpdateAction; diff --git a/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_ACTION_EXECUTION.js b/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_ACTION_EXECUTION.js new file mode 100644 index 000000000000..22463b6e1eee --- /dev/null +++ b/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_ACTION_EXECUTION.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.AutoModerationActionExecution.handle(packet.d); +}; diff --git a/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_CREATE.js b/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_CREATE.js new file mode 100644 index 000000000000..af64b9cbcc98 --- /dev/null +++ b/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.AutoModerationRuleCreate.handle(packet.d); +}; diff --git a/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_DELETE.js b/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_DELETE.js new file mode 100644 index 000000000000..56ec504a9460 --- /dev/null +++ b/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.AutoModerationRuleDelete.handle(packet.d); +}; diff --git a/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_UPDATE.js b/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_UPDATE.js new file mode 100644 index 000000000000..3caf6ba55028 --- /dev/null +++ b/packages/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.AutoModerationRuleUpdate.handle(packet.d); +}; diff --git a/packages/discord.js/src/client/websocket/handlers/index.js b/packages/discord.js/src/client/websocket/handlers/index.js index c6b6bf53b4d6..ee90befafe7d 100644 --- a/packages/discord.js/src/client/websocket/handlers/index.js +++ b/packages/discord.js/src/client/websocket/handlers/index.js @@ -2,6 +2,10 @@ const handlers = Object.fromEntries([ ['APPLICATION_COMMAND_PERMISSIONS_UPDATE', require('./APPLICATION_COMMAND_PERMISSIONS_UPDATE')], + ['AUTO_MODERATION_ACTION_EXECUTION', require('./AUTO_MODERATION_ACTION_EXECUTION')], + ['AUTO_MODERATION_RULE_CREATE', require('./AUTO_MODERATION_RULE_CREATE')], + ['AUTO_MODERATION_RULE_DELETE', require('./AUTO_MODERATION_RULE_DELETE')], + ['AUTO_MODERATION_RULE_UPDATE', require('./AUTO_MODERATION_RULE_UPDATE')], ['CHANNEL_CREATE', require('./CHANNEL_CREATE')], ['CHANNEL_DELETE', require('./CHANNEL_DELETE')], ['CHANNEL_PINS_UPDATE', require('./CHANNEL_PINS_UPDATE')], diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 61927e2b964c..35f885ed2f98 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -48,6 +48,7 @@ exports.version = require('../package.json').version; // Managers exports.ApplicationCommandManager = require('./managers/ApplicationCommandManager'); exports.ApplicationCommandPermissionsManager = require('./managers/ApplicationCommandPermissionsManager'); +exports.AutoModerationRuleManager = require('./managers/AutoModerationRuleManager'); exports.BaseGuildEmojiManager = require('./managers/BaseGuildEmojiManager'); exports.CachedManager = require('./managers/CachedManager'); exports.ChannelManager = require('./managers/ChannelManager'); @@ -88,6 +89,8 @@ exports.AnonymousGuild = require('./structures/AnonymousGuild'); exports.Application = require('./structures/interfaces/Application'); exports.ApplicationCommand = require('./structures/ApplicationCommand'); exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction'); +exports.AutoModerationActionExecution = require('./structures/AutoModerationActionExecution'); +exports.AutoModerationRule = require('./structures/AutoModerationRule'); exports.Base = require('./structures/Base'); exports.BaseGuild = require('./structures/BaseGuild'); exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji'); diff --git a/packages/discord.js/src/managers/AutoModerationRuleManager.js b/packages/discord.js/src/managers/AutoModerationRuleManager.js new file mode 100644 index 000000000000..22a88122c934 --- /dev/null +++ b/packages/discord.js/src/managers/AutoModerationRuleManager.js @@ -0,0 +1,276 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const AutoModerationRule = require('../structures/AutoModerationRule'); + +/** + * Manages API methods for auto moderation rules and stores their cache. + * @extends {CachedManager} + */ +class AutoModerationRuleManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, AutoModerationRule, iterable); + + /** + * The guild this manager belongs to. + * @type {Guild} + */ + this.guild = guild; + } + + _add(data, cache) { + return super._add(data, cache, { extras: [this.guild] }); + } + + /** + * Options used to set the trigger metadata of an auto moderation rule. + * @typedef {Object} AutoModerationTriggerMetadataOptions + * @property {string[]} [keywordFilter] The substrings that will be searched for in the content + * @property {string[]} [regexPatterns] The regular expression patterns which will be matched against the content + * Only Rust-flavored regular expressions are supported. + * @property {AutoModerationRuleKeywordPresetType[]} [presets] + * The internally pre-defined wordsets which will be searched for in the content + * @property {string[]} [allowList] The substrings that will be exempt from triggering + * {@link AutoModerationRuleTriggerType.Keyword} and {@link AutoModerationRuleTriggerType.KeywordPreset} + * @property {?number} [mentionTotalLimit] The total number of role & user mentions allowed per message + */ + + /** + * Options used to set the actions of an auto moderation rule. + * @typedef {Object} AutoModerationActionOptions + * @property {AutoModerationActionType} type The type of this auto moderation rule action + * @property {AutoModerationActionMetadataOptions} [metadata] Additional metadata needed during execution + * This property is required if using a `type` of + * {@link AutoModerationActionType.SendAlertMessage} or {@link AutoModerationActionType.Timeout}. + */ + + /** + * Options used to set the metadata of an auto moderation rule action. + * @typedef {Object} AutoModerationActionMetadataOptions + * @property {GuildTextChannelResolvable|ThreadChannel} [channel] The channel to which content will be logged + * @property {number} [durationSeconds] The timeout duration in seconds + */ + + /** + * Options used to create an auto moderation rule. + * @typedef {Object} AutoModerationRuleCreateOptions + * @property {string} name The name of the auto moderation rule + * @property {AutoModerationRuleEventType} eventType The event type of the auto moderation rule + * @property {AutoModerationRuleTriggerType} triggerType The trigger type of the auto moderation rule + * @property {AutoModerationTriggerMetadataOptions} [triggerMetadata] The trigger metadata of the auto moderation rule + * This property is required if using a `triggerType` of + * {@link AutoModerationRuleTriggerType.Keyword}, {@link AutoModerationRuleTriggerType.KeywordPreset}, + * or {@link AutoModerationRuleTriggerType.MentionSpam}. + * @property {AutoModerationActionOptions[]} actions + * The actions that will execute when the auto moderation rule is triggered + * @property {boolean} [enabled] Whether the auto moderation rule should be enabled + * @property {Collection|RoleResolvable[]} [exemptRoles] + * The roles that should not be affected by the auto moderation rule + * @property {Collection|GuildChannelResolvable[]} [exemptChannels] + * The channels that should not be affected by the auto moderation rule + * @property {string} [reason] The reason for creating the auto moderation rule + */ + + /** + * Creates a new auto moderation rule. + * @param {AutoModerationRuleCreateOptions} options Options for creating the auto moderation rule + * @returns {Promise} + */ + async create({ + name, + eventType, + triggerType, + triggerMetadata, + actions, + enabled, + exemptRoles, + exemptChannels, + reason, + }) { + const data = await this.client.rest.post(Routes.guildAutoModerationRules(this.guild.id), { + body: { + name, + event_type: eventType, + trigger_type: triggerType, + trigger_metadata: triggerMetadata && { + keyword_filter: triggerMetadata.keywordFilter, + regex_patterns: triggerMetadata.regexPatterns, + presets: triggerMetadata.presets, + allow_list: triggerMetadata.allowList, + mention_total_limit: triggerMetadata.mentionTotalLimit, + }, + actions: actions.map(action => ({ + type: action.type, + metadata: { + duration_seconds: action.metadata?.durationSeconds, + channel_id: action.metadata?.channel && this.guild.channels.resolveId(action.metadata.channel), + }, + })), + enabled, + exempt_roles: exemptRoles?.map(exemptRole => this.guild.roles.resolveId(exemptRole)), + exempt_channels: exemptChannels?.map(exemptChannel => this.guild.channels.resolveId(exemptChannel)), + }, + reason, + }); + + return this._add(data); + } + + /** + * Options used to edit an auto moderation rule. + * @typedef {Object} AutoModerationRuleEditOptions + * @property {string} [name] The name of the auto moderation rule + * @property {AutoModerationRuleEventType} [eventType] The event type of the auto moderation rule + * @property {AutoModerationTriggerMetadataOptions} [triggerMetadata] The trigger metadata of the auto moderation rule + * @property {AutoModerationActionOptions[]} [actions] + * The actions that will execute when the auto moderation rule is triggered + * @property {boolean} [enabled] Whether the auto moderation rule should be enabled + * @property {Collection|RoleResolvable[]} [exemptRoles] + * The roles that should not be affected by the auto moderation rule + * @property {Collection|GuildChannelResolvable[]} [exemptChannels] + * The channels that should not be affected by the auto moderation rule + * @property {string} [reason] The reason for creating the auto moderation rule + */ + + /** + * Edits an auto moderation rule. + * @param {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to edit + * @param {AutoModerationRuleEditOptions} options Options for editing the auto moderation rule + * @returns {Promise} + */ + async edit( + autoModerationRule, + { name, eventType, triggerMetadata, actions, enabled, exemptRoles, exemptChannels, reason }, + ) { + const autoModerationRuleId = this.resolveId(autoModerationRule); + + const data = await this.client.rest.patch(Routes.guildAutoModerationRule(this.guild.id, autoModerationRuleId), { + body: { + name, + event_type: eventType, + trigger_metadata: triggerMetadata && { + keyword_filter: triggerMetadata.keywordFilter, + regex_patterns: triggerMetadata.regexPatterns, + presets: triggerMetadata.presets, + allow_list: triggerMetadata.allowList, + mention_total_limit: triggerMetadata.mentionTotalLimit, + }, + actions: actions?.map(action => ({ + type: action.type, + metadata: { + duration_seconds: action.metadata?.durationSeconds, + channel_id: action.metadata?.channel && this.guild.channels.resolveId(action.metadata.channel), + }, + })), + enabled, + exempt_roles: exemptRoles?.map(exemptRole => this.guild.roles.resolveId(exemptRole)), + exempt_channels: exemptChannels?.map(exemptChannel => this.guild.channels.resolveId(exemptChannel)), + }, + reason, + }); + + return this._add(data); + } + + /** + * Data that can be resolved to give an AutoModerationRule object. This can be: + * * An AutoModerationRule + * * A Snowflake + * @typedef {AutoModerationRule|Snowflake} AutoModerationRuleResolvable + */ + + /** + * Options used to fetch a single auto moderation rule from a guild. + * @typedef {BaseFetchOptions} FetchAutoModerationRuleOptions + * @property {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to fetch + */ + + /** + * Options used to fetch all auto moderation rules from a guild. + * @typedef {Object} FetchAutoModerationRulesOptions + * @property {boolean} [cache] Whether to cache the fetched auto moderation rules + */ + + /** + * Fetches auto moderation rules from Discord. + * @param {AutoModerationRuleResolvable|FetchAutoModerationRuleOptions|FetchAutoModerationRulesOptions} [options] + * Options for fetching auto moderation rule(s) + * @returns {Promise>} + * @example + * // Fetch all auto moderation rules from a guild without caching + * guild.autoModerationRules.fetch({ cache: false }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single auto moderation rule + * guild.autoModerationRules.fetch('979083472868098119') + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single auto moderation rule without checking cache and without caching + * guild.autoModerationRules.fetch({ autoModerationRule: '979083472868098119', cache: false, force: true }) + * .then(console.log) + * .catch(console.error) + */ + fetch(options) { + if (!options) return this._fetchMany(); + const { autoModerationRule, cache, force } = options; + const resolvedAutoModerationRule = this.resolveId(autoModerationRule ?? options); + if (resolvedAutoModerationRule) { + return this._fetchSingle({ autoModerationRule: resolvedAutoModerationRule, cache, force }); + } + return this._fetchMany(options); + } + + async _fetchSingle({ autoModerationRule, cache, force = false }) { + if (!force) { + const existing = this.cache.get(autoModerationRule); + if (existing) return existing; + } + + const data = await this.client.rest.get(Routes.guildAutoModerationRule(this.guild.id, autoModerationRule)); + return this._add(data, cache); + } + + async _fetchMany(options = {}) { + const data = await this.client.rest.get(Routes.guildAutoModerationRules(this.guild.id)); + + return data.reduce( + (col, autoModerationRule) => col.set(autoModerationRule.id, this._add(autoModerationRule, options.cache)), + new Collection(), + ); + } + + /** + * Deletes an auto moderation rule. + * @param {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to delete + * @param {string} [reason] The reason for deleting the auto moderation rule + * @returns {Promise} + */ + async delete(autoModerationRule, reason) { + const autoModerationRuleId = this.resolveId(autoModerationRule); + await this.client.rest.delete(Routes.guildAutoModerationRule(this.guild.id, autoModerationRuleId), { reason }); + } + + /** + * Resolves an {@link AutoModerationRuleResolvable} to an {@link AutoModerationRule} object. + * @method resolve + * @memberof AutoModerationRuleManager + * @instance + * @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve + * @returns {?AutoModerationRule} + */ + + /** + * Resolves an {@link AutoModerationRuleResolvable} to a {@link AutoModerationRule} id. + * @method resolveId + * @memberof AutoModerationRuleManager + * @instance + * @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve + * @returns {?Snowflake} + */ +} + +module.exports = AutoModerationRuleManager; diff --git a/packages/discord.js/src/structures/AutoModerationActionExecution.js b/packages/discord.js/src/structures/AutoModerationActionExecution.js new file mode 100644 index 000000000000..45c2949ab1a6 --- /dev/null +++ b/packages/discord.js/src/structures/AutoModerationActionExecution.js @@ -0,0 +1,87 @@ +'use strict'; + +/** + * Represents the structure of an executed action when an {@link AutoModerationRule} is triggered. + */ +class AutoModerationActionExecution { + constructor(data, guild) { + /** + * The guild where this action was executed from. + * @type {Guild} + */ + this.guild = guild; + + /** + * The action that was executed. + * @type {AutoModerationAction} + */ + this.action = data.action; + + /** + * The id of the auto moderation rule this action belongs to. + * @type {Snowflake} + */ + this.ruleId = data.rule_id; + + /** + * The trigger type of the auto moderation rule which was triggered. + * @type {AutoModerationRuleTriggerType} + */ + this.ruleTriggerType = data.rule_trigger_type; + + /** + * The id of the user that triggered this action. + * @type {Snowflake} + */ + this.userId = data.user_id; + + /** + * The id of the channel where this action was triggered from. + * @type {?Snowflake} + */ + this.channelId = data.channel_id ?? null; + + /** + * The id of the message that triggered this action. + * @type {?Snowflake} + * This will not be present if the message was blocked or the content was not part of any message. + */ + this.messageId = data.message_id ?? null; + + /** + * The id of any system auto moderation messages posted as a result of this action. + * @type {?Snowflake} + */ + this.alertSystemMessageId = data.alert_system_message_id ?? null; + + /** + * The content that triggered this action. + * This property requires the {@link GatewayIntentBits.MessageContent} privileged gateway intent. + * @type {string} + */ + this.content = data.content; + + /** + * The word or phrase configured in the rule that triggered this action. + * @type {?string} + */ + this.matchedKeyword = data.matched_keyword ?? null; + + /** + * The substring in content that triggered this action. + * @type {?string} + */ + this.matchedContent = data.matched_content ?? null; + } + + /** + * The auto moderation rule this action belongs to. + * @type {?AutoModerationRule} + * @readonly + */ + get autoModerationRule() { + return this.guild.autoModerationRules.cache.get(this.ruleId) ?? null; + } +} + +module.exports = AutoModerationActionExecution; diff --git a/packages/discord.js/src/structures/AutoModerationRule.js b/packages/discord.js/src/structures/AutoModerationRule.js new file mode 100644 index 000000000000..2939e790983c --- /dev/null +++ b/packages/discord.js/src/structures/AutoModerationRule.js @@ -0,0 +1,273 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); + +/** + * Represents an auto moderation rule. + * @extends {Base} + */ +class AutoModerationRule extends Base { + constructor(client, data, guild) { + super(client); + + /** + * The id of this auto moderation rule. + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The guild this auto moderation rule is for. + * @type {Guild} + */ + this.guild = guild; + + /** + * The user that created this auto moderation rule. + * @type {Snowflake} + */ + this.creatorId = data.creator_id; + + /** + * The trigger type of this auto moderation rule. + * @type {AutoModerationRuleTriggerType} + */ + this.triggerType = data.trigger_type; + + this._patch(data); + } + + _patch(data) { + if ('name' in data) { + /** + * The name of this auto moderation rule. + * @type {string} + */ + this.name = data.name; + } + + if ('event_type' in data) { + /** + * The event type of this auto moderation rule. + * @type {AutoModerationRuleEventType} + */ + this.eventType = data.event_type; + } + + if ('trigger_metadata' in data) { + /** + * Additional data used to determine whether an auto moderation rule should be triggered. + * @typedef {Object} AutoModerationTriggerMetadata + * @property {string[]} keywordFilter The substrings that will be searched for in the content + * @property {string[]} regexPatterns The regular expression patterns which will be matched against the content + * Only Rust-flavored regular expressions are supported. + * @property {AutoModerationRuleKeywordPresetType[]} presets + * The internally pre-defined wordsets which will be searched for in the content + * @property {string[]} allowList The substrings that will be exempt from triggering + * {@link AutoModerationRuleTriggerType.Keyword} and {@link AutoModerationRuleTriggerType.KeywordPreset} + * @property {?number} mentionTotalLimit The total number of role & user mentions allowed per message + */ + + /** + * The trigger metadata of the rule. + * @type {AutoModerationTriggerMetadata} + */ + this.triggerMetadata = { + keywordFilter: data.trigger_metadata.keyword_filter ?? [], + regexPatterns: data.trigger_metadata.regex_patterns ?? [], + presets: data.trigger_metadata.presets ?? [], + allowList: data.trigger_metadata.allow_list ?? [], + mentionTotalLimit: data.trigger_metadata.mention_total_limit ?? null, + }; + } + + if ('actions' in data) { + /** + * An object containing information about an auto moderation rule action. + * @typedef {Object} AutoModerationAction + * @property {AutoModerationActionType} type The type of this auto moderation rule action + * @property {AutoModerationActionMetadata} metadata Additional metadata needed during execution + */ + + /** + * Additional data used when an auto moderation rule is executed. + * @typedef {Object} AutoModerationActionMetadata + * @property {?Snowflake} channelId The id of the channel to which content will be logged + * @property {?number} durationSeconds The timeout duration in seconds + */ + + /** + * The actions of this auto moderation rule. + * @type {AutoModerationAction[]} + */ + this.actions = data.actions.map(action => ({ + type: action.type, + metadata: { + durationSeconds: action.metadata.duration_seconds ?? null, + channelId: action.metadata.channel_id ?? null, + }, + })); + } + + if ('enabled' in data) { + /** + * Whether this auto moderation rule is enabled. + * @type {boolean} + */ + this.enabled = data.enabled; + } + + if ('exempt_roles' in data) { + /** + * The roles exempt by this auto moderation rule. + * @type {Collection} + */ + this.exemptRoles = new Collection( + data.exempt_roles.map(exemptRole => [exemptRole, this.guild.roles.cache.get(exemptRole)]), + ); + } + + if ('exempt_channels' in data) { + /** + * The channels exempt by this auto moderation rule. + * @type {Collection} + */ + this.exemptChannels = new Collection( + data.exempt_channels.map(exemptChannel => [exemptChannel, this.guild.channels.cache.get(exemptChannel)]), + ); + } + } + + /** + * Edits this auto moderation rule. + * @param {AutoModerationRuleEditOptions} options Options for editing this auto moderation rule + * @returns {Promise} + */ + edit(options) { + return this.guild.autoModerationRules.edit(this.id, options); + } + + /** + * Deletes this auto moderation rule. + * @param {string} [reason] The reason for deleting this auto moderation rule + * @returns {Promise} + */ + delete(reason) { + return this.guild.autoModerationRules.delete(this.id, reason); + } + + /** + * Sets the name for this auto moderation rule. + * @param {string} name The name of this auto moderation rule + * @param {string} [reason] The reason for changing the name of this auto moderation rule + * @returns {Promise} + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Sets the event type for this auto moderation rule. + * @param {AutoModerationRuleEventType} eventType The event type of this auto moderation rule + * @param {string} [reason] The reason for changing the event type of this auto moderation rule + * @returns {Promise} + */ + setEventType(eventType, reason) { + return this.edit({ eventType, reason }); + } + + /** + * Sets the keyword filter for this auto moderation rule. + * @param {string[]} keywordFilter The keyword filter of this auto moderation rule + * @param {string} [reason] The reason for changing the keyword filter of this auto moderation rule + * @returns {Promise} + */ + setKeywordFilter(keywordFilter, reason) { + return this.edit({ triggerMetadata: { keywordFilter }, reason }); + } + + /** + * Sets the regular expression patterns for this auto moderation rule. + * @param {string[]} regexPatterns The regular expression patterns of this auto moderation rule + * Only Rust-flavored regular expressions are supported. + * @param {string} [reason] The reason for changing the regular expression patterns of this auto moderation rule + * @returns {Promise} + */ + setRegexPatterns(regexPatterns, reason) { + return this.edit({ triggerMetadata: { regexPatterns }, reason }); + } + + /** + * Sets the presets for this auto moderation rule. + * @param {AutoModerationRuleKeywordPresetType[]} presets The presets of this auto moderation rule + * @param {string} [reason] The reason for changing the presets of this auto moderation rule + * @returns {Promise} + */ + setPresets(presets, reason) { + return this.edit({ triggerMetadata: { presets }, reason }); + } + + /** + * Sets the allow list for this auto moderation rule. + * @param {string[]} allowList The allow list of this auto moderation rule + * @param {string} [reason] The reason for changing the allow list of this auto moderation rule + * @returns {Promise} + */ + setAllowList(allowList, reason) { + return this.edit({ triggerMetadata: { allowList }, reason }); + } + + /** + * Sets the mention total limit for this auto moderation rule. + * @param {number} mentionTotalLimit The mention total limit of this auto moderation rule + * @param {string} [reason] The reason for changing the mention total limit of this auto moderation rule + * @returns {Promise} + */ + setMentionTotalLimit(mentionTotalLimit, reason) { + return this.edit({ triggerMetadata: { mentionTotalLimit }, reason }); + } + + /** + * Sets the actions for this auto moderation rule. + * @param {AutoModerationActionOptions} actions The actions of this auto moderation rule + * @param {string} [reason] The reason for changing the actions of this auto moderation rule + * @returns {Promise} + */ + setActions(actions, reason) { + return this.edit({ actions, reason }); + } + + /** + * Sets whether this auto moderation rule should be enabled. + * @param {boolean} [enabled=true] Whether to enable this auto moderation rule + * @param {string} [reason] The reason for enabling or disabling this auto moderation rule + * @returns {Promise} + */ + setEnabled(enabled = true, reason) { + return this.edit({ enabled, reason }); + } + + /** + * Sets the exempt roles for this auto moderation rule. + * @param {Collection|RoleResolvable[]} [exemptRoles] The exempt roles of this auto moderation rule + * @param {string} [reason] The reason for changing the exempt roles of this auto moderation rule + * @returns {Promise} + */ + setExemptRoles(exemptRoles, reason) { + return this.edit({ exemptRoles, reason }); + } + + /** + * Sets the exempt channels for this auto moderation rule. + * @param {Collection|GuildChannelResolvable[]} [exemptChannels] + * The exempt channels of this auto moderation rule + * @param {string} [reason] The reason for changing the exempt channels of this auto moderation rule + * @returns {Promise} + */ + setExemptChannels(exemptChannels, reason) { + return this.edit({ exemptChannels, reason }); + } +} + +module.exports = AutoModerationRule; diff --git a/packages/discord.js/src/structures/Guild.js b/packages/discord.js/src/structures/Guild.js index ba1d2b3b42ae..ae925b5ca5b0 100644 --- a/packages/discord.js/src/structures/Guild.js +++ b/packages/discord.js/src/structures/Guild.js @@ -12,6 +12,7 @@ const Integration = require('./Integration'); const Webhook = require('./Webhook'); const WelcomeScreen = require('./WelcomeScreen'); const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); +const AutoModerationRuleManager = require('../managers/AutoModerationRuleManager'); const GuildApplicationCommandManager = require('../managers/GuildApplicationCommandManager'); const GuildBanManager = require('../managers/GuildBanManager'); const GuildChannelManager = require('../managers/GuildChannelManager'); @@ -99,6 +100,12 @@ class Guild extends AnonymousGuild { */ this.scheduledEvents = new GuildScheduledEventManager(this); + /** + * A manager of the auto moderation rules of this guild. + * @type {AutoModerationRuleManager} + */ + this.autoModerationRules = new AutoModerationRuleManager(this); + if (!data) return; if (data.unavailable) { /** diff --git a/packages/discord.js/src/structures/GuildAuditLogs.js b/packages/discord.js/src/structures/GuildAuditLogs.js index b5635a37807d..e4ddfe0ab726 100644 --- a/packages/discord.js/src/structures/GuildAuditLogs.js +++ b/packages/discord.js/src/structures/GuildAuditLogs.js @@ -61,6 +61,17 @@ class GuildAuditLogs { } } + /** + * Cached auto moderation rules. + * @type {Collection} + * @private + */ + this.autoModerationRules = data.auto_moderation_rules.reduce( + (autoModerationRules, autoModerationRule) => + autoModerationRules.set(autoModerationRule.id, guild.autoModerationRules._add(autoModerationRule)), + new Collection(), + ); + /** * The entries for this guild's audit logs * @type {Collection} diff --git a/packages/discord.js/src/structures/GuildAuditLogsEntry.js b/packages/discord.js/src/structures/GuildAuditLogsEntry.js index dde8b6e136a0..b5c1620c604d 100644 --- a/packages/discord.js/src/structures/GuildAuditLogsEntry.js +++ b/packages/discord.js/src/structures/GuildAuditLogsEntry.js @@ -2,6 +2,7 @@ const { DiscordSnowflake } = require('@sapphire/snowflake'); const { AuditLogOptionsType, AuditLogEvent } = require('discord-api-types/v10'); +const AutoModerationRule = require('./AutoModerationRule'); const { GuildScheduledEvent } = require('./GuildScheduledEvent'); const Integration = require('./Integration'); const Invite = require('./Invite'); @@ -27,6 +28,7 @@ const Targets = { Sticker: 'Sticker', Thread: 'Thread', ApplicationCommand: 'ApplicationCommand', + AutoModeration: 'AutoModeration', Unknown: 'Unknown', }; @@ -46,10 +48,11 @@ const Targets = { * * A guild scheduled event * * A thread * * An application command + * * An auto moderation rule * * An object with an id key if target was deleted or fake entity * * An object where the keys represent either the new value or the old value * @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker| - * GuildScheduledEvent|ApplicationCommand)} AuditLogEntryTarget + * GuildScheduledEvent|ApplicationCommand|AutoModerationRule)} AuditLogEntryTarget */ /** @@ -223,6 +226,15 @@ class GuildAuditLogsEntry { }; break; + case AuditLogEvent.AutoModerationBlockMessage: + case AuditLogEvent.AutoModerationFlagToChannel: + case AuditLogEvent.AutoModerationUserCommunicationDisabled: + this.extra = { + autoModerationRuleName: data.options.auto_moderation_rule_name, + autoModerationRuleTriggerType: data.options.auto_moderation_rule_trigger_type, + }; + break; + default: break; } @@ -352,6 +364,20 @@ class GuildAuditLogsEntry { ); } else if (targetType === Targets.ApplicationCommand) { this.target = logs.applicationCommands.get(data.target_id) ?? { id: data.target_id }; + } else if (targetType === Targets.AutoModeration) { + this.target = + guild.autoModerationRules.cache.get(data.target_id) ?? + new AutoModerationRule( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { id: data.target_id, guild_id: guild.id }, + ), + guild, + ); } else if (data.target_id) { this.target = guild[`${targetType.toLowerCase()}s`]?.cache.get(data.target_id) ?? { id: data.target_id }; } @@ -377,6 +403,7 @@ class GuildAuditLogsEntry { if (target < 110) return Targets.GuildScheduledEvent; if (target < 120) return Targets.Thread; if (target < 130) return Targets.ApplicationCommand; + if (target >= 140 && target < 150) return Targets.AutoModeration; return Targets.Unknown; } @@ -402,6 +429,8 @@ class GuildAuditLogsEntry { AuditLogEvent.StickerCreate, AuditLogEvent.GuildScheduledEventCreate, AuditLogEvent.ThreadCreate, + AuditLogEvent.AutoModerationRuleCreate, + AuditLogEvent.AutoModerationBlockMessage, ].includes(action) ) { return 'Create'; @@ -427,6 +456,7 @@ class GuildAuditLogsEntry { AuditLogEvent.StickerDelete, AuditLogEvent.GuildScheduledEventDelete, AuditLogEvent.ThreadDelete, + AuditLogEvent.AutoModerationRuleDelete, ].includes(action) ) { return 'Delete'; @@ -450,6 +480,7 @@ class GuildAuditLogsEntry { AuditLogEvent.GuildScheduledEventUpdate, AuditLogEvent.ThreadUpdate, AuditLogEvent.ApplicationCommandPermissionUpdate, + AuditLogEvent.AutoModerationRuleUpdate, ].includes(action) ) { return 'Update'; diff --git a/packages/discord.js/src/util/APITypes.js b/packages/discord.js/src/util/APITypes.js index cfde0d4dbe95..553d6aeb3e39 100644 --- a/packages/discord.js/src/util/APITypes.js +++ b/packages/discord.js/src/util/APITypes.js @@ -168,6 +168,26 @@ * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationFlags} */ +/** + * @external AutoModerationActionType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AutoModerationActionType} + */ + +/** + * @external AutoModerationRuleEventType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AutoModerationRuleEventType} + */ + +/** + * @external AutoModerationRuleTriggerType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AutoModerationRuleTriggerType} + */ + +/** + * @external AutoModerationRuleKeywordPresetType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AutoModerationRuleKeywordPresetType} + */ + /** * @external AuditLogEvent * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AuditLogEvent} diff --git a/packages/discord.js/src/util/Constants.js b/packages/discord.js/src/util/Constants.js index 3f06c3fd50d1..b43e610ba8a2 100644 --- a/packages/discord.js/src/util/Constants.js +++ b/packages/discord.js/src/util/Constants.js @@ -10,6 +10,7 @@ exports.MaxBulkDeletableMessageAge = 1_209_600_000; /** * The name of an item to be swept in Sweepers + * * `autoModerationRules` * * `applicationCommands` - both global and guild commands * * `bans` * * `emojis` @@ -27,6 +28,7 @@ exports.MaxBulkDeletableMessageAge = 1_209_600_000; * @typedef {string} SweeperKey */ exports.SweeperKeys = [ + 'autoModerationRules', 'applicationCommands', 'bans', 'emojis', diff --git a/packages/discord.js/src/util/Events.js b/packages/discord.js/src/util/Events.js index 38f81a0a8513..630034a10af5 100644 --- a/packages/discord.js/src/util/Events.js +++ b/packages/discord.js/src/util/Events.js @@ -79,6 +79,10 @@ */ module.exports = { ApplicationCommandPermissionsUpdate: 'applicationCommandPermissionsUpdate', + AutoModerationActionExecution: 'autoModerationActionExecution', + AutoModerationRuleCreate: 'autoModerationRuleCreate', + AutoModerationRuleDelete: 'autoModerationRuleDelete', + AutoModerationRuleUpdate: 'autoModerationRuleUpdate', CacheSweep: 'cacheSweep', ChannelCreate: 'channelCreate', ChannelDelete: 'channelDelete', diff --git a/packages/discord.js/src/util/Sweepers.js b/packages/discord.js/src/util/Sweepers.js index 83892592ac6f..7796ddc99a96 100644 --- a/packages/discord.js/src/util/Sweepers.js +++ b/packages/discord.js/src/util/Sweepers.js @@ -78,6 +78,16 @@ class Sweepers { return guildCommands + globalCommands; } + /** + * Sweeps all auto moderation rules and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine + * which auto moderation rules will be removed from the caches + * @returns {number} Amount of auto moderation rules that were removed from the caches + */ + sweepAutoModerationRules(filter) { + return this._sweepGuildDirectProp('autoModerationRules', filter).items; + } + /** * Sweeps all guild bans and removes the ones which are indicated by the filter. * @param {Function} filter The function used to determine which bans will be removed from the caches. diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 7aa467adbc79..eaa6e83bad87 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -139,6 +139,13 @@ import { APIMessageRoleSelectInteractionData, APIMessageMentionableSelectInteractionData, APIMessageChannelSelectInteractionData, + AutoModerationRuleKeywordPresetType, + AutoModerationActionType, + AutoModerationRuleEventType, + AutoModerationRuleTriggerType, + AuditLogRuleTriggerType, + GatewayAutoModerationActionExecutionDispatchData, + APIAutoModerationRule, } from 'discord-api-types/v10'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -319,6 +326,56 @@ export abstract class AnonymousGuild extends BaseGuild { public splashURL(options?: ImageURLOptions): string | null; } +export class AutoModerationActionExecution { + private constructor(data: GatewayAutoModerationActionExecutionDispatchData, guild: Guild); + public guild: Guild; + public action: AutoModerationAction; + public ruleId: Snowflake; + public ruleTriggerType: AutoModerationRuleTriggerType; + public userId: Snowflake; + public channelId: Snowflake | null; + public messageId: Snowflake | null; + public alertSystemMessageId: Snowflake | null; + public content: string; + public matchedKeyword: string | null; + public matchedContent: string | null; + public get autoModerationRule(): AutoModerationRule | null; +} + +export class AutoModerationRule extends Base { + private constructor(client: Client, data: APIAutoModerationRule, guild: Guild); + public id: Snowflake; + public guild: Guild; + public name: string; + public creatorId: Snowflake; + public eventType: AutoModerationRuleEventType; + public triggerType: AutoModerationRuleTriggerType; + public triggerMetadata: AutoModerationTriggerMetadata; + public actions: AutoModerationAction[]; + public enabled: boolean; + public exemptRoles: Collection; + public exemptChannels: Collection; + public edit(options: AutoModerationRuleEditOptions): Promise; + public delete(reason?: string): Promise; + public setName(name: string, reason?: string): Promise; + public setEventType(eventType: AutoModerationRuleEventType, reason?: string): Promise; + public setKeywordFilter(keywordFilter: string[], reason?: string): Promise; + public setRegexPatterns(regexPatterns: string[], reason?: string): Promise; + public setPresets(presets: AutoModerationRuleKeywordPresetType[], reason?: string): Promise; + public setAllowList(allowList: string[], reason?: string): Promise; + public setMentionTotalLimit(mentionTotalLimit: number, reason?: string): Promise; + public setActions(actions: AutoModerationActionOptions, reason?: string): Promise; + public setEnabled(enabled?: boolean, reason?: string): Promise; + public setExemptRoles( + roles: Collection | RoleResolvable[], + reason?: string, + ): Promise; + public setExemptChannels( + channels: Collection | GuildChannelResolvable[], + reason?: string, + ): Promise; +} + export abstract class Application extends Base { protected constructor(client: Client, data: RawApplicationData); public get createdAt(): Date; @@ -398,6 +455,8 @@ export class ApplicationFlagsBitField extends BitField { public static resolve(bit?: BitFieldResolvable): number; } +export type AutoModerationRuleResolvable = AutoModerationRule | Snowflake; + export abstract class Base { public constructor(client: Client); public readonly client: Client; @@ -1157,6 +1216,7 @@ export class Guild extends AnonymousGuild { public maxVideoChannelUsers: number | null; public approximateMemberCount: number | null; public approximatePresenceCount: number | null; + public autoModerationRules: AutoModerationRuleManager; public available: boolean; public bans: GuildBanManager; public channels: GuildChannelManager; @@ -1255,6 +1315,7 @@ export class GuildAuditLogs private webhooks: Collection; private integrations: Collection; private guildScheduledEvents: Collection; + private autoModerationRules: Collection; public entries: Collection>; public toJSON(): unknown; } @@ -2671,6 +2732,12 @@ export class Sweepers { SweeperDefinitions['applicationCommands'][1] >, ): number; + public sweepAutoModerationRules( + filter: CollectionSweepFilter< + SweeperDefinitions['autoModerationRules'][0], + SweeperDefinitions['autoModerationRules'][1] + >, + ): number; public sweepBans(filter: CollectionSweepFilter): number; public sweepEmojis( filter: CollectionSweepFilter, @@ -3610,6 +3677,23 @@ export class ApplicationCommandPermissionsManager< private permissionsPath(guildId: Snowflake, commandId?: Snowflake): string; } +export class AutoModerationRuleManager extends CachedManager< + Snowflake, + AutoModerationRule, + AutoModerationRuleResolvable +> { + private constructor(guild: Guild, iterable: unknown); + public guild: Guild; + public create(options: AutoModerationRuleCreateOptions): Promise; + public edit( + autoModerationRule: AutoModerationRuleResolvable, + options: AutoModerationRuleEditOptions, + ): Promise; + public fetch(options: AutoModerationRuleResolvable | FetchAutoModerationRuleOptions): Promise; + public fetch(options?: FetchAutoModerationRulesOptions): Promise>; + public delete(autoModerationRule: AutoModerationRuleResolvable, reason?: string): Promise; +} + export class BaseGuildEmojiManager extends CachedManager { protected constructor(client: Client, iterable?: Iterable); public resolveIdentifier(emoji: EmojiIdentifierResolvable): string | null; @@ -4362,6 +4446,24 @@ export interface AuditLogChange { new?: APIAuditLogChange['new_value']; } +export interface AutoModerationAction { + type: AutoModerationActionType; + metadata: AutoModerationActionMetadata; +} + +export interface AutoModerationActionMetadata { + channelId: Snowflake | null; + durationSeconds: number | null; +} + +export interface AutoModerationTriggerMetadata { + keywordFilter: string[]; + regexPatterns: string[]; + presets: AutoModerationRuleKeywordPresetType[]; + allowList: string[]; + mentionTotalLimit: number | null; +} + export type AwaitMessageComponentOptions = Omit< MessageComponentCollectorOptions, 'max' | 'maxComponents' | 'maxUsers' @@ -4413,6 +4515,7 @@ export type BitFieldResolvable = export type BufferResolvable = Buffer | string; export interface Caches { + AutoModerationRuleManager: [manager: typeof AutoModerationRuleManager, holds: typeof AutoModerationRule]; ApplicationCommandManager: [manager: typeof ApplicationCommandManager, holds: typeof ApplicationCommand]; BaseGuildEmojiManager: [manager: typeof BaseGuildEmojiManager, holds: typeof GuildEmoji]; GuildEmojiManager: [manager: typeof GuildEmojiManager, holds: typeof GuildEmoji]; @@ -4505,6 +4608,13 @@ export interface WebhookCreateOptions extends ChannelWebhookCreateOptions { export interface ClientEvents { applicationCommandPermissionsUpdate: [data: ApplicationCommandPermissionsUpdateData]; + autoModerationActionExecution: [autoModerationActionExecution: AutoModerationActionExecution]; + autoModerationRuleCreate: [autoModerationRule: AutoModerationRule]; + autoModerationRuleDelete: [autoModerationRule: AutoModerationRule]; + autoModerationRuleUpdate: [ + oldAutoModerationRule: AutoModerationRule | null, + newAutoModerationRule: AutoModerationRule, + ]; cacheSweep: [message: string]; channelCreate: [channel: NonThreadGuildBasedChannel]; channelDelete: [channel: DMChannel | NonThreadGuildBasedChannel]; @@ -4720,6 +4830,10 @@ export declare const Colors: { export enum Events { ApplicationCommandPermissionsUpdate = 'applicationCommandPermissionsUpdate', + AutoModerationActionExecution = 'autoModerationActionExecution', + AutoModerationRuleCreate = 'autoModerationRuleCreate', + AutoModerationRuleDelete = 'autoModerationRuleDelete', + AutoModerationRuleUpdate = 'autoModerationRuleUpdate', ClientReady = 'ready', GuildCreate = 'guildCreate', GuildDelete = 'guildDelete', @@ -4900,6 +5014,14 @@ export interface FetchArchivedThreadOptions { limit?: number; } +export interface FetchAutoModerationRuleOptions extends BaseFetchOptions { + autoModerationRule: AutoModerationRuleResolvable; +} + +export interface FetchAutoModerationRulesOptions { + cache?: boolean; +} + export interface FetchBanOptions extends BaseFetchOptions { user: UserResolvable; } @@ -5056,6 +5178,12 @@ interface GuildAuditLogsTypes { [AuditLogEvent.ThreadUpdate]: ['Thread', 'Update']; [AuditLogEvent.ThreadDelete]: ['Thread', 'Delete']; [AuditLogEvent.ApplicationCommandPermissionUpdate]: ['ApplicationCommand', 'Update']; + [AuditLogEvent.AutoModerationRuleCreate]: ['AutoModerationRule', 'Create']; + [AuditLogEvent.AutoModerationRuleUpdate]: ['AutoModerationRule', 'Update']; + [AuditLogEvent.AutoModerationRuleDelete]: ['AutoModerationRule', 'Delete']; + [AuditLogEvent.AutoModerationBlockMessage]: ['AutoModerationRule', 'Create']; + [AuditLogEvent.AutoModerationFlagToChannel]: ['AutoModerationRule', 'Create']; + [AuditLogEvent.AutoModerationUserCommunicationDisabled]: ['AutoModerationRule', 'Create']; } export type GuildAuditLogsActionType = GuildAuditLogsTypes[keyof GuildAuditLogsTypes][1] | 'All'; @@ -5087,6 +5215,18 @@ export interface GuildAuditLogsEntryExtraField { [AuditLogEvent.StageInstanceDelete]: StageChannel | { id: Snowflake }; [AuditLogEvent.StageInstanceUpdate]: StageChannel | { id: Snowflake }; [AuditLogEvent.ApplicationCommandPermissionUpdate]: { applicationId: Snowflake }; + [AuditLogEvent.AutoModerationBlockMessage]: { + autoModerationRuleName: string; + autoModerationRuleTriggerType: AuditLogRuleTriggerType; + }; + [AuditLogEvent.AutoModerationFlagToChannel]: { + autoModerationRuleName: string; + autoModerationRuleTriggerType: AuditLogRuleTriggerType; + }; + [AuditLogEvent.AutoModerationUserCommunicationDisabled]: { + autoModerationRuleName: string; + autoModerationRuleTriggerType: AuditLogRuleTriggerType; + }; } export interface GuildAuditLogsEntryTargetField { @@ -5102,6 +5242,7 @@ export interface GuildAuditLogsEntryTargetField { @@ -5123,6 +5264,31 @@ export type GuildBanResolvable = GuildBan | UserResolvable; export type GuildChannelResolvable = Snowflake | GuildBasedChannel; +export interface AutoModerationRuleCreateOptions { + name: string; + eventType: AutoModerationRuleEventType; + triggerType: AutoModerationRuleTriggerType; + triggerMetadata?: AutoModerationTriggerMetadataOptions; + actions: AutoModerationActionOptions; + enabled?: boolean; + exemptRoles?: Collection | RoleResolvable[]; + exemptChannels?: Collection | GuildChannelResolvable[]; + reason?: string; +} + +export interface AutoModerationRuleEditOptions extends Partial> {} + +export interface AutoModerationTriggerMetadataOptions extends Partial {} + +export interface AutoModerationActionOptions { + type: AutoModerationActionType; + metadata?: AutoModerationActionMetadataOptions; +} + +export interface AutoModerationActionMetadataOptions extends Partial> { + channel: GuildTextChannelResolvable | ThreadChannel; +} + export interface GuildChannelCreateOptions extends Omit { parent?: CategoryChannelResolvable | null; type?: Exclude< @@ -5864,6 +6030,7 @@ export interface LifetimeSweepOptions { export interface SweeperDefinitions { applicationCommands: [Snowflake, ApplicationCommand]; + autoModerationRules: [Snowflake, AutoModerationRule]; bans: [Snowflake, GuildBan]; emojis: [Snowflake, GuildEmoji]; invites: [string, Invite, true]; diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index c78de7c6715d..25d7996e51b6 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -148,6 +148,9 @@ import { RoleSelectMenuInteraction, ChannelSelectMenuInteraction, MentionableSelectMenuInteraction, + AutoModerationActionExecution, + AutoModerationRule, + AutoModerationRuleManager, } from '.'; import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders'; @@ -174,8 +177,17 @@ const testUserId = '987654321098765432'; // example id const globalCommandId = '123456789012345678'; // example id const guildCommandId = '234567890123456789'; // example id -declare const slashCommandBuilder: SlashCommandBuilder; -declare const contextMenuCommandBuilder: ContextMenuCommandBuilder; +client.on('autoModerationActionExecution', autoModerationActionExecution => + expectType(autoModerationActionExecution), +); + +client.on('autoModerationRuleCreate', ({ client }) => expectType>(client)); +client.on('autoModerationRuleDelete', ({ client }) => expectType>(client)); + +client.on('autoModerationRuleUpdate', (oldAutoModerationRule, { client: newClient }) => { + expectType>(oldAutoModerationRule!.client); + expectType>(newClient); +}); client.on('channelCreate', ({ client }) => expectType>(client)); client.on('channelDelete', ({ client }) => expectType>(client)); @@ -556,6 +568,9 @@ client.on('presenceUpdate', (oldPresence, { client }) => { expectType>(client); }); +declare const slashCommandBuilder: SlashCommandBuilder; +declare const contextMenuCommandBuilder: ContextMenuCommandBuilder; + client.on('ready', async client => { expectType>(client); console.log(`Client is logged in as ${client.user.tag} and ready!`); @@ -1362,6 +1377,26 @@ declare const applicationSubGroupCommandData: ApplicationCommandSubGroupData; expectType(applicationSubGroupCommandData.options); } +declare const autoModerationRuleManager: AutoModerationRuleManager; +{ + expectType>(autoModerationRuleManager.fetch('1234567890')); + expectType>(autoModerationRuleManager.fetch({ autoModerationRule: '1234567890' })); + expectType>( + autoModerationRuleManager.fetch({ autoModerationRule: '1234567890', cache: false }), + ); + expectType>( + autoModerationRuleManager.fetch({ autoModerationRule: '1234567890', force: true }), + ); + expectType>( + autoModerationRuleManager.fetch({ autoModerationRule: '1234567890', cache: false, force: true }), + ); + expectType>>(autoModerationRuleManager.fetch()); + expectType>>(autoModerationRuleManager.fetch({})); + expectType>>(autoModerationRuleManager.fetch({ cache: false })); + // @ts-expect-error The `force` option cannot be used alongside fetching all auto moderation rules. + autoModerationRuleManager.fetch({ force: false }); +} + declare const guildApplicationCommandManager: GuildApplicationCommandManager; expectType>>(guildApplicationCommandManager.fetch()); expectType>>(guildApplicationCommandManager.fetch(undefined, {}));