Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Guild): add api methods for membership screening #5144

Closed
wants to merge 13 commits into from
93 changes: 93 additions & 0 deletions src/structures/Guild.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,10 @@ class Guild extends Base {
* * DISCOVERABLE
* * FEATURABLE
* * INVITE_SPLASH
* * MEMBER_VERIFICATION_GATE_ENABLED
* * NEWS
* * PARTNERED
* * PREVIEW_ENABLED
* * RELAY_ENABLED
* * VANITY_URL
* * VERIFIED
Expand Down Expand Up @@ -484,6 +486,15 @@ class Guild extends Base {
return new Date(this.joinedTimestamp);
}

/**
* Whether Membership Screening is enabled on this guild
* @type {boolean}
* @readonly
*/
get membershipScreeningEnabled() {
return this.features.includes('MEMBER_VERIFICATION_GATE_ENABLED');
}

ckohen marked this conversation as resolved.
Show resolved Hide resolved
/**
* If this guild is partnered
* @type {boolean}
Expand Down Expand Up @@ -959,6 +970,49 @@ class Guild extends Base {
.then(data => GuildAuditLogs.build(this, data));
}

/**
* Data for a field in Membership Screening
* @typedef {Object} GuildMembershipScreeningField
* @property {MembershipScreeningType} fieldType The type of the field
* @property {string} label The title of the field
* @property {string[]} [values] The list of values in the field
* @property {boolean} required Whether the user has to fill out this field
*/

/**
* Data for the Guild Membership Screening object
* @typedef {Object} GuildMembershipScreening
* @property {boolean} enabled Whether membership screening is enabled
* @property {string} description The server description shown in the membership screening form
* @property {GuildMembershipScreeningField[]} formFields The steps in the membership screening form
*/

/**
* Fetches the guild Membership Screening data.
* @returns {Promise<GuildMembershipScreening>}
* @example
* // Fetches the guild membership screening options
* guild.fetchMembershipScreening()
* .then(memberScreen => console.log(`Membership Screening is ${memberScreen.enabled ? 'enabled' : 'disabled'}`))
* .catch(console.error);
*/
ckohen marked this conversation as resolved.
Show resolved Hide resolved
async fetchMembershipScreening() {
if (!this.features.includes('COMMUNITY')) {
throw new Error('COMMUNITY');
}
ckohen marked this conversation as resolved.
Show resolved Hide resolved
const data = await this.client.api.guilds(this.id)['member-verification'].get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discord responds with a 204 if membership screening is not yet set up.
(data will be an empty buffer then, causing an error to be thrown when trying to map the undefined form_fields)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be a bug with how the API is currently handling this, see discord/discord-api-docs#2530
A check is still a good idea though, will add.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const data = await this.client.api.guilds(this.id)['member-verification'].get();
const data = await this.client.api.guilds(this.id, 'member-verification').get();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll address this when I can resume work on this PR.

return {
enabled: this.membershipScreeningEnabled,
description: data.description,
formFields: data.form_fields.map(field => ({
fieldType: field.field_type,
label: field.label,
values: field.values,
required: field.required,
})),
};
}

/**
* Adds a user to the guild using OAuth2. Requires the `CREATE_INSTANT_INVITE` permission.
* @param {UserResolvable} user User to add to the guild
Expand Down Expand Up @@ -1422,6 +1476,45 @@ class Guild extends Base {
.then(() => this);
}

/**
* A `Partial` object is a representation of any existing object.
* This object contains between 1 and all of the original objects parameters.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you actually can edit with an empty object.

Suggested change
* This object contains between 1 and all of the original objects parameters.
* This object contains between 0 and all of the original objects parameters.

* This is true regardless of whether the parameters are optional in the base object.
* @typedef {Object} Partial
*/

/**
* Edits the guild's membership screening form.
* @param {Partial<GuildMembershipScreening>} memberScreen The membership screening data for the guild
ckohen marked this conversation as resolved.
Show resolved Hide resolved
* @returns {Promise<GuildMembershipScreening>}
*/
async setMembershipScreening(memberScreen) {
if (!this.features.includes('COMMUNITY')) {
throw new Error('COMMUNITY');
}
const fields = memberScreen.formFields?.map(field => ({
field_type: field.field_type,
label: field.label,
values: field.values,
required: field.required,
}));
const data = {};
if (typeof memberScreen.enabled !== 'undefined') data.enabled = memberScreen.enabled;
if (typeof memberScreen.description !== 'undefined') data.description = memberScreen.description;
if (typeof memberScreen.formFields !== 'undefined') data.form_fields = JSON.stringify(fields);
ckohen marked this conversation as resolved.
Show resolved Hide resolved
const res = await this.client.api.guilds(this.id)['member-verification'].patch({ data });
return {
enabled: memberScreen.enabled,
description: res.description,
formFields: res.form_fields.map(field => ({
fieldType: field.field_type,
label: field.label,
values: field.values,
required: field.required,
})),
};
}

/**
* Leaves the guild.
* @returns {Promise<Guild>}
Expand Down
7 changes: 7 additions & 0 deletions src/util/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,13 @@ exports.ExplicitContentFilterLevels = ['DISABLED', 'MEMBERS_WITHOUT_ROLES', 'ALL
*/
exports.VerificationLevels = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH'];

/**
* The type of membership screening. Here are the available types:
* * TERMS
* @typedef {string} MembershipScreeningType
*/
exports.MembershipScreeningType = ['TERMS'];
ckohen marked this conversation as resolved.
Show resolved Hide resolved

/**
* An error encountered while performing an API request. Here are the potential errors:
* * UNKNOWN_ACCOUNT
Expand Down
21 changes: 21 additions & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ declare module 'discord.js' {
ExplicitContentFilterLevels: ExplicitContentFilterLevel[];
DefaultMessageNotifications: DefaultMessageNotifications[];
VerificationLevels: VerificationLevel[];
MembershipScreeningType: MembershipScreeningType[];
MembershipStates: 'INVITED' | 'ACCEPTED';
};

Expand Down Expand Up @@ -602,6 +603,7 @@ declare module 'discord.js' {
public readonly me: GuildMember | null;
public memberCount: number;
public members: GuildMemberManager;
public readonly membershipScreeningEnabled: boolean;
public mfaLevel: number;
public name: string;
public readonly nameAcronym: string;
Expand Down Expand Up @@ -647,6 +649,7 @@ declare module 'discord.js' {
public fetchEmbed(): Promise<GuildWidget>;
public fetchIntegrations(options?: FetchIntegrationsOptions): Promise<Collection<string, Integration>>;
public fetchInvites(): Promise<Collection<string, Invite>>;
public fetchMembershipScreening(): Promise<GuildMembershipScreening>;
public fetchPreview(): Promise<GuildPreview>;
public fetchTemplates(): Promise<Collection<GuildTemplate['code'], GuildTemplate>>;
public fetchVanityCode(): Promise<string>;
Expand All @@ -671,6 +674,7 @@ declare module 'discord.js' {
reason?: string,
): Promise<Guild>;
public setIcon(icon: Base64Resolvable | null, reason?: string): Promise<Guild>;
public setMembershipScreening(memberScreen: Partial<GuildMembershipScreening>): Promise<GuildMembershipScreening>;
public setName(name: string, reason?: string): Promise<Guild>;
public setOwner(owner: GuildMemberResolvable, reason?: string): Promise<Guild>;
public setPreferredLocale(preferredLocale: string, reason?: string): Promise<Guild>;
Expand Down Expand Up @@ -2646,8 +2650,10 @@ declare module 'discord.js' {
| 'DISCOVERABLE'
| 'FEATURABLE'
| 'INVITE_SPLASH'
| 'MEMBER_VERIFICATION_GATE_ENABLED'
| 'NEWS'
| 'PARTNERED'
| 'PREVIEW_ENABLED'
| 'RELAY_ENABLED'
| 'VANITY_URL'
| 'VERIFIED'
Expand All @@ -2664,6 +2670,19 @@ declare module 'discord.js' {

type GuildMemberResolvable = GuildMember | UserResolvable;

interface GuildMembershipScreening {
description: string;
enabled: boolean;
formFields: GuildMembershipScreeningField[];
}

interface GuildMembershipScreeningField {
fieldType: MembershipScreeningType;
label: string;
required: boolean;
values?: string;
}

type GuildResolvable = Guild | GuildChannel | GuildMember | GuildEmoji | Invite | Role | Snowflake;

interface GuildPruneMembersOptions {
Expand Down Expand Up @@ -2745,6 +2764,8 @@ declare module 'discord.js' {

type GuildTemplateResolvable = string;

type MembershipScreeningType = 'TERMS';

type MembershipStates = 'INVITED' | 'ACCEPTED';

type MessageAdditions = MessageEmbed | MessageAttachment | (MessageEmbed | MessageAttachment)[];
Expand Down