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

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