feat: guild onboarding (#9120)

* feat: guild onboarding

* feat: types and /core method

* fix: route

* fix: make emoji name non-nullable

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Almeida
2023-07-07 22:48:17 +01:00
committed by GitHub
parent a48d0efb09
commit dc73c938ff
9 changed files with 393 additions and 92 deletions

View File

@@ -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<RESTPatchAPIGuildVoiceStateCurrentMemberResult>;
}
/**
* 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<RequestData, 'signal'> = {}) {
return this.rest.get(Routes.guildOnboarding(guildId), { signal }) as Promise<RESTGetAPIGuildOnboardingResult>;
}
}

View File

@@ -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;

View File

@@ -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<GuildOnboarding>}
*/
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

View File

@@ -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<Snowflake, GuildOnboardingPrompt>}
*/
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<Snowflake, GuildChannel>}
*/
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;

View File

@@ -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<Snowflake, GuildOnboardingPromptOption>}
*/
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;

View File

@@ -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<Snowflake, GuildChannel>}
*/
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<Snowflake, Role>}
*/
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;

View File

@@ -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}

View File

@@ -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<T>,
): Promise<GuildAuditLogs<T>>;
public fetchIntegrations(): Promise<Collection<Snowflake | string, Integration>>;
public fetchOnboarding(): Promise<GuildOnboarding>;
public fetchOwner(options?: BaseFetchOptions): Promise<GuildMember>;
public fetchPreview(): Promise<GuildPreview>;
public fetchTemplates(): Promise<Collection<GuildTemplate['code'], GuildTemplate>>;
@@ -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<Snowflake, GuildOnboardingPrompt>;
public defaultChannels: Collection<Snowflake, GuildChannel>;
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<Snowflake, GuildOnboardingPromptOption>;
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<Snowflake, GuildChannel>;
public roles: Collection<Snowflake, Role>;
public emoji: GuildOnboardingPromptOptionEmoji | null;
public title: string;
public description: string | null;
}
export class GuildPreview extends Base {
private constructor(client: Client<true>, 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 {

View File

@@ -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<Readonly<GuildMemberFlagsBitField>>(guildMember.flags);
{
const onboarding = await guild.fetchOnboarding();
expectType<GuildOnboarding>(onboarding);
}