From fa0d4b507d4fec4770c75d59e5bafb6d85e50d06 Mon Sep 17 00:00:00 2001 From: Jiralite <33201955+Jiralite@users.noreply.github.com> Date: Tue, 28 Jan 2025 19:43:20 +0000 Subject: [PATCH] feat: Incident Actions (#10727) * feat: initial commit * feat: add guild helper * docs: `guild` is required * docs(IncidentActions): move to guild * fix: `incidents_data` is nullable * fix: method typo * fix: default to `null` * fix: use `new Date()` * docs: note that it is not received over the gateway * refactor: use transformer * chore: resolve TODO * chore: typo Co-authored-by: Danial Raza * chore: suggestions Co-authored-by: Almeida * chore: consistency Co-authored-by: Almeida --------- Co-authored-by: Danial Raza Co-authored-by: Almeida Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../discord.js/src/managers/GuildManager.js | 34 +++++++++++++++++++ packages/discord.js/src/structures/Guild.js | 31 +++++++++++++++++ packages/discord.js/src/util/APITypes.js | 5 +++ packages/discord.js/src/util/Transformers.js | 16 +++++++++ packages/discord.js/typings/index.d.ts | 18 ++++++++++ 5 files changed, 104 insertions(+) diff --git a/packages/discord.js/src/managers/GuildManager.js b/packages/discord.js/src/managers/GuildManager.js index e754db367..011810304 100644 --- a/packages/discord.js/src/managers/GuildManager.js +++ b/packages/discord.js/src/managers/GuildManager.js @@ -18,6 +18,7 @@ const { resolveImage } = require('../util/DataResolver.js'); const { Events } = require('../util/Events.js'); const { PermissionsBitField } = require('../util/PermissionsBitField.js'); const { SystemChannelFlagsBitField } = require('../util/SystemChannelFlagsBitField.js'); +const { _transformAPIIncidentsData } = require('../util/Transformers.js'); const { resolveColor } = require('../util/Util.js'); let cacheWarningEmitted = false; @@ -281,6 +282,39 @@ class GuildManager extends CachedManager { return data.reduce((coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)), new Collection()); } + /** + * Options used to set incident actions. Supplying `null` to any option will disable the action. + * @typedef {Object} IncidentActionsEditOptions + * @property {?DateResolvable} [invitesDisabledUntil] When invites should be enabled again + * @property {?DateResolvable} [dmsDisabledUntil] When direct messages should be enabled again + */ + + /** + * Sets the incident actions for a guild. + * @param {GuildResolvable} guild The guild + * @param {IncidentActionsEditOptions} incidentActions The incident actions to set + * @returns {Promise} + */ + async setIncidentActions(guild, { invitesDisabledUntil, dmsDisabledUntil }) { + const guildId = this.resolveId(guild); + + const data = await this.client.rest.put(Routes.guildIncidentActions(guildId), { + body: { + invites_disabled_until: invitesDisabledUntil && new Date(invitesDisabledUntil).toISOString(), + dms_disabled_until: dmsDisabledUntil && new Date(dmsDisabledUntil).toISOString(), + }, + }); + + const parsedData = _transformAPIIncidentsData(data); + const resolvedGuild = this.resolve(guild); + + if (resolvedGuild) { + resolvedGuild.incidentsData = parsedData; + } + + return parsedData; + } + /** * Returns a URL for the PNG widget of a guild. * @param {GuildResolvable} guild The guild of the widget image diff --git a/packages/discord.js/src/structures/Guild.js b/packages/discord.js/src/structures/Guild.js index 44b17a382..0f69bb41d 100644 --- a/packages/discord.js/src/structures/Guild.js +++ b/packages/discord.js/src/structures/Guild.js @@ -28,6 +28,7 @@ const { StageInstanceManager } = require('../managers/StageInstanceManager.js'); const { VoiceStateManager } = require('../managers/VoiceStateManager.js'); const { resolveImage } = require('../util/DataResolver.js'); const { SystemChannelFlagsBitField } = require('../util/SystemChannelFlagsBitField.js'); +const { _transformAPIIncidentsData } = require('../util/Transformers.js'); const { discordSort, getSortableGroupTypes, resolvePartialEmoji } = require('../util/Util.js'); /** @@ -460,6 +461,27 @@ class Guild extends AnonymousGuild { stickers: data.stickers, }); } + + if ('incidents_data' in data) { + /** + * Incident actions of a guild. + * @typedef {Object} IncidentActions + * @property {?Date} invitesDisabledUntil When invites would be enabled again + * @property {?Date} dmsDisabledUntil When direct messages would be enabled again + * @property {?Date} dmSpamDetectedAt When direct message spam was detected + * @property {?Date} raidDetectedAt When a raid was detected + */ + + /** + * The incidents data of this guild. + * You will need to fetch the guild using {@link BaseGuild#fetch} if you want to receive + * this property. + * @type {?IncidentActions} + */ + this.incidentsData = data.incidents_data && _transformAPIIncidentsData(data.incidents_data); + } else { + this.incidentsData ??= null; + } } /** @@ -1355,6 +1377,15 @@ class Guild extends AnonymousGuild { return this.edit({ features }); } + /** + * Sets the incident actions for a guild. + * @param {IncidentActionsEditOptions} incidentActions The incident actions to set + * @returns {Promise} + */ + async setIncidentActions(incidentActions) { + return this.client.guilds.setIncidentActions(this.id, incidentActions); + } + /** * Whether this guild equals another guild. It compares all properties, so for most operations * it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often diff --git a/packages/discord.js/src/util/APITypes.js b/packages/discord.js/src/util/APITypes.js index 50a9f6c47..6a38ed450 100644 --- a/packages/discord.js/src/util/APITypes.js +++ b/packages/discord.js/src/util/APITypes.js @@ -105,6 +105,11 @@ * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildScheduledEventRecurrenceRule} */ +/** + * @external APIIncidentsData + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIIncidentsData} + */ + /** * @external APIInteraction * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIInteraction} diff --git a/packages/discord.js/src/util/Transformers.js b/packages/discord.js/src/util/Transformers.js index bf454a877..13182e38f 100644 --- a/packages/discord.js/src/util/Transformers.js +++ b/packages/discord.js/src/util/Transformers.js @@ -72,7 +72,23 @@ function _transformGuildScheduledEventRecurrenceRule(recurrenceRule) { }; } +/** + * Transforms API incidents data to a camel-cased variant. + * @param {APIIncidentsData} data The incidents data to transform + * @returns {IncidentActions} + * @ignore + */ +function _transformAPIIncidentsData(data) { + return { + invitesDisabledUntil: data.invites_disabled_until ? new Date(data.invites_disabled_until) : null, + dmsDisabledUntil: data.dms_disabled_until ? new Date(data.dms_disabled_until) : null, + dmSpamDetectedAt: data.dm_spam_detected_at ? new Date(data.dm_spam_detected_at) : null, + raidDetectedAt: data.raid_detected_at ? new Date(data.raid_detected_at) : null, + }; +} + exports.toSnakeCase = toSnakeCase; exports._transformAPIAutoModerationAction = _transformAPIAutoModerationAction; exports._transformAPIMessageInteractionMetadata = _transformAPIMessageInteractionMetadata; exports._transformGuildScheduledEventRecurrenceRule = _transformGuildScheduledEventRecurrenceRule; +exports._transformAPIIncidentsData = _transformAPIIncidentsData; diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index b4f6dfd0c..91f1f4b88 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1407,6 +1407,7 @@ export class Guild extends AnonymousGuild { public shardId: number; public stageInstances: StageInstanceManager; public stickers: GuildStickerManager; + public incidentsData: IncidentActions | null; public get systemChannel(): TextChannel | null; public systemChannelFlags: Readonly; public systemChannelId: Snowflake | null; @@ -1446,6 +1447,7 @@ export class Guild extends AnonymousGuild { public widgetImageURL(style?: GuildWidgetStyle): string; public leave(): Promise; public disableInvites(disabled?: boolean): Promise; + public setIncidentActions(incidentActions: IncidentActionsEditOptions): Promise; public setAFKChannel(afkChannel: VoiceChannelResolvable | null, reason?: string): Promise; public setAFKTimeout(afkTimeout: number, reason?: string): Promise; public setBanner(banner: BufferResolvable | Base64Resolvable | null, reason?: string): Promise; @@ -4172,6 +4174,10 @@ export class GuildManager extends CachedManager; public fetch(options: Snowflake | FetchGuildOptions): Promise; public fetch(options?: FetchGuildsOptions): Promise>; + public setIncidentActions( + guild: GuildResolvable, + incidentActions: IncidentActionsEditOptions, + ): Promise; public widgetImageURL(guild: GuildResolvable, style?: GuildWidgetStyle): string; } @@ -6064,6 +6070,18 @@ export interface GuildOnboardingPromptOptionData { export type HexColorString = `#${string}`; +export interface IncidentActions { + invitesDisabledUntil: Date | null; + dmsDisabledUntil: Date | null; + dmSpamDetectedAt: Date | null; + raidDetectedAt: Date | null; +} + +export interface IncidentActionsEditOptions { + invitesDisabledUntil?: DateResolvable | null | undefined; + dmsDisabledUntil?: DateResolvable | null | undefined; +} + export interface IntegrationAccount { id: string | Snowflake; name: string;