From 2ee0f1cdc69dcd0c8f870bcecc3d36aef0e44ad1 Mon Sep 17 00:00:00 2001 From: Sugden <28943913+NotSugden@users.noreply.github.com> Date: Sat, 29 Feb 2020 06:43:42 +0000 Subject: [PATCH] =?UTF-8?q?feat(GuildManager):=20Allow=20for=20more=20opti?= =?UTF-8?q?ons=20for=20GuildManager.cre=E2=80=A6=20(#3742)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * typings: add GuildVerificationLevel and GuildExplicitContentFilter * implement new types * fix jsdoc on stores * typo * add more options for GuildStore#create * add channels and roles * update typings * fix typings and use snake case for permissionOverwrites * typings & jsdoc * fix tslint * remove trailing whitespace * fix jsdoc * fix jsdoc * fix oopsies * fix lint * fix lint * fix mr lint man * add typedefs and support for setting channel parents * fix tab indenation * update jsdoc * suggested changes * style: fix silly format * docs(PartialChannelData): name is not optional * style: remove silly format --- src/managers/GuildManager.js | 144 ++++++++++++++++++++----- src/structures/Guild.js | 36 ++++--- src/structures/GuildChannel.js | 4 +- src/structures/PermissionOverwrites.js | 6 +- src/util/Constants.js | 33 ++++-- typings/index.d.ts | 40 +++++-- 6 files changed, 202 insertions(+), 61 deletions(-) diff --git a/src/managers/GuildManager.js b/src/managers/GuildManager.js index 8a28e06b2..407118633 100644 --- a/src/managers/GuildManager.js +++ b/src/managers/GuildManager.js @@ -2,10 +2,17 @@ const BaseManager = require('./BaseManager'); const DataResolver = require('../util/DataResolver'); -const { Events } = require('../util/Constants'); +const { + Events, + VerificationLevels, + DefaultMessageNotifications, + ExplicitContentFilterLevels, +} = require('../util/Constants'); const Guild = require('../structures/Guild'); const GuildChannel = require('../structures/GuildChannel'); const GuildMember = require('../structures/GuildMember'); +const Permissions = require('../util/Permissions'); +const { resolveColor } = require('../util/Util'); const GuildEmoji = require('../structures/GuildEmoji'); const Invite = require('../structures/Invite'); const Role = require('../structures/Role'); @@ -36,6 +43,45 @@ class GuildManager extends BaseManager { * @typedef {Guild|GuildChannel|GuildMember|GuildEmoji|Role|Snowflake|Invite} GuildResolvable */ + /** + * Partial data for a Role. + * @typedef {Object} PartialRoleData + * @property {number} id The ID for this role, used to set channel overrides, + * this is a placeholder and will be replaced by the API after consumption + * @property {string} [name] The name of the role + * @property {ColorResolvable} [color] The color of the role, either a hex string or a base 10 number + * @property {boolean} [hoist] Whether or not the role should be hoisted + * @property {number} [position] The position of the role + * @property {PermissionResolvable|number} [permissions] The permissions of the role + * @property {boolean} [mentionable] Whether or not the role should be mentionable + */ + + /** + * Partial overwrite data. + * @typedef {Object} PartialOverwriteData + * @property {number|Snowflake} id The Role or User ID for this overwrite + * @property {string} [type] The type of this overwrite + * @property {PermissionResolvable} [allow] The permissions to allow + * @property {PermissionResolvable} [deny] The permissions to deny + */ + + /** + * Partial data for a Channel. + * @typedef {Object} PartialChannelData + * @property {number} [id] The ID for this channel, used to set its parent, + * this is a placeholder and will be replaced by the API after consumption + * @property {number} [parentID] The parent ID for this channel + * @property {string} [type] The type of the channel + * @property {string} name The name of the channel + * @property {string} [topic] The topic of the text channel + * @property {boolean} [nsfw] Whether the channel is NSFW + * @property {number} [bitrate] The bitrate of the voice channel + * @property {number} [userLimit] The user limit of the channel + * @property {PartialOverwriteData} [permissionOverwrites] + * Overwrites of the channel + * @property {number} [rateLimitPerUser] The rate limit per user of the channel in seconds + */ + /** * Resolves a GuildResolvable to a Guild object. * @method resolve @@ -75,37 +121,81 @@ class GuildManager extends BaseManager { * This is only available to bots in fewer than 10 guilds. * @param {string} name The name of the guild * @param {Object} [options] Options for the creating - * @param {string} [options.region] The region for the server, defaults to the closest one available + * @param {PartialChannelData[]} [options.channels] The channels for this guild + * @param {DefaultMessageNotifications} [options.defaultMessageNotifications] The default message notifications + * for the guild + * @param {ExplicitContentFilterLevel} [options.explicitContentFilter] The explicit content filter level for the guild * @param {BufferResolvable|Base64Resolvable} [options.icon=null] The icon for the guild + * @param {string} [options.region] The region for the server, defaults to the closest one available + * @param {PartialRoleData[]} [options.roles] The roles for this guild, + * the first element of this array is used to change properties of the guild's everyone role. + * @param {VerificationLevel} [options.verificationLevel] The verification level for the guild * @returns {Promise} The guild that was created */ - create(name, { region, icon = null } = {}) { - if (!icon || (typeof icon === 'string' && icon.startsWith('data:'))) { - return new Promise((resolve, reject) => - this.client.api.guilds.post({ data: { name, region, icon } }) - .then(data => { - if (this.client.guilds.cache.has(data.id)) return resolve(this.client.guilds.cache.get(data.id)); - - const handleGuild = guild => { - if (guild.id === data.id) { - this.client.removeListener(Events.GUILD_CREATE, handleGuild); - this.client.clearTimeout(timeout); - resolve(guild); - } - }; - this.client.on(Events.GUILD_CREATE, handleGuild); - - const timeout = this.client.setTimeout(() => { - this.client.removeListener(Events.GUILD_CREATE, handleGuild); - resolve(this.client.guilds.add(data)); - }, 10000); - return undefined; - }, reject), - ); + async create(name, { + channels = [], + defaultMessageNotifications, + explicitContentFilter, + icon = null, + region, + roles = [], + verificationLevel, + } = {}) { + icon = await DataResolver.resolveImage(icon); + if (typeof verificationLevel !== 'undefined' && typeof verificationLevel !== 'number') { + verificationLevel = VerificationLevels.indexOf(verificationLevel); } + if (typeof defaultMessageNotifications !== 'undefined' && typeof defaultMessageNotifications !== 'number') { + defaultMessageNotifications = DefaultMessageNotifications.indexOf(defaultMessageNotifications); + } + if (typeof explicitContentFilter !== 'undefined' && typeof explicitContentFilter !== 'number') { + explicitContentFilter = ExplicitContentFilterLevels.indexOf(explicitContentFilter); + } + for (const channel of channels) { + channel.parent_id = channel.parentID; + delete channel.parentID; + if (!channel.permissionOverwrites) continue; + for (const overwrite of channel.permissionOverwrites) { + if (overwrite.allow) overwrite.allow = Permissions.resolve(overwrite.allow); + if (overwrite.deny) overwrite.deny = Permissions.resolve(overwrite.deny); + } + channel.permission_overwrites = channel.permissionOverwrites; + delete channel.permissionOverwrites; + } + for (const role of roles) { + if (role.color) role.color = resolveColor(role.color); + if (role.permissions) role.permissions = Permissions.resolve(role.permissions); + } + return new Promise((resolve, reject) => + this.client.api.guilds.post({ data: { + name, + region, + icon, + verification_level: verificationLevel, + default_message_notifications: defaultMessageNotifications, + explicit_content_filter: explicitContentFilter, + channels, + roles, + } }) + .then(data => { + if (this.client.guilds.cache.has(data.id)) return resolve(this.client.guilds.cache.get(data.id)); - return DataResolver.resolveImage(icon) - .then(data => this.create(name, { region, icon: data || null })); + const handleGuild = guild => { + if (guild.id === data.id) { + this.client.removeListener(Events.GUILD_CREATE, handleGuild); + this.client.clearTimeout(timeout); + resolve(guild); + } + }; + this.client.on(Events.GUILD_CREATE, handleGuild); + + const timeout = this.client.setTimeout(() => { + this.client.removeListener(Events.GUILD_CREATE, handleGuild); + resolve(this.client.guilds.add(data)); + }, 10000); + return undefined; + }, reject), + ); } } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 2368ae626..57431e42c 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -5,7 +5,13 @@ const Integration = require('./Integration'); const GuildAuditLogs = require('./GuildAuditLogs'); const Webhook = require('./Webhook'); const VoiceRegion = require('./VoiceRegion'); -const { ChannelTypes, DefaultMessageNotifications, PartialTypes } = require('../util/Constants'); +const { + ChannelTypes, + DefaultMessageNotifications, + PartialTypes, + VerificationLevels, + ExplicitContentFilterLevels, +} = require('../util/Constants'); const Collection = require('../util/Collection'); const Util = require('../util/Util'); const DataResolver = require('../util/DataResolver'); @@ -247,15 +253,15 @@ class Guild extends Base { /** * The verification level of the guild - * @type {number} + * @type {VerificationLevel} */ - this.verificationLevel = data.verification_level; + this.verificationLevel = VerificationLevels[data.verification_level]; /** * The explicit content filter level of the guild - * @type {number} + * @type {ExplicitContentFilterLevel} */ - this.explicitContentFilter = data.explicit_content_filter; + this.explicitContentFilter = ExplicitContentFilterLevels[data.explicit_content_filter]; /** * The required MFA level for the guild @@ -821,8 +827,8 @@ class Guild extends Base { * @typedef {Object} GuildEditData * @property {string} [name] The name of the guild * @property {string} [region] The region of the guild - * @property {number} [verificationLevel] The verification level of the guild - * @property {number} [explicitContentFilter] The level of the explicit content filter + * @property {VerificationLevel|number} [verificationLevel] The verification level of the guild + * @property {ExplicitContentFilterLevel|number} [explicitContentFilter] The level of the explicit content filter * @property {ChannelResolvable} [afkChannel] The AFK channel of the guild * @property {ChannelResolvable} [systemChannel] The system channel of the guild * @property {number} [afkTimeout] The AFK timeout of the guild @@ -852,7 +858,11 @@ class Guild extends Base { const _data = {}; if (data.name) _data.name = data.name; if (data.region) _data.region = data.region; - if (typeof data.verificationLevel !== 'undefined') _data.verification_level = Number(data.verificationLevel); + if (typeof data.verificationLevel !== 'undefined') { + _data.verification_level = typeof data.verificationLevel === 'number' ? + Number(data.verificationLevel) : + ExplicitContentFilterLevels.indexOf(data.verificationLevel); + } if (typeof data.afkChannel !== 'undefined') { _data.afk_channel_id = this.client.channels.resolveID(data.afkChannel); } @@ -865,12 +875,14 @@ class Guild extends Base { if (data.splash) _data.splash = data.splash; if (data.banner) _data.banner = data.banner; if (typeof data.explicitContentFilter !== 'undefined') { - _data.explicit_content_filter = Number(data.explicitContentFilter); + _data.explicit_content_filter = typeof data.explicitContentFilter === 'number' ? + data.explicitContentFilter : + ExplicitContentFilterLevels.indexOf(data.explicitContentFilter); } if (typeof data.defaultMessageNotifications !== 'undefined') { _data.default_message_notifications = typeof data.defaultMessageNotifications === 'string' ? DefaultMessageNotifications.indexOf(data.defaultMessageNotifications) : - Number(data.defaultMessageNotifications); + data.defaultMessageNotifications; } if (typeof data.systemChannelFlags !== 'undefined') { _data.system_channel_flags = SystemChannelFlags.resolve(data.systemChannelFlags); @@ -881,7 +893,7 @@ class Guild extends Base { /** * Edits the level of the explicit content filter. - * @param {number} explicitContentFilter The new level of the explicit content filter + * @param {ExplicitContentFilterLevel|number} explicitContentFilter The new level of the explicit content filter * @param {string} [reason] Reason for changing the level of the guild's explicit content filter * @returns {Promise} */ @@ -943,7 +955,7 @@ class Guild extends Base { /** * Edits the verification level of the guild. - * @param {number} verificationLevel The new verification level of the guild + * @param {VerificationLevel|number} verificationLevel The new verification level of the guild * @param {string} [reason] Reason for changing the guild's verification level * @returns {Promise} * @example diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 35a0bc2ab..d80f5b24f 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -211,7 +211,7 @@ class GuildChannel extends Channel { /** * Updates Overwrites for a user or role in this channel. (creates if non-existent) * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update - * @param {PermissionOverwriteOption} options The options for the update + * @param {PermissionOverwriteOptions} options The options for the update * @param {string} [reason] Reason for creating/editing this overwrite * @returns {Promise} * @example @@ -234,7 +234,7 @@ class GuildChannel extends Channel { /** * Overwrites the permissions for a user or role in this channel. (replaces if existent) * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update - * @param {PermissionOverwriteOption} options The options for the update + * @param {PermissionOverwriteOptions} options The options for the update * @param {string} [reason] Reason for creating/editing this overwrite * @returns {Promise} * @example diff --git a/src/structures/PermissionOverwrites.js b/src/structures/PermissionOverwrites.js index cf11eaa94..59a5d93f8 100644 --- a/src/structures/PermissionOverwrites.js +++ b/src/structures/PermissionOverwrites.js @@ -56,7 +56,7 @@ class PermissionOverwrites { /** * Updates this permissionOverwrites. - * @param {PermissionOverwriteOption} options The options for the update + * @param {PermissionOverwriteOptions} options The options for the update * @param {string} [reason] Reason for creating/editing this overwrite * @returns {Promise} * @example @@ -99,7 +99,7 @@ class PermissionOverwrites { * 'ATTACH_FILES': false, * } * ``` - * @typedef {Object} PermissionOverwriteOption + * @typedef {Object} PermissionOverwriteOptions */ /** @@ -110,7 +110,7 @@ class PermissionOverwrites { /** * Deletes this Permission Overwrite. - * @param {PermissionOverwriteOption} options The options for the update + * @param {PermissionOverwriteOptions} options The options for the update * @param {Object} initialPermissions The initial permissions * @param {PermissionResolvable} initialPermissions.allow Initial allowed permissions * @param {PermissionResolvable} initialPermissions.deny Initial denied permissions diff --git a/src/util/Constants.js b/src/util/Constants.js index b38e0b580..547ffde1d 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -493,21 +493,34 @@ exports.Colors = { NOT_QUITE_BLACK: 0x23272A, }; +/** + * The value set for the explicit content filter levels for a guild: + * * DISABLED + * * MEMBERS_WITHOUT_ROLES + * * ALL_MEMBERS + * @typedef {string} ExplicitContentFilterLevel + */ +exports.ExplicitContentFilterLevels = [ + 'DISABLED', + 'MEMBERS_WITHOUT_ROLES', + 'ALL_MEMBERS', +]; + /** * The value set for the verification levels for a guild: - * * None - * * Low - * * Medium - * * (╯°□°)╯︵ ┻━┻ - * * ┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻ + * * NONE + * * LOW + * * MEDIUM + * * HIGH + * * VERY_HIGH * @typedef {string} VerificationLevel */ exports.VerificationLevels = [ - 'None', - 'Low', - 'Medium', - '(╯°□°)╯︵ ┻━┻', - '┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻', + 'NONE', + 'LOW', + 'MEDIUM', + 'HIGH', + 'VERY_HIGH', ]; /** diff --git a/typings/index.d.ts b/typings/index.d.ts index bed7fa219..1bc54ae47 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -582,7 +582,9 @@ declare module 'discord.js' { }; MessageTypes: MessageType[]; ActivityTypes: ActivityType[]; + ExplicitContentFilterLevels: ExplicitContentFilterLevel[]; DefaultMessageNotifications: DefaultMessageNotifications[]; + VerificationLevels: VerificationLevel[]; MembershipStates: 'INVITED' | 'ACCEPTED'; }; @@ -647,7 +649,7 @@ declare module 'discord.js' { public embedChannelID: Snowflake | null; public embedEnabled: boolean; public emojis: GuildEmojiManager; - public explicitContentFilter: number; + public explicitContentFilter: ExplicitContentFilterLevel; public features: GuildFeatures[]; public icon: string | null; public id: Snowflake; @@ -681,7 +683,7 @@ declare module 'discord.js' { public systemChannelFlags: Readonly; public systemChannelID: Snowflake | null; public vanityURLCode: string | null; - public verificationLevel: number; + public verificationLevel: VerificationLevel; public readonly verified: boolean; public readonly voice: VoiceState | null; public readonly voiceStates: VoiceStateManager; @@ -713,7 +715,7 @@ declare module 'discord.js' { public setChannelPositions(channelPositions: ChannelPosition[]): Promise; public setDefaultMessageNotifications(defaultMessageNotifications: DefaultMessageNotifications | number, reason?: string): Promise; public setEmbed(embed: GuildEmbedData, reason?: string): Promise; - public setExplicitContentFilter(explicitContentFilter: number, reason?: string): Promise; + public setExplicitContentFilter(explicitContentFilter: ExplicitContentFilterLevel, reason?: string): Promise; public setIcon(icon: Base64Resolvable | null, reason?: string): Promise; public setName(name: string, reason?: string): Promise; public setOwner(owner: GuildMemberResolvable, reason?: string): Promise; @@ -722,7 +724,7 @@ declare module 'discord.js' { public setSplash(splash: Base64Resolvable | null, reason?: string): Promise; public setSystemChannel(systemChannel: ChannelResolvable | null, reason?: string): Promise; public setSystemChannelFlags(systemChannelFlags: SystemChannelFlagsResolvable, reason?: string): Promise; - public setVerificationLevel(verificationLevel: number, reason?: string): Promise; + public setVerificationLevel(verificationLevel: VerificationLevel, reason?: string): Promise; public splashURL(options?: ImageURLOptions): string | null; public toJSON(): object; public toString(): string; @@ -2179,6 +2181,8 @@ declare module 'discord.js' { codeBlockContent?: boolean; } + type ExplicitContentFilterLevel = 'DISABLED' | 'MEMBERS_WITHOUT_ROLES' | 'ALL_MEMBERS'; + interface Extendable { GuildEmoji: typeof GuildEmoji; DMChannel: typeof DMChannel; @@ -2303,8 +2307,8 @@ declare module 'discord.js' { interface GuildEditData { name?: string; region?: string; - verificationLevel?: number; - explicitContentFilter?: number; + verificationLevel?: VerificationLevel; + explicitContentFilter?: ExplicitContentFilterLevel; defaultMessageNotifications?: DefaultMessageNotifications | number; afkChannel?: ChannelResolvable; systemChannel?: ChannelResolvable; @@ -2625,9 +2629,27 @@ declare module 'discord.js' { }; interface PartialChannel extends Partialize {} + + interface PartialChannelData { + id?: number; + name: string; + topic?: string; + type?: ChannelType; + parentID?: number; + permissionOverwrites?: { + id: number | Snowflake; + type?: OverwriteType; + allow?: PermissionResolvable; + deny?: PermissionResolvable; + }[]; + } + interface PartialGuildMember extends Partialize {} interface PartialMessage extends Partialize {} - interface PartialUser extends Partialize {} + + interface PartialRoleData extends RoleData { + id?: number; + } type PartialTypes = 'USER' | 'CHANNEL' @@ -2635,6 +2657,8 @@ declare module 'discord.js' { | 'MESSAGE' | 'REACTION'; + interface PartialUser extends Partialize {} + type PresenceStatus = ClientPresenceStatus | 'offline'; type PresenceStatusData = ClientPresenceStatus | 'invisible'; @@ -2720,6 +2744,8 @@ declare module 'discord.js' { type UserResolvable = User | Snowflake | Message | GuildMember; + type VerificationLevel = 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'; + type VoiceStatus = number; interface WebhookEditData {