diff --git a/packages/core/src/api/guild.ts b/packages/core/src/api/guild.ts index 13ee1c8a995a..d2a31643a7f5 100644 --- a/packages/core/src/api/guild.ts +++ b/packages/core/src/api/guild.ts @@ -1,98 +1,99 @@ /* eslint-disable jsdoc/check-param-names */ import { makeURLSearchParams, type REST, type RawFile, type RequestData } from '@discordjs/rest'; -import { Routes } from 'discord-api-types/v10'; -import type { - RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody, - RESTPatchAPIGuildVoiceStateCurrentMemberResult, - GuildMFALevel, - GuildWidgetStyle, - RESTGetAPIAuditLogQuery, - RESTGetAPIAuditLogResult, - RESTGetAPIAutoModerationRuleResult, - RESTGetAPIAutoModerationRulesResult, - RESTGetAPIGuildBanResult, - RESTGetAPIGuildBansQuery, - RESTGetAPIGuildBansResult, - RESTGetAPIGuildChannelsResult, - RESTGetAPIGuildEmojiResult, - RESTGetAPIGuildEmojisResult, - RESTGetAPIGuildIntegrationsResult, - RESTGetAPIGuildInvitesResult, - RESTGetAPIGuildMemberResult, - RESTGetAPIGuildMembersResult, - RESTGetAPIGuildMembersQuery, - RESTGetAPIGuildMembersSearchResult, - RESTGetAPIGuildPreviewResult, - RESTGetAPIGuildPruneCountResult, - RESTGetAPIGuildResult, - RESTGetAPIGuildRolesResult, - RESTGetAPIGuildScheduledEventQuery, - RESTGetAPIGuildScheduledEventResult, - RESTGetAPIGuildScheduledEventsQuery, - RESTGetAPIGuildScheduledEventsResult, - RESTGetAPIGuildScheduledEventUsersQuery, - RESTGetAPIGuildScheduledEventUsersResult, - RESTGetAPIGuildStickerResult, - RESTGetAPIGuildStickersResult, - RESTGetAPIGuildTemplatesResult, - RESTGetAPIGuildThreadsResult, - RESTGetAPIGuildVanityUrlResult, - RESTGetAPIGuildVoiceRegionsResult, - RESTGetAPIGuildPruneCountQuery, - RESTPostAPIGuildStickerFormDataBody, - RESTPostAPIGuildStickerResult, - RESTGetAPIGuildMembersSearchQuery, - RESTGetAPIGuildWebhooksResult, - RESTGetAPIGuildWelcomeScreenResult, - RESTGetAPIGuildWidgetImageResult, - RESTGetAPIGuildWidgetJSONResult, - RESTGetAPIGuildWidgetSettingsResult, - RESTGetAPITemplateResult, - RESTPatchAPIAutoModerationRuleJSONBody, - RESTPatchAPIAutoModerationRuleResult, - RESTPatchAPIGuildChannelPositionsJSONBody, - RESTPatchAPIGuildEmojiJSONBody, - RESTPatchAPIGuildEmojiResult, - RESTPatchAPIGuildJSONBody, - RESTPatchAPIGuildMemberJSONBody, - RESTPatchAPIGuildMemberResult, - RESTPatchAPIGuildResult, - RESTPatchAPIGuildRoleJSONBody, - RESTPatchAPIGuildRolePositionsJSONBody, - RESTPatchAPIGuildRolePositionsResult, - RESTPatchAPIGuildRoleResult, - RESTPatchAPIGuildScheduledEventJSONBody, - RESTPatchAPIGuildScheduledEventResult, - RESTPatchAPIGuildStickerJSONBody, - RESTPatchAPIGuildStickerResult, - RESTPatchAPIGuildTemplateJSONBody, - RESTPatchAPIGuildTemplateResult, - RESTPatchAPIGuildVoiceStateUserJSONBody, - RESTPatchAPIGuildWelcomeScreenJSONBody, - RESTPatchAPIGuildWelcomeScreenResult, - RESTPatchAPIGuildWidgetSettingsJSONBody, - RESTPatchAPIGuildWidgetSettingsResult, - RESTPostAPIAutoModerationRuleJSONBody, - RESTPostAPIAutoModerationRuleResult, - RESTPostAPIGuildChannelJSONBody, - RESTPostAPIGuildChannelResult, - RESTPostAPIGuildEmojiJSONBody, - RESTPostAPIGuildEmojiResult, - RESTPostAPIGuildPruneJSONBody, - RESTPostAPIGuildPruneResult, - RESTPostAPIGuildRoleJSONBody, - RESTPostAPIGuildRoleResult, - RESTPostAPIGuildScheduledEventJSONBody, - RESTPostAPIGuildScheduledEventResult, - RESTPostAPIGuildsJSONBody, - RESTPostAPIGuildsMFAResult, - RESTPostAPIGuildsResult, - RESTPostAPIGuildTemplatesJSONBody, - RESTPostAPIGuildTemplatesResult, - RESTPutAPIGuildBanJSONBody, - RESTPutAPIGuildTemplateSyncResult, - Snowflake, +import { + Routes, + type GuildMFALevel, + type GuildWidgetStyle, + type RESTGetAPIAuditLogQuery, + type RESTGetAPIAuditLogResult, + type RESTGetAPIAutoModerationRuleResult, + type RESTGetAPIAutoModerationRulesResult, + type RESTGetAPIGuildBanResult, + type RESTGetAPIGuildBansQuery, + type RESTGetAPIGuildBansResult, + type RESTGetAPIGuildChannelsResult, + type RESTGetAPIGuildEmojiResult, + type RESTGetAPIGuildEmojisResult, + type RESTGetAPIGuildIntegrationsResult, + type RESTGetAPIGuildInvitesResult, + type RESTGetAPIGuildMemberResult, + type RESTGetAPIGuildMembersQuery, + type RESTGetAPIGuildMembersResult, + type RESTGetAPIGuildMembersSearchQuery, + type RESTGetAPIGuildMembersSearchResult, + type RESTGetAPIGuildOnboardingResult, + type RESTGetAPIGuildPreviewResult, + type RESTGetAPIGuildPruneCountQuery, + type RESTGetAPIGuildPruneCountResult, + type RESTGetAPIGuildResult, + type RESTGetAPIGuildRolesResult, + type RESTGetAPIGuildScheduledEventQuery, + type RESTGetAPIGuildScheduledEventResult, + type RESTGetAPIGuildScheduledEventUsersQuery, + type RESTGetAPIGuildScheduledEventUsersResult, + type RESTGetAPIGuildScheduledEventsQuery, + type RESTGetAPIGuildScheduledEventsResult, + type RESTGetAPIGuildStickerResult, + type RESTGetAPIGuildStickersResult, + type RESTGetAPIGuildTemplatesResult, + type RESTGetAPIGuildThreadsResult, + type RESTGetAPIGuildVanityUrlResult, + type RESTGetAPIGuildVoiceRegionsResult, + type RESTGetAPIGuildWebhooksResult, + type RESTGetAPIGuildWelcomeScreenResult, + type RESTGetAPIGuildWidgetImageResult, + type RESTGetAPIGuildWidgetJSONResult, + type RESTGetAPIGuildWidgetSettingsResult, + type RESTGetAPITemplateResult, + type RESTPatchAPIAutoModerationRuleJSONBody, + type RESTPatchAPIAutoModerationRuleResult, + type RESTPatchAPIGuildChannelPositionsJSONBody, + type RESTPatchAPIGuildEmojiJSONBody, + type RESTPatchAPIGuildEmojiResult, + type RESTPatchAPIGuildJSONBody, + type RESTPatchAPIGuildMemberJSONBody, + type RESTPatchAPIGuildMemberResult, + type RESTPatchAPIGuildResult, + type RESTPatchAPIGuildRoleJSONBody, + type RESTPatchAPIGuildRolePositionsJSONBody, + type RESTPatchAPIGuildRolePositionsResult, + type RESTPatchAPIGuildRoleResult, + type RESTPatchAPIGuildScheduledEventJSONBody, + type RESTPatchAPIGuildScheduledEventResult, + type RESTPatchAPIGuildStickerJSONBody, + type RESTPatchAPIGuildStickerResult, + type RESTPatchAPIGuildTemplateJSONBody, + type RESTPatchAPIGuildTemplateResult, + type RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody, + type RESTPatchAPIGuildVoiceStateCurrentMemberResult, + type RESTPatchAPIGuildVoiceStateUserJSONBody, + type RESTPatchAPIGuildWelcomeScreenJSONBody, + type RESTPatchAPIGuildWelcomeScreenResult, + type RESTPatchAPIGuildWidgetSettingsJSONBody, + type RESTPatchAPIGuildWidgetSettingsResult, + type RESTPostAPIAutoModerationRuleJSONBody, + type RESTPostAPIAutoModerationRuleResult, + type RESTPostAPIGuildChannelJSONBody, + type RESTPostAPIGuildChannelResult, + type RESTPostAPIGuildEmojiJSONBody, + type RESTPostAPIGuildEmojiResult, + type RESTPostAPIGuildPruneJSONBody, + type RESTPostAPIGuildPruneResult, + type RESTPostAPIGuildRoleJSONBody, + type RESTPostAPIGuildRoleResult, + type RESTPostAPIGuildScheduledEventJSONBody, + type RESTPostAPIGuildScheduledEventResult, + type RESTPostAPIGuildStickerFormDataBody, + type RESTPostAPIGuildStickerResult, + type RESTPostAPIGuildTemplatesJSONBody, + type RESTPostAPIGuildTemplatesResult, + type RESTPostAPIGuildsJSONBody, + type RESTPostAPIGuildsMFAResult, + type RESTPostAPIGuildsResult, + type RESTPutAPIGuildBanJSONBody, + type RESTPutAPIGuildTemplateSyncResult, + type Snowflake, } from 'discord-api-types/v10'; export class GuildsAPI { @@ -1229,4 +1230,15 @@ export class GuildsAPI { body, }) as Promise; } + + /** + * Fetches a guild onboarding + * + * @see {@link https://discord.com/developers/docs/resources/guild#get-guild-onboarding} + * @param guildId - The id of the guild + * @param options - The options for fetching the guild onboarding + */ + public async getOnboarding(guildId: Snowflake, { signal }: Pick = {}) { + return this.rest.get(Routes.guildOnboarding(guildId), { signal }) as Promise; + } } diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 737115d34fbc..3c0dcd96319b 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -127,6 +127,9 @@ exports.GuildBan = require('./structures/GuildBan'); exports.GuildChannel = require('./structures/GuildChannel'); exports.GuildEmoji = require('./structures/GuildEmoji'); exports.GuildMember = require('./structures/GuildMember').GuildMember; +exports.GuildOnboarding = require('./structures/GuildOnboarding').GuildOnboarding; +exports.GuildOnboardingPrompt = require('./structures/GuildOnboardingPrompt').GuildOnboardingPrompt; +exports.GuildOnboardingPromptOption = require('./structures/GuildOnboardingPromptOption').GuildOnboardingPromptOption; exports.GuildPreview = require('./structures/GuildPreview'); exports.GuildPreviewEmoji = require('./structures/GuildPreviewEmoji'); exports.GuildScheduledEvent = require('./structures/GuildScheduledEvent').GuildScheduledEvent; diff --git a/packages/discord.js/src/structures/Guild.js b/packages/discord.js/src/structures/Guild.js index ce3405813f56..489ca93393f7 100644 --- a/packages/discord.js/src/structures/Guild.js +++ b/packages/discord.js/src/structures/Guild.js @@ -5,6 +5,7 @@ const { makeURLSearchParams } = require('@discordjs/rest'); const { ChannelType, GuildPremiumTier, Routes, GuildFeature } = require('discord-api-types/v10'); const AnonymousGuild = require('./AnonymousGuild'); const GuildAuditLogs = require('./GuildAuditLogs'); +const { GuildOnboarding } = require('./GuildOnboarding'); const GuildPreview = require('./GuildPreview'); const GuildTemplate = require('./GuildTemplate'); const Integration = require('./Integration'); @@ -760,6 +761,15 @@ class Guild extends AnonymousGuild { return new GuildAuditLogs(this, data); } + /** + * Fetches the guild onboarding data for this guild. + * @returns {Promise} + */ + async fetchOnboarding() { + const data = await this.client.rest.get(Routes.guildOnboarding(this.id)); + return new GuildOnboarding(this.client, data); + } + /** * The data for editing a guild. * @typedef {Object} GuildEditOptions diff --git a/packages/discord.js/src/structures/GuildOnboarding.js b/packages/discord.js/src/structures/GuildOnboarding.js new file mode 100644 index 000000000000..119f905ee96e --- /dev/null +++ b/packages/discord.js/src/structures/GuildOnboarding.js @@ -0,0 +1,58 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); +const { GuildOnboardingPrompt } = require('./GuildOnboardingPrompt'); + +/** + * Represents the onboarding data of a guild. + * @extends {Base} + */ +class GuildOnboarding extends Base { + constructor(client, data) { + super(client); + + /** + * The id of the guild this onboarding data is for + * @type {Snowflake} + */ + this.guildId = data.guild_id; + + const guild = this.guild; + + /** + * The prompts shown during onboarding and in customize community + * @type {Collection} + */ + this.prompts = data.prompts.reduce( + (prompts, prompt) => prompts.set(prompt.id, new GuildOnboardingPrompt(client, prompt, this.guildId)), + new Collection(), + ); + + /** + * The ids of the channels that new members get opted into automatically + * @type {Collection} + */ + this.defaultChannels = data.default_channel_ids.reduce( + (channels, channelId) => channels.set(channelId, guild.channels.cache.get(channelId)), + new Collection(), + ); + + /** + * Whether onboarding is enabled + * @type {boolean} + */ + this.enabled = data.enabled; + } + + /** + * The guild this onboarding is from + * @type {Guild} + * @readonly + */ + get guild() { + return this.client.guilds.cache.get(this.guildId); + } +} + +exports.GuildOnboarding = GuildOnboarding; diff --git a/packages/discord.js/src/structures/GuildOnboardingPrompt.js b/packages/discord.js/src/structures/GuildOnboardingPrompt.js new file mode 100644 index 000000000000..4de3f5dd7958 --- /dev/null +++ b/packages/discord.js/src/structures/GuildOnboardingPrompt.js @@ -0,0 +1,78 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); +const { GuildOnboardingPromptOption } = require('./GuildOnboardingPromptOption'); + +/** + * Represents the data of a prompt of a guilds onboarding. + * @extends {Base} + */ +class GuildOnboardingPrompt extends Base { + constructor(client, data, guildId) { + super(client); + + /** + * The id of the guild this onboarding prompt is from + * @type {Snowflake} + */ + this.guildId = guildId; + + /** + * The id of the prompt + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The options available within the prompt + * @type {Collection} + */ + this.options = data.options.reduce( + (options, option) => options.set(option.id, new GuildOnboardingPromptOption(client, option, guildId)), + new Collection(), + ); + + /** + * The title of the prompt + * @type {string} + */ + this.title = data.title; + + /** + * Whether users are limited to selecting one option for the prompt + * @type {boolean} + */ + this.singleSelect = data.single_select; + + /** + * Whether the prompt is required before a user completes the onboarding flow + * @type {boolean} + */ + this.required = data.required; + + /** + * Whether the prompt is present in the onboarding flow. + * If `false`, the prompt will only appear in the Channels & Roles tab + * @type {boolean} + */ + this.inOnboarding = data.in_onboarding; + + /** + * The type of the prompt + * @type {GuildOnboardingPromptType} + */ + this.type = data.type; + } + + /** + * The guild this onboarding prompt is from + * @type {Guild} + * @readonly + */ + get guild() { + return this.client.guilds.cache.get(this.guildId); + } +} + +exports.GuildOnboardingPrompt = GuildOnboardingPrompt; diff --git a/packages/discord.js/src/structures/GuildOnboardingPromptOption.js b/packages/discord.js/src/structures/GuildOnboardingPromptOption.js new file mode 100644 index 000000000000..3002144cf725 --- /dev/null +++ b/packages/discord.js/src/structures/GuildOnboardingPromptOption.js @@ -0,0 +1,84 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); +const { resolvePartialEmoji } = require('../util/Util'); + +/** + * Represents the data of an option from a prompt of a guilds onboarding. + * @extends {Base} + */ +class GuildOnboardingPromptOption extends Base { + constructor(client, data, guildId) { + super(client); + + /** + * The id of the guild this onboarding prompt option is from + * @type {Snowflake} + */ + this.guildId = guildId; + + const guild = this.guild; + + /** + * The id of the option + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The channels a member is added to when the option is selected + * @type {Collection} + */ + this.channels = data.channel_ids.reduce( + (channels, channelId) => channels.set(channelId, guild.channels.cache.get(channelId)), + new Collection(), + ); + + /** + * The roles assigned to a member when the option is selected + * @type {Collection} + */ + this.roles = data.role_ids.reduce( + (roles, roleId) => roles.set(roleId, guild.roles.cache.get(roleId)), + new Collection(), + ); + + /** + * The data for an emoji of a guilds onboarding prompt option + * @typedef {Object} GuildOnboardingPromptOptionEmoji + * @property {?Snowflake} id The id of the emoji + * @property {string} name The name of the emoji + * @property {boolean} animated Whether the emoji is animated + */ + + /** + * The emoji of the option + * @type {?GuildOnboardingPromptOptionEmoji} + */ + this.emoji = resolvePartialEmoji(data.emoji); + + /** + * The title of the option + * @type {string} + */ + this.title = data.title; + + /** + * The description of the option + * @type {?string} + */ + this.description = data.description; + } + + /** + * The guild this onboarding prompt option is from + * @type {Guild} + * @readonly + */ + get guild() { + return this.client.guilds.cache.get(this.guildId); + } +} + +exports.GuildOnboardingPromptOption = GuildOnboardingPromptOption; diff --git a/packages/discord.js/src/util/APITypes.js b/packages/discord.js/src/util/APITypes.js index d0a48660b01f..11d468ada3a5 100644 --- a/packages/discord.js/src/util/APITypes.js +++ b/packages/discord.js/src/util/APITypes.js @@ -310,6 +310,11 @@ * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildNSFWLevel} */ +/** + * @external GuildOnboardingPromptType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildOnboardingPromptType} + */ + /** * @external GuildPremiumTier * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildPremiumTier} diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 930ee3f5f7b3..509c53428f4b 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -157,6 +157,10 @@ import { ImageFormat, GuildMemberFlags, RESTGetAPIGuildThreadsResult, + RESTGetAPIGuildOnboardingResult, + APIGuildOnboardingPrompt, + APIGuildOnboardingPromptOption, + GuildOnboardingPromptType, } from 'discord-api-types/v10'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -1356,6 +1360,7 @@ export class Guild extends AnonymousGuild { options?: GuildAuditLogsFetchOptions, ): Promise>; public fetchIntegrations(): Promise>; + public fetchOnboarding(): Promise; public fetchOwner(options?: BaseFetchOptions): Promise; public fetchPreview(): Promise; public fetchTemplates(): Promise>; @@ -1566,6 +1571,40 @@ export class GuildMember extends PartialTextBasedChannel(Base) { public valueOf(): string; } +export class GuildOnboarding extends Base { + private constructor(client: Client, data: RESTGetAPIGuildOnboardingResult); + public get guild(): Guild; + public guildId: Snowflake; + public prompts: Collection; + public defaultChannels: Collection; + public enabled: boolean; +} + +export class GuildOnboardingPrompt extends Base { + private constructor(client: Client, data: APIGuildOnboardingPrompt, guildId: Snowflake); + public id: Snowflake; + public get guild(): Guild; + public guildId: Snowflake; + public options: Collection; + public title: string; + public singleSelect: boolean; + public required: boolean; + public inOnboarding: boolean; + public type: GuildOnboardingPromptType; +} + +export class GuildOnboardingPromptOption extends Base { + private constructor(client: Client, data: APIGuildOnboardingPromptOption, guildId: Snowflake); + public id: Snowflake; + public get guild(): Guild; + public guildId: Snowflake; + public channels: Collection; + public roles: Collection; + public emoji: GuildOnboardingPromptOptionEmoji | null; + public title: string; + public description: string | null; +} + export class GuildPreview extends Base { private constructor(client: Client, data: RawGuildPreviewData); public approximateMemberCount: number; @@ -5648,6 +5687,12 @@ export type GuildTemplateResolvable = string; export type GuildVoiceChannelResolvable = VoiceBasedChannel | Snowflake; +export interface GuildOnboardingPromptOptionEmoji { + id: Snowflake | null; + name: string; + animated: boolean; +} + export type HexColorString = `#${string}`; export interface IntegrationAccount { diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 70f84d973e85..9c9f79938d44 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -173,6 +173,7 @@ import { ApplicationCommandSubCommand, ChatInputApplicationCommandData, ApplicationCommandPermissionsManager, + GuildOnboarding, } from '.'; import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders'; @@ -2288,3 +2289,8 @@ client.on('guildAuditLogEntryCreate', (auditLogEntry, guild) => { }); expectType>(guildMember.flags); + +{ + const onboarding = await guild.fetchOnboarding(); + expectType(onboarding); +}