From d78a10ed760b2f5faa68ad13aff6ab8fe98fc134 Mon Sep 17 00:00:00 2001 From: March 7th <71698422+aiko-chan-ai@users.noreply.github.com> Date: Tue, 12 Apr 2022 20:46:25 +0700 Subject: [PATCH] 1.3.4 Get interaction commands using gateway --- DOCUMENT.md | 1 + Document/API.md | 68 + Document/Message.md | 25 + package.json | 2 +- src/client/actions/InteractionCreate.js | 2 +- .../GUILD_APPLICATION_COMMANDS_UPDATE.js | 10 + .../handlers/GUILD_MEMBER_LIST_UPDATE.js | 1 + .../websocket/handlers/INTERACTION_CREATE.js | 4 +- .../websocket/handlers/INTERACTION_FAILED.js | 6 + .../websocket/handlers/INTERACTION_SUCCESS.js | 6 + src/client/websocket/handlers/index.js | 6 + src/managers/ApplicationCommandManager.js | 5 +- src/structures/Guild.js | 3019 +++++++++-------- src/util/Constants.js | 2 + typings/index.d.ts | 170 +- 15 files changed, 1791 insertions(+), 1536 deletions(-) create mode 100644 Document/API.md create mode 100644 src/client/websocket/handlers/GUILD_APPLICATION_COMMANDS_UPDATE.js create mode 100644 src/client/websocket/handlers/INTERACTION_FAILED.js create mode 100644 src/client/websocket/handlers/INTERACTION_SUCCESS.js diff --git a/DOCUMENT.md b/DOCUMENT.md index 55465bf..8ef3476 100644 --- a/DOCUMENT.md +++ b/DOCUMENT.md @@ -9,6 +9,7 @@ - [Guild](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/Guild.md) - [Message](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/Message.md) - [User](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/User.md) +- [API](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/API.md) ## More features diff --git a/Document/API.md b/Document/API.md new file mode 100644 index 00000000..c6fd643 --- /dev/null +++ b/Document/API.md @@ -0,0 +1,68 @@ +# Extending Discord.js-selfbot-v13 +> `credit: Discum` + +How to add extra API wraps to Discord.js-selfbot-v13? +# Table of Contents +- [HTTP APIs](#HTTP-APIs) +- [Gateway APIs](#Gateway-APIs) + +### HTTP APIs: + +```js +URL example: +'https://discord.com/api/v9/users/@me' +const url = client.api.users['@me']; +/* Method: GET | POST | PUT | PATCH | DELETE */ +``` + + +###### GET: +```js +await client.api.users['@me'].get({ versioned: true }); +/* Request: https://discord.com/api/v9/users/@me */ +await client.api.users['@me'].get({ versioned: false }); +/* Request: https://discord.com/api/users/@me */ +``` +###### POST: +```js +await client.api.channels[channel.id].messages.post({ versioned: true, data: {}, files: [] }); +/* Request: https://discord.com/api/v9/channels/{channel.id}/messages */ +``` +###### PUT: +```js +await client.api + .guilds(guild.id) + .bans(user.id) + .put({ + versioned: true, + data: {}, + }); +/* Request: https://discord.com/api/guilds/{guild.id}/bans/{user.id} */ +``` +###### PATCH: +```js +await client.api.users['@me'].patch({ versioned: true, data: {} }); +/* Request: https://discord.com/api/v9/users/@me */ +``` +###### DELETE: +```js +await client.api.hypesquad.online.delete({ versioned: true }); +/* Request: https://discord.com/api/v9/hypesquad/online */ +``` +### Gateway APIs +You need to send data to the port and listen for an event. This is quite complicated but if you want to use an existing event, here are the instructions + +###### SEND: +```js +const { Constants } = require('discord.js-selfbot-v13'); +// Global gateway (example update presence) +client.ws.broadcast({ + op: Constants.Opcodes.STATUS_UPDATE, + d: {}, +}); +// Guild gateway (example get all members) +guild.shard.send({ + op: Constants.Opcodes.REQUEST_GUILD_MEMBERS, + d: {}, +}); +``` \ No newline at end of file diff --git a/Document/Message.md b/Document/Message.md index aa59b49..459f8be 100644 --- a/Document/Message.md +++ b/Document/Message.md @@ -4,6 +4,30 @@ ## Interaction
+Fetch Commands data + +```js +/* Save to cache */ +// In guild (Opcode 24) +await guild.searchInteraction( + { + limit: 100, // default: 1 + query: 'ping', // optional + type: 'CHAT_INPUT', // default: 'CHAT_INPUT' + offset: 0, // default: 0 + botID: ['botid1', 'botid2'], // optional + } +); +// Fetch all commands (1 bot) Shouldn't be used +await bot.applications.fetch( + { + guildId: 'guild id to search', // optional + force: false, // Using cache or createDMs to bot + } +); +``` +
+
Button Click ```js @@ -57,6 +81,7 @@ await message.contextMenu(botID, commandName); > In this way, all Slash commands can be obtained - I will try to find another way to not need to create DMs with Bot anymore - Credit: [Here](https://www.reddit.com/r/Discord_selfbots/comments/tczprx/discum_help_creating_a_selfbot_that_can_do_ping/) +- Update: Now to get more secure interaction commands you need to use guild.searchInteraction() (using gateway)
## MessageEmbed ? diff --git a/package.json b/package.json index ac0f646..ddede6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord.js-selfbot-v13", - "version": "1.3.3", + "version": "1.3.4", "description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]", "main": "./src/index.js", "types": "./typings/index.d.ts", diff --git a/src/client/actions/InteractionCreate.js b/src/client/actions/InteractionCreate.js index 04774a6..94be343 100644 --- a/src/client/actions/InteractionCreate.js +++ b/src/client/actions/InteractionCreate.js @@ -60,7 +60,7 @@ class InteractionCreateAction extends Action { InteractionType = AutocompleteInteraction; break; default: - client.emit(Events.DEBUG, `[INTERACTION] Received interaction with unknown type: ${data.type}`); + client.emit(Events.DEBUG, `[INTERACTION] Received [BOT] / Send (Selfbot) interactionID ${data.id} with unknown type: ${data.type}`); return; } diff --git a/src/client/websocket/handlers/GUILD_APPLICATION_COMMANDS_UPDATE.js b/src/client/websocket/handlers/GUILD_APPLICATION_COMMANDS_UPDATE.js new file mode 100644 index 00000000..e4d64ad --- /dev/null +++ b/src/client/websocket/handlers/GUILD_APPLICATION_COMMANDS_UPDATE.js @@ -0,0 +1,10 @@ +'use strict'; + +module.exports = (client, { d: data }) => { + if (!data.application_commands[0]) return; + for (const command of data.application_commands) { + const user = client.users.cache.get(command.application_id); + if (!user) continue; + user.applications._add(command, true); + }; +}; \ No newline at end of file diff --git a/src/client/websocket/handlers/GUILD_MEMBER_LIST_UPDATE.js b/src/client/websocket/handlers/GUILD_MEMBER_LIST_UPDATE.js index 950678e..80687d2 100644 --- a/src/client/websocket/handlers/GUILD_MEMBER_LIST_UPDATE.js +++ b/src/client/websocket/handlers/GUILD_MEMBER_LIST_UPDATE.js @@ -9,6 +9,7 @@ module.exports = (client, { d: data }) => { const guild = client.guilds.cache.get(data.guild_id); if (!guild) return; const members = new Collection(); + // Get Member from side Discord Channel (online counting if large server) for (const object of data.ops) { if (object.op == 'SYNC') { for (const member_ of object.items) { diff --git a/src/client/websocket/handlers/INTERACTION_CREATE.js b/src/client/websocket/handlers/INTERACTION_CREATE.js index 5bf30fc..86913ac 100644 --- a/src/client/websocket/handlers/INTERACTION_CREATE.js +++ b/src/client/websocket/handlers/INTERACTION_CREATE.js @@ -1,5 +1,7 @@ 'use strict'; +const { Events } = require('../../../util/Constants'); module.exports = (client, packet) => { - client.actions.InteractionCreate.handle(packet.d); + if (client.user.bot) client.actions.InteractionCreate.handle(packet.d); + else client.emit(Events.INTERACTION_CREATE, packet.d); }; diff --git a/src/client/websocket/handlers/INTERACTION_FAILED.js b/src/client/websocket/handlers/INTERACTION_FAILED.js new file mode 100644 index 00000000..9a9f086 --- /dev/null +++ b/src/client/websocket/handlers/INTERACTION_FAILED.js @@ -0,0 +1,6 @@ +'use strict'; +const { Events } = require('../../../util/Constants'); + +module.exports = (client, { d: data }) => { + client.emit(Events.INTERACTION_FAILED, data); +}; diff --git a/src/client/websocket/handlers/INTERACTION_SUCCESS.js b/src/client/websocket/handlers/INTERACTION_SUCCESS.js new file mode 100644 index 00000000..5acff20 --- /dev/null +++ b/src/client/websocket/handlers/INTERACTION_SUCCESS.js @@ -0,0 +1,6 @@ +'use strict'; +const { Events } = require('../../../util/Constants'); + +module.exports = (client, { d: data }) => { + client.emit(Events.INTERACTION_SUCCESS, data); +}; \ No newline at end of file diff --git a/src/client/websocket/handlers/index.js b/src/client/websocket/handlers/index.js index bdad4f8..c5743de 100644 --- a/src/client/websocket/handlers/index.js +++ b/src/client/websocket/handlers/index.js @@ -18,6 +18,10 @@ const handlers = Object.fromEntries([ ['GUILD_MEMBER_UPDATE', require('./GUILD_MEMBER_UPDATE')], ['GUILD_MEMBERS_CHUNK', require('./GUILD_MEMBERS_CHUNK')], ['GUILD_MEMBER_LIST_UPDATE', require('./GUILD_MEMBER_LIST_UPDATE.js')], + [ + 'GUILD_APPLICATION_COMMANDS_UPDATE', + require('./GUILD_APPLICATION_COMMANDS_UPDATE.js'), + ], ['GUILD_INTEGRATIONS_UPDATE', require('./GUILD_INTEGRATIONS_UPDATE')], ['GUILD_ROLE_CREATE', require('./GUILD_ROLE_CREATE')], ['GUILD_ROLE_DELETE', require('./GUILD_ROLE_DELETE')], @@ -50,6 +54,8 @@ const handlers = Object.fromEntries([ ['VOICE_SERVER_UPDATE', require('./VOICE_SERVER_UPDATE')], ['WEBHOOKS_UPDATE', require('./WEBHOOKS_UPDATE')], ['INTERACTION_CREATE', require('./INTERACTION_CREATE')], + ['INTERACTION_SUCCESS', require('./INTERACTION_SUCCESS')], + ['INTERACTION_FAILED', require('./INTERACTION_FAILED')], ['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')], ['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')], ['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')], diff --git a/src/managers/ApplicationCommandManager.js b/src/managers/ApplicationCommandManager.js index 6aeeedc..df93e25 100644 --- a/src/managers/ApplicationCommandManager.js +++ b/src/managers/ApplicationCommandManager.js @@ -84,7 +84,7 @@ class ApplicationCommandManager extends CachedManager { * .catch(console.error); */ async fetch(id, { guildId, cache = true, force = false } = {}) { - await this.user.createDM().catch(() => {}); + // change from user.createDM to opcode (risky action) if (typeof id === 'object') { ({ guildId, cache = true } = id); } else if (id) { @@ -92,10 +92,11 @@ class ApplicationCommandManager extends CachedManager { const existing = this.cache.get(id); if (existing) return existing; } + await this.user.createDM().catch(() => {}); const command = await this.commandPath({ id, guildId }).get(); return this._add(command, cache); } - + await this.user.createDM().catch(() => {}); const data = await this.commandPath({ guildId }).get(); return data.reduce((coll, command) => coll.set(command.id, this._add(command, cache, guildId)), new Collection()); } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 871d44e..52718b1 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -31,6 +31,7 @@ const { Status, MFALevels, PremiumTiers, + Opcodes, } = require('../util/Constants'); const DataResolver = require('../util/DataResolver'); const SystemChannelFlags = require('../util/SystemChannelFlags'); @@ -54,1468 +55,1562 @@ const deletedGuilds = new WeakSet(); * @extends {AnonymousGuild} */ class Guild extends AnonymousGuild { - constructor(client, data) { - super(client, data, false); - - /** - * A manager of the members belonging to this guild - * @type {GuildMemberManager} - */ - this.members = new GuildMemberManager(this); - - /** - * A manager of the channels belonging to this guild - * @type {GuildChannelManager} - */ - this.channels = new GuildChannelManager(this); - - /** - * A manager of the bans belonging to this guild - * @type {GuildBanManager} - */ - this.bans = new GuildBanManager(this); - - /** - * A manager of the roles belonging to this guild - * @type {RoleManager} - */ - this.roles = new RoleManager(this); - - /** - * A manager of the presences belonging to this guild - * @type {PresenceManager} - */ - this.presences = new PresenceManager(this.client); - - /** - * A manager of the voice states of this guild - * @type {VoiceStateManager} - */ - this.voiceStates = new VoiceStateManager(this); - - /** - * A manager of the stage instances of this guild - * @type {StageInstanceManager} - */ - this.stageInstances = new StageInstanceManager(this); - - /** - * A manager of the invites of this guild - * @type {GuildInviteManager} - */ - 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) { - /** - * Whether the guild is available to access. If it is not available, it indicates a server outage - * @type {boolean} - */ - this.available = false; - } else { - this._patch(data); - if (!data.channels) this.available = false; - } - - /** - * The id of the shard this Guild belongs to. - * @type {number} - */ - this.shardId = data.shardId; - - this.disableDM = false; - } - - /** - * Whether or not the structure has been deleted - * @type {boolean} - * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091 - */ - get deleted() { - if (!deprecationEmittedForDeleted) { - deprecationEmittedForDeleted = true; - process.emitWarning( - 'Guild#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.', - 'DeprecationWarning', - ); - } - - return deletedGuilds.has(this); - } - - set deleted(value) { - if (!deprecationEmittedForDeleted) { - deprecationEmittedForDeleted = true; - process.emitWarning( - 'Guild#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.', - 'DeprecationWarning', - ); - } - - if (value) deletedGuilds.add(this); - else deletedGuilds.delete(this); - } - - /** - * The Shard this Guild belongs to. - * @type {WebSocketShard} - * @readonly - */ - get shard() { - return this.client.ws.shards.get(this.shardId); - } - - _patch(data) { - super._patch(data); - this.id = data.id; - if ('name' in data) this.name = data.name; - if ('icon' in data) this.icon = data.icon; - if ('unavailable' in data) { - this.available = !data.unavailable; - } else { - this.available ??= true; - } - - if ('discovery_splash' in data) { - /** - * The hash of the guild discovery splash image - * @type {?string} - */ - this.discoverySplash = data.discovery_splash; - } - - if ('member_count' in data) { - /** - * The full amount of members in this guild - * @type {number} - */ - this.memberCount = data.member_count; - } - - if ('large' in data) { - /** - * Whether the guild is "large" (has more than {@link WebsocketOptions large_threshold} members, 50 by default) - * @type {boolean} - */ - this.large = Boolean(data.large); - } - - if ('premium_progress_bar_enabled' in data) { - /** - * Whether this guild has its premium (boost) progress bar enabled - * @type {boolean} - */ - this.premiumProgressBarEnabled = data.premium_progress_bar_enabled; - } - - /** - * An array of enabled guild features, here are the possible values: - * * ANIMATED_ICON - * * BANNER - * * COMMERCE - * * COMMUNITY - * * DISCOVERABLE - * * FEATURABLE - * * INVITE_SPLASH - * * MEMBER_VERIFICATION_GATE_ENABLED - * * NEWS - * * PARTNERED - * * PREVIEW_ENABLED - * * VANITY_URL - * * VERIFIED - * * VIP_REGIONS - * * WELCOME_SCREEN_ENABLED - * * TICKETED_EVENTS_ENABLED - * * MONETIZATION_ENABLED - * * MORE_STICKERS - * * THREE_DAY_THREAD_ARCHIVE - * * SEVEN_DAY_THREAD_ARCHIVE - * * PRIVATE_THREADS - * * ROLE_ICONS - * @typedef {string} Features - * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-guild-features} - */ - - if ('application_id' in data) { - /** - * The id of the application that created this guild (if applicable) - * @type {?Snowflake} - */ - this.applicationId = data.application_id; - } - - if ('afk_timeout' in data) { - /** - * The time in seconds before a user is counted as "away from keyboard" - * @type {?number} - */ - this.afkTimeout = data.afk_timeout; - } - - if ('afk_channel_id' in data) { - /** - * The id of the voice channel where AFK members are moved - * @type {?Snowflake} - */ - this.afkChannelId = data.afk_channel_id; - } - - if ('system_channel_id' in data) { - /** - * The system channel's id - * @type {?Snowflake} - */ - this.systemChannelId = data.system_channel_id; - } - - if ('premium_tier' in data) { - /** - * The premium tier of this guild - * @type {PremiumTier} - */ - this.premiumTier = PremiumTiers[data.premium_tier]; - } - - if ('premium_subscription_count' in data) { - /** - * The total number of boosts for this server - * @type {?number} - */ - this.premiumSubscriptionCount = data.premium_subscription_count; - } - - if ('widget_enabled' in data) { - /** - * Whether widget images are enabled on this guild - * @type {?boolean} - */ - this.widgetEnabled = data.widget_enabled; - } - - if ('widget_channel_id' in data) { - /** - * The widget channel's id, if enabled - * @type {?string} - */ - this.widgetChannelId = data.widget_channel_id; - } - - if ('explicit_content_filter' in data) { - /** - * The explicit content filter level of the guild - * @type {ExplicitContentFilterLevel} - */ - this.explicitContentFilter = ExplicitContentFilterLevels[data.explicit_content_filter]; - } - - if ('mfa_level' in data) { - /** - * The required MFA level for this guild - * @type {MFALevel} - */ - this.mfaLevel = MFALevels[data.mfa_level]; - } - - if ('joined_at' in data) { - /** - * The timestamp the client user joined the guild at - * @type {number} - */ - this.joinedTimestamp = new Date(data.joined_at).getTime(); - } - - if ('default_message_notifications' in data) { - /** - * The default message notification level of the guild - * @type {DefaultMessageNotificationLevel} - */ - this.defaultMessageNotifications = DefaultMessageNotificationLevels[data.default_message_notifications]; - } - - if ('system_channel_flags' in data) { - /** - * The value set for the guild's system channel flags - * @type {Readonly} - */ - this.systemChannelFlags = new SystemChannelFlags(data.system_channel_flags).freeze(); - } - - if ('max_members' in data) { - /** - * The maximum amount of members the guild can have - * @type {?number} - */ - this.maximumMembers = data.max_members; - } else { - this.maximumMembers ??= null; - } - - if ('max_presences' in data) { - /** - * The maximum amount of presences the guild can have - * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter - * @type {?number} - */ - this.maximumPresences = data.max_presences ?? 25_000; - } else { - this.maximumPresences ??= null; - } - - if ('approximate_member_count' in data) { - /** - * The approximate amount of members the guild has - * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter - * @type {?number} - */ - this.approximateMemberCount = data.approximate_member_count; - } else { - this.approximateMemberCount ??= null; - } - - if ('approximate_presence_count' in data) { - /** - * The approximate amount of presences the guild has - * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter - * @type {?number} - */ - this.approximatePresenceCount = data.approximate_presence_count; - } else { - this.approximatePresenceCount ??= null; - } - - /** - * The use count of the vanity URL code of the guild, if any - * You will need to fetch this parameter using {@link Guild#fetchVanityData} if you want to receive it - * @type {?number} - */ - this.vanityURLUses ??= null; - - if ('rules_channel_id' in data) { - /** - * The rules channel's id for the guild - * @type {?Snowflake} - */ - this.rulesChannelId = data.rules_channel_id; - } - - if ('public_updates_channel_id' in data) { - /** - * The community updates channel's id for the guild - * @type {?Snowflake} - */ - this.publicUpdatesChannelId = data.public_updates_channel_id; - } - - if ('preferred_locale' in data) { - /** - * The preferred locale of the guild, defaults to `en-US` - * @type {string} - * @see {@link https://discord.com/developers/docs/dispatch/field-values#predefined-field-values-accepted-locales} - */ - this.preferredLocale = data.preferred_locale; - } - - if (data.channels) { - this.channels.cache.clear(); - for (const rawChannel of data.channels) { - this.client.channels._add(rawChannel, this); - } - } - - if (data.threads) { - for (const rawThread of data.threads) { - this.client.channels._add(rawThread, this); - } - } - - if (data.roles) { - this.roles.cache.clear(); - for (const role of data.roles) this.roles._add(role); - } - - if (data.members) { - this.members.cache.clear(); - for (const guildUser of data.members) this.members._add(guildUser); - } - - if ('owner_id' in data) { - /** - * The user id of this guild's owner - * @type {Snowflake} - */ - this.ownerId = data.owner_id; - } - - if (data.presences) { - for (const presence of data.presences) { - this.presences._add(Object.assign(presence, { guild: this })); - } - } - - if (data.stage_instances) { - this.stageInstances.cache.clear(); - for (const stageInstance of data.stage_instances) { - this.stageInstances._add(stageInstance); - } - } - - 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) { - this.voiceStates._add(voiceState); - } - } - - if (!this.emojis) { - /** - * A manager of the emojis belonging to this guild - * @type {GuildEmojiManager} - */ - this.emojis = new GuildEmojiManager(this); - if (data.emojis) for (const emoji of data.emojis) this.emojis._add(emoji); - } else if (data.emojis) { - this.client.actions.GuildEmojisUpdate.handle({ - guild_id: this.id, - emojis: data.emojis, - }); - } - - if (!this.stickers) { - /** - * A manager of the stickers belonging to this guild - * @type {GuildStickerManager} - */ - this.stickers = new GuildStickerManager(this); - if (data.stickers) for (const sticker of data.stickers) this.stickers._add(sticker); - } else if (data.stickers) { - this.client.actions.GuildStickersUpdate.handle({ - guild_id: this.id, - stickers: data.stickers, - }); - } - } - - /** - * The time the client user joined the guild - * @type {Date} - * @readonly - */ - get joinedAt() { - return new Date(this.joinedTimestamp); - } - - /** - * The URL to this guild's discovery splash image. - * @param {StaticImageURLOptions} [options={}] Options for the Image URL - * @returns {?string} - */ - discoverySplashURL({ format, size } = {}) { - return this.discoverySplash && this.client.rest.cdn.DiscoverySplash(this.id, this.discoverySplash, format, size); - } - - /** - * Fetches the owner of the guild. - * If the member object isn't needed, use {@link Guild#ownerId} instead. - * @param {BaseFetchOptions} [options] The options for fetching the member - * @returns {Promise} - */ - fetchOwner(options) { - return this.members.fetch({ ...options, user: this.ownerId }); - } - - /** - * AFK voice channel for this guild - * @type {?VoiceChannel} - * @readonly - */ - get afkChannel() { - return this.client.channels.resolve(this.afkChannelId); - } - - /** - * System channel for this guild - * @type {?TextChannel} - * @readonly - */ - get systemChannel() { - return this.client.channels.resolve(this.systemChannelId); - } - - /** - * Widget channel for this guild - * @type {?TextChannel} - * @readonly - */ - get widgetChannel() { - return this.client.channels.resolve(this.widgetChannelId); - } - - /** - * Rules channel for this guild - * @type {?TextChannel} - * @readonly - */ - get rulesChannel() { - return this.client.channels.resolve(this.rulesChannelId); - } - - /** - * Public updates channel for this guild - * @type {?TextChannel} - * @readonly - */ - get publicUpdatesChannel() { - return this.client.channels.resolve(this.publicUpdatesChannelId); - } - - /** - * The client user as a GuildMember of this guild - * @type {?GuildMember} - * @readonly - */ - get me() { - return ( - this.members.resolve(this.client.user.id) ?? - (this.client.options.partials.includes(PartialTypes.GUILD_MEMBER) - ? this.members._add({ user: { id: this.client.user.id } }, true) - : null) - ); - } - - /** - * The maximum bitrate available for this guild - * @type {number} - * @readonly - */ - get maximumBitrate() { - if (this.features.includes('VIP_REGIONS')) { - return 384_000; - } - - switch (PremiumTiers[this.premiumTier]) { - case PremiumTiers.TIER_1: - return 128_000; - case PremiumTiers.TIER_2: - return 256_000; - case PremiumTiers.TIER_3: - return 384_000; - default: - return 96_000; - } - } - - /** - * Fetches a collection of integrations to this guild. - * Resolves with a collection mapping integrations by their ids. - * @returns {Promise>} - * @example - * // Fetch integrations - * guild.fetchIntegrations() - * .then(integrations => console.log(`Fetched ${integrations.size} integrations`)) - * .catch(console.error); - */ - async fetchIntegrations() { - const data = await this.client.api.guilds(this.id).integrations.get(); - return data.reduce( - (collection, integration) => collection.set(integration.id, new Integration(this.client, integration, this)), - new Collection(), - ); - } - - /** - * Fetches a collection of templates from this guild. - * Resolves with a collection mapping templates by their codes. - * @returns {Promise>} - */ - async fetchTemplates() { - const templates = await this.client.api.guilds(this.id).templates.get(); - return templates.reduce((col, data) => col.set(data.code, new GuildTemplate(this.client, data)), new Collection()); - } - - /** - * Fetches the welcome screen for this guild. - * @returns {Promise} - */ - async fetchWelcomeScreen() { - const data = await this.client.api.guilds(this.id, 'welcome-screen').get(); - return new WelcomeScreen(this, data); - } - - /** - * Creates a template for the guild. - * @param {string} name The name for the template - * @param {string} [description] The description for the template - * @returns {Promise} - */ - async createTemplate(name, description) { - const data = await this.client.api.guilds(this.id).templates.post({ data: { name, description } }); - return new GuildTemplate(this.client, data); - } - - /** - * Obtains a guild preview for this guild from Discord. - * @returns {Promise} - */ - async fetchPreview() { - const data = await this.client.api.guilds(this.id).preview.get(); - return new GuildPreview(this.client, data); - } - - /** - * An object containing information about a guild's vanity invite. - * @typedef {Object} Vanity - * @property {?string} code Vanity invite code - * @property {number} uses How many times this invite has been used - */ - - /** - * Fetches the vanity URL invite object to this guild. - * Resolves with an object containing the vanity URL invite code and the use count - * @returns {Promise} - * @example - * // Fetch invite data - * guild.fetchVanityData() - * .then(res => { - * console.log(`Vanity URL: https://discord.gg/${res.code} with ${res.uses} uses`); - * }) - * .catch(console.error); - */ - async fetchVanityData() { - if (!this.features.includes('VANITY_URL')) { - throw new Error('VANITY_URL'); - } - const data = await this.client.api.guilds(this.id, 'vanity-url').get(); - this.vanityURLCode = data.code; - this.vanityURLUses = data.uses; - - return data; - } - - /** - * Fetches all webhooks for the guild. - * @returns {Promise>} - * @example - * // Fetch webhooks - * guild.fetchWebhooks() - * .then(webhooks => console.log(`Fetched ${webhooks.size} webhooks`)) - * .catch(console.error); - */ - async fetchWebhooks() { - const apiHooks = await this.client.api.guilds(this.id).webhooks.get(); - const hooks = new Collection(); - for (const hook of apiHooks) hooks.set(hook.id, new Webhook(this.client, hook)); - return hooks; - } - - /** - * Fetches the guild widget data, requires the widget to be enabled. - * @returns {Promise} - * @example - * // Fetches the guild widget data - * guild.fetchWidget() - * .then(widget => console.log(`The widget shows ${widget.channels.size} channels`)) - * .catch(console.error); - */ - fetchWidget() { - return this.client.fetchGuildWidget(this.id); - } - - /** - * Data for the Guild Widget Settings object - * @typedef {Object} GuildWidgetSettings - * @property {boolean} enabled Whether the widget is enabled - * @property {?GuildChannel} channel The widget invite channel - */ - - /** - * The Guild Widget Settings object - * @typedef {Object} GuildWidgetSettingsData - * @property {boolean} enabled Whether the widget is enabled - * @property {?GuildChannelResolvable} channel The widget invite channel - */ - - /** - * Fetches the guild widget settings. - * @returns {Promise} - * @example - * // Fetches the guild widget settings - * guild.fetchWidgetSettings() - * .then(widget => console.log(`The widget is ${widget.enabled ? 'enabled' : 'disabled'}`)) - * .catch(console.error); - */ - async fetchWidgetSettings() { - const data = await this.client.api.guilds(this.id).widget.get(); - this.widgetEnabled = data.enabled; - this.widgetChannelId = data.channel_id; - return { - enabled: data.enabled, - channel: data.channel_id ? this.channels.cache.get(data.channel_id) : null, - }; - } - - /** - * Options used to fetch audit logs. - * @typedef {Object} GuildAuditLogsFetchOptions - * @property {Snowflake|GuildAuditLogsEntry} [before] Only return entries before this entry - * @property {number} [limit] The number of entries to return - * @property {UserResolvable} [user] Only return entries for actions made by this user - * @property {AuditLogAction|number} [type] Only return entries for this action type - */ - - /** - * Fetches audit logs for this guild. - * @param {GuildAuditLogsFetchOptions} [options={}] Options for fetching audit logs - * @returns {Promise} - * @example - * // Output audit log entries - * guild.fetchAuditLogs() - * .then(audit => console.log(audit.entries.first())) - * .catch(console.error); - */ - async fetchAuditLogs(options = {}) { - if (options.before && options.before instanceof GuildAuditLogs.Entry) options.before = options.before.id; - if (typeof options.type === 'string') options.type = GuildAuditLogs.Actions[options.type]; - - const data = await this.client.api.guilds(this.id)['audit-logs'].get({ - query: { - before: options.before, - limit: options.limit, - user_id: this.client.users.resolveId(options.user), - action_type: options.type, - }, - }); - return GuildAuditLogs.build(this, data); - } - - /** - * The data for editing a guild. - * @typedef {Object} GuildEditData - * @property {string} [name] The name of the guild - * @property {VerificationLevel|number} [verificationLevel] The verification level of the guild - * @property {ExplicitContentFilterLevel|number} [explicitContentFilter] The level of the explicit content filter - * @property {VoiceChannelResolvable} [afkChannel] The AFK channel of the guild - * @property {TextChannelResolvable} [systemChannel] The system channel of the guild - * @property {number} [afkTimeout] The AFK timeout of the guild - * @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the guild - * @property {GuildMemberResolvable} [owner] The owner of the guild - * @property {?(BufferResolvable|Base64Resolvable)} [splash] The invite splash image of the guild - * @property {?(BufferResolvable|Base64Resolvable)} [discoverySplash] The discovery splash image of the guild - * @property {?(BufferResolvable|Base64Resolvable)} [banner] The banner of the guild - * @property {DefaultMessageNotificationLevel|number} [defaultMessageNotifications] The default message notification - * level of the guild - * @property {SystemChannelFlagsResolvable} [systemChannelFlags] The system channel flags of the guild - * @property {TextChannelResolvable} [rulesChannel] The rules channel of the guild - * @property {TextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild - * @property {string} [preferredLocale] The preferred locale of the guild - * @property {boolean} [premiumProgressBarEnabled] Whether the guild's premium progress bar is enabled - * @property {string} [description] The discovery description of the guild - * @property {Features[]} [features] The features of the guild - */ - - /** - * Data that can be resolved to a Text Channel object. This can be: - * * A TextChannel - * * A Snowflake - * @typedef {TextChannel|Snowflake} TextChannelResolvable - */ - - /** - * Data that can be resolved to a Voice Channel object. This can be: - * * A VoiceChannel - * * A Snowflake - * @typedef {VoiceChannel|Snowflake} VoiceChannelResolvable - */ - - /** - * Updates the guild with new information - e.g. a new name. - * @param {GuildEditData} data The data to update the guild with - * @param {string} [reason] Reason for editing this guild - * @returns {Promise} - * @example - * // Set the guild name - * guild.edit({ - * name: 'Discord Guild', - * }) - * .then(updated => console.log(`New guild name ${updated}`)) - * .catch(console.error); - */ - async edit(data, reason) { - const _data = {}; - if (data.name) _data.name = data.name; - if (typeof data.verificationLevel !== 'undefined') { - _data.verification_level = - typeof data.verificationLevel === 'number' - ? data.verificationLevel - : VerificationLevels[data.verificationLevel]; - } - if (typeof data.afkChannel !== 'undefined') { - _data.afk_channel_id = this.client.channels.resolveId(data.afkChannel); - } - if (typeof data.systemChannel !== 'undefined') { - _data.system_channel_id = this.client.channels.resolveId(data.systemChannel); - } - if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); - if (typeof data.icon !== 'undefined') _data.icon = await DataResolver.resolveImage(data.icon); - if (data.owner) _data.owner_id = this.client.users.resolveId(data.owner); - if (typeof data.splash !== 'undefined') _data.splash = await DataResolver.resolveImage(data.splash); - if (typeof data.discoverySplash !== 'undefined') { - _data.discovery_splash = await DataResolver.resolveImage(data.discoverySplash); - } - if (typeof data.banner !== 'undefined') _data.banner = await DataResolver.resolveImage(data.banner); - if (typeof data.explicitContentFilter !== 'undefined') { - _data.explicit_content_filter = - typeof data.explicitContentFilter === 'number' - ? data.explicitContentFilter - : ExplicitContentFilterLevels[data.explicitContentFilter]; - } - if (typeof data.defaultMessageNotifications !== 'undefined') { - _data.default_message_notifications = - typeof data.defaultMessageNotifications === 'number' - ? data.defaultMessageNotifications - : DefaultMessageNotificationLevels[data.defaultMessageNotifications]; - } - if (typeof data.systemChannelFlags !== 'undefined') { - _data.system_channel_flags = SystemChannelFlags.resolve(data.systemChannelFlags); - } - if (typeof data.rulesChannel !== 'undefined') { - _data.rules_channel_id = this.client.channels.resolveId(data.rulesChannel); - } - if (typeof data.publicUpdatesChannel !== 'undefined') { - _data.public_updates_channel_id = this.client.channels.resolveId(data.publicUpdatesChannel); - } - if (typeof data.features !== 'undefined') { - _data.features = data.features; - } - if (typeof data.description !== 'undefined') { - _data.description = data.description; - } - if (data.preferredLocale) _data.preferred_locale = data.preferredLocale; - if ('premiumProgressBarEnabled' in data) _data.premium_progress_bar_enabled = data.premiumProgressBarEnabled; - const newData = await this.client.api.guilds(this.id).patch({ data: _data, reason }); - return this.client.actions.GuildUpdate.handle(newData).updated; - } - - /** - * Welcome channel data - * @typedef {Object} WelcomeChannelData - * @property {string} description The description to show for this welcome channel - * @property {TextChannel|NewsChannel|StoreChannel|Snowflake} channel The channel to link for this welcome channel - * @property {EmojiIdentifierResolvable} [emoji] The emoji to display for this welcome channel - */ - - /** - * Welcome screen edit data - * @typedef {Object} WelcomeScreenEditData - * @property {boolean} [enabled] Whether the welcome screen is enabled - * @property {string} [description] The description for the welcome screen - * @property {WelcomeChannelData[]} [welcomeChannels] The welcome channel data for the welcome screen - */ - - /** - * Data that can be resolved to a GuildTextChannel object. This can be: - * * A TextChannel - * * A NewsChannel - * * A Snowflake - * @typedef {TextChannel|NewsChannel|Snowflake} GuildTextChannelResolvable - */ - - /** - * Data that can be resolved to a GuildVoiceChannel object. This can be: - * * A VoiceChannel - * * A StageChannel - * * A Snowflake - * @typedef {VoiceChannel|StageChannel|Snowflake} GuildVoiceChannelResolvable - */ - - /** - * Updates the guild's welcome screen - * @param {WelcomeScreenEditData} data Data to edit the welcome screen with - * @returns {Promise} - * @example - * guild.editWelcomeScreen({ - * description: 'Hello World', - * enabled: true, - * welcomeChannels: [ - * { - * description: 'foobar', - * channel: '222197033908436994', - * } - * ], - * }) - */ - async editWelcomeScreen(data) { - const { enabled, description, welcomeChannels } = data; - const welcome_channels = welcomeChannels?.map(welcomeChannelData => { - const emoji = this.emojis.resolve(welcomeChannelData.emoji); - return { - emoji_id: emoji?.id, - emoji_name: emoji?.name ?? welcomeChannelData.emoji, - channel_id: this.channels.resolveId(welcomeChannelData.channel), - description: welcomeChannelData.description, - }; - }); - - const patchData = await this.client.api.guilds(this.id, 'welcome-screen').patch({ - data: { - welcome_channels, - description, - enabled, - }, - }); - return new WelcomeScreen(this, patchData); - } - - /** - * Edits the level of the explicit content filter. - * @param {ExplicitContentFilterLevel|number} explicitContentFilter The new level of the explicit content filter - * @param {string} [reason] Reason for changing the level of the guild's explicit content filter - * @returns {Promise} - */ - setExplicitContentFilter(explicitContentFilter, reason) { - return this.edit({ explicitContentFilter }, reason); - } - - /* eslint-disable max-len */ - /** - * Edits the setting of the default message notifications of the guild. - * @param {DefaultMessageNotificationLevel|number} defaultMessageNotifications The new default message notification level of the guild - * @param {string} [reason] Reason for changing the setting of the default message notifications - * @returns {Promise} - */ - setDefaultMessageNotifications(defaultMessageNotifications, reason) { - return this.edit({ defaultMessageNotifications }, reason); - } - /* eslint-enable max-len */ - - /** - * Edits the flags of the default message notifications of the guild. - * @param {SystemChannelFlagsResolvable} systemChannelFlags The new flags for the default message notifications - * @param {string} [reason] Reason for changing the flags of the default message notifications - * @returns {Promise} - */ - setSystemChannelFlags(systemChannelFlags, reason) { - return this.edit({ systemChannelFlags }, reason); - } - - /** - * Edits the name of the guild. - * @param {string} name The new name of the guild - * @param {string} [reason] Reason for changing the guild's name - * @returns {Promise} - * @example - * // Edit the guild name - * guild.setName('Discord Guild') - * .then(updated => console.log(`Updated guild name to ${updated.name}`)) - * .catch(console.error); - */ - setName(name, reason) { - return this.edit({ name }, reason); - } - - /** - * Edits the verification level of the guild. - * @param {VerificationLevel|number} verificationLevel The new verification level of the guild - * @param {string} [reason] Reason for changing the guild's verification level - * @returns {Promise} - * @example - * // Edit the guild verification level - * guild.setVerificationLevel(1) - * .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`)) - * .catch(console.error); - */ - setVerificationLevel(verificationLevel, reason) { - return this.edit({ verificationLevel }, reason); - } - - /** - * Edits the AFK channel of the guild. - * @param {VoiceChannelResolvable} afkChannel The new AFK channel - * @param {string} [reason] Reason for changing the guild's AFK channel - * @returns {Promise} - * @example - * // Edit the guild AFK channel - * guild.setAFKChannel(channel) - * .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel.name}`)) - * .catch(console.error); - */ - setAFKChannel(afkChannel, reason) { - return this.edit({ afkChannel }, reason); - } - - /** - * Edits the system channel of the guild. - * @param {TextChannelResolvable} systemChannel The new system channel - * @param {string} [reason] Reason for changing the guild's system channel - * @returns {Promise} - * @example - * // Edit the guild system channel - * guild.setSystemChannel(channel) - * .then(updated => console.log(`Updated guild system channel to ${guild.systemChannel.name}`)) - * .catch(console.error); - */ - setSystemChannel(systemChannel, reason) { - return this.edit({ systemChannel }, reason); - } - - /** - * Edits the AFK timeout of the guild. - * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK - * @param {string} [reason] Reason for changing the guild's AFK timeout - * @returns {Promise} - * @example - * // Edit the guild AFK channel - * guild.setAFKTimeout(60) - * .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`)) - * .catch(console.error); - */ - setAFKTimeout(afkTimeout, reason) { - return this.edit({ afkTimeout }, reason); - } - - /** - * Sets a new guild icon. - * @param {?(Base64Resolvable|BufferResolvable)} icon The new icon of the guild - * @param {string} [reason] Reason for changing the guild's icon - * @returns {Promise} - * @example - * // Edit the guild icon - * guild.setIcon('./icon.png') - * .then(updated => console.log('Updated the guild icon')) - * .catch(console.error); - */ - setIcon(icon, reason) { - return this.edit({ icon }, reason); - } - - /** - * Sets a new owner of the guild. - * @param {GuildMemberResolvable} owner The new owner of the guild - * @param {string} [reason] Reason for setting the new owner - * @returns {Promise} - * @example - * // Edit the guild owner - * guild.setOwner(guild.members.cache.first()) - * .then(guild => guild.fetchOwner()) - * .then(owner => console.log(`Updated the guild owner to ${owner.displayName}`)) - * .catch(console.error); - */ - setOwner(owner, reason) { - return this.edit({ owner }, reason); - } - - /** - * Sets a new guild invite splash image. - * @param {?(Base64Resolvable|BufferResolvable)} splash The new invite splash image of the guild - * @param {string} [reason] Reason for changing the guild's invite splash image - * @returns {Promise} - * @example - * // Edit the guild splash - * guild.setSplash('./splash.png') - * .then(updated => console.log('Updated the guild splash')) - * .catch(console.error); - */ - setSplash(splash, reason) { - return this.edit({ splash }, reason); - } - - /** - * Sets a new guild discovery splash image. - * @param {?(Base64Resolvable|BufferResolvable)} discoverySplash The new discovery splash image of the guild - * @param {string} [reason] Reason for changing the guild's discovery splash image - * @returns {Promise} - * @example - * // Edit the guild discovery splash - * guild.setDiscoverySplash('./discoverysplash.png') - * .then(updated => console.log('Updated the guild discovery splash')) - * .catch(console.error); - */ - setDiscoverySplash(discoverySplash, reason) { - return this.edit({ discoverySplash }, reason); - } - - /** - * Sets a new guild banner. - * @param {?(Base64Resolvable|BufferResolvable)} banner The new banner of the guild - * @param {string} [reason] Reason for changing the guild's banner - * @returns {Promise} - * @example - * guild.setBanner('./banner.png') - * .then(updated => console.log('Updated the guild banner')) - * .catch(console.error); - */ - setBanner(banner, reason) { - return this.edit({ banner }, reason); - } - - /** - * Edits the rules channel of the guild. - * @param {TextChannelResolvable} rulesChannel The new rules channel - * @param {string} [reason] Reason for changing the guild's rules channel - * @returns {Promise} - * @example - * // Edit the guild rules channel - * guild.setRulesChannel(channel) - * .then(updated => console.log(`Updated guild rules channel to ${guild.rulesChannel.name}`)) - * .catch(console.error); - */ - setRulesChannel(rulesChannel, reason) { - return this.edit({ rulesChannel }, reason); - } - /** - * Change Guild Position (from * to Folder or Home) - * @param {number} position Guild Position - * * **WARNING**: Type = `FOLDER`, newPosition is the guild's index in the Folder. - * @param {String|Number} type Move to folder or home - * * `FOLDER`: 1 - * * `HOME`: 2 - * @param {String|Number|void|null} folderID If you want to move to folder - * @returns {Promise} - * @example - * // Move guild to folderID 123456, index 1 - * guild.setPosition(1, 'FOLDER', 123456) - * .then(guild => console.log(`Guild moved to folderID ${guild.folder.folderId}`)); - */ - async setPosition(position, type, folderID) { - if (type == 1 || `${type}`.toUpperCase() === 'FOLDER') { - folderID = folderID || this.folder.folderId; - if (!['number', 'string'].includes(typeof folderID)) - throw new TypeError( - 'INVALID_TYPE', - 'folderID', - 'String | Number', - ); - // Get Data from Folder ID - const folder = await this.client.setting.rawSetting.guild_folders.find( - (obj) => obj.id == folderID, - ); - if (!folder) throw new Error('FOLDER_NOT_FOUND'); - if (folder.guild_ids.length - 1 < position || position < 0) - throw new Error('FOLDER_POSITION_INVALID'); - if (position !== folder.guild_ids.indexOf(this.id)) { - await this.client.setting.guildChangePosition( - this.id, - position, - 1, - folderID, - ); - } - } else if (type == 2 || `${type}`.toUpperCase() === 'HOME') { - if (this.client.setting.guild_positions - 1 < position || position < 0) - throw new Error('FOLDER_POSITION_INVALID'); - if (position !== this.position) { - await this.client.setting.guildChangePosition( - this.id, - position, - 2, - null, - ); - } - } else { - throw new TypeError('INVALID_TYPE', 'type', '`Folder`| `Home`'); - } - return this; - } - - /** - * Edits the community updates channel of the guild. - * @param {TextChannelResolvable} publicUpdatesChannel The new community updates channel - * @param {string} [reason] Reason for changing the guild's community updates channel - * @returns {Promise} - * @example - * // Edit the guild community updates channel - * guild.setPublicUpdatesChannel(channel) - * .then(updated => console.log(`Updated guild community updates channel to ${guild.publicUpdatesChannel.name}`)) - * .catch(console.error); - */ - setPublicUpdatesChannel(publicUpdatesChannel, reason) { - return this.edit({ publicUpdatesChannel }, reason); - } - - /** - * Edits the preferred locale of the guild. - * @param {string} preferredLocale The new preferred locale of the guild - * @param {string} [reason] Reason for changing the guild's preferred locale - * @returns {Promise} - * @example - * // Edit the guild preferred locale - * guild.setPreferredLocale('en-US') - * .then(updated => console.log(`Updated guild preferred locale to ${guild.preferredLocale}`)) - * .catch(console.error); - */ - setPreferredLocale(preferredLocale, reason) { - return this.edit({ preferredLocale }, reason); - } - - /** - * Edits the enabled state of the guild's premium progress bar - * @param {boolean} [enabled=true] The new enabled state of the guild's premium progress bar - * @param {string} [reason] Reason for changing the state of the guild's premium progress bar - * @returns {Promise} - */ - setPremiumProgressBarEnabled(enabled = true, reason) { - return this.edit({ premiumProgressBarEnabled: enabled }, reason); - } - - /** - * Data that can be resolved to give a Category Channel object. This can be: - * * A CategoryChannel object - * * A Snowflake - * @typedef {CategoryChannel|Snowflake} CategoryChannelResolvable - */ - - /** - * The data needed for updating a channel's position. - * @typedef {Object} ChannelPosition - * @property {GuildChannel|Snowflake} channel Channel to update - * @property {number} [position] New position for the channel - * @property {CategoryChannelResolvable} [parent] Parent channel for this channel - * @property {boolean} [lockPermissions] If the overwrites should be locked to the parents overwrites - */ - - /** - * Batch-updates the guild's channels' positions. - * Only one channel's parent can be changed at a time - * @param {ChannelPosition[]} channelPositions Channel positions to update - * @returns {Promise} - * @deprecated Use {@link GuildChannelManager#setPositions} instead - * @example - * guild.setChannelPositions([{ channel: channelId, position: newChannelIndex }]) - * .then(guild => console.log(`Updated channel positions for ${guild}`)) - * .catch(console.error); - */ - setChannelPositions(channelPositions) { - if (!deprecationEmittedForSetChannelPositions) { - process.emitWarning( - 'The Guild#setChannelPositions method is deprecated. Use GuildChannelManager#setPositions instead.', - 'DeprecationWarning', - ); - - deprecationEmittedForSetChannelPositions = true; - } - - return this.channels.setPositions(channelPositions); - } - - /** - * The data needed for updating a guild role's position - * @typedef {Object} GuildRolePosition - * @property {RoleResolvable} role The role's id - * @property {number} position The position to update - */ - - /** - * Batch-updates the guild's role positions - * @param {GuildRolePosition[]} rolePositions Role positions to update - * @returns {Promise} - * @deprecated Use {@link RoleManager#setPositions} instead - * @example - * guild.setRolePositions([{ role: roleId, position: updatedRoleIndex }]) - * .then(guild => console.log(`Role positions updated for ${guild}`)) - * .catch(console.error); - */ - setRolePositions(rolePositions) { - if (!deprecationEmittedForSetRolePositions) { - process.emitWarning( - 'The Guild#setRolePositions method is deprecated. Use RoleManager#setPositions instead.', - 'DeprecationWarning', - ); - - deprecationEmittedForSetRolePositions = true; - } - - return this.roles.setPositions(rolePositions); - } - - /** - * Edits the guild's widget settings. - * @param {GuildWidgetSettingsData} settings The widget settings for the guild - * @param {string} [reason] Reason for changing the guild's widget settings - * @returns {Promise} - */ - async setWidgetSettings(settings, reason) { - await this.client.api.guilds(this.id).widget.patch({ - data: { - enabled: settings.enabled, - channel_id: this.channels.resolveId(settings.channel), - }, - reason, - }); - return this; - } - - /** - * Leaves the guild. - * @returns {Promise} - * @example - * // Leave a guild - * guild.leave() - * .then(g => console.log(`Left the guild ${g}`)) - * .catch(console.error); - */ - async leave() { - if (this.ownerId === this.client.user.id) throw new Error('GUILD_OWNED'); - await this.client.api.users('@me').guilds(this.id).delete(); - return this.client.actions.GuildDelete.handle({ id: this.id }).guild; - } - - /** - * Deletes the guild. - * @returns {Promise} - * @example - * // Delete a guild - * guild.delete() - * .then(g => console.log(`Deleted the guild ${g}`)) - * .catch(console.error); - */ - async delete() { - await this.client.api.guilds(this.id).delete(); - return this.client.actions.GuildDelete.handle({ id: this.id }).guild; - } - - /** - * 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 - * what most users need. - * @param {Guild} guild The guild to compare with - * @returns {boolean} - */ - equals(guild) { - return ( - guild && - guild instanceof this.constructor && - this.id === guild.id && - this.available === guild.available && - this.splash === guild.splash && - this.discoverySplash === guild.discoverySplash && - this.name === guild.name && - this.memberCount === guild.memberCount && - this.large === guild.large && - this.icon === guild.icon && - this.ownerId === guild.ownerId && - this.verificationLevel === guild.verificationLevel && - (this.features === guild.features || - (this.features.length === guild.features.length && - this.features.every((feat, i) => feat === guild.features[i]))) - ); - } - - /** - * Set Community Feature - * @param {Boolean} stats True / False to enable / disable Community Feature - * @param {TextChannelResolvable} publicUpdatesChannel - * @param {TextChannelResolvable} rulesChannel - * @param {String} reason - */ - async setCommunity(stats = true, publicUpdatesChannel = '1', rulesChannel = '1', reason) { - if (stats) { - // Check everyone role - const everyoneRole = this.roles.everyone; - if (everyoneRole.mentionable) { - await everyoneRole.setMentionable(false, reason); - } - // Setting - this.edit( - { - defaultMessageNotifications: 'ONLY_MENTIONS', - explicitContentFilter: 'ALL_MEMBERS', - features: [...this.features, 'COMMUNITY'], - publicUpdatesChannel, - rulesChannel, - verificationLevel: - VerificationLevels[this.verificationLevel] < 1 - ? 'LOW' - : this.verificationLevel, // Email - }, - reason, - ); - } else { - this.edit({ - publicUpdatesChannel: null, - rulesChannel: null, - features: this.features.filter(f => f !== 'COMMUNITY'), - preferredLocale: this.preferredLocale, - description: this.description, - }, reason); - } - } - - toJSON() { - const json = super.toJSON({ - available: false, - createdTimestamp: true, - nameAcronym: true, - presences: false, - voiceStates: false, - }); - json.iconURL = this.iconURL(); - json.splashURL = this.splashURL(); - json.discoverySplashURL = this.discoverySplashURL(); - json.bannerURL = this.bannerURL(); - return json; - } - - /** - * The voice state adapter for this guild that can be used with @discordjs/voice to play audio in voice - * and stage channels. - * @type {Function} - * @readonly - */ - get voiceAdapterCreator() { - return methods => { - this.client.voice.adapters.set(this.id, methods); - return { - sendPayload: data => { - if (this.shard.status !== Status.READY) return false; - this.shard.send(data); - return true; - }, - destroy: () => { - this.client.voice.adapters.delete(this.id); - }, - }; - }; - } - - /** - * Creates a collection of this guild's roles, sorted by their position and ids. - * @returns {Collection} - * @private - */ - _sortedRoles() { - return Util.discordSort(this.roles.cache); - } - - /** - * Creates a collection of this guild's or a specific category's channels, sorted by their position and ids. - * @param {GuildChannel} [channel] Category to get the channels of - * @returns {Collection} - * @private - */ - _sortedChannels(channel) { - const category = channel.type === ChannelTypes.GUILD_CATEGORY; - return Util.discordSort( - this.channels.cache.filter( - c => - (['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_STORE'].includes(channel.type) - ? ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_STORE'].includes(c.type) - : c.type === channel.type) && - (category || c.parent === channel.parent), - ), - ); - } + constructor(client, data) { + super(client, data, false); + + /** + * A manager of the members belonging to this guild + * @type {GuildMemberManager} + */ + this.members = new GuildMemberManager(this); + + /** + * A manager of the channels belonging to this guild + * @type {GuildChannelManager} + */ + this.channels = new GuildChannelManager(this); + + /** + * A manager of the bans belonging to this guild + * @type {GuildBanManager} + */ + this.bans = new GuildBanManager(this); + + /** + * A manager of the roles belonging to this guild + * @type {RoleManager} + */ + this.roles = new RoleManager(this); + + /** + * A manager of the presences belonging to this guild + * @type {PresenceManager} + */ + this.presences = new PresenceManager(this.client); + + /** + * A manager of the voice states of this guild + * @type {VoiceStateManager} + */ + this.voiceStates = new VoiceStateManager(this); + + /** + * A manager of the stage instances of this guild + * @type {StageInstanceManager} + */ + this.stageInstances = new StageInstanceManager(this); + + /** + * A manager of the invites of this guild + * @type {GuildInviteManager} + */ + 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) { + /** + * Whether the guild is available to access. If it is not available, it indicates a server outage + * @type {boolean} + */ + this.available = false; + } else { + this._patch(data); + if (!data.channels) this.available = false; + } + + /** + * The id of the shard this Guild belongs to. + * @type {number} + */ + this.shardId = data.shardId; + + this.disableDM = false; + } + + /** + * Whether or not the structure has been deleted + * @type {boolean} + * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091 + */ + get deleted() { + if (!deprecationEmittedForDeleted) { + deprecationEmittedForDeleted = true; + process.emitWarning( + 'Guild#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.', + 'DeprecationWarning', + ); + } + + return deletedGuilds.has(this); + } + + set deleted(value) { + if (!deprecationEmittedForDeleted) { + deprecationEmittedForDeleted = true; + process.emitWarning( + 'Guild#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.', + 'DeprecationWarning', + ); + } + + if (value) deletedGuilds.add(this); + else deletedGuilds.delete(this); + } + + /** + * The Shard this Guild belongs to. + * @type {WebSocketShard} + * @readonly + */ + get shard() { + return this.client.ws.shards.get(this.shardId); + } + + _patch(data) { + super._patch(data); + this.id = data.id; + if ('name' in data) this.name = data.name; + if ('icon' in data) this.icon = data.icon; + if ('unavailable' in data) { + this.available = !data.unavailable; + } else { + this.available ??= true; + } + + if ('discovery_splash' in data) { + /** + * The hash of the guild discovery splash image + * @type {?string} + */ + this.discoverySplash = data.discovery_splash; + } + + if ('member_count' in data) { + /** + * The full amount of members in this guild + * @type {number} + */ + this.memberCount = data.member_count; + } + + if ('large' in data) { + /** + * Whether the guild is "large" (has more than {@link WebsocketOptions large_threshold} members, 50 by default) + * @type {boolean} + */ + this.large = Boolean(data.large); + } + + if ('premium_progress_bar_enabled' in data) { + /** + * Whether this guild has its premium (boost) progress bar enabled + * @type {boolean} + */ + this.premiumProgressBarEnabled = data.premium_progress_bar_enabled; + } + + /** + * An array of enabled guild features, here are the possible values: + * * ANIMATED_ICON + * * BANNER + * * COMMERCE + * * COMMUNITY + * * DISCOVERABLE + * * FEATURABLE + * * INVITE_SPLASH + * * MEMBER_VERIFICATION_GATE_ENABLED + * * NEWS + * * PARTNERED + * * PREVIEW_ENABLED + * * VANITY_URL + * * VERIFIED + * * VIP_REGIONS + * * WELCOME_SCREEN_ENABLED + * * TICKETED_EVENTS_ENABLED + * * MONETIZATION_ENABLED + * * MORE_STICKERS + * * THREE_DAY_THREAD_ARCHIVE + * * SEVEN_DAY_THREAD_ARCHIVE + * * PRIVATE_THREADS + * * ROLE_ICONS + * @typedef {string} Features + * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-guild-features} + */ + + if ('application_id' in data) { + /** + * The id of the application that created this guild (if applicable) + * @type {?Snowflake} + */ + this.applicationId = data.application_id; + } + + if ('afk_timeout' in data) { + /** + * The time in seconds before a user is counted as "away from keyboard" + * @type {?number} + */ + this.afkTimeout = data.afk_timeout; + } + + if ('afk_channel_id' in data) { + /** + * The id of the voice channel where AFK members are moved + * @type {?Snowflake} + */ + this.afkChannelId = data.afk_channel_id; + } + + if ('system_channel_id' in data) { + /** + * The system channel's id + * @type {?Snowflake} + */ + this.systemChannelId = data.system_channel_id; + } + + if ('premium_tier' in data) { + /** + * The premium tier of this guild + * @type {PremiumTier} + */ + this.premiumTier = PremiumTiers[data.premium_tier]; + } + + if ('premium_subscription_count' in data) { + /** + * The total number of boosts for this server + * @type {?number} + */ + this.premiumSubscriptionCount = data.premium_subscription_count; + } + + if ('widget_enabled' in data) { + /** + * Whether widget images are enabled on this guild + * @type {?boolean} + */ + this.widgetEnabled = data.widget_enabled; + } + + if ('widget_channel_id' in data) { + /** + * The widget channel's id, if enabled + * @type {?string} + */ + this.widgetChannelId = data.widget_channel_id; + } + + if ('explicit_content_filter' in data) { + /** + * The explicit content filter level of the guild + * @type {ExplicitContentFilterLevel} + */ + this.explicitContentFilter = + ExplicitContentFilterLevels[data.explicit_content_filter]; + } + + if ('mfa_level' in data) { + /** + * The required MFA level for this guild + * @type {MFALevel} + */ + this.mfaLevel = MFALevels[data.mfa_level]; + } + + if ('joined_at' in data) { + /** + * The timestamp the client user joined the guild at + * @type {number} + */ + this.joinedTimestamp = new Date(data.joined_at).getTime(); + } + + if ('default_message_notifications' in data) { + /** + * The default message notification level of the guild + * @type {DefaultMessageNotificationLevel} + */ + this.defaultMessageNotifications = + DefaultMessageNotificationLevels[data.default_message_notifications]; + } + + if ('system_channel_flags' in data) { + /** + * The value set for the guild's system channel flags + * @type {Readonly} + */ + this.systemChannelFlags = new SystemChannelFlags( + data.system_channel_flags, + ).freeze(); + } + + if ('max_members' in data) { + /** + * The maximum amount of members the guild can have + * @type {?number} + */ + this.maximumMembers = data.max_members; + } else { + this.maximumMembers ??= null; + } + + if ('max_presences' in data) { + /** + * The maximum amount of presences the guild can have + * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter + * @type {?number} + */ + this.maximumPresences = data.max_presences ?? 25_000; + } else { + this.maximumPresences ??= null; + } + + if ('approximate_member_count' in data) { + /** + * The approximate amount of members the guild has + * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter + * @type {?number} + */ + this.approximateMemberCount = data.approximate_member_count; + } else { + this.approximateMemberCount ??= null; + } + + if ('approximate_presence_count' in data) { + /** + * The approximate amount of presences the guild has + * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter + * @type {?number} + */ + this.approximatePresenceCount = data.approximate_presence_count; + } else { + this.approximatePresenceCount ??= null; + } + + /** + * The use count of the vanity URL code of the guild, if any + * You will need to fetch this parameter using {@link Guild#fetchVanityData} if you want to receive it + * @type {?number} + */ + this.vanityURLUses ??= null; + + if ('rules_channel_id' in data) { + /** + * The rules channel's id for the guild + * @type {?Snowflake} + */ + this.rulesChannelId = data.rules_channel_id; + } + + if ('public_updates_channel_id' in data) { + /** + * The community updates channel's id for the guild + * @type {?Snowflake} + */ + this.publicUpdatesChannelId = data.public_updates_channel_id; + } + + if ('preferred_locale' in data) { + /** + * The preferred locale of the guild, defaults to `en-US` + * @type {string} + * @see {@link https://discord.com/developers/docs/dispatch/field-values#predefined-field-values-accepted-locales} + */ + this.preferredLocale = data.preferred_locale; + } + + if (data.channels) { + this.channels.cache.clear(); + for (const rawChannel of data.channels) { + this.client.channels._add(rawChannel, this); + } + } + + if (data.threads) { + for (const rawThread of data.threads) { + this.client.channels._add(rawThread, this); + } + } + + if (data.roles) { + this.roles.cache.clear(); + for (const role of data.roles) this.roles._add(role); + } + + if (data.members) { + this.members.cache.clear(); + for (const guildUser of data.members) this.members._add(guildUser); + } + + if ('owner_id' in data) { + /** + * The user id of this guild's owner + * @type {Snowflake} + */ + this.ownerId = data.owner_id; + } + + if (data.presences) { + for (const presence of data.presences) { + this.presences._add(Object.assign(presence, { guild: this })); + } + } + + if (data.stage_instances) { + this.stageInstances.cache.clear(); + for (const stageInstance of data.stage_instances) { + this.stageInstances._add(stageInstance); + } + } + + 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) { + this.voiceStates._add(voiceState); + } + } + + if (!this.emojis) { + /** + * A manager of the emojis belonging to this guild + * @type {GuildEmojiManager} + */ + this.emojis = new GuildEmojiManager(this); + if (data.emojis) for (const emoji of data.emojis) this.emojis._add(emoji); + } else if (data.emojis) { + this.client.actions.GuildEmojisUpdate.handle({ + guild_id: this.id, + emojis: data.emojis, + }); + } + + if (!this.stickers) { + /** + * A manager of the stickers belonging to this guild + * @type {GuildStickerManager} + */ + this.stickers = new GuildStickerManager(this); + if (data.stickers) + for (const sticker of data.stickers) this.stickers._add(sticker); + } else if (data.stickers) { + this.client.actions.GuildStickersUpdate.handle({ + guild_id: this.id, + stickers: data.stickers, + }); + } + } + + /** + * The time the client user joined the guild + * @type {Date} + * @readonly + */ + get joinedAt() { + return new Date(this.joinedTimestamp); + } + + /** + * The URL to this guild's discovery splash image. + * @param {StaticImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + discoverySplashURL({ format, size } = {}) { + return ( + this.discoverySplash && + this.client.rest.cdn.DiscoverySplash( + this.id, + this.discoverySplash, + format, + size, + ) + ); + } + + /** + * Fetches the owner of the guild. + * If the member object isn't needed, use {@link Guild#ownerId} instead. + * @param {BaseFetchOptions} [options] The options for fetching the member + * @returns {Promise} + */ + fetchOwner(options) { + return this.members.fetch({ ...options, user: this.ownerId }); + } + + /** + * AFK voice channel for this guild + * @type {?VoiceChannel} + * @readonly + */ + get afkChannel() { + return this.client.channels.resolve(this.afkChannelId); + } + + /** + * System channel for this guild + * @type {?TextChannel} + * @readonly + */ + get systemChannel() { + return this.client.channels.resolve(this.systemChannelId); + } + + /** + * Widget channel for this guild + * @type {?TextChannel} + * @readonly + */ + get widgetChannel() { + return this.client.channels.resolve(this.widgetChannelId); + } + + /** + * Rules channel for this guild + * @type {?TextChannel} + * @readonly + */ + get rulesChannel() { + return this.client.channels.resolve(this.rulesChannelId); + } + + /** + * Public updates channel for this guild + * @type {?TextChannel} + * @readonly + */ + get publicUpdatesChannel() { + return this.client.channels.resolve(this.publicUpdatesChannelId); + } + + /** + * The client user as a GuildMember of this guild + * @type {?GuildMember} + * @readonly + */ + get me() { + return ( + this.members.resolve(this.client.user.id) ?? + (this.client.options.partials.includes(PartialTypes.GUILD_MEMBER) + ? this.members._add({ user: { id: this.client.user.id } }, true) + : null) + ); + } + + /** + * The maximum bitrate available for this guild + * @type {number} + * @readonly + */ + get maximumBitrate() { + if (this.features.includes('VIP_REGIONS')) { + return 384_000; + } + + switch (PremiumTiers[this.premiumTier]) { + case PremiumTiers.TIER_1: + return 128_000; + case PremiumTiers.TIER_2: + return 256_000; + case PremiumTiers.TIER_3: + return 384_000; + default: + return 96_000; + } + } + + /** + * Search slash command / message context + * @param {guildSearchInteraction} options + * { + * + * type: 1 | 2 | 3, [CHAT_INPUT | USER | MESSAGE] + * + * query: string | undefined, + * + * limit: number | 1, + * + * offset: number | 0, + * + * botID: [Snowflake] | undefined, + * + * } + */ + async searchInteraction(options = {}) { + let { query, type, limit, offset, botID } = Object.assign( + { query: undefined, type: 1, limit: 1, offset: 0, botID: [] }, + options, + ); + if (typeof type == 'string') { + if (type == 'CHAT_INPUT') type = 1; + else if (type == 'USER') type = 2; + else if (type == 'MESSAGE') type = 3; + } + if (type < 1 || type > 3) + throw new RangeError('Type must be 1, 2, 3'); + if (typeof type !== 'number') + throw new TypeError('Type must be a number | string'); + this.shard.send({ + op: Opcodes.REQUEST_APPLICATION_COMMANDS, + d: { + guild_id: this.id, + applications: false, + limit, + offset, + type, + query: query, + command_ids: Array.isArray(botID) ? botID : undefined, + }, + }); + } + + /** + * Fetches a collection of integrations to this guild. + * Resolves with a collection mapping integrations by their ids. + * @returns {Promise>} + * @example + * // Fetch integrations + * guild.fetchIntegrations() + * .then(integrations => console.log(`Fetched ${integrations.size} integrations`)) + * .catch(console.error); + */ + async fetchIntegrations() { + const data = await this.client.api.guilds(this.id).integrations.get(); + return data.reduce( + (collection, integration) => + collection.set( + integration.id, + new Integration(this.client, integration, this), + ), + new Collection(), + ); + } + + /** + * Fetches a collection of templates from this guild. + * Resolves with a collection mapping templates by their codes. + * @returns {Promise>} + */ + async fetchTemplates() { + const templates = await this.client.api.guilds(this.id).templates.get(); + return templates.reduce( + (col, data) => col.set(data.code, new GuildTemplate(this.client, data)), + new Collection(), + ); + } + + /** + * Fetches the welcome screen for this guild. + * @returns {Promise} + */ + async fetchWelcomeScreen() { + const data = await this.client.api.guilds(this.id, 'welcome-screen').get(); + return new WelcomeScreen(this, data); + } + + /** + * Creates a template for the guild. + * @param {string} name The name for the template + * @param {string} [description] The description for the template + * @returns {Promise} + */ + async createTemplate(name, description) { + const data = await this.client.api + .guilds(this.id) + .templates.post({ data: { name, description } }); + return new GuildTemplate(this.client, data); + } + + /** + * Obtains a guild preview for this guild from Discord. + * @returns {Promise} + */ + async fetchPreview() { + const data = await this.client.api.guilds(this.id).preview.get(); + return new GuildPreview(this.client, data); + } + + /** + * An object containing information about a guild's vanity invite. + * @typedef {Object} Vanity + * @property {?string} code Vanity invite code + * @property {number} uses How many times this invite has been used + */ + + /** + * Fetches the vanity URL invite object to this guild. + * Resolves with an object containing the vanity URL invite code and the use count + * @returns {Promise} + * @example + * // Fetch invite data + * guild.fetchVanityData() + * .then(res => { + * console.log(`Vanity URL: https://discord.gg/${res.code} with ${res.uses} uses`); + * }) + * .catch(console.error); + */ + async fetchVanityData() { + if (!this.features.includes('VANITY_URL')) { + throw new Error('VANITY_URL'); + } + const data = await this.client.api.guilds(this.id, 'vanity-url').get(); + this.vanityURLCode = data.code; + this.vanityURLUses = data.uses; + + return data; + } + + /** + * Fetches all webhooks for the guild. + * @returns {Promise>} + * @example + * // Fetch webhooks + * guild.fetchWebhooks() + * .then(webhooks => console.log(`Fetched ${webhooks.size} webhooks`)) + * .catch(console.error); + */ + async fetchWebhooks() { + const apiHooks = await this.client.api.guilds(this.id).webhooks.get(); + const hooks = new Collection(); + for (const hook of apiHooks) + hooks.set(hook.id, new Webhook(this.client, hook)); + return hooks; + } + + /** + * Fetches the guild widget data, requires the widget to be enabled. + * @returns {Promise} + * @example + * // Fetches the guild widget data + * guild.fetchWidget() + * .then(widget => console.log(`The widget shows ${widget.channels.size} channels`)) + * .catch(console.error); + */ + fetchWidget() { + return this.client.fetchGuildWidget(this.id); + } + + /** + * Data for the Guild Widget Settings object + * @typedef {Object} GuildWidgetSettings + * @property {boolean} enabled Whether the widget is enabled + * @property {?GuildChannel} channel The widget invite channel + */ + + /** + * The Guild Widget Settings object + * @typedef {Object} GuildWidgetSettingsData + * @property {boolean} enabled Whether the widget is enabled + * @property {?GuildChannelResolvable} channel The widget invite channel + */ + + /** + * Fetches the guild widget settings. + * @returns {Promise} + * @example + * // Fetches the guild widget settings + * guild.fetchWidgetSettings() + * .then(widget => console.log(`The widget is ${widget.enabled ? 'enabled' : 'disabled'}`)) + * .catch(console.error); + */ + async fetchWidgetSettings() { + const data = await this.client.api.guilds(this.id).widget.get(); + this.widgetEnabled = data.enabled; + this.widgetChannelId = data.channel_id; + return { + enabled: data.enabled, + channel: data.channel_id + ? this.channels.cache.get(data.channel_id) + : null, + }; + } + + /** + * Options used to fetch audit logs. + * @typedef {Object} GuildAuditLogsFetchOptions + * @property {Snowflake|GuildAuditLogsEntry} [before] Only return entries before this entry + * @property {number} [limit] The number of entries to return + * @property {UserResolvable} [user] Only return entries for actions made by this user + * @property {AuditLogAction|number} [type] Only return entries for this action type + */ + + /** + * Fetches audit logs for this guild. + * @param {GuildAuditLogsFetchOptions} [options={}] Options for fetching audit logs + * @returns {Promise} + * @example + * // Output audit log entries + * guild.fetchAuditLogs() + * .then(audit => console.log(audit.entries.first())) + * .catch(console.error); + */ + async fetchAuditLogs(options = {}) { + if (options.before && options.before instanceof GuildAuditLogs.Entry) + options.before = options.before.id; + if (typeof options.type === 'string') + options.type = GuildAuditLogs.Actions[options.type]; + + const data = await this.client.api.guilds(this.id)['audit-logs'].get({ + query: { + before: options.before, + limit: options.limit, + user_id: this.client.users.resolveId(options.user), + action_type: options.type, + }, + }); + return GuildAuditLogs.build(this, data); + } + + /** + * The data for editing a guild. + * @typedef {Object} GuildEditData + * @property {string} [name] The name of the guild + * @property {VerificationLevel|number} [verificationLevel] The verification level of the guild + * @property {ExplicitContentFilterLevel|number} [explicitContentFilter] The level of the explicit content filter + * @property {VoiceChannelResolvable} [afkChannel] The AFK channel of the guild + * @property {TextChannelResolvable} [systemChannel] The system channel of the guild + * @property {number} [afkTimeout] The AFK timeout of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the guild + * @property {GuildMemberResolvable} [owner] The owner of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [splash] The invite splash image of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [discoverySplash] The discovery splash image of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [banner] The banner of the guild + * @property {DefaultMessageNotificationLevel|number} [defaultMessageNotifications] The default message notification + * level of the guild + * @property {SystemChannelFlagsResolvable} [systemChannelFlags] The system channel flags of the guild + * @property {TextChannelResolvable} [rulesChannel] The rules channel of the guild + * @property {TextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild + * @property {string} [preferredLocale] The preferred locale of the guild + * @property {boolean} [premiumProgressBarEnabled] Whether the guild's premium progress bar is enabled + * @property {string} [description] The discovery description of the guild + * @property {Features[]} [features] The features of the guild + */ + + /** + * Data that can be resolved to a Text Channel object. This can be: + * * A TextChannel + * * A Snowflake + * @typedef {TextChannel|Snowflake} TextChannelResolvable + */ + + /** + * Data that can be resolved to a Voice Channel object. This can be: + * * A VoiceChannel + * * A Snowflake + * @typedef {VoiceChannel|Snowflake} VoiceChannelResolvable + */ + + /** + * Updates the guild with new information - e.g. a new name. + * @param {GuildEditData} data The data to update the guild with + * @param {string} [reason] Reason for editing this guild + * @returns {Promise} + * @example + * // Set the guild name + * guild.edit({ + * name: 'Discord Guild', + * }) + * .then(updated => console.log(`New guild name ${updated}`)) + * .catch(console.error); + */ + async edit(data, reason) { + const _data = {}; + if (data.name) _data.name = data.name; + if (typeof data.verificationLevel !== 'undefined') { + _data.verification_level = + typeof data.verificationLevel === 'number' + ? data.verificationLevel + : VerificationLevels[data.verificationLevel]; + } + if (typeof data.afkChannel !== 'undefined') { + _data.afk_channel_id = this.client.channels.resolveId(data.afkChannel); + } + if (typeof data.systemChannel !== 'undefined') { + _data.system_channel_id = this.client.channels.resolveId( + data.systemChannel, + ); + } + if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); + if (typeof data.icon !== 'undefined') + _data.icon = await DataResolver.resolveImage(data.icon); + if (data.owner) _data.owner_id = this.client.users.resolveId(data.owner); + if (typeof data.splash !== 'undefined') + _data.splash = await DataResolver.resolveImage(data.splash); + if (typeof data.discoverySplash !== 'undefined') { + _data.discovery_splash = await DataResolver.resolveImage( + data.discoverySplash, + ); + } + if (typeof data.banner !== 'undefined') + _data.banner = await DataResolver.resolveImage(data.banner); + if (typeof data.explicitContentFilter !== 'undefined') { + _data.explicit_content_filter = + typeof data.explicitContentFilter === 'number' + ? data.explicitContentFilter + : ExplicitContentFilterLevels[data.explicitContentFilter]; + } + if (typeof data.defaultMessageNotifications !== 'undefined') { + _data.default_message_notifications = + typeof data.defaultMessageNotifications === 'number' + ? data.defaultMessageNotifications + : DefaultMessageNotificationLevels[data.defaultMessageNotifications]; + } + if (typeof data.systemChannelFlags !== 'undefined') { + _data.system_channel_flags = SystemChannelFlags.resolve( + data.systemChannelFlags, + ); + } + if (typeof data.rulesChannel !== 'undefined') { + _data.rules_channel_id = this.client.channels.resolveId( + data.rulesChannel, + ); + } + if (typeof data.publicUpdatesChannel !== 'undefined') { + _data.public_updates_channel_id = this.client.channels.resolveId( + data.publicUpdatesChannel, + ); + } + if (typeof data.features !== 'undefined') { + _data.features = data.features; + } + if (typeof data.description !== 'undefined') { + _data.description = data.description; + } + if (data.preferredLocale) _data.preferred_locale = data.preferredLocale; + if ('premiumProgressBarEnabled' in data) + _data.premium_progress_bar_enabled = data.premiumProgressBarEnabled; + const newData = await this.client.api + .guilds(this.id) + .patch({ data: _data, reason }); + return this.client.actions.GuildUpdate.handle(newData).updated; + } + + /** + * Welcome channel data + * @typedef {Object} WelcomeChannelData + * @property {string} description The description to show for this welcome channel + * @property {TextChannel|NewsChannel|StoreChannel|Snowflake} channel The channel to link for this welcome channel + * @property {EmojiIdentifierResolvable} [emoji] The emoji to display for this welcome channel + */ + + /** + * Welcome screen edit data + * @typedef {Object} WelcomeScreenEditData + * @property {boolean} [enabled] Whether the welcome screen is enabled + * @property {string} [description] The description for the welcome screen + * @property {WelcomeChannelData[]} [welcomeChannels] The welcome channel data for the welcome screen + */ + + /** + * Data that can be resolved to a GuildTextChannel object. This can be: + * * A TextChannel + * * A NewsChannel + * * A Snowflake + * @typedef {TextChannel|NewsChannel|Snowflake} GuildTextChannelResolvable + */ + + /** + * Data that can be resolved to a GuildVoiceChannel object. This can be: + * * A VoiceChannel + * * A StageChannel + * * A Snowflake + * @typedef {VoiceChannel|StageChannel|Snowflake} GuildVoiceChannelResolvable + */ + + /** + * Updates the guild's welcome screen + * @param {WelcomeScreenEditData} data Data to edit the welcome screen with + * @returns {Promise} + * @example + * guild.editWelcomeScreen({ + * description: 'Hello World', + * enabled: true, + * welcomeChannels: [ + * { + * description: 'foobar', + * channel: '222197033908436994', + * } + * ], + * }) + */ + async editWelcomeScreen(data) { + const { enabled, description, welcomeChannels } = data; + const welcome_channels = welcomeChannels?.map((welcomeChannelData) => { + const emoji = this.emojis.resolve(welcomeChannelData.emoji); + return { + emoji_id: emoji?.id, + emoji_name: emoji?.name ?? welcomeChannelData.emoji, + channel_id: this.channels.resolveId(welcomeChannelData.channel), + description: welcomeChannelData.description, + }; + }); + + const patchData = await this.client.api + .guilds(this.id, 'welcome-screen') + .patch({ + data: { + welcome_channels, + description, + enabled, + }, + }); + return new WelcomeScreen(this, patchData); + } + + /** + * Edits the level of the explicit content filter. + * @param {ExplicitContentFilterLevel|number} explicitContentFilter The new level of the explicit content filter + * @param {string} [reason] Reason for changing the level of the guild's explicit content filter + * @returns {Promise} + */ + setExplicitContentFilter(explicitContentFilter, reason) { + return this.edit({ explicitContentFilter }, reason); + } + + /* eslint-disable max-len */ + /** + * Edits the setting of the default message notifications of the guild. + * @param {DefaultMessageNotificationLevel|number} defaultMessageNotifications The new default message notification level of the guild + * @param {string} [reason] Reason for changing the setting of the default message notifications + * @returns {Promise} + */ + setDefaultMessageNotifications(defaultMessageNotifications, reason) { + return this.edit({ defaultMessageNotifications }, reason); + } + /* eslint-enable max-len */ + + /** + * Edits the flags of the default message notifications of the guild. + * @param {SystemChannelFlagsResolvable} systemChannelFlags The new flags for the default message notifications + * @param {string} [reason] Reason for changing the flags of the default message notifications + * @returns {Promise} + */ + setSystemChannelFlags(systemChannelFlags, reason) { + return this.edit({ systemChannelFlags }, reason); + } + + /** + * Edits the name of the guild. + * @param {string} name The new name of the guild + * @param {string} [reason] Reason for changing the guild's name + * @returns {Promise} + * @example + * // Edit the guild name + * guild.setName('Discord Guild') + * .then(updated => console.log(`Updated guild name to ${updated.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name }, reason); + } + + /** + * Edits the verification level of the guild. + * @param {VerificationLevel|number} verificationLevel The new verification level of the guild + * @param {string} [reason] Reason for changing the guild's verification level + * @returns {Promise} + * @example + * // Edit the guild verification level + * guild.setVerificationLevel(1) + * .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`)) + * .catch(console.error); + */ + setVerificationLevel(verificationLevel, reason) { + return this.edit({ verificationLevel }, reason); + } + + /** + * Edits the AFK channel of the guild. + * @param {VoiceChannelResolvable} afkChannel The new AFK channel + * @param {string} [reason] Reason for changing the guild's AFK channel + * @returns {Promise} + * @example + * // Edit the guild AFK channel + * guild.setAFKChannel(channel) + * .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel.name}`)) + * .catch(console.error); + */ + setAFKChannel(afkChannel, reason) { + return this.edit({ afkChannel }, reason); + } + + /** + * Edits the system channel of the guild. + * @param {TextChannelResolvable} systemChannel The new system channel + * @param {string} [reason] Reason for changing the guild's system channel + * @returns {Promise} + * @example + * // Edit the guild system channel + * guild.setSystemChannel(channel) + * .then(updated => console.log(`Updated guild system channel to ${guild.systemChannel.name}`)) + * .catch(console.error); + */ + setSystemChannel(systemChannel, reason) { + return this.edit({ systemChannel }, reason); + } + + /** + * Edits the AFK timeout of the guild. + * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK + * @param {string} [reason] Reason for changing the guild's AFK timeout + * @returns {Promise} + * @example + * // Edit the guild AFK channel + * guild.setAFKTimeout(60) + * .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`)) + * .catch(console.error); + */ + setAFKTimeout(afkTimeout, reason) { + return this.edit({ afkTimeout }, reason); + } + + /** + * Sets a new guild icon. + * @param {?(Base64Resolvable|BufferResolvable)} icon The new icon of the guild + * @param {string} [reason] Reason for changing the guild's icon + * @returns {Promise} + * @example + * // Edit the guild icon + * guild.setIcon('./icon.png') + * .then(updated => console.log('Updated the guild icon')) + * .catch(console.error); + */ + setIcon(icon, reason) { + return this.edit({ icon }, reason); + } + + /** + * Sets a new owner of the guild. + * @param {GuildMemberResolvable} owner The new owner of the guild + * @param {string} [reason] Reason for setting the new owner + * @returns {Promise} + * @example + * // Edit the guild owner + * guild.setOwner(guild.members.cache.first()) + * .then(guild => guild.fetchOwner()) + * .then(owner => console.log(`Updated the guild owner to ${owner.displayName}`)) + * .catch(console.error); + */ + setOwner(owner, reason) { + return this.edit({ owner }, reason); + } + + /** + * Sets a new guild invite splash image. + * @param {?(Base64Resolvable|BufferResolvable)} splash The new invite splash image of the guild + * @param {string} [reason] Reason for changing the guild's invite splash image + * @returns {Promise} + * @example + * // Edit the guild splash + * guild.setSplash('./splash.png') + * .then(updated => console.log('Updated the guild splash')) + * .catch(console.error); + */ + setSplash(splash, reason) { + return this.edit({ splash }, reason); + } + + /** + * Sets a new guild discovery splash image. + * @param {?(Base64Resolvable|BufferResolvable)} discoverySplash The new discovery splash image of the guild + * @param {string} [reason] Reason for changing the guild's discovery splash image + * @returns {Promise} + * @example + * // Edit the guild discovery splash + * guild.setDiscoverySplash('./discoverysplash.png') + * .then(updated => console.log('Updated the guild discovery splash')) + * .catch(console.error); + */ + setDiscoverySplash(discoverySplash, reason) { + return this.edit({ discoverySplash }, reason); + } + + /** + * Sets a new guild banner. + * @param {?(Base64Resolvable|BufferResolvable)} banner The new banner of the guild + * @param {string} [reason] Reason for changing the guild's banner + * @returns {Promise} + * @example + * guild.setBanner('./banner.png') + * .then(updated => console.log('Updated the guild banner')) + * .catch(console.error); + */ + setBanner(banner, reason) { + return this.edit({ banner }, reason); + } + + /** + * Edits the rules channel of the guild. + * @param {TextChannelResolvable} rulesChannel The new rules channel + * @param {string} [reason] Reason for changing the guild's rules channel + * @returns {Promise} + * @example + * // Edit the guild rules channel + * guild.setRulesChannel(channel) + * .then(updated => console.log(`Updated guild rules channel to ${guild.rulesChannel.name}`)) + * .catch(console.error); + */ + setRulesChannel(rulesChannel, reason) { + return this.edit({ rulesChannel }, reason); + } + /** + * Change Guild Position (from * to Folder or Home) + * @param {number} position Guild Position + * * **WARNING**: Type = `FOLDER`, newPosition is the guild's index in the Folder. + * @param {String|Number} type Move to folder or home + * * `FOLDER`: 1 + * * `HOME`: 2 + * @param {String|Number|void|null} folderID If you want to move to folder + * @returns {Promise} + * @example + * // Move guild to folderID 123456, index 1 + * guild.setPosition(1, 'FOLDER', 123456) + * .then(guild => console.log(`Guild moved to folderID ${guild.folder.folderId}`)); + */ + async setPosition(position, type, folderID) { + if (type == 1 || `${type}`.toUpperCase() === 'FOLDER') { + folderID = folderID || this.folder.folderId; + if (!['number', 'string'].includes(typeof folderID)) + throw new TypeError('INVALID_TYPE', 'folderID', 'String | Number'); + // Get Data from Folder ID + const folder = await this.client.setting.rawSetting.guild_folders.find( + (obj) => obj.id == folderID, + ); + if (!folder) throw new Error('FOLDER_NOT_FOUND'); + if (folder.guild_ids.length - 1 < position || position < 0) + throw new Error('FOLDER_POSITION_INVALID'); + if (position !== folder.guild_ids.indexOf(this.id)) { + await this.client.setting.guildChangePosition( + this.id, + position, + 1, + folderID, + ); + } + } else if (type == 2 || `${type}`.toUpperCase() === 'HOME') { + if (this.client.setting.guild_positions - 1 < position || position < 0) + throw new Error('FOLDER_POSITION_INVALID'); + if (position !== this.position) { + await this.client.setting.guildChangePosition( + this.id, + position, + 2, + null, + ); + } + } else { + throw new TypeError('INVALID_TYPE', 'type', '`Folder`| `Home`'); + } + return this; + } + + /** + * Edits the community updates channel of the guild. + * @param {TextChannelResolvable} publicUpdatesChannel The new community updates channel + * @param {string} [reason] Reason for changing the guild's community updates channel + * @returns {Promise} + * @example + * // Edit the guild community updates channel + * guild.setPublicUpdatesChannel(channel) + * .then(updated => console.log(`Updated guild community updates channel to ${guild.publicUpdatesChannel.name}`)) + * .catch(console.error); + */ + setPublicUpdatesChannel(publicUpdatesChannel, reason) { + return this.edit({ publicUpdatesChannel }, reason); + } + + /** + * Edits the preferred locale of the guild. + * @param {string} preferredLocale The new preferred locale of the guild + * @param {string} [reason] Reason for changing the guild's preferred locale + * @returns {Promise} + * @example + * // Edit the guild preferred locale + * guild.setPreferredLocale('en-US') + * .then(updated => console.log(`Updated guild preferred locale to ${guild.preferredLocale}`)) + * .catch(console.error); + */ + setPreferredLocale(preferredLocale, reason) { + return this.edit({ preferredLocale }, reason); + } + + /** + * Edits the enabled state of the guild's premium progress bar + * @param {boolean} [enabled=true] The new enabled state of the guild's premium progress bar + * @param {string} [reason] Reason for changing the state of the guild's premium progress bar + * @returns {Promise} + */ + setPremiumProgressBarEnabled(enabled = true, reason) { + return this.edit({ premiumProgressBarEnabled: enabled }, reason); + } + + /** + * Data that can be resolved to give a Category Channel object. This can be: + * * A CategoryChannel object + * * A Snowflake + * @typedef {CategoryChannel|Snowflake} CategoryChannelResolvable + */ + + /** + * The data needed for updating a channel's position. + * @typedef {Object} ChannelPosition + * @property {GuildChannel|Snowflake} channel Channel to update + * @property {number} [position] New position for the channel + * @property {CategoryChannelResolvable} [parent] Parent channel for this channel + * @property {boolean} [lockPermissions] If the overwrites should be locked to the parents overwrites + */ + + /** + * Batch-updates the guild's channels' positions. + * Only one channel's parent can be changed at a time + * @param {ChannelPosition[]} channelPositions Channel positions to update + * @returns {Promise} + * @deprecated Use {@link GuildChannelManager#setPositions} instead + * @example + * guild.setChannelPositions([{ channel: channelId, position: newChannelIndex }]) + * .then(guild => console.log(`Updated channel positions for ${guild}`)) + * .catch(console.error); + */ + setChannelPositions(channelPositions) { + if (!deprecationEmittedForSetChannelPositions) { + process.emitWarning( + 'The Guild#setChannelPositions method is deprecated. Use GuildChannelManager#setPositions instead.', + 'DeprecationWarning', + ); + + deprecationEmittedForSetChannelPositions = true; + } + + return this.channels.setPositions(channelPositions); + } + + /** + * The data needed for updating a guild role's position + * @typedef {Object} GuildRolePosition + * @property {RoleResolvable} role The role's id + * @property {number} position The position to update + */ + + /** + * Batch-updates the guild's role positions + * @param {GuildRolePosition[]} rolePositions Role positions to update + * @returns {Promise} + * @deprecated Use {@link RoleManager#setPositions} instead + * @example + * guild.setRolePositions([{ role: roleId, position: updatedRoleIndex }]) + * .then(guild => console.log(`Role positions updated for ${guild}`)) + * .catch(console.error); + */ + setRolePositions(rolePositions) { + if (!deprecationEmittedForSetRolePositions) { + process.emitWarning( + 'The Guild#setRolePositions method is deprecated. Use RoleManager#setPositions instead.', + 'DeprecationWarning', + ); + + deprecationEmittedForSetRolePositions = true; + } + + return this.roles.setPositions(rolePositions); + } + + /** + * Edits the guild's widget settings. + * @param {GuildWidgetSettingsData} settings The widget settings for the guild + * @param {string} [reason] Reason for changing the guild's widget settings + * @returns {Promise} + */ + async setWidgetSettings(settings, reason) { + await this.client.api.guilds(this.id).widget.patch({ + data: { + enabled: settings.enabled, + channel_id: this.channels.resolveId(settings.channel), + }, + reason, + }); + return this; + } + + /** + * Leaves the guild. + * @returns {Promise} + * @example + * // Leave a guild + * guild.leave() + * .then(g => console.log(`Left the guild ${g}`)) + * .catch(console.error); + */ + async leave() { + if (this.ownerId === this.client.user.id) throw new Error('GUILD_OWNED'); + await this.client.api.users('@me').guilds(this.id).delete(); + return this.client.actions.GuildDelete.handle({ id: this.id }).guild; + } + + /** + * Deletes the guild. + * @returns {Promise} + * @example + * // Delete a guild + * guild.delete() + * .then(g => console.log(`Deleted the guild ${g}`)) + * .catch(console.error); + */ + async delete() { + await this.client.api.guilds(this.id).delete(); + return this.client.actions.GuildDelete.handle({ id: this.id }).guild; + } + + /** + * 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 + * what most users need. + * @param {Guild} guild The guild to compare with + * @returns {boolean} + */ + equals(guild) { + return ( + guild && + guild instanceof this.constructor && + this.id === guild.id && + this.available === guild.available && + this.splash === guild.splash && + this.discoverySplash === guild.discoverySplash && + this.name === guild.name && + this.memberCount === guild.memberCount && + this.large === guild.large && + this.icon === guild.icon && + this.ownerId === guild.ownerId && + this.verificationLevel === guild.verificationLevel && + (this.features === guild.features || + (this.features.length === guild.features.length && + this.features.every((feat, i) => feat === guild.features[i]))) + ); + } + + /** + * Set Community Feature + * @param {Boolean} stats True / False to enable / disable Community Feature + * @param {TextChannelResolvable} publicUpdatesChannel + * @param {TextChannelResolvable} rulesChannel + * @param {String} reason + */ + async setCommunity( + stats = true, + publicUpdatesChannel = '1', + rulesChannel = '1', + reason, + ) { + if (stats) { + // Check everyone role + const everyoneRole = this.roles.everyone; + if (everyoneRole.mentionable) { + await everyoneRole.setMentionable(false, reason); + } + // Setting + this.edit( + { + defaultMessageNotifications: 'ONLY_MENTIONS', + explicitContentFilter: 'ALL_MEMBERS', + features: [...this.features, 'COMMUNITY'], + publicUpdatesChannel, + rulesChannel, + verificationLevel: + VerificationLevels[this.verificationLevel] < 1 + ? 'LOW' + : this.verificationLevel, // Email + }, + reason, + ); + } else { + this.edit( + { + publicUpdatesChannel: null, + rulesChannel: null, + features: this.features.filter((f) => f !== 'COMMUNITY'), + preferredLocale: this.preferredLocale, + description: this.description, + }, + reason, + ); + } + } + + toJSON() { + const json = super.toJSON({ + available: false, + createdTimestamp: true, + nameAcronym: true, + presences: false, + voiceStates: false, + }); + json.iconURL = this.iconURL(); + json.splashURL = this.splashURL(); + json.discoverySplashURL = this.discoverySplashURL(); + json.bannerURL = this.bannerURL(); + return json; + } + + /** + * The voice state adapter for this guild that can be used with @discordjs/voice to play audio in voice + * and stage channels. + * @type {Function} + * @readonly + */ + get voiceAdapterCreator() { + return (methods) => { + this.client.voice.adapters.set(this.id, methods); + return { + sendPayload: (data) => { + if (this.shard.status !== Status.READY) return false; + this.shard.send(data); + return true; + }, + destroy: () => { + this.client.voice.adapters.delete(this.id); + }, + }; + }; + } + + /** + * Creates a collection of this guild's roles, sorted by their position and ids. + * @returns {Collection} + * @private + */ + _sortedRoles() { + return Util.discordSort(this.roles.cache); + } + + /** + * Creates a collection of this guild's or a specific category's channels, sorted by their position and ids. + * @param {GuildChannel} [channel] Category to get the channels of + * @returns {Collection} + * @private + */ + _sortedChannels(channel) { + const category = channel.type === ChannelTypes.GUILD_CATEGORY; + return Util.discordSort( + this.channels.cache.filter( + (c) => + (['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_STORE'].includes(channel.type) + ? ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_STORE'].includes(c.type) + : c.type === channel.type) && + (category || c.parent === channel.parent), + ), + ); + } } exports.Guild = Guild; diff --git a/src/util/Constants.js b/src/util/Constants.js index 42448f6..3c94256 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -347,6 +347,8 @@ exports.Events = { TYPING_START: 'typingStart', WEBHOOKS_UPDATE: 'webhookUpdate', INTERACTION_CREATE: 'interactionCreate', + INTERACTION_SUCCESS: 'interactionSuccess', + INTERACTION_FAILED: 'interactionFailed', ERROR: 'error', WARN: 'warn', DEBUG: 'debug', diff --git a/typings/index.d.ts b/typings/index.d.ts index e79d132..b5898e7 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -146,6 +146,7 @@ import { RawWidgetData, RawWidgetMemberData, } from './rawDataTypes'; +import { RelationshipType } from '../src/util/Constants'; //#region Classes @@ -975,6 +976,7 @@ export class Guild extends AnonymousGuild { public fetchAuditLogs( options?: GuildAuditLogsFetchOptions, ): Promise>; + public searchInteraction(options?: guildSearchInteraction): Promise; public fetchIntegrations(): Promise>; public fetchOwner(options?: BaseFetchOptions): Promise; public fetchPreview(): Promise; @@ -3886,6 +3888,14 @@ export interface BaseFetchOptions { force?: boolean; } +export interface guildSearchInteraction { + type?: ApplicationCommandTypes, + query?: String | void, + limit?: Number, + offset?: Number, + botID?: Array, +} + export interface BaseMessageComponentOptions { type?: MessageComponentType | MessageComponentTypes; } @@ -4052,6 +4062,11 @@ export interface ClientEvents extends BaseClientEvents { data: { count: number; index: number; nonce: string | undefined }, ]; guildMemberUpdate: [oldMember: GuildMember | PartialGuildMember, newMember: GuildMember]; + guildMemberListUpdate: [ + members: Collection, + guild: Guild, + data: {}, // see: https://luna.gitlab.io/discord-unofficial-docs/lazy_guilds.html + ] guildUpdate: [oldGuild: Guild, newGuild: Guild]; inviteCreate: [invite: Invite]; inviteDelete: [invite: Invite]; @@ -4089,7 +4104,9 @@ export interface ClientEvents extends BaseClientEvents { webhookUpdate: [channel: TextChannel | NewsChannel]; /** @deprecated Use interactionCreate instead */ interaction: [interaction: Interaction]; - interactionCreate: [interaction: Interaction]; + interactionCreate: [interaction: Interaction | { nonce: Snowflake, id: Snowflake }]; + interactionSuccess: [interaction: { nonce: Snowflake, id: Snowflake }]; + interactionFailed: [interaction: { nonce: Snowflake, id: Snowflake }]; shardDisconnect: [closeEvent: CloseEvent, shardId: number]; shardError: [error: Error, shardId: number]; shardReady: [shardId: number, unavailableGuilds: Set | undefined]; @@ -4106,6 +4123,16 @@ export interface ClientEvents extends BaseClientEvents { guildScheduledEventDelete: [guildScheduledEvent: GuildScheduledEvent]; guildScheduledEventUserAdd: [guildScheduledEvent: GuildScheduledEvent, user: User]; guildScheduledEventUserRemove: [guildScheduledEvent: GuildScheduledEvent, user: User]; + relationshipAdd: [ + id: Snowflake, + type: RelationshipType, + user: User, + ] + relationshipRemove: [ + id: Snowflake, + type: RelationshipType, + user: User, + ] } export interface ClientFetchInviteOptions { @@ -4285,74 +4312,79 @@ export interface ConstantsEvents { /** @deprecated See [this issue](https://github.com/discord/discord-api-docs/issues/3690) for more information. */ APPLICATION_COMMAND_DELETE: 'applicationCommandDelete'; /** @deprecated See [this issue](https://github.com/discord/discord-api-docs/issues/3690) for more information. */ - APPLICATION_COMMAND_UPDATE: 'applicationCommandUpdate'; - GUILD_CREATE: 'guildCreate'; - GUILD_DELETE: 'guildDelete'; - GUILD_UPDATE: 'guildUpdate'; - INVITE_CREATE: 'inviteCreate'; - INVITE_DELETE: 'inviteDelete'; - GUILD_UNAVAILABLE: 'guildUnavailable'; - GUILD_MEMBER_ADD: 'guildMemberAdd'; - GUILD_MEMBER_REMOVE: 'guildMemberRemove'; - GUILD_MEMBER_UPDATE: 'guildMemberUpdate'; - GUILD_MEMBER_AVAILABLE: 'guildMemberAvailable'; - GUILD_MEMBERS_CHUNK: 'guildMembersChunk'; - GUILD_INTEGRATIONS_UPDATE: 'guildIntegrationsUpdate'; - GUILD_ROLE_CREATE: 'roleCreate'; - GUILD_ROLE_DELETE: 'roleDelete'; - GUILD_ROLE_UPDATE: 'roleUpdate'; - GUILD_EMOJI_CREATE: 'emojiCreate'; - GUILD_EMOJI_DELETE: 'emojiDelete'; - GUILD_EMOJI_UPDATE: 'emojiUpdate'; - GUILD_BAN_ADD: 'guildBanAdd'; - GUILD_BAN_REMOVE: 'guildBanRemove'; - CHANNEL_CREATE: 'channelCreate'; - CHANNEL_DELETE: 'channelDelete'; - CHANNEL_UPDATE: 'channelUpdate'; - CHANNEL_PINS_UPDATE: 'channelPinsUpdate'; - MESSAGE_CREATE: 'messageCreate'; - MESSAGE_DELETE: 'messageDelete'; - MESSAGE_UPDATE: 'messageUpdate'; - MESSAGE_BULK_DELETE: 'messageDeleteBulk'; - MESSAGE_REACTION_ADD: 'messageReactionAdd'; - MESSAGE_REACTION_REMOVE: 'messageReactionRemove'; - MESSAGE_REACTION_REMOVE_ALL: 'messageReactionRemoveAll'; - MESSAGE_REACTION_REMOVE_EMOJI: 'messageReactionRemoveEmoji'; - THREAD_CREATE: 'threadCreate'; - THREAD_DELETE: 'threadDelete'; - THREAD_UPDATE: 'threadUpdate'; - THREAD_LIST_SYNC: 'threadListSync'; - THREAD_MEMBER_UPDATE: 'threadMemberUpdate'; - THREAD_MEMBERS_UPDATE: 'threadMembersUpdate'; - USER_UPDATE: 'userUpdate'; - PRESENCE_UPDATE: 'presenceUpdate'; - VOICE_SERVER_UPDATE: 'voiceServerUpdate'; - VOICE_STATE_UPDATE: 'voiceStateUpdate'; - TYPING_START: 'typingStart'; - WEBHOOKS_UPDATE: 'webhookUpdate'; - INTERACTION_CREATE: 'interactionCreate'; - ERROR: 'error'; - WARN: 'warn'; - DEBUG: 'debug'; - CACHE_SWEEP: 'cacheSweep'; - SHARD_DISCONNECT: 'shardDisconnect'; - SHARD_ERROR: 'shardError'; - SHARD_RECONNECTING: 'shardReconnecting'; - SHARD_READY: 'shardReady'; - SHARD_RESUME: 'shardResume'; - INVALIDATED: 'invalidated'; - RAW: 'raw'; - STAGE_INSTANCE_CREATE: 'stageInstanceCreate'; - STAGE_INSTANCE_UPDATE: 'stageInstanceUpdate'; - STAGE_INSTANCE_DELETE: 'stageInstanceDelete'; - 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'; + APPLICATION_COMMAND_UPDATE: 'applicationCommandUpdate', + GUILD_CREATE: 'guildCreate', + GUILD_DELETE: 'guildDelete', + GUILD_UPDATE: 'guildUpdate', + GUILD_UNAVAILABLE: 'guildUnavailable', + GUILD_MEMBER_ADD: 'guildMemberAdd', + GUILD_MEMBER_REMOVE: 'guildMemberRemove', + GUILD_MEMBER_UPDATE: 'guildMemberUpdate', + GUILD_MEMBER_AVAILABLE: 'guildMemberAvailable', + GUILD_MEMBERS_CHUNK: 'guildMembersChunk', + GUILD_MEMBER_LIST_UPDATE: 'guildMemberListUpdate', + GUILD_INTEGRATIONS_UPDATE: 'guildIntegrationsUpdate', + GUILD_ROLE_CREATE: 'roleCreate', + GUILD_ROLE_DELETE: 'roleDelete', + INVITE_CREATE: 'inviteCreate', + INVITE_DELETE: 'inviteDelete', + GUILD_ROLE_UPDATE: 'roleUpdate', + GUILD_EMOJI_CREATE: 'emojiCreate', + GUILD_EMOJI_DELETE: 'emojiDelete', + GUILD_EMOJI_UPDATE: 'emojiUpdate', + GUILD_BAN_ADD: 'guildBanAdd', + GUILD_BAN_REMOVE: 'guildBanRemove', + CHANNEL_CREATE: 'channelCreate', + CHANNEL_DELETE: 'channelDelete', + CHANNEL_UPDATE: 'channelUpdate', + CHANNEL_PINS_UPDATE: 'channelPinsUpdate', + MESSAGE_CREATE: 'messageCreate', + MESSAGE_DELETE: 'messageDelete', + MESSAGE_UPDATE: 'messageUpdate', + MESSAGE_BULK_DELETE: 'messageDeleteBulk', + MESSAGE_REACTION_ADD: 'messageReactionAdd', + MESSAGE_REACTION_REMOVE: 'messageReactionRemove', + MESSAGE_REACTION_REMOVE_ALL: 'messageReactionRemoveAll', + MESSAGE_REACTION_REMOVE_EMOJI: 'messageReactionRemoveEmoji', + THREAD_CREATE: 'threadCreate', + THREAD_DELETE: 'threadDelete', + THREAD_UPDATE: 'threadUpdate', + THREAD_LIST_SYNC: 'threadListSync', + THREAD_MEMBER_UPDATE: 'threadMemberUpdate', + THREAD_MEMBERS_UPDATE: 'threadMembersUpdate', + USER_UPDATE: 'userUpdate', + PRESENCE_UPDATE: 'presenceUpdate', + VOICE_SERVER_UPDATE: 'voiceServerUpdate', + VOICE_STATE_UPDATE: 'voiceStateUpdate', + TYPING_START: 'typingStart', + WEBHOOKS_UPDATE: 'webhookUpdate', + INTERACTION_CREATE: 'interactionCreate', + INTERACTION_SUCCESS: 'interactionSuccess', + INTERACTION_FAILED: 'interactionFailed', + ERROR: 'error', + WARN: 'warn', + DEBUG: 'debug', + CACHE_SWEEP: 'cacheSweep', + SHARD_DISCONNECT: 'shardDisconnect', + SHARD_ERROR: 'shardError', + SHARD_RECONNECTING: 'shardReconnecting', + SHARD_READY: 'shardReady', + SHARD_RESUME: 'shardResume', + INVALIDATED: 'invalidated', + RAW: 'raw', + STAGE_INSTANCE_CREATE: 'stageInstanceCreate', + STAGE_INSTANCE_UPDATE: 'stageInstanceUpdate', + STAGE_INSTANCE_DELETE: 'stageInstanceDelete', + 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', + RELATIONSHIP_ADD: 'relationshipAdd', + RELATIONSHIP_REMOVE: 'relationshipRemove', } export interface ConstantsOpcodes {