From 1316fd4c6ad47729f566d3dd088bb3c144f4bf8e Mon Sep 17 00:00:00 2001 From: Shubham Parihar Date: Thu, 23 Dec 2021 18:09:09 +0530 Subject: [PATCH] feat: add support for GuildScheduledEvent (#6493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rodry <38259440+ImRodry@users.noreply.github.com> Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com> Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: GoldenAngel <50855202+GoldenAngel2@users.noreply.github.com> Co-authored-by: Vlad Frangu Co-authored-by: Antonio Román Co-authored-by: SpaceEEC --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 + src/client/Client.js | 14 +- src/client/actions/Action.js | 10 + src/client/actions/ActionsManager.js | 5 + .../actions/GuildScheduledEventCreate.js | 27 ++ .../actions/GuildScheduledEventDelete.js | 31 ++ .../actions/GuildScheduledEventUpdate.js | 30 ++ .../actions/GuildScheduledEventUserAdd.js | 32 ++ .../actions/GuildScheduledEventUserRemove.js | 32 ++ .../handlers/GUILD_SCHEDULED_EVENT_CREATE.js | 5 + .../handlers/GUILD_SCHEDULED_EVENT_DELETE.js | 5 + .../handlers/GUILD_SCHEDULED_EVENT_UPDATE.js | 5 + .../GUILD_SCHEDULED_EVENT_USER_ADD.js | 5 + .../GUILD_SCHEDULED_EVENT_USER_REMOVE.js | 5 + src/client/websocket/handlers/index.js | 5 + src/errors/Messages.js | 3 + src/index.js | 2 + src/managers/GuildScheduledEventManager.js | 289 ++++++++++++ src/structures/Guild.js | 14 + src/structures/GuildAuditLogs.js | 32 +- src/structures/GuildScheduledEvent.js | 422 ++++++++++++++++++ src/structures/Invite.js | 11 + src/util/Constants.js | 64 ++- src/util/Intents.js | 2 + src/util/Options.js | 2 + src/util/Permissions.js | 2 + typings/enums.d.ts | 17 + typings/index.d.ts | 198 +++++++- typings/rawDataTypes.d.ts | 3 + 29 files changed, 1261 insertions(+), 13 deletions(-) create mode 100644 src/client/actions/GuildScheduledEventCreate.js create mode 100644 src/client/actions/GuildScheduledEventDelete.js create mode 100644 src/client/actions/GuildScheduledEventUpdate.js create mode 100644 src/client/actions/GuildScheduledEventUserAdd.js create mode 100644 src/client/actions/GuildScheduledEventUserRemove.js create mode 100644 src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js create mode 100644 src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js create mode 100644 src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js create mode 100644 src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js create mode 100644 src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js create mode 100644 src/managers/GuildScheduledEventManager.js create mode 100644 src/structures/GuildScheduledEvent.js diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f3d4e471147e..1701cf00d3d0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -95,6 +95,7 @@ body: - GUILD_MEMBER - MESSAGE - REACTION + - GUILD_SCHEDULED_EVENT multiple: true validations: required: true @@ -122,6 +123,7 @@ body: - DIRECT_MESSAGES - DIRECT_MESSAGE_REACTIONS - DIRECT_MESSAGE_TYPING + - GUILD_SCHEDULED_EVENTS multiple: true validations: required: true diff --git a/src/client/Client.js b/src/client/Client.js index 72c49b1fba1d..e8fc17ef0538 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -283,18 +283,28 @@ class Client extends BaseClient { this.token = null; } + /** + * Options used when fetching an invite from Discord. + * @typedef {Object} ClientFetchInviteOptions + * @property {Snowflake} [guildScheduledEventId] The id of the guild scheduled event to include with + * the invite + */ + /** * Obtains an invite from Discord. * @param {InviteResolvable} invite Invite code or URL + * @param {ClientFetchInviteOptions} [options] Options for fetching the invite * @returns {Promise} * @example * client.fetchInvite('https://discord.gg/djs') * .then(invite => console.log(`Obtained invite with code: ${invite.code}`)) * .catch(console.error); */ - async fetchInvite(invite) { + async fetchInvite(invite, options) { const code = DataResolver.resolveInviteCode(invite); - const data = await this.api.invites(code).get({ query: { with_counts: true, with_expiration: true } }); + const data = await this.api.invites(code).get({ + query: { with_counts: true, with_expiration: true, guild_scheduled_event_id: options?.guildScheduledEventId }, + }); return new Invite(this, data); } diff --git a/src/client/actions/Action.js b/src/client/actions/Action.js index 0e87d7d0021c..8d0b6df8b943 100644 --- a/src/client/actions/Action.js +++ b/src/client/actions/Action.js @@ -100,6 +100,16 @@ class GenericAction { } return this.getUser(data); } + + getScheduledEvent(data, guild) { + const id = data.guild_scheduled_event_id ?? data.id; + return this.getPayload( + { id, guild_id: data.guild_id ?? guild.id }, + guild.scheduledEvents, + id, + PartialTypes.GUILD_SCHEDULED_EVENT, + ); + } } module.exports = GenericAction; diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index b4694b7059ce..584177719339 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -22,6 +22,11 @@ class ActionsManager { this.register(require('./GuildRoleDelete')); this.register(require('./GuildRoleUpdate')); this.register(require('./GuildRolesPositionUpdate')); + this.register(require('./GuildScheduledEventCreate')); + this.register(require('./GuildScheduledEventDelete')); + this.register(require('./GuildScheduledEventUpdate')); + this.register(require('./GuildScheduledEventUserAdd')); + this.register(require('./GuildScheduledEventUserRemove')); this.register(require('./GuildStickerCreate')); this.register(require('./GuildStickerDelete')); this.register(require('./GuildStickerUpdate')); diff --git a/src/client/actions/GuildScheduledEventCreate.js b/src/client/actions/GuildScheduledEventCreate.js new file mode 100644 index 000000000000..b1a2d928bdad --- /dev/null +++ b/src/client/actions/GuildScheduledEventCreate.js @@ -0,0 +1,27 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildScheduledEventCreateAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + const guildScheduledEvent = guild.scheduledEvents._add(data); + + /** + * Emitted whenever a guild scheduled event is created. + * @event Client#guildScheduledEventCreate + * @param {GuildScheduledEvent} guildScheduledEvent The created guild scheduled event + */ + client.emit(Events.GUILD_SCHEDULED_EVENT_CREATE, guildScheduledEvent); + + return { guildScheduledEvent }; + } + + return {}; + } +} + +module.exports = GuildScheduledEventCreateAction; diff --git a/src/client/actions/GuildScheduledEventDelete.js b/src/client/actions/GuildScheduledEventDelete.js new file mode 100644 index 000000000000..0e7baa142210 --- /dev/null +++ b/src/client/actions/GuildScheduledEventDelete.js @@ -0,0 +1,31 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildScheduledEventDeleteAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const guildScheduledEvent = this.getScheduledEvent(data, guild); + if (guildScheduledEvent) { + guild.scheduledEvents.cache.delete(guildScheduledEvent.id); + + /** + * Emitted whenever a guild scheduled event is deleted. + * @event Client#guildScheduledEventDelete + * @param {GuildScheduledEvent} guildScheduledEvent The deleted guild scheduled event + */ + client.emit(Events.GUILD_SCHEDULED_EVENT_DELETE, guildScheduledEvent); + + return { guildScheduledEvent }; + } + } + + return {}; + } +} + +module.exports = GuildScheduledEventDeleteAction; diff --git a/src/client/actions/GuildScheduledEventUpdate.js b/src/client/actions/GuildScheduledEventUpdate.js new file mode 100644 index 000000000000..dfb86dbcd9fa --- /dev/null +++ b/src/client/actions/GuildScheduledEventUpdate.js @@ -0,0 +1,30 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildScheduledEventUpdateAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const oldGuildScheduledEvent = guild.scheduledEvents.cache.get(data.id)?._clone() ?? null; + const newGuildScheduledEvent = guild.scheduledEvents._add(data); + + /** + * Emitted whenever a guild scheduled event gets updated. + * @event Client#guildScheduledEventUpdate + * @param {?GuildScheduledEvent} oldGuildScheduledEvent The guild scheduled event object before the update + * @param {GuildScheduledEvent} newGuildScheduledEvent The guild scheduled event object after the update + */ + client.emit(Events.GUILD_SCHEDULED_EVENT_UPDATE, oldGuildScheduledEvent, newGuildScheduledEvent); + + return { oldGuildScheduledEvent, newGuildScheduledEvent }; + } + + return {}; + } +} + +module.exports = GuildScheduledEventUpdateAction; diff --git a/src/client/actions/GuildScheduledEventUserAdd.js b/src/client/actions/GuildScheduledEventUserAdd.js new file mode 100644 index 000000000000..ea6e4c450fd5 --- /dev/null +++ b/src/client/actions/GuildScheduledEventUserAdd.js @@ -0,0 +1,32 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildScheduledEventUserAddAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const guildScheduledEvent = this.getScheduledEvent(data, guild); + const user = this.getUser(data); + + if (guildScheduledEvent && user) { + /** + * Emitted whenever a user subscribes to a guild scheduled event + * @event Client#guildScheduledEventUserAdd + * @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event + * @param {User} user The user who subscribed + */ + client.emit(Events.GUILD_SCHEDULED_EVENT_USER_ADD, guildScheduledEvent, user); + + return { guildScheduledEvent, user }; + } + } + + return {}; + } +} + +module.exports = GuildScheduledEventUserAddAction; diff --git a/src/client/actions/GuildScheduledEventUserRemove.js b/src/client/actions/GuildScheduledEventUserRemove.js new file mode 100644 index 000000000000..b32e3cee3782 --- /dev/null +++ b/src/client/actions/GuildScheduledEventUserRemove.js @@ -0,0 +1,32 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildScheduledEventUserRemoveAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const guildScheduledEvent = this.getScheduledEvent(data, guild); + const user = this.getUser(data); + + if (guildScheduledEvent && user) { + /** + * Emitted whenever a user unsubscribes from a guild scheduled event + * @event Client#guildScheduledEventUserRemove + * @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event + * @param {User} user The user who unsubscribed + */ + client.emit(Events.GUILD_SCHEDULED_EVENT_USER_REMOVE, guildScheduledEvent, user); + + return { guildScheduledEvent, user }; + } + } + + return {}; + } +} + +module.exports = GuildScheduledEventUserRemoveAction; diff --git a/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js new file mode 100644 index 000000000000..04ff2df82532 --- /dev/null +++ b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventCreate.handle(packet.d); +}; diff --git a/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js new file mode 100644 index 000000000000..b660c096616f --- /dev/null +++ b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventDelete.handle(packet.d); +}; diff --git a/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js new file mode 100644 index 000000000000..006470831ba6 --- /dev/null +++ b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventUpdate.handle(packet.d); +}; diff --git a/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js new file mode 100644 index 000000000000..d5adca2cd485 --- /dev/null +++ b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventUserAdd.handle(packet.d); +}; diff --git a/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js new file mode 100644 index 000000000000..114df68231e7 --- /dev/null +++ b/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventUserRemove.handle(packet.d); +}; diff --git a/src/client/websocket/handlers/index.js b/src/client/websocket/handlers/index.js index 393727a8e245..65880ad9ddbf 100644 --- a/src/client/websocket/handlers/index.js +++ b/src/client/websocket/handlers/index.js @@ -51,6 +51,11 @@ const handlers = Object.fromEntries([ ['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')], ['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')], ['GUILD_STICKERS_UPDATE', require('./GUILD_STICKERS_UPDATE')], + ['GUILD_SCHEDULED_EVENT_CREATE', require('./GUILD_SCHEDULED_EVENT_CREATE')], + ['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE')], + ['GUILD_SCHEDULED_EVENT_DELETE', require('./GUILD_SCHEDULED_EVENT_DELETE')], + ['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')], + ['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')], ]); module.exports = handlers; diff --git a/src/errors/Messages.js b/src/errors/Messages.js index e20aadda9971..97618abdee3e 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -39,6 +39,8 @@ const Messages = { COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).', COLOR_CONVERT: 'Unable to convert color to a number.', + INVITE_OPTIONS_MISSING_CHANNEL: 'A valid guild channel must be provided when GuildScheduledEvent is EXTERNAL.', + EMBED_TITLE: 'MessageEmbed title must be a string.', EMBED_FIELD_NAME: 'MessageEmbed field names must be non-empty strings.', EMBED_FIELD_VALUE: 'MessageEmbed field values must be non-empty strings.', @@ -94,6 +96,7 @@ const Messages = { GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.', CHANNEL_NOT_CACHED: 'Could not find the channel where this message came from in the cache!', STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.', + GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.', INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`, INVALID_ELEMENT: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`, diff --git a/src/index.js b/src/index.js index bea7d113a000..eb99a644de2e 100644 --- a/src/index.js +++ b/src/index.js @@ -50,6 +50,7 @@ exports.GuildInviteManager = require('./managers/GuildInviteManager'); exports.GuildManager = require('./managers/GuildManager'); exports.GuildMemberManager = require('./managers/GuildMemberManager'); exports.GuildMemberRoleManager = require('./managers/GuildMemberRoleManager'); +exports.GuildScheduledEventManager = require('./managers/GuildScheduledEventManager'); exports.GuildStickerManager = require('./managers/GuildStickerManager'); exports.MessageManager = require('./managers/MessageManager'); exports.PermissionOverwriteManager = require('./managers/PermissionOverwriteManager'); @@ -99,6 +100,7 @@ exports.GuildEmoji = require('./structures/GuildEmoji'); exports.GuildMember = require('./structures/GuildMember').GuildMember; exports.GuildPreview = require('./structures/GuildPreview'); exports.GuildPreviewEmoji = require('./structures/GuildPreviewEmoji'); +exports.GuildScheduledEvent = require('./structures/GuildScheduledEvent').GuildScheduledEvent; exports.GuildTemplate = require('./structures/GuildTemplate'); exports.Integration = require('./structures/Integration'); exports.IntegrationApplication = require('./structures/IntegrationApplication'); diff --git a/src/managers/GuildScheduledEventManager.js b/src/managers/GuildScheduledEventManager.js new file mode 100644 index 000000000000..b35e45c3969a --- /dev/null +++ b/src/managers/GuildScheduledEventManager.js @@ -0,0 +1,289 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const CachedManager = require('./CachedManager'); +const { TypeError, Error } = require('../errors'); +const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent'); +const { PrivacyLevels, GuildScheduledEventEntityTypes, GuildScheduledEventStatuses } = require('../util/Constants'); + +/** + * Manages API methods for GuildScheduledEvents and stores their cache. + * @extends {CachedManager} + */ +class GuildScheduledEventManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, GuildScheduledEvent, iterable); + + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this manager + * @type {Collection} + * @name GuildScheduledEventManager#cache + */ + + /** + * Data that resolves to give a GuildScheduledEvent object. This can be: + * * A Snowflake + * * A GuildScheduledEvent object + * @typedef {Snowflake|GuildScheduledEvent} GuildScheduledEventResolvable + */ + + /** + * Options used to create a guild scheduled event. + * @typedef {Object} GuildScheduledEventCreateOptions + * @property {string} name The name of the guild scheduled event + * @property {DateResolvable} scheduledStartTime The time to schedule the event at + * @property {DateResolvable} [scheduledEndTime] The time to end the event at + * This is required if `entityType` is 'EXTERNAL' + * @property {PrivacyLevel|number} privacyLevel The privacy level of the guild scheduled event + * @property {GuildScheduledEventEntityType|number} entityType The scheduled entity type of the event + * @property {string} [description] The description of the guild scheduled event + * @property {GuildVoiceChannelResolvable} [channel] The channel of the guild scheduled event + * This is required if `entityType` is 'STAGE_INSTANCE' or `VOICE` + * @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the + * guild scheduled event + * This is required if `entityType` is 'EXTERNAL' + * @property {string} [reason] The reason for creating the guild scheduled event + */ + + /** + * Options used to set entity metadata of a guild scheduled event. + * @typedef {Object} GuildScheduledEventEntityMetadataOptions + * @property {string} [location] The location of the guild scheduled event + * This is required if `entityType` is 'EXTERNAL' + */ + + /** + * Creates a new guild scheduled event. + * @param {GuildScheduledEventCreateOptions} options Options for creating the guild scheduled event + * @returns {Promise} + */ + async create(options) { + if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); + let { + privacyLevel, + entityType, + channel, + name, + scheduledStartTime, + description, + scheduledEndTime, + entityMetadata, + reason, + } = options; + + if (typeof privacyLevel === 'string') privacyLevel = PrivacyLevels[privacyLevel]; + if (typeof entityType === 'string') entityType = GuildScheduledEventEntityTypes[entityType]; + + let entity_metadata, channel_id; + if (entityType === GuildScheduledEventEntityTypes.EXTERNAL) { + channel_id = typeof channel === 'undefined' ? channel : null; + entity_metadata = { location: entityMetadata?.location }; + } else { + channel_id = this.guild.channels.resolveId(channel); + if (!channel_id) throw new Error('GUILD_VOICE_CHANNEL_RESOLVE'); + entity_metadata = typeof entityMetadata === 'undefined' ? entityMetadata : null; + } + + const data = await this.client.api.guilds(this.guild.id, 'scheduled-events').post({ + data: { + channel_id, + name, + privacy_level: privacyLevel, + scheduled_start_time: new Date(scheduledStartTime).toISOString(), + scheduled_end_time: scheduledEndTime ? new Date(scheduledEndTime).toISOString() : scheduledEndTime, + description, + entity_type: entityType, + entity_metadata, + }, + reason, + }); + + return this._add(data); + } + + /** + * Options used to fetch a single guild scheduled event from a guild. + * @typedef {BaseFetchOptions} FetchGuildScheduledEventOptions + * @property {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to fetch + * @property {boolean} [withUserCount=true] Whether to fetch the number of users subscribed to the scheduled event + */ + + /** + * Options used to fetch multiple guild scheduled events from a guild. + * @typedef {Object} FetchGuildScheduledEventsOptions + * @property {boolean} [cache] Whether or not to cache the fetched guild scheduled events + * @property {boolean} [withUserCount=true] Whether to fetch the number of users subscribed to each scheduled event + * should be returned + */ + + /** + * Obtains one or more guild scheduled events from Discord, or the guild cache if it's already available. + * @param {GuildScheduledEventResolvable|FetchGuildScheduledEventOptions|FetchGuildScheduledEventsOptions} [options] + * The id of the guild scheduled event or options + * @returns {Promise>} + */ + async fetch(options = {}) { + const id = this.resolveId(options.guildScheduledEvent ?? options); + + if (id) { + if (!options.force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + + const data = await this.client.api + .guilds(this.guild.id, 'scheduled-events', id) + .get({ query: { with_user_count: options.withUserCount ?? true } }); + return this._add(data, options.cache); + } + + const data = await this.client.api + .guilds(this.guild.id, 'scheduled-events') + .get({ query: { with_user_count: options.withUserCount ?? true } }); + + return data.reduce( + (coll, rawGuildScheduledEventData) => + coll.set( + rawGuildScheduledEventData.id, + this.guild.scheduledEvents._add(rawGuildScheduledEventData, options.cache), + ), + new Collection(), + ); + } + + /** + * Options used to edit a guild scheduled event. + * @typedef {Object} GuildScheduledEventEditOptions + * @property {string} [name] The name of the guild scheduled event + * @property {DateResolvable} [scheduledStartTime] The time to schedule the event at + * @property {DateResolvable} [scheduledEndTime] The time to end the event at + * @property {PrivacyLevel|number} [privacyLevel] The privacy level of the guild scheduled event + * @property {GuildScheduledEventEntityType|number} [entityType] The scheduled entity type of the event + * @property {string} [description] The description of the guild scheduled event + * @property {?GuildVoiceChannelResolvable} [channel] The channel of the guild scheduled event + * @property {GuildScheduledEventStatus|number} [status] The status of the guild scheduled event + * @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the + * guild scheduled event + * This can be modified only if `entityType` of the `GuildScheduledEvent` to be edited is 'EXTERNAL' + * @property {string} [reason] The reason for editing the guild scheduled event + */ + + /** + * Edits a guild scheduled event. + * @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to edit + * @param {GuildScheduledEventEditOptions} options Options to edit the guild scheduled event + * @returns {Promise} + */ + async edit(guildScheduledEvent, options) { + const guildScheduledEventId = this.resolveId(guildScheduledEvent); + if (!guildScheduledEventId) throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE'); + + if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); + let { + privacyLevel, + entityType, + channel, + status, + name, + scheduledStartTime, + description, + scheduledEndTime, + entityMetadata, + reason, + } = options; + + if (typeof privacyLevel === 'string') privacyLevel = PrivacyLevels[privacyLevel]; + if (typeof entityType === 'string') entityType = GuildScheduledEventEntityTypes[entityType]; + if (typeof status === 'string') status = GuildScheduledEventStatuses[status]; + + let entity_metadata; + if (entityMetadata) { + entity_metadata = { + location: entityMetadata.location, + }; + } + + const data = await this.client.api.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId).patch({ + data: { + channel_id: typeof channel === 'undefined' ? channel : this.guild.channels.resolveId(channel), + name, + privacy_level: privacyLevel, + scheduled_start_time: scheduledStartTime ? new Date(scheduledStartTime).toISOString() : undefined, + scheduled_end_time: scheduledEndTime ? new Date(scheduledEndTime).toISOString() : scheduledEndTime, + description, + entity_type: entityType, + status, + entity_metadata, + }, + reason, + }); + + return this._add(data); + } + + /** + * Deletes a guild scheduled event. + * @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to delete + * @returns {Promise} + */ + async delete(guildScheduledEvent) { + const guildScheduledEventId = this.resolveId(guildScheduledEvent); + if (!guildScheduledEventId) throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE'); + + await this.client.api.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId).delete(); + } + + /** + * Options used to fetch subscribers of a guild scheduled event + * @typedef {Object} FetchGuildScheduledEventSubscribersOptions + * @property {number} [limit] The maximum numbers of users to fetch + * @property {boolean} [withMember] Whether to fetch guild member data of the users + * @property {Snowflake} [before] Consider only users before this user id + * @property {Snowflake} [after] Consider only users after this user id + * If both `before` and `after` are provided, only `before` is respected + */ + + /** + * Represents a subscriber of a {@link GuildScheduledEvent} + * @typedef {Object} GuildScheduledEventUser + * @property {Snowflake} guildScheduledEventId The id of the guild scheduled event which the user subscribed to + * @property {User} user The user that subscribed to the guild scheduled event + * @property {?GuildMember} member The guild member associated with the user, if any + */ + + /** + * Fetches subscribers of a guild scheduled event. + * @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to fetch subscribers of + * @param {FetchGuildScheduledEventSubscribersOptions} [options={}] Options for fetching the subscribers + * @returns {Promise>} + */ + async fetchSubscribers(guildScheduledEvent, options = {}) { + const guildScheduledEventId = this.resolveId(guildScheduledEvent); + if (!guildScheduledEventId) throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE'); + + let { limit, withMember, before, after } = options; + + const data = await this.client.api.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId).users.get({ + query: { limit, with_member: withMember, before, after }, + }); + + return data.reduce( + (coll, rawData) => + coll.set(rawData.user.id, { + guildScheduledEventId: rawData.guild_scheduled_event_id, + user: this.client.users._add(rawData.user), + member: rawData.member ? this.guild.members._add({ ...rawData.member, user: rawData.user }) : null, + }), + new Collection(), + ); + } +} + +module.exports = GuildScheduledEventManager; diff --git a/src/structures/Guild.js b/src/structures/Guild.js index fcbd9df5a407..66c0e460b328 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -15,6 +15,7 @@ const GuildChannelManager = require('../managers/GuildChannelManager'); const GuildEmojiManager = require('../managers/GuildEmojiManager'); const GuildInviteManager = require('../managers/GuildInviteManager'); const GuildMemberManager = require('../managers/GuildMemberManager'); +const GuildScheduledEventManager = require('../managers/GuildScheduledEventManager'); const GuildStickerManager = require('../managers/GuildStickerManager'); const PresenceManager = require('../managers/PresenceManager'); const RoleManager = require('../managers/RoleManager'); @@ -109,6 +110,12 @@ class Guild extends AnonymousGuild { */ this.invites = new GuildInviteManager(this); + /** + * A manager of the scheduled events of this guild + * @type {GuildScheduledEventManager} + */ + this.scheduledEvents = new GuildScheduledEventManager(this); + if (!data) return; if (data.unavailable) { /** @@ -461,6 +468,13 @@ class Guild extends AnonymousGuild { } } + if (data.guild_scheduled_events) { + this.scheduledEvents.cache.clear(); + for (const scheduledEvent of data.guild_scheduled_events) { + this.scheduledEvents._add(scheduledEvent); + } + } + if (data.voice_states) { this.voiceStates.cache.clear(); for (const voiceState of data.voice_states) { diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 364630d7f1e2..39371847bdb0 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -1,6 +1,7 @@ 'use strict'; const { Collection } = require('@discordjs/collection'); +const { GuildScheduledEvent } = require('./GuildScheduledEvent'); const Integration = require('./Integration'); const Invite = require('./Invite'); const { StageInstance } = require('./StageInstance'); @@ -24,6 +25,7 @@ const Util = require('../util/Util'); * * STAGE_INSTANCE * * STICKER * * THREAD + * * GUILD_SCHEDULED_EVENT * @typedef {string} AuditLogTargetType */ @@ -35,6 +37,7 @@ const Util = require('../util/Util'); const Targets = { ALL: 'ALL', GUILD: 'GUILD', + GUILD_SCHEDULED_EVENT: 'GUILD_SCHEDULED_EVENT', CHANNEL: 'CHANNEL', USER: 'USER', ROLE: 'ROLE', @@ -93,6 +96,9 @@ const Targets = { * * STICKER_CREATE: 90 * * STICKER_UPDATE: 91 * * STICKER_DELETE: 92 + * * GUILD_SCHEDULED_EVENT_CREATE: 100 + * * GUILD_SCHEDULED_EVENT_UPDATE: 101 + * * GUILD_SCHEDULED_EVENT_DELETE: 102 * * THREAD_CREATE: 110 * * THREAD_UPDATE: 111 * * THREAD_DELETE: 112 @@ -148,6 +154,9 @@ const Actions = { STICKER_CREATE: 90, STICKER_UPDATE: 91, STICKER_DELETE: 92, + GUILD_SCHEDULED_EVENT_CREATE: 100, + GUILD_SCHEDULED_EVENT_UPDATE: 101, + GUILD_SCHEDULED_EVENT_DELETE: 102, THREAD_CREATE: 110, THREAD_UPDATE: 111, THREAD_DELETE: 112, @@ -218,11 +227,12 @@ class GuildAuditLogs { * * An integration * * A stage instance * * A sticker + * * A guild scheduled event * * A thread * * An object with an id key if target was deleted * * An object where the keys represent either the new value or the old value - * @typedef {?(Object|Guild|Channel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker)} - * AuditLogEntryTarget + * @typedef {?(Object|Guild|Channel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker| + * GuildScheduledEvent)} AuditLogEntryTarget */ /** @@ -242,7 +252,7 @@ class GuildAuditLogs { if (target < 83) return Targets.INTEGRATION; if (target < 86) return Targets.STAGE_INSTANCE; if (target < 100) return Targets.STICKER; - if (target < 110) return Targets.UNKNOWN; + if (target < 110) return Targets.GUILD_SCHEDULED_EVENT; if (target < 120) return Targets.THREAD; return Targets.UNKNOWN; } @@ -276,6 +286,7 @@ class GuildAuditLogs { Actions.INTEGRATION_CREATE, Actions.STAGE_INSTANCE_CREATE, Actions.STICKER_CREATE, + Actions.GUILD_SCHEDULED_EVENT_CREATE, Actions.THREAD_CREATE, ].includes(action) ) { @@ -300,6 +311,7 @@ class GuildAuditLogs { Actions.INTEGRATION_DELETE, Actions.STAGE_INSTANCE_DELETE, Actions.STICKER_DELETE, + Actions.GUILD_SCHEDULED_EVENT_DELETE, Actions.THREAD_DELETE, ].includes(action) ) { @@ -321,6 +333,7 @@ class GuildAuditLogs { Actions.INTEGRATION_UPDATE, Actions.STAGE_INSTANCE_UPDATE, Actions.STICKER_UPDATE, + Actions.GUILD_SCHEDULED_EVENT_UPDATE, Actions.THREAD_UPDATE, ].includes(action) ) { @@ -577,6 +590,19 @@ class GuildAuditLogsEntry { { id: data.target_id }, ), ); + } else if (targetType === Targets.GUILD_SCHEDULED_EVENT) { + this.target = + guild.scheduledEvents.cache.get(data.target_id) ?? + new GuildScheduledEvent( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { id: data.target_id, guild_id: guild.id }, + ), + ); } else if (data.target_id) { this.target = guild[`${targetType.toLowerCase()}s`]?.cache.get(data.target_id) ?? { id: data.target_id }; } diff --git a/src/structures/GuildScheduledEvent.js b/src/structures/GuildScheduledEvent.js new file mode 100644 index 000000000000..ff862b87cf44 --- /dev/null +++ b/src/structures/GuildScheduledEvent.js @@ -0,0 +1,422 @@ +'use strict'; + +const Base = require('./Base'); +const { Error } = require('../errors'); +const { + GuildScheduledEventEntityTypes, + GuildScheduledEventStatuses, + GuildScheduledEventPrivacyLevels, + Endpoints, +} = require('../util/Constants'); +const SnowflakeUtil = require('../util/SnowflakeUtil'); + +/** + * Represents a scheduled event in a {@link Guild}. + * @extends {Base} + */ +class GuildScheduledEvent extends Base { + constructor(client, data) { + super(client); + + /** + * The id of the guild scheduled event + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The id of the guild this guild scheduled event belongs to + * @type {Snowflake} + */ + this.guildId = data.guild_id; + + this._patch(data); + } + + _patch(data) { + if ('channel_id' in data) { + /** + * The channel id in which the scheduled event will be hosted, or `null` if entity type is `EXTERNAL` + * @type {?Snowflake} + */ + this.channelId = data.channel_id; + } else { + this.channelId ??= null; + } + + if ('creator_id' in data) { + /** + * The id of the user that created this guild scheduled event + * @type {?Snowflake} + */ + this.creatorId = data.creator_id; + } else { + this.creatorId ??= null; + } + + /** + * The name of the guild scheduled event + * @type {string} + */ + this.name = data.name; + + if ('description' in data) { + /** + * The description of the guild scheduled event + * @type {?string} + */ + this.description = data.description; + } else { + this.description ??= null; + } + + /** + * The timestamp the guild scheduled event will start at + * This can be potentially `null` only when it's an {@link AuditLogEntryTarget} + * @type {?number} + */ + this.scheduledStartTimestamp = data.scheduled_start_time ? Date.parse(data.scheduled_start_time) : null; + + /** + * The timestamp the guild scheduled event will end at, + * or `null` if the event does not have a scheduled time to end + * @type {?number} + */ + this.scheduledEndTimestamp = data.scheduled_end_time ? Date.parse(data.scheduled_end_time) : null; + + /** + * The privacy level of the guild scheduled event + * @type {PrivacyLevel} + */ + this.privacyLevel = GuildScheduledEventPrivacyLevels[data.privacy_level]; + + /** + * The status of the guild scheduled event + * @type {GuildScheduledEventStatus} + */ + this.status = GuildScheduledEventStatuses[data.status]; + + /** + * The type of hosting entity associated with the scheduled event + * @type {GuildScheduledEventEntityType} + */ + this.entityType = GuildScheduledEventEntityTypes[data.entity_type]; + + if ('entity_id' in data) { + /** + * The id of the hosting entity associated with the scheduled event + * @type {?Snowflake} + */ + this.entityId = data.entity_id; + } else { + this.entityId ??= null; + } + + if ('user_count' in data) { + /** + * The number of users who are subscribed to this guild scheduled event + * @type {?number} + */ + this.userCount = data.user_count; + } else { + this.userCount ??= null; + } + + if ('creator' in data) { + /** + * The user that created this guild scheduled event + * @type {?User} + */ + this.creator = this.client.users._add(data.creator); + } else { + this.creator ??= this.client.users.resolve(this.creatorId); + } + + /* eslint-disable max-len */ + /** + * Represents the additional metadata for a {@link GuildScheduledEvent} + * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata} + * @typedef {Object} GuildScheduledEventEntityMetadata + * @property {?string} location The location of the guild scheduled event + */ + /* eslint-enable max-len */ + + if ('entity_metadata' in data) { + if (data.entity_metadata) { + /** + * Additional metadata + * @type {?GuildScheduledEventEntityMetadata} + */ + this.entityMetadata = { + location: data.entity_metadata.location ?? this.entityMetadata?.location ?? null, + }; + } else { + this.entityMetadata = null; + } + } else { + this.entityMetadata ??= null; + } + } + + /** + * The timestamp the guild scheduled event was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return SnowflakeUtil.timestampFrom(this.id); + } + + /** + * The time the guild scheduled event was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The time the guild scheduled event will start at + * @type {Date} + * @readonly + */ + get scheduledStartAt() { + return new Date(this.scheduledStartTimestamp); + } + + /** + * The time the guild scheduled event will end at, + * or `null` if the event does not have a scheduled time to end + * @type {?Date} + * @readonly + */ + get scheduledEndAt() { + return this.scheduledEndTimestamp && new Date(this.scheduledEndTimestamp); + } + + /** + * The channel associated with this scheduled event + * @type {?(VoiceChannel|StageChannel)} + * @readonly + */ + get channel() { + return this.client.channels.resolve(this.channelId); + } + + /** + * The guild this scheduled event belongs to + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.resolve(this.guildId); + } + + /** + * The URL to the guild scheduled event + * @type {string} + * @readonly + */ + get url() { + return Endpoints.scheduledEvent(this.client.options.http.scheduledEvent, this.guildId, this.id); + } + + /** + * Options used to create an invite URL to a {@link GuildScheduledEvent} + * @typedef {CreateInviteOptions} CreateGuildScheduledEventInviteURLOptions + * @property {GuildInvitableChannelResolvable} [channel] The channel to create the invite in. + * This is required when the `entityType` of `GuildScheduledEvent` is `EXTERNAL`, gets ignored otherwise + */ + + /** + * Creates an invite URL to this guild scheduled event. + * @param {CreateGuildScheduledEventInviteURLOptions} [options] The options to create the invite + * @returns {Promise} + */ + async createInviteURL(options) { + let channelId = this.channelId; + if (this.entityType === 'EXTERNAL') { + if (!options?.channel) throw new Error('INVITE_OPTIONS_MISSING_CHANNEL'); + channelId = this.guild.channels.resolveId(options.channel); + if (!channelId) throw new Error('GUILD_CHANNEL_RESOLVE'); + } + const invite = await this.guild.invites.create(channelId, options); + return Endpoints.invite(this.client.options.http.invite, invite.code, this.id); + } + + /** + * Edits this guild scheduled event. + * @param {GuildScheduledEventEditOptions} options The options to edit the guild scheduled event + * @returns {Promise} + * @example + * // Edit a guild scheduled event + * guildScheduledEvent.edit({ name: 'Party' }) + * .then(guildScheduledEvent => console.log(guildScheduledEvent)) + * .catch(console.error); + */ + edit(options) { + return this.guild.scheduledEvents.edit(this.id, options); + } + + /** + * Deletes this guild scheduled event. + * @returns {Promise} + * @example + * // Delete a guild scheduled event + * guildScheduledEvent.delete() + * .then(guildScheduledEvent => console.log(guildScheduledEvent)) + * .catch(console.error); + */ + async delete() { + await this.guild.scheduledEvents.delete(this.id); + return this; + } + + /** + * Sets a new name for the guild scheduled event. + * @param {string} name The new name of the guild scheduled event + * @param {string} [reason] The reason for changing the name + * @returns {Promise} + * @example + * // Set name of a guild scheduled event + * guildScheduledEvent.setName('Birthday Party') + * .then(guildScheduledEvent => console.log(`Set the name to: ${guildScheduledEvent.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Sets a new time to schedule the event at. + * @param {DateResolvable} scheduledStartTime The time to schedule the event at + * @param {string} [reason] The reason for changing the scheduled start time + * @returns {Promise} + * @example + * // Set start time of a guild scheduled event + * guildScheduledEvent.setScheduledStartTime('2022-09-24T00:00:00+05:30') + * .then(guildScheduledEvent => console.log(`Set the start time to: ${guildScheduledEvent.scheduledStartTime}`)) + * .catch(console.error); + */ + setScheduledStartTime(scheduledStartTime, reason) { + return this.edit({ scheduledStartTime, reason }); + } + + // TODO: scheduledEndTime gets reset on passing null but it hasn't been documented + /** + * Sets a new time to end the event at. + * @param {DateResolvable} scheduledEndTime The time to end the event at + * @param {string} [reason] The reason for changing the scheduled end time + * @returns {Promise} + * @example + * // Set end time of a guild scheduled event + * guildScheduledEvent.setScheduledEndTime('2022-09-25T00:00:00+05:30') + * .then(guildScheduledEvent => console.log(`Set the end time to: ${guildScheduledEvent.scheduledEndTime}`)) + * .catch(console.error); + */ + setScheduledEndTime(scheduledEndTime, reason) { + return this.edit({ scheduledEndTime, reason }); + } + + /** + * Sets the new description of the guild scheduled event. + * @param {string} description The description of the guild scheduled event + * @param {string} [reason] The reason for changing the description + * @returns {Promise} + * @example + * // Set description of a guild scheduled event + * guildScheduledEvent.setDescription('A virtual birthday party') + * .then(guildScheduledEvent => console.log(`Set the description to: ${guildScheduledEvent.description}`)) + * .catch(console.error); + */ + setDescription(description, reason) { + return this.edit({ description, reason }); + } + + /** + * Sets the new status of the guild scheduled event. + * If you're working with TypeScript, use this method in conjunction with status type-guards + * like {@link GuildScheduledEvent#isScheduled} to get only valid status as suggestion + * @param {GuildScheduledEventStatus|number} status The status of the guild scheduled event + * @param {string} [reason] The reason for changing the status + * @returns {Promise} + * @example + * // Set status of a guild scheduled event + * guildScheduledEvent.setStatus('ACTIVE') + * .then(guildScheduledEvent => console.log(`Set the status to: ${guildScheduledEvent.status}`)) + * .catch(console.error); + */ + setStatus(status, reason) { + return this.edit({ status, reason }); + } + + /** + * Sets the new location of the guild scheduled event. + * @param {string} location The location of the guild scheduled event + * @param {string} [reason] The reason for changing the location + * @returns {Promise} + * @example + * // Set location of a guild scheduled event + * guildScheduledEvent.setLocation('Earth') + * .then(guildScheduledEvent => console.log(`Set the location to: ${guildScheduledEvent.entityMetadata.location}`)) + * .catch(console.error); + */ + setLocation(location, reason) { + return this.edit({ entityMetadata: { location }, reason }); + } + + /** + * Fetches subscribers of this guild scheduled event. + * @param {FetchGuildScheduledEventSubscribersOptions} [options] Options for fetching the subscribers + * @returns {Promise>} + */ + fetchSubscribers(options) { + return this.guild.scheduledEvents.fetchSubscribers(this.id, options); + } + + /** + * When concatenated with a string, this automatically concatenates the event's URL instead of the object. + * @returns {string} + * @example + * // Logs: Event: https://discord.com/events/412345678901234567/499876543211234567 + * console.log(`Event: ${guildScheduledEvent}`); + */ + toString() { + return this.url; + } + + /** + * Indicates whether this guild scheduled event has an `ACTIVE` status. + * @returns {boolean} + */ + isActive() { + return GuildScheduledEventStatuses[this.status] === GuildScheduledEventStatuses.ACTIVE; + } + + /** + * Indicates whether this guild scheduled event has a `CANCELED` status. + * @returns {boolean} + */ + isCanceled() { + return GuildScheduledEventStatuses[this.status] === GuildScheduledEventStatuses.CANCELED; + } + + /** + * Indicates whether this guild scheduled event has a `COMPLETED` status. + * @returns {boolean} + */ + isCompleted() { + return GuildScheduledEventStatuses[this.status] === GuildScheduledEventStatuses.COMPLETED; + } + + /** + * Indicates whether this guild scheduled event has a `SCHEDULED` status. + * @returns {boolean} + */ + isScheduled() { + return GuildScheduledEventStatuses[this.status] === GuildScheduledEventStatuses.SCHEDULED; + } +} + +exports.GuildScheduledEvent = GuildScheduledEvent; diff --git a/src/structures/Invite.js b/src/structures/Invite.js index 2826f5c81593..0ed8b45e24b7 100644 --- a/src/structures/Invite.js +++ b/src/structures/Invite.js @@ -1,6 +1,7 @@ 'use strict'; const Base = require('./Base'); +const { GuildScheduledEvent } = require('./GuildScheduledEvent'); const IntegrationApplication = require('./IntegrationApplication'); const InviteStageInstance = require('./InviteStageInstance'); const { Error } = require('../errors'); @@ -208,6 +209,16 @@ class Invite extends Base { } else { this.stageInstance ??= null; } + + if ('guild_scheduled_event' in data) { + /** + * The guild scheduled event data if there is a {@link GuildScheduledEvent} in the channel this invite is for + * @type {?GuildScheduledEvent} + */ + this.guildScheduledEvent = new GuildScheduledEvent(this.client, data.guild_scheduled_event); + } else { + this.guildScheduledEvent ??= null; + } } /** diff --git a/src/util/Constants.js b/src/util/Constants.js index b8c01ad966f9..59f8381fca3e 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -78,7 +78,8 @@ exports.Endpoints = { makeImageUrl(`${root}/role-icons/${roleId}/${hash}`, { size, format }), }; }, - invite: (root, code) => `${root}/${code}`, + invite: (root, code, eventId) => (eventId ? `${root}/${code}?event=${eventId}` : `${root}/${code}`), + scheduledEvent: (root, guildId, eventId) => `${root}/${guildId}/${eventId}`, botGateway: '/gateway/bot', }; @@ -202,6 +203,11 @@ exports.Events = { GUILD_STICKER_CREATE: 'stickerCreate', GUILD_STICKER_DELETE: 'stickerDelete', GUILD_STICKER_UPDATE: 'stickerUpdate', + GUILD_SCHEDULED_EVENT_CREATE: 'guildScheduledEventCreate', + GUILD_SCHEDULED_EVENT_UPDATE: 'guildScheduledEventUpdate', + GUILD_SCHEDULED_EVENT_DELETE: 'guildScheduledEventDelete', + GUILD_SCHEDULED_EVENT_USER_ADD: 'guildScheduledEventUserAdd', + GUILD_SCHEDULED_EVENT_USER_REMOVE: 'guildScheduledEventUserRemove', }; exports.ShardEvents = { @@ -220,11 +226,12 @@ exports.ShardEvents = { * * GUILD_MEMBER * * MESSAGE * * REACTION + * * GUILD_SCHEDULED_EVENT * Partials require you to put checks in place when handling data. See the "Partial Structures" topic on the * [guide](https://discordjs.guide/popular-topics/partials.html) for more information. * @typedef {string} PartialType */ -exports.PartialTypes = keyMirror(['USER', 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE', 'REACTION']); +exports.PartialTypes = keyMirror(['USER', 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE', 'REACTION', 'GUILD_SCHEDULED_EVENT']); /** * The type of a WebSocket message event, e.g. `MESSAGE_CREATE`. Here are the available events: @@ -278,6 +285,11 @@ exports.PartialTypes = keyMirror(['USER', 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE', * * STAGE_INSTANCE_UPDATE * * STAGE_INSTANCE_DELETE * * GUILD_STICKERS_UPDATE + * * GUILD_SCHEDULED_EVENT_CREATE + * * GUILD_SCHEDULED_EVENT_UPDATE + * * GUILD_SCHEDULED_EVENT_DELETE + * * GUILD_SCHEDULED_EVENT_USER_ADD + * * GUILD_SCHEDULED_EVENT_USER_REMOVE * @typedef {string} WSEventType * @see {@link https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events} */ @@ -332,6 +344,11 @@ exports.WSEvents = keyMirror([ 'STAGE_INSTANCE_UPDATE', 'STAGE_INSTANCE_DELETE', 'GUILD_STICKERS_UPDATE', + 'GUILD_SCHEDULED_EVENT_CREATE', + 'GUILD_SCHEDULED_EVENT_UPDATE', + 'GUILD_SCHEDULED_EVENT_DELETE', + 'GUILD_SCHEDULED_EVENT_USER_ADD', + 'GUILD_SCHEDULED_EVENT_USER_REMOVE', ]); /** @@ -705,6 +722,7 @@ exports.VerificationLevels = createEnum(['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_ * * MAXIMUM_THREAD_PARTICIPANTS * * MAXIMUM_NON_GUILD_MEMBERS_BANS * * MAXIMUM_BAN_FETCHES + * * MAXIMUM_NUMBER_OF_UNCOMPLETED_GUILD_SCHEDULED_EVENTS_REACHED * * MAXIMUM_NUMBER_OF_STICKERS_REACHED * * MAXIMUM_PRUNE_REQUESTS * * MAXIMUM_GUILD_WIDGET_SETTINGS_UPDATE @@ -776,6 +794,8 @@ exports.VerificationLevels = createEnum(['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_ * * LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED * * STICKER_FRAME_RATE_IS_TOO_SMALL_OR_TOO_LARGE * * STICKER_ANIMATION_DURATION_EXCEEDS_MAXIMUM_OF_5_SECONDS + * * CANNOT_UPDATE_A_FINISHED_EVENT + * * FAILED_TO_CREATE_STAGE_NEEDED_FOR_STAGE_EVENT * @typedef {string} APIError * @see {@link https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes} */ @@ -849,6 +869,7 @@ exports.APIErrors = { MAXIMUM_THREAD_PARTICIPANTS: 30033, MAXIMUM_NON_GUILD_MEMBERS_BANS: 30035, MAXIMUM_BAN_FETCHES: 30037, + MAXIMUM_NUMBER_OF_UNCOMPLETED_GUILD_SCHEDULED_EVENTS_REACHED: 30038, MAXIMUM_NUMBER_OF_STICKERS_REACHED: 30039, MAXIMUM_PRUNE_REQUESTS: 30040, MAXIMUM_GUILD_WIDGET_SETTINGS_UPDATE: 30042, @@ -920,6 +941,8 @@ exports.APIErrors = { LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005, STICKER_FRAME_RATE_IS_TOO_SMALL_OR_TOO_LARGE: 170006, STICKER_ANIMATION_DURATION_EXCEEDS_MAXIMUM_OF_5_SECONDS: 170007, + CANNOT_UPDATE_A_FINISHED_EVENT: 180000, + FAILED_TO_CREATE_STAGE_NEEDED_FOR_STAGE_EVENT: 180002, }; /** @@ -1066,7 +1089,6 @@ exports.InteractionResponseTypes = createEnum([ 'UPDATE_MESSAGE', 'APPLICATION_COMMAND_AUTOCOMPLETE_RESULT', ]); -/* eslint-enable max-len */ /** * The type of a message component @@ -1119,6 +1141,14 @@ exports.NSFWLevels = createEnum(['DEFAULT', 'EXPLICIT', 'SAFE', 'AGE_RESTRICTED' */ exports.PrivacyLevels = createEnum([null, 'PUBLIC', 'GUILD_ONLY']); +/** + * Privacy level of a {@link GuildScheduledEvent} object: + * * GUILD_ONLY + * @typedef {string} GuildScheduledEventPrivacyLevel + * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level} + */ +exports.GuildScheduledEventPrivacyLevels = createEnum([null, null, 'GUILD_ONLY']); + /** * The premium tier (Server Boost level) of a guild: * * NONE @@ -1130,6 +1160,29 @@ exports.PrivacyLevels = createEnum([null, 'PUBLIC', 'GUILD_ONLY']); */ exports.PremiumTiers = createEnum(['NONE', 'TIER_1', 'TIER_2', 'TIER_3']); +/** + * The status of a {@link GuildScheduledEvent}: + * * SCHEDULED + * * ACTIVE + * * COMPLETED + * * CANCELED + * @typedef {string} GuildScheduledEventStatus + * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status} + */ +exports.GuildScheduledEventStatuses = createEnum([null, 'SCHEDULED', 'ACTIVE', 'COMPLETED', 'CANCELED']); + +/** + * The entity type of a {@link GuildScheduledEvent}: + * * NONE + * * STAGE_INSTANCE + * * VOICE + * * EXTERNAL + * @typedef {string} GuildScheduledEventEntityType + * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types} + */ +exports.GuildScheduledEventEntityTypes = createEnum([null, 'STAGE_INSTANCE', 'VOICE', 'EXTERNAL']); +/* eslint-enable max-len */ + exports._cleanupSymbol = Symbol('djsCleanup'); function keyMirror(arr) { @@ -1161,6 +1214,11 @@ function createEnum(keys) { * The value set for a guild's default message notifications. * @property {ExplicitContentFilterLevel} ExplicitContentFilterLevels * The value set for the explicit content filter levels for a guild. + * @property {GuildScheduledEventStatus} GuildScheduledEventStatuses The status of a {@link GuildScheduledEvent} object. + * @property {GuildScheduledEventEntityType} GuildScheduledEventEntityTypes The entity type of a + * {@link GuildScheduledEvent} object. + * @property {GuildScheduledEventPrivacyLevel} GuildScheduledEventPrivacyLevels Privacy level of a + * {@link GuildScheduledEvent} object. * @property {InteractionResponseType} InteractionResponseTypes The type of an interaction response. * @property {InteractionType} InteractionTypes The type of an {@link Interaction} object. * @property {MembershipState} MembershipStates The value set for a team member's membership state. diff --git a/src/util/Intents.js b/src/util/Intents.js index a96628c5505a..359e10b4bb99 100644 --- a/src/util/Intents.js +++ b/src/util/Intents.js @@ -40,6 +40,7 @@ class Intents extends BitField {} * * `DIRECT_MESSAGES` * * `DIRECT_MESSAGE_REACTIONS` * * `DIRECT_MESSAGE_TYPING` + * * `GUILD_SCHEDULED_EVENTS` * @type {Object} * @see {@link https://discord.com/developers/docs/topics/gateway#list-of-intents} */ @@ -59,6 +60,7 @@ Intents.FLAGS = { DIRECT_MESSAGES: 1 << 12, DIRECT_MESSAGE_REACTIONS: 1 << 13, DIRECT_MESSAGE_TYPING: 1 << 14, + GUILD_SCHEDULED_EVENTS: 1 << 16, }; module.exports = Intents; diff --git a/src/util/Options.js b/src/util/Options.js index 3180cf4713c5..01690b4cfe4d 100644 --- a/src/util/Options.js +++ b/src/util/Options.js @@ -115,6 +115,7 @@ * @property {string} [invite='https://discord.gg'] Base URL of invites * @property {string} [template='https://discord.new'] Base URL of templates * @property {Object} [headers] Additional headers to send for all API requests + * @property {string} [scheduledEvent='https://discord.com/events'] Base URL of guild scheduled events */ /** @@ -160,6 +161,7 @@ class Options extends null { cdn: 'https://cdn.discordapp.com', invite: 'https://discord.gg', template: 'https://discord.new', + scheduledEvent: 'https://discord.com/events', }, }; } diff --git a/src/util/Permissions.js b/src/util/Permissions.js index b44e5539470d..8646e1358032 100644 --- a/src/util/Permissions.js +++ b/src/util/Permissions.js @@ -90,6 +90,7 @@ class Permissions extends BitField { * * `MANAGE_EMOJIS_AND_STICKERS` * * `USE_APPLICATION_COMMANDS` * * `REQUEST_TO_SPEAK` + * * `MANAGE_EVENTS` * * `MANAGE_THREADS` * * `USE_PUBLIC_THREADS` (deprecated) * * `CREATE_PUBLIC_THREADS` @@ -136,6 +137,7 @@ Permissions.FLAGS = { MANAGE_EMOJIS_AND_STICKERS: 1n << 30n, USE_APPLICATION_COMMANDS: 1n << 31n, REQUEST_TO_SPEAK: 1n << 32n, + MANAGE_EVENTS: 1n << 33n, MANAGE_THREADS: 1n << 34n, // TODO: Remove deprecated USE_*_THREADS flags in v14 USE_PUBLIC_THREADS: 1n << 35n, diff --git a/typings/enums.d.ts b/typings/enums.d.ts index 4332983a55ec..f8efb4a170f5 100644 --- a/typings/enums.d.ts +++ b/typings/enums.d.ts @@ -86,6 +86,23 @@ export const enum ExplicitContentFilterLevels { ALL_MEMBERS = 2, } +export const enum GuildScheduledEventEntityTypes { + STAGE_INSTANCE = 1, + VOICE = 2, + EXTERNAL = 3, +} + +export const enum GuildScheduledEventPrivacyLevels { + GUILD_ONLY = 2, +} + +export const enum GuildScheduledEventStatuses { + SCHEDULED = 1, + ACTIVE = 2, + COMPLETED = 3, + CANCELED = 4, +} + export const enum InteractionResponseTypes { PONG = 1, CHANNEL_MESSAGE_WITH_SOURCE = 4, diff --git a/typings/index.d.ts b/typings/index.d.ts index a48681ce52aa..319aca3c0d3f 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -80,6 +80,9 @@ import { StickerTypes, VerificationLevels, WebhookTypes, + GuildScheduledEventEntityTypes, + GuildScheduledEventStatuses, + GuildScheduledEventPrivacyLevels, } from './enums'; import { RawActivityData, @@ -99,6 +102,7 @@ import { RawGuildEmojiData, RawGuildMemberData, RawGuildPreviewData, + RawGuildScheduledEventData, RawGuildTemplateData, RawIntegrationApplicationData, RawIntegrationData, @@ -557,7 +561,7 @@ export class Client extends BaseClient { public ws: WebSocketManager; public destroy(): void; public fetchGuildPreview(guild: GuildResolvable): Promise; - public fetchInvite(invite: InviteResolvable): Promise; + public fetchInvite(invite: InviteResolvable, options?: ClientFetchInviteOptions): Promise; public fetchGuildTemplate(template: GuildTemplateResolvable): Promise; public fetchVoiceRegions(): Promise>; public fetchSticker(id: Snowflake): Promise; @@ -909,6 +913,7 @@ export class Guild extends AnonymousGuild { public roles: RoleManager; public readonly rulesChannel: TextChannel | null; public rulesChannelId: Snowflake | null; + public scheduledEvents: GuildScheduledEventManager; public readonly shard: WebSocketShard; public shardId: number; public stageInstances: StageInstanceManager; @@ -1151,6 +1156,54 @@ export class GuildPreview extends Base { public toString(): string; } +export class GuildScheduledEvent extends Base { + private constructor(client: Client, data: RawGuildScheduledEventData); + public id: Snowflake; + public guildId: Snowflake; + public channelId: Snowflake | null; + public creatorId: Snowflake | null; + public name: string; + public description: string | null; + public scheduledStartTimestamp: number | null; + public scheduledEndTimestamp: number | null; + public privacyLevel: GuildScheduledEventPrivacyLevel; + public status: S; + public entityType: GuildScheduledEventEntityType; + public entityId: Snowflake | null; + public entityMetadata: GuildScheduledEventEntityMetadata; + public userCount: number | null; + public creator: User | null; + public readonly createdTimestamp: number; + public readonly createdAt: Date; + public readonly scheduledStartAt: Date; + public readonly scheduledEndAt: Date | null; + public readonly channel: VoiceChannel | StageChannel | null; + public readonly guild: Guild | null; + public readonly url: string; + public createInviteURL(options?: CreateGuildScheduledEventInviteURLOptions): Promise; + public edit>( + options: GuildScheduledEventEditOptions, + ): Promise>; + public delete(): Promise>; + public setName(name: string, reason?: string): Promise>; + public setScheduledStartTime(scheduledStartTime: DateResolvable, reason?: string): Promise>; + public setScheduledEndTime(scheduledEndTime: DateResolvable, reason?: string): Promise>; + public setDescription(description: string, reason?: string): Promise>; + public setStatus>( + status: T, + reason?: string, + ): Promise>; + public setLocation(location: string, reason?: string): Promise>; + public fetchSubscribers( + options?: T, + ): Promise>; + public toString(): string; + public isActive(): this is GuildScheduledEvent<'ACTIVE'>; + public isCanceled(): this is GuildScheduledEvent<'CANCELED'>; + public isCompleted(): this is GuildScheduledEvent<'COMPLETED'>; + public isScheduled(): this is GuildScheduledEvent<'SCHEDULED'>; +} + export class GuildTemplate extends Base { private constructor(client: Client, data: RawGuildTemplateData); public readonly createdTimestamp: number; @@ -1352,6 +1405,7 @@ export class Invite extends Base { public toString(): string; public static INVITES_PATTERN: RegExp; public stageInstance: InviteStageInstance | null; + public guildScheduledEvent: GuildScheduledEvent | null; } export class InviteStageInstance extends Base { @@ -2670,7 +2724,8 @@ export const Constants: { UserAgent: string; Endpoints: { botGateway: string; - invite: (root: string, code: string) => string; + invite: (root: string, code: string, eventId?: Snowflake) => string; + scheduledEvent: (root: string, guildId: Snowflake, eventId: Snowflake) => string; CDN: (root: string) => { Emoji: (emojiId: Snowflake, format: DynamicImageFormat) => string; Asset: (name: string) => string; @@ -2774,6 +2829,9 @@ export const Constants: { WebhookTypes: EnumHolder; PremiumTiers: EnumHolder; ApplicationCommandTypes: EnumHolder; + GuildScheduledEventEntityTypes: EnumHolder; + GuildScheduledEventStatuses: EnumHolder; + GuildScheduledEventPrivacyLevels: EnumHolder; }; export const version: string; @@ -3021,6 +3079,29 @@ export class GuildInviteManager extends DataManager; } +export class GuildScheduledEventManager extends CachedManager< + Snowflake, + GuildScheduledEvent, + GuildScheduledEventResolvable +> { + private constructor(guild: Guild, iterable?: Iterable); + public guild: Guild; + public create(options: GuildScheduledEventCreateOptions): Promise; + public fetch(): Promise>; + public fetch< + T extends GuildScheduledEventResolvable | FetchGuildScheduledEventOptions | FetchGuildScheduledEventsOptions, + >(options?: T): Promise>; + public edit>( + guildScheduledEvent: GuildScheduledEventResolvable, + options: GuildScheduledEventEditOptions, + ): Promise>; + public delete(guildScheduledEvent: GuildScheduledEventResolvable): Promise; + public fetchSubscribers( + guildScheduledEvent: GuildScheduledEventResolvable, + options?: T, + ): Promise>; +} + export class GuildStickerManager extends CachedManager { private constructor(guild: Guild, iterable?: Iterable); public guild: Guild; @@ -3366,6 +3447,7 @@ export interface APIErrors { MAXIMUM_THREAD_PARICIPANTS: 30033; MAXIMUM_NON_GUILD_MEMBERS_BANS: 30035; MAXIMUM_BAN_FETCHES: 30037; + MAXIMUM_NUMBER_OF_UNCOMPLETED_GUILD_SCHEDULED_EVENTS_REACHED: 30038; MAXIMUM_NUMBER_OF_STICKERS_REACHED: 30039; MAXIMUM_PRUNE_REQUESTS: 30040; MAXIMUM_GUILD_WIDGET_SETTINGS_UPDATE: 30042; @@ -3437,6 +3519,8 @@ export interface APIErrors { LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005; STICKER_FRAME_RATE_IS_TOO_SMALL_OR_TOO_LARGE: 170006; STICKER_ANIMATION_DURATION_EXCEEDS_MAXIMUM_OF_5_SECONDS: 170007; + CANNOT_UPDATE_A_FINISHED_EVENT: 180000; + FAILED_TO_CREATE_STAGE_NEEDED_FOR_STAGE_EVENT: 180002; } export interface APIRequest { @@ -3700,6 +3784,7 @@ export interface Caches { GuildMemberManager: [manager: typeof GuildMemberManager, holds: typeof GuildMember]; GuildBanManager: [manager: typeof GuildBanManager, holds: typeof GuildBan]; GuildInviteManager: [manager: typeof GuildInviteManager, holds: typeof Invite]; + GuildScheduledEventManager: [manager: typeof GuildScheduledEventManager, holds: typeof GuildScheduledEvent]; GuildStickerManager: [manager: typeof GuildStickerManager, holds: typeof Sticker]; MessageManager: [manager: typeof MessageManager, holds: typeof Message]; // TODO: PermissionOverwriteManager: [manager: typeof PermissionOverwriteManager, holds: typeof PermissionOverwrites]; @@ -3891,6 +3976,15 @@ export interface ClientEvents extends BaseClientEvents { stickerCreate: [sticker: Sticker]; stickerDelete: [sticker: Sticker]; stickerUpdate: [oldSticker: Sticker, newSticker: Sticker]; + guildScheduledEventCreate: [guildScheduledEvent: GuildScheduledEvent]; + guildScheduledEventUpdate: [oldGuildScheduledEvent: GuildScheduledEvent, newGuildScheduledEvent: GuildScheduledEvent]; + guildScheduledEventDelete: [guildScheduledEvent: GuildScheduledEvent]; + guildScheduledEventUserAdd: [guildScheduledEvent: GuildScheduledEvent, user: User]; + guildScheduledEventUserRemove: [guildScheduledEvent: GuildScheduledEvent, user: User]; +} + +export interface ClientFetchInviteOptions { + guildScheduledEventId?: Snowflake; } export interface ClientOptions { @@ -4124,6 +4218,11 @@ export interface ConstantsEvents { GUILD_STICKER_CREATE: 'stickerCreate'; GUILD_STICKER_DELETE: 'stickerDelete'; GUILD_STICKER_UPDATE: 'stickerUpdate'; + GUILD_SCHEDULED_EVENT_CREATE: 'guildScheduledEventCreate'; + GUILD_SCHEDULED_EVENT_UPDATE: 'guildScheduledEventUpdate'; + GUILD_SCHEDULED_EVENT_DELETE: 'guildScheduledEventDelete'; + GUILD_SCHEDULED_EVENT_USER_ADD: 'guildScheduledEventUserAdd'; + GUILD_SCHEDULED_EVENT_USER_REMOVE: 'guildScheduledEventUserRemove'; } export interface ConstantsOpcodes { @@ -4158,6 +4257,10 @@ export interface ConstantsStatus { DISCONNECTED: 5; } +export interface CreateGuildScheduledEventInviteURLOptions extends CreateInviteOptions { + channel?: GuildInvitableChannelResolvable; +} + export interface CreateRoleOptions extends RoleData { reason?: string; } @@ -4276,6 +4379,21 @@ export interface FetchGuildsOptions { limit?: number; } +export interface FetchGuildScheduledEventOptions extends BaseFetchOptions { + guildScheduledEvent: GuildScheduledEventResolvable; + withUserCount?: boolean; +} + +export interface FetchGuildScheduledEventsOptions { + cache?: boolean; + withUserCount?: boolean; +} + +export interface FetchGuildScheduledEventSubscribersOptions { + limit?: number; + withMember?: boolean; +} + interface FetchInviteOptions extends BaseFetchOptions { code: string; } @@ -4364,6 +4482,9 @@ interface GuildAuditLogsTypes { STICKER_CREATE: ['STICKER', 'CREATE']; STICKER_UPDATE: ['STICKER', 'UPDATE']; STICKER_DELETE: ['STICKER', 'DELETE']; + GUILD_SCHEDULED_EVENT_CREATE: ['GUILD_SCHEDULED_EVENT', 'CREATE']; + GUILD_SCHEDULED_EVENT_UPDATE: ['GUILD_SCHEDULED_EVENT', 'UPDATE']; + GUILD_SCHEDULED_EVENT_DELETE: ['GUILD_SCHEDULED_EVENT', 'DELETE']; THREAD_CREATE: ['THREAD', 'CREATE']; THREAD_UPDATE: ['THREAD', 'UPDATE']; THREAD_DELETE: ['THREAD', 'DELETE']; @@ -4411,6 +4532,9 @@ export interface GuildAuditLogsIds { 90: 'STICKER_CREATE'; 91: 'STICKER_UPDATE'; 92: 'STICKER_DELETE'; + 100: 'GUILD_SCHEDULED_EVENT_CREATE'; + 101: 'GUILD_SCHEDULED_EVENT_UPDATE'; + 102: 'GUILD_SCHEDULED_EVENT_DELETE'; 110: 'THREAD_CREATE'; 111: 'THREAD_UPDATE'; 112: 'THREAD_DELETE'; @@ -4461,6 +4585,7 @@ export interface GuildAuditLogsEntryTargetField { @@ -4627,6 +4752,66 @@ export interface GuildListMembersOptions { cache?: boolean; } +// TODO: use conditional types for better TS support +export interface GuildScheduledEventCreateOptions { + name: string; + scheduledStartTime: DateResolvable; + scheduledEndTime?: DateResolvable; + privacyLevel: GuildScheduledEventPrivacyLevel | number; + entityType: GuildScheduledEventEntityType | number; + description?: string; + channel?: GuildVoiceChannelResolvable; + entityMetadata?: GuildScheduledEventEntityMetadataOptions; + reason?: string; +} + +export interface GuildScheduledEventEditOptions< + S extends GuildScheduledEventStatus, + T extends GuildScheduledEventSetStatusArg, +> extends Omit, 'channel'> { + channel?: GuildVoiceChannelResolvable | null; + status?: T | number; +} + +export interface GuildScheduledEventEntityMetadata { + location: string | null; +} + +export interface GuildScheduledEventEntityMetadataOptions { + location?: string; +} + +export type GuildScheduledEventEntityType = keyof typeof GuildScheduledEventEntityTypes; + +export type GuildScheduledEventManagerFetchResult< + T extends GuildScheduledEventResolvable | FetchGuildScheduledEventOptions | FetchGuildScheduledEventsOptions, +> = T extends GuildScheduledEventResolvable | FetchGuildScheduledEventOptions + ? GuildScheduledEvent + : Collection; + +export type GuildScheduledEventManagerFetchSubscribersResult = + T extends { withMember: true } + ? Collection> + : Collection>; + +export type GuildScheduledEventPrivacyLevel = keyof typeof GuildScheduledEventPrivacyLevels; + +export type GuildScheduledEventResolvable = Snowflake | GuildScheduledEvent; + +export type GuildScheduledEventSetStatusArg = T extends 'SCHEDULED' + ? 'ACTIVE' | 'CANCELED' + : T extends 'ACTIVE' + ? 'COMPLETED' + : never; + +export type GuildScheduledEventStatus = keyof typeof GuildScheduledEventStatuses; + +export interface GuildScheduledEventUser { + guildScheduledEventId: Snowflake; + user: User; + member: T extends true ? GuildMember : null; +} + export type GuildTemplateResolvable = string; export type GuildVoiceChannelResolvable = VoiceBasedChannel | Snowflake; @@ -4653,6 +4838,7 @@ export interface HTTPOptions { invite?: string; template?: string; headers?: Record; + scheduledEvent?: string; } export interface ImageURLOptions extends Omit { @@ -4714,7 +4900,8 @@ export type IntentsString = | 'GUILD_MESSAGE_TYPING' | 'DIRECT_MESSAGES' | 'DIRECT_MESSAGE_REACTIONS' - | 'DIRECT_MESSAGE_TYPING'; + | 'DIRECT_MESSAGE_TYPING' + | 'GUILD_SCHEDULED_EVENTS'; export interface InviteGenerationOptions { permissions?: PermissionResolvable; @@ -5087,7 +5274,8 @@ export type PermissionString = | 'USE_EXTERNAL_STICKERS' | 'SEND_MESSAGES_IN_THREADS' | 'START_EMBEDDED_ACTIVITIES' - | 'MODERATE_MEMBERS'; + | 'MODERATE_MEMBERS' + | 'MANAGE_EVENTS'; export type RecursiveArray = ReadonlyArray>; @@ -5168,7 +5356,7 @@ export interface PartialRoleData extends RoleData { id?: Snowflake | number; } -export type PartialTypes = 'USER' | 'CHANNEL' | 'GUILD_MEMBER' | 'MESSAGE' | 'REACTION'; +export type PartialTypes = 'USER' | 'CHANNEL' | 'GUILD_MEMBER' | 'MESSAGE' | 'REACTION' | 'GUILD_SCHEDULED_EVENT'; export interface PartialUser extends Partialize {} diff --git a/typings/rawDataTypes.d.ts b/typings/rawDataTypes.d.ts index 27156fe8a994..885566d40d0c 100644 --- a/typings/rawDataTypes.d.ts +++ b/typings/rawDataTypes.d.ts @@ -75,6 +75,7 @@ import { RESTPostAPIInteractionFollowupJSONBody, RESTPostAPIWebhookWithTokenJSONBody, Snowflake, + APIGuildScheduledEvent, } from 'discord-api-types/v9'; import { GuildChannel, Guild, PermissionOverwrites } from '.'; @@ -128,6 +129,8 @@ export type RawThreadMemberData = APIThreadMember; export type RawGuildPreviewData = APIGuildPreview; +export type RawGuildScheduledEventData = APIGuildScheduledEvent; + export type RawGuildTemplateData = APITemplate; export type RawIntegrationData = APIGuildIntegration;