diff --git a/packages/discord.js/src/managers/GuildScheduledEventManager.js b/packages/discord.js/src/managers/GuildScheduledEventManager.js index a0d0c8375e72..383875d70002 100644 --- a/packages/discord.js/src/managers/GuildScheduledEventManager.js +++ b/packages/discord.js/src/managers/GuildScheduledEventManager.js @@ -7,6 +7,7 @@ const CachedManager = require('./CachedManager'); const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors'); const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent'); const { resolveImage } = require('../util/DataResolver'); +const { _transformGuildScheduledEventRecurrenceRule } = require('../util/Transformers'); /** * Manages API methods for GuildScheduledEvents and stores their cache. @@ -36,6 +37,21 @@ class GuildScheduledEventManager extends CachedManager { * @typedef {Snowflake|GuildScheduledEvent} GuildScheduledEventResolvable */ + /** + * Options for setting a recurrence rule for a guild scheduled event. + * @typedef {Object} GuildScheduledEventRecurrenceRuleOptions + * @property {DateResolvable} startAt The time the recurrence rule interval starts at + * @property {?DateResolvable} endAt The time the recurrence rule interval ends at + * @property {GuildScheduledEventRecurrenceRuleFrequency} frequency How often the event occurs + * @property {number} interval The spacing between the events + * @property {?GuildScheduledEventRecurrenceRuleWeekday[]} byWeekday The days within a week to recur on + * @property {?GuildScheduledEventRecurrenceRuleNWeekday[]} byNWeekday The days within a week to recur on + * @property {?GuildScheduledEventRecurrenceRuleMonth[]} byMonth The months to recur on + * @property {?number[]} byMonthDay The days within a month to recur on + * @property {?number[]} byYearDay The days within a year to recur on + * @property {?number} count The total amount of times the event is allowed to recur before stopping + */ + /** * Options used to create a guild scheduled event. * @typedef {Object} GuildScheduledEventCreateOptions @@ -54,6 +70,8 @@ class GuildScheduledEventManager extends CachedManager { * This is required if `entityType` is {@link GuildScheduledEventEntityType.External} * @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event * @property {string} [reason] The reason for creating the guild scheduled event + * @property {GuildScheduledEventRecurrenceRuleOptions} [recurrenceRule] + * The recurrence rule of the guild scheduled event */ /** @@ -81,6 +99,7 @@ class GuildScheduledEventManager extends CachedManager { entityMetadata, reason, image, + recurrenceRule, } = options; let entity_metadata, channel_id; @@ -104,6 +123,7 @@ class GuildScheduledEventManager extends CachedManager { entity_type: entityType, entity_metadata, image: image && (await resolveImage(image)), + recurrence_rule: recurrenceRule && _transformGuildScheduledEventRecurrenceRule(recurrenceRule), }, reason, }); @@ -178,6 +198,8 @@ class GuildScheduledEventManager extends CachedManager { * {@link GuildScheduledEventEntityType.External} * @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event * @property {string} [reason] The reason for editing the guild scheduled event + * @property {?GuildScheduledEventRecurrenceRuleOptions} [recurrenceRule] + * The recurrence rule of the guild scheduled event */ /** @@ -203,6 +225,7 @@ class GuildScheduledEventManager extends CachedManager { entityMetadata, reason, image, + recurrenceRule, } = options; let entity_metadata; @@ -224,6 +247,7 @@ class GuildScheduledEventManager extends CachedManager { status, image: image && (await resolveImage(image)), entity_metadata, + recurrence_rule: recurrenceRule && _transformGuildScheduledEventRecurrenceRule(recurrenceRule), }, reason, }); diff --git a/packages/discord.js/src/structures/GuildScheduledEvent.js b/packages/discord.js/src/structures/GuildScheduledEvent.js index f5a5291232e5..9f6124f0198c 100644 --- a/packages/discord.js/src/structures/GuildScheduledEvent.js +++ b/packages/discord.js/src/structures/GuildScheduledEvent.js @@ -189,6 +189,56 @@ class GuildScheduledEvent extends Base { } else { this.image ??= null; } + + /** + * Represents the recurrence rule for a {@link GuildScheduledEvent}. + * @typedef {Object} GuildScheduledEventRecurrenceRule + * @property {number} startTimestamp The timestamp the recurrence rule interval starts at + * @property {Date} startAt The time the recurrence rule interval starts at + * @property {?number} endTimestamp The timestamp the recurrence rule interval ends at + * @property {?Date} endAt The time the recurrence rule interval ends at + * @property {GuildScheduledEventRecurrenceRuleFrequency} frequency How often the event occurs + * @property {number} interval The spacing between the events + * @property {?GuildScheduledEventRecurrenceRuleWeekday[]} byWeekday The days within a week to recur on + * @property {?GuildScheduledEventRecurrenceRuleNWeekday[]} byNWeekday The days within a week to recur on + * @property {?GuildScheduledEventRecurrenceRuleMonth[]} byMonth The months to recur on + * @property {?number[]} byMonthDay The days within a month to recur on + * @property {?number[]} byYearDay The days within a year to recur on + * @property {?number} count The total amount of times the event is allowed to recur before stopping + */ + + /** + * @typedef {Object} GuildScheduledEventRecurrenceRuleNWeekday + * @property {number} n The week to recur on + * @property {GuildScheduledEventRecurrenceRuleWeekday} day The day within the week to recur on + */ + + if ('recurrence_rule' in data) { + /** + * The recurrence rule for this scheduled event + * @type {?GuildScheduledEventRecurrenceRule} + */ + this.recurrenceRule = { + startTimestamp: Date.parse(data.recurrence_rule.start), + get startAt() { + return new Date(this.startTimestamp); + }, + endTimestamp: data.recurrence_rule.end && Date.parse(data.recurrence_rule.end), + get endAt() { + return this.endTimestamp && new Date(this.endTimestamp); + }, + frequency: data.recurrence_rule.frequency, + interval: data.recurrence_rule.interval, + byWeekday: data.recurrence_rule.by_weekday, + byNWeekday: data.recurrence_rule.by_n_weekday, + byMonth: data.recurrence_rule.by_month, + byMonthDay: data.recurrence_rule.by_month_day, + byYearDay: data.recurrence_rule.by_year_day, + count: data.recurrence_rule.count, + }; + } else { + this.recurrenceRule ??= null; + } } /** diff --git a/packages/discord.js/src/util/APITypes.js b/packages/discord.js/src/util/APITypes.js index 42b2ddae0943..43a8bcf81ada 100644 --- a/packages/discord.js/src/util/APITypes.js +++ b/packages/discord.js/src/util/APITypes.js @@ -100,6 +100,11 @@ * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildMember} */ +/** + * @external APIGuildScheduledEventRecurrenceRule + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildScheduledEventRecurrenceRule} + */ + /** * @external APIInteraction * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIInteraction} @@ -390,6 +395,21 @@ * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventPrivacyLevel} */ +/** + * @external GuildScheduledEventRecurrenceRuleFrequency + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleFrequency} + */ + +/** + * @external GuildScheduledEventRecurrenceRuleMonth + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleMonth} + */ + +/** + * @external GuildScheduledEventRecurrenceRuleWeekday + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleWeekday} + */ + /** * @external GuildScheduledEventStatus * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventStatus} diff --git a/packages/discord.js/src/util/Transformers.js b/packages/discord.js/src/util/Transformers.js index 0e6148a10034..e37fb456c65a 100644 --- a/packages/discord.js/src/util/Transformers.js +++ b/packages/discord.js/src/util/Transformers.js @@ -54,4 +54,31 @@ function _transformAPIMessageInteractionMetadata(client, messageInteractionMetad }; } -module.exports = { toSnakeCase, _transformAPIAutoModerationAction, _transformAPIMessageInteractionMetadata }; +/** + * Transforms a guild scheduled event recurrence rule object to a snake-cased variant. + * @param {GuildScheduledEventRecurrenceRuleOptions} recurrenceRule The recurrence rule to transform + * @returns {APIGuildScheduledEventRecurrenceRule} + * @ignore + */ +function _transformGuildScheduledEventRecurrenceRule(recurrenceRule) { + return { + start: new Date(recurrenceRule.startAt).toISOString(), + // eslint-disable-next-line eqeqeq + end: recurrenceRule.endAt != null ? new Date(recurrenceRule.endAt).toISOString() : recurrenceRule.endAt, + frequency: recurrenceRule.frequency, + interval: recurrenceRule.interval, + by_weekday: recurrenceRule.byWeekday, + by_n_weekday: recurrenceRule.byNWeekday, + by_month: recurrenceRule.byMonth, + by_month_day: recurrenceRule.byMonthDay, + by_year_day: recurrenceRule.byYearDay, + count: recurrenceRule.count, + }; +} + +module.exports = { + toSnakeCase, + _transformAPIAutoModerationAction, + _transformAPIMessageInteractionMetadata, + _transformGuildScheduledEventRecurrenceRule, +}; diff --git a/packages/discord.js/test/random.js b/packages/discord.js/test/random.js index e33af44d7675..48d5ab847b77 100644 --- a/packages/discord.js/test/random.js +++ b/packages/discord.js/test/random.js @@ -2,7 +2,7 @@ 'use strict'; -const { token } = require('./auth.js'); +const { token, owner } = require('./auth.js'); const { Client } = require('../src'); const { ChannelType, GatewayIntentBits } = require('discord-api-types/v10'); @@ -14,6 +14,7 @@ const client = new Client({ GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildMembers, + GatewayIntentBits.MessageContent, ], }); @@ -186,7 +187,7 @@ client.on('messageCreate', msg => { msg.channel.send(`\`\`\`${msg.content}\`\`\``); } - if (msg.content.startsWith('#eval') && msg.author.id === '66564597481480192') { + if (msg.content.startsWith('#eval') && msg.author.id === owner) { try { const com = eval(msg.content.split(' ').slice(1).join(' ')); msg.channel.send(`\`\`\`\n${com}\`\`\``); diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index f29e646f7c94..57eed8b24e48 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -186,6 +186,9 @@ import { ReactionType, APIAuthorizingIntegrationOwnersMap, MessageReferenceType, + GuildScheduledEventRecurrenceRuleWeekday, + GuildScheduledEventRecurrenceRuleMonth, + GuildScheduledEventRecurrenceRuleFrequency, } from 'discord-api-types/v10'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -1779,6 +1782,7 @@ export class GuildScheduledEvent; } +export interface GuildScheduledEventRecurrenceRule { + startTimestamp: number; + get startAt(): Date; + endTimestamp: number | null; + get endAt(): Date | null; + frequency: GuildScheduledEventRecurrenceRuleFrequency; + interval: number; + byWeekday: readonly GuildScheduledEventRecurrenceRuleWeekday[] | null; + byNWeekday: readonly GuildScheduledEventRecurrenceRuleNWeekday[] | null; + byMonth: readonly GuildScheduledEventRecurrenceRuleMonth[] | null; + byMonthDay: readonly number[] | null; + byYearDay: readonly number[] | null; + count: number | null; +} + +export interface GuildScheduledEventRecurrenceRuleNWeekday { + n: number; + day: GuildScheduledEventRecurrenceRuleWeekday; +} + export class GuildTemplate extends Base { private constructor(client: Client, data: RawGuildTemplateData); public createdTimestamp: number; @@ -6167,14 +6191,29 @@ export interface GuildScheduledEventCreateOptions { entityMetadata?: GuildScheduledEventEntityMetadataOptions; image?: BufferResolvable | Base64Resolvable | null; reason?: string; + recurrenceRule?: GuildScheduledEventRecurrenceRuleOptions; +} + +export interface GuildScheduledEventRecurrenceRuleOptions { + startAt: DateResolvable; + endAt: DateResolvable; + frequency: GuildScheduledEventRecurrenceRuleFrequency; + interval: number; + byWeekday: readonly GuildScheduledEventRecurrenceRuleWeekday[]; + byNWeekday: readonly GuildScheduledEventRecurrenceRuleNWeekday[]; + byMonth: readonly GuildScheduledEventRecurrenceRuleMonth[]; + byMonthDay: readonly number[]; + byYearDay: readonly number[]; + count: number; } export interface GuildScheduledEventEditOptions< Status extends GuildScheduledEventStatus, AcceptableStatus extends GuildScheduledEventSetStatusArg, -> extends Omit, 'channel'> { +> extends Omit, 'channel' | 'recurrenceRule'> { channel?: GuildVoiceChannelResolvable | null; status?: AcceptableStatus; + recurrenceRule?: GuildScheduledEventRecurrenceRuleOptions | null; } export interface GuildScheduledEventEntityMetadata { diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index f23023f1acca..1f1b34f3a0b6 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -209,6 +209,7 @@ import { ApplicationEmoji, ApplicationEmojiManager, StickerPack, + GuildScheduledEventManager, SendableChannels, PollData, } from '.'; @@ -2618,3 +2619,6 @@ client.on('interactionCreate', interaction => { interaction.channel.send({ embeds: [] }); } }); + +declare const guildScheduledEventManager: GuildScheduledEventManager; +await guildScheduledEventManager.edit(snowflake, { recurrenceRule: null });