Skip to content

Commit 7671a83

Browse files
almeidxJiralitekodiakhq[bot]
authored
feat: onboarding mode and edit method (#9647)
* feat: onboarding mode and edit method * feat(guild): add `editOnboarding` * fix: use discord-api-types * types: make arrays readonly Co-authored-by: Jiralite <[email protected]> * fix: bring up to date * docs: id is a snowflake * fix: requested changes * refactor: make most options optional * refactor: provide GuildEmoji or Emoji instance * revert: changes to Util * fix: rebase leftovers * fix: allow passing option id * fix: requested changes --------- Co-authored-by: Jiralite <[email protected]> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent e307581 commit 7671a83

File tree

8 files changed

+185
-20
lines changed

8 files changed

+185
-20
lines changed

packages/core/src/api/guild.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ import {
9292
type RESTPostAPIGuildsMFAResult,
9393
type RESTPostAPIGuildsResult,
9494
type RESTPutAPIGuildBanJSONBody,
95+
type RESTPutAPIGuildOnboardingJSONBody,
96+
type RESTPutAPIGuildOnboardingResult,
9597
type RESTPutAPIGuildTemplateSyncResult,
9698
type Snowflake,
9799
} from 'discord-api-types/v10';
@@ -1241,4 +1243,24 @@ export class GuildsAPI {
12411243
public async getOnboarding(guildId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
12421244
return this.rest.get(Routes.guildOnboarding(guildId), { signal }) as Promise<RESTGetAPIGuildOnboardingResult>;
12431245
}
1246+
1247+
/**
1248+
* Edits a guild onboarding
1249+
*
1250+
* @see {@link https://discord.com/developers/docs/resources/guild#modify-guild-onboarding}
1251+
* @param guildId - The id of the guild
1252+
* @param body - The data for editing the guild onboarding
1253+
* @param options - The options for editing the guild onboarding
1254+
*/
1255+
public async editOnboarding(
1256+
guildId: Snowflake,
1257+
body: RESTPutAPIGuildOnboardingJSONBody,
1258+
{ reason, signal }: Pick<RequestData, 'reason' | 'signal'> = {},
1259+
) {
1260+
return this.rest.put(Routes.guildOnboarding(guildId), {
1261+
reason,
1262+
body,
1263+
signal,
1264+
}) as Promise<RESTPutAPIGuildOnboardingResult>;
1265+
}
12441266
}

packages/discord.js/src/structures/Guild.js

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const { Collection } = require('@discordjs/collection');
44
const { makeURLSearchParams } = require('@discordjs/rest');
5+
const { DiscordSnowflake } = require('@sapphire/snowflake');
56
const { ChannelType, GuildPremiumTier, Routes, GuildFeature } = require('discord-api-types/v10');
67
const AnonymousGuild = require('./AnonymousGuild');
78
const GuildAuditLogs = require('./GuildAuditLogs');
@@ -28,7 +29,7 @@ const VoiceStateManager = require('../managers/VoiceStateManager');
2829
const DataResolver = require('../util/DataResolver');
2930
const Status = require('../util/Status');
3031
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
31-
const { discordSort, getSortableGroupTypes } = require('../util/Util');
32+
const { discordSort, getSortableGroupTypes, resolvePartialEmoji } = require('../util/Util');
3233

3334
/**
3435
* Represents a guild (or a server) on Discord.
@@ -881,6 +882,85 @@ class Guild extends AnonymousGuild {
881882
return this.client.actions.GuildUpdate.handle(data).updated;
882883
}
883884

885+
/**
886+
* Options used to edit the guild onboarding.
887+
* @typedef {Object} GuildOnboardingEditOptions
888+
* @property {GuildOnboardingPromptData[]|Collection<Snowflake, GuildOnboardingPrompt>} [prompts]
889+
* The prompts shown during onboarding and in customize community
890+
* @property {ChannelResolvable[]|Collection<Snowflake, GuildChannel>} [defaultChannels]
891+
* The channels that new members get opted into automatically
892+
* @property {boolean} [enabled] Whether the onboarding is enabled
893+
* @property {GuildOnboardingMode} [mode] The mode to edit the guild onboarding with
894+
* @property {string} [reason] The reason for editing the guild onboarding
895+
*/
896+
897+
/**
898+
* Data for editing a guild onboarding prompt.
899+
* @typedef {Object} GuildOnboardingPromptData
900+
* @property {Snowflake} [id] The id of the prompt
901+
* @property {string} title The title for the prompt
902+
* @property {boolean} [singleSelect] Whether users are limited to selecting one option for the prompt
903+
* @property {boolean} [required] Whether the prompt is required before a user completes the onboarding flow
904+
* @property {boolean} [inOnboarding] Whether the prompt is present in the onboarding flow
905+
* @property {GuildOnboardingPromptType} [type] The type of the prompt
906+
* @property {GuildOnboardingPromptOptionData[]|Collection<Snowflake, GuildOnboardingPrompt>} options
907+
* The options available within the prompt
908+
*/
909+
910+
/**
911+
* Data for editing a guild onboarding prompt option.
912+
* @typedef {Object} GuildOnboardingPromptOptionData
913+
* @property {?Snowflake} [id] The id of the option
914+
* @property {ChannelResolvable[]|Collection<Snowflake, GuildChannel>} [channels]
915+
* The channels a member is added to when the option is selected
916+
* @property {RoleResolvable[]|Collection<Snowflake, Role>} [roles]
917+
* The roles assigned to a member when the option is selected
918+
* @property {string} title The title of the option
919+
* @property {?string} [description] The description of the option
920+
* @property {?(EmojiIdentifierResolvable|Emoji)} [emoji] The emoji of the option
921+
*/
922+
923+
/**
924+
* Edits the guild onboarding data for this guild.
925+
* @param {GuildOnboardingEditOptions} options The options to provide
926+
* @returns {Promise<GuildOnboarding>}
927+
*/
928+
async editOnboarding(options) {
929+
const newData = await this.client.rest.put(Routes.guildOnboarding(this.id), {
930+
body: {
931+
prompts: options.prompts?.map(prompt => ({
932+
// Currently, the prompt ids are required even for new ones (which won't be used)
933+
id: prompt.id ?? DiscordSnowflake.generate().toString(),
934+
title: prompt.title,
935+
single_select: prompt.singleSelect,
936+
required: prompt.required,
937+
in_onboarding: prompt.inOnboarding,
938+
type: prompt.type,
939+
options: prompt.options.map(option => {
940+
const emoji = resolvePartialEmoji(option.emoji);
941+
942+
return {
943+
id: option.id,
944+
channel_ids: option.channels?.map(channel => this.channels.resolveId(channel)),
945+
role_ids: option.roles?.map(role => this.roles.resolveId(role)),
946+
title: option.title,
947+
description: option.description,
948+
emoji_animated: emoji?.animated,
949+
emoji_id: emoji?.id,
950+
emoji_name: emoji?.name,
951+
};
952+
}),
953+
})),
954+
default_channel_ids: options.defaultChannels?.map(channel => this.channels.resolveId(channel)),
955+
enabled: options.enabled,
956+
mode: options.mode,
957+
},
958+
reason: options.reason,
959+
});
960+
961+
return new GuildOnboarding(this.client, newData);
962+
}
963+
884964
/**
885965
* Welcome channel data
886966
* @typedef {Object} WelcomeChannelData

packages/discord.js/src/structures/GuildOnboarding.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ class GuildOnboarding extends Base {
4343
* @type {boolean}
4444
*/
4545
this.enabled = data.enabled;
46+
47+
/**
48+
* The mode of this onboarding
49+
* @type {GuildOnboardingMode}
50+
*/
51+
this.mode = data.mode;
4652
}
4753

4854
/**

packages/discord.js/src/structures/GuildOnboardingPromptOption.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const { Collection } = require('@discordjs/collection');
44
const Base = require('./Base');
5-
const { resolvePartialEmoji } = require('../util/Util');
5+
const { Emoji } = require('./Emoji.js');
66

77
/**
88
* Represents the data of an option from a prompt of a guilds onboarding.
@@ -45,18 +45,11 @@ class GuildOnboardingPromptOption extends Base {
4545
);
4646

4747
/**
48-
* The data for an emoji of a guilds onboarding prompt option
49-
* @typedef {Object} GuildOnboardingPromptOptionEmoji
50-
* @property {?Snowflake} id The id of the emoji
51-
* @property {string} name The name of the emoji
52-
* @property {boolean} animated Whether the emoji is animated
48+
* The raw emoji of the option
49+
* @type {APIPartialEmoji}
50+
* @private
5351
*/
54-
55-
/**
56-
* The emoji of the option
57-
* @type {?GuildOnboardingPromptOptionEmoji}
58-
*/
59-
this.emoji = resolvePartialEmoji(data.emoji);
52+
this._emoji = data.emoji;
6053

6154
/**
6255
* The title of the option
@@ -79,6 +72,15 @@ class GuildOnboardingPromptOption extends Base {
7972
get guild() {
8073
return this.client.guilds.cache.get(this.guildId);
8174
}
75+
76+
/**
77+
* The emoji of this onboarding prompt option
78+
* @type {?(GuildEmoji|Emoji)}
79+
*/
80+
get emoji() {
81+
if (!this._emoji.id && !this._emoji.name) return null;
82+
return this.client.emojis.resolve(this._emoji.id) ?? new Emoji(this.client, this._emoji);
83+
}
8284
}
8385

8486
exports.GuildOnboardingPromptOption = GuildOnboardingPromptOption;

packages/discord.js/src/util/APITypes.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@
160160
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIModalSubmission}
161161
*/
162162

163+
/**
164+
* @external APIPartialEmoji
165+
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIPartialEmoji}
166+
*/
167+
163168
/**
164169
* @external APIRole
165170
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIRole}
@@ -335,6 +340,11 @@
335340
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildNSFWLevel}
336341
*/
337342

343+
/**
344+
* @external GuildOnboardingMode
345+
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildOnboardingMode}
346+
*/
347+
338348
/**
339349
* @external GuildOnboardingPromptType
340350
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildOnboardingPromptType}

packages/discord.js/src/util/Util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ function parseEmoji(text) {
111111
/**
112112
* Resolves a partial emoji object from an {@link EmojiIdentifierResolvable}, without checking a Client.
113113
* @param {Emoji|EmojiIdentifierResolvable} emoji Emoji identifier to resolve
114-
* @returns {?(PartialEmoji|PartialEmojiOnlyId)} Suppling a snowflake yields `PartialEmojiOnlyId`.
114+
* @returns {?(PartialEmoji|PartialEmojiOnlyId)} Supplying a snowflake yields `PartialEmojiOnlyId`.
115115
* @private
116116
*/
117117
function resolvePartialEmoji(emoji) {

packages/discord.js/typings/index.d.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
ApplicationCommandOptionAllowedChannelTypes,
3636
} from '@discordjs/builders';
3737
import { Awaitable, JSONEncodable } from '@discordjs/util';
38-
import { Collection } from '@discordjs/collection';
38+
import { Collection, ReadonlyCollection } from '@discordjs/collection';
3939
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
4040
import {
4141
WebSocketManager as WSWebSocketManager,
@@ -167,6 +167,7 @@ import {
167167
RoleFlags,
168168
TeamMemberRole,
169169
GuildWidgetStyle,
170+
GuildOnboardingMode,
170171
} from 'discord-api-types/v10';
171172
import { ChildProcess } from 'node:child_process';
172173
import { EventEmitter } from 'node:events';
@@ -1378,6 +1379,7 @@ export class Guild extends AnonymousGuild {
13781379
public delete(): Promise<Guild>;
13791380
public discoverySplashURL(options?: ImageURLOptions): string | null;
13801381
public edit(options: GuildEditOptions): Promise<Guild>;
1382+
public editOnboarding(options: GuildOnboardingEditOptions): Promise<GuildOnboarding>;
13811383
public editWelcomeScreen(options: WelcomeScreenEditOptions): Promise<WelcomeScreen>;
13821384
public equals(guild: Guild): boolean;
13831385
public fetchAuditLogs<T extends GuildAuditLogsResolvable = null>(
@@ -1603,6 +1605,7 @@ export class GuildOnboarding extends Base {
16031605
public prompts: Collection<Snowflake, GuildOnboardingPrompt>;
16041606
public defaultChannels: Collection<Snowflake, GuildChannel>;
16051607
public enabled: boolean;
1608+
public mode: GuildOnboardingMode;
16061609
}
16071610

16081611
export class GuildOnboardingPrompt extends Base {
@@ -1620,12 +1623,14 @@ export class GuildOnboardingPrompt extends Base {
16201623

16211624
export class GuildOnboardingPromptOption extends Base {
16221625
private constructor(client: Client, data: APIGuildOnboardingPromptOption, guildId: Snowflake);
1626+
private _emoji: APIPartialEmoji;
1627+
16231628
public id: Snowflake;
1629+
public get emoji(): Emoji | GuildEmoji | null;
16241630
public get guild(): Guild;
16251631
public guildId: Snowflake;
16261632
public channels: Collection<Snowflake, GuildChannel>;
16271633
public roles: Collection<Snowflake, Role>;
1628-
public emoji: GuildOnboardingPromptOptionEmoji | null;
16291634
public title: string;
16301635
public description: string | null;
16311636
}
@@ -5784,10 +5789,31 @@ export type GuildTemplateResolvable = string;
57845789

57855790
export type GuildVoiceChannelResolvable = VoiceBasedChannel | Snowflake;
57865791

5787-
export interface GuildOnboardingPromptOptionEmoji {
5788-
id: Snowflake | null;
5789-
name: string;
5790-
animated: boolean;
5792+
export interface GuildOnboardingEditOptions {
5793+
prompts?: readonly GuildOnboardingPromptData[] | ReadonlyCollection<Snowflake, GuildOnboardingPrompt>;
5794+
defaultChannels?: readonly ChannelResolvable[] | ReadonlyCollection<Snowflake, GuildChannel>;
5795+
enabled?: boolean;
5796+
mode?: GuildOnboardingMode;
5797+
reason?: string;
5798+
}
5799+
5800+
export interface GuildOnboardingPromptData {
5801+
id?: Snowflake;
5802+
title: string;
5803+
singleSelect?: boolean;
5804+
required?: boolean;
5805+
inOnboarding?: boolean;
5806+
type?: GuildOnboardingPromptType;
5807+
options: readonly GuildOnboardingPromptOptionData[] | ReadonlyCollection<Snowflake, GuildOnboardingPromptOption>;
5808+
}
5809+
5810+
export interface GuildOnboardingPromptOptionData {
5811+
id?: Snowflake | null;
5812+
channels?: readonly ChannelResolvable[] | ReadonlyCollection<Snowflake, GuildChannel>;
5813+
roles?: readonly RoleResolvable[] | ReadonlyCollection<Snowflake, Role>;
5814+
title: string;
5815+
description?: string | null;
5816+
emoji?: EmojiIdentifierResolvable | Emoji | null;
57915817
}
57925818

57935819
export type HexColorString = `#${string}`;

packages/discord.js/typings/index.test-d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2327,9 +2327,28 @@ client.on('guildAuditLogEntryCreate', (auditLogEntry, guild) => {
23272327

23282328
expectType<Readonly<GuildMemberFlagsBitField>>(guildMember.flags);
23292329

2330+
declare const emojiResolvable: GuildEmoji | Emoji | string;
2331+
23302332
{
23312333
const onboarding = await guild.fetchOnboarding();
23322334
expectType<GuildOnboarding>(onboarding);
2335+
2336+
expectType<GuildOnboarding>(await guild.editOnboarding(onboarding));
2337+
2338+
await guild.editOnboarding({
2339+
defaultChannels: onboarding.defaultChannels,
2340+
enabled: onboarding.enabled,
2341+
mode: onboarding.mode,
2342+
prompts: onboarding.prompts,
2343+
});
2344+
2345+
const prompt = onboarding.prompts.first()!;
2346+
const option = prompt.options.first()!;
2347+
2348+
await guild.editOnboarding({ prompts: [prompt] });
2349+
await guild.editOnboarding({ prompts: [{ ...prompt, options: [option] }] });
2350+
2351+
await guild.editOnboarding({ prompts: [{ ...prompt, options: [{ ...option, emoji: emojiResolvable }] }] });
23332352
}
23342353

23352354
declare const partialDMChannel: PartialDMChannel;

0 commit comments

Comments
 (0)