diff --git a/src/index.js b/src/index.js index 446766f..f102a17 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ exports.Constants = require('./util/Constants'); exports.DataResolver = require('./util/DataResolver'); exports.DiscordAPIError = require('./rest/DiscordAPIError'); exports.Formatters = require('./util/Formatters'); +exports.GuildMemberFlags = require('./util/GuildMemberFlags'); exports.HTTPError = require('./rest/HTTPError'); exports.Intents = require('./util/Intents'); exports.LimitedCollection = require('./util/LimitedCollection'); diff --git a/src/managers/GuildMemberManager.js b/src/managers/GuildMemberManager.js index 4d0abd7..c061b08 100644 --- a/src/managers/GuildMemberManager.js +++ b/src/managers/GuildMemberManager.js @@ -11,6 +11,7 @@ const { Role } = require('../structures/Role'); const { Events, Opcodes } = require('../util/Constants'); const { PartialTypes } = require('../util/Constants'); const DataResolver = require('../util/DataResolver'); +const GuildMemberFlags = require('../util/GuildMemberFlags'); const SnowflakeUtil = require('../util/SnowflakeUtil'); /** @@ -276,6 +277,7 @@ class GuildMemberManager extends CachedManager { * (if they are connected to voice), or `null` if you want to disconnect them from voice * @property {DateResolvable|null} [communicationDisabledUntil] The date or timestamp * for the member's communication to be disabled until. Provide `null` to enable communication again. + * @property {GuildMemberFlagsResolvable} [flags] The flags to set for the member * @property {?(BufferResolvable|Base64Resolvable)} [avatar] The new guild avatar * @property {?(BufferResolvable|Base64Resolvable)} [banner] The new guild banner * @property {?string} [bio] The new guild about me @@ -311,6 +313,8 @@ class GuildMemberManager extends CachedManager { _data.communication_disabled_until = _data.communicationDisabledUntil && new Date(_data.communicationDisabledUntil).toISOString(); + _data.flags = _data.flags && GuildMemberFlags.resolve(_data.flags); + // Avatar, banner, bio if (typeof _data.avatar !== 'undefined') { _data.avatar = await DataResolver.resolveImage(_data.avatar); diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 6ec2b3e..72c1a86 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -6,6 +6,7 @@ const VoiceState = require('./VoiceState'); const TextBasedChannel = require('./interfaces/TextBasedChannel'); const { Error } = require('../errors'); const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager'); +const GuildMemberFlags = require('../util/GuildMemberFlags'); const Permissions = require('../util/Permissions'); const Util = require('../util/Util'); @@ -96,6 +97,16 @@ class GuildMember extends Base { this.communicationDisabledUntilTimestamp = data.communication_disabled_until && Date.parse(data.communication_disabled_until); } + + if ('flags' in data) { + /** + * The flags of this member + * @type {Readonly} + */ + this.flags = new GuildMemberFlags(data.flags).freeze(); + } else { + this.flags ??= new GuildMemberFlags().freeze(); + } } _ProfilePatch(data) { @@ -408,6 +419,16 @@ class GuildMember extends Base { return this.edit({ nick }, reason); } + /** + * Sets the flags for this member. + * @param {GuildMemberFlagsResolvable} flags The flags to set + * @param {string} [reason] Reason for setting the flags + * @returns {Promise} + */ + setFlags(flags, reason) { + return this.edit({ flags, reason }); + } + /** * Sets the guild avatar of the logged in client. * @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar @@ -582,6 +603,7 @@ class GuildMember extends Base { this.bio === member.bio && this.pending === member.pending && this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp && + this.flags.equals(member.flags) && (this._roles === member._roles || (this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i]))) ); diff --git a/src/util/GuildMemberFlags.js b/src/util/GuildMemberFlags.js new file mode 100644 index 00000000..b4c3709 --- /dev/null +++ b/src/util/GuildMemberFlags.js @@ -0,0 +1,43 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with an {@link GuildMember#flags} bitfield. + * @extends {BitField} + */ +class GuildMemberFlags extends BitField {} + +/** + * @name GuildMemberFlags + * @kind constructor + * @memberof GuildMemberFlags + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Numeric guild member flags. All available properties: + * * `DID_REJOIN` + * * `COMPLETED_ONBOARDING` + * * `BYPASSES_VERIFICATION` + * * `STARTED_ONBOARDING` + * @type {Object} + * @see {@link https://discord.com/developers/docs/resources/guild#guild-member-object-guild-member-flags} + */ +GuildMemberFlags.FLAGS = { + DID_REJOIN: 1 << 0, + COMPLETED_ONBOARDING: 1 << 1, + BYPASSES_VERIFICATION: 1 << 2, + STARTED_ONBOARDING: 1 << 3, +}; + +/** + * Data that can be resolved to give a guild member flag bitfield. This can be: + * * A string (see {@link GuildMemberFlags.FLAGS}) + * * A guild member flag + * * An instance of GuildMemberFlags + * * An Array of GuildMemberFlagsResolvable + * @typedef {string|number|GuildMemberFlags|GuildMemberFlagsResolvable[]} GuildMemberFlagsResolvable + */ + +module.exports = GuildMemberFlags; diff --git a/typings/index.d.ts b/typings/index.d.ts index cd5126a..4a65dca 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1573,6 +1573,7 @@ export class GuildMember extends PartialTextBasedChannel(Base) { public readonly displayColor: number; public readonly displayHexColor: HexColorString; public readonly displayName: string; + public flags: Readonly; public guild: Guild; public readonly id: Snowflake; public pending: boolean; @@ -1610,6 +1611,7 @@ export class GuildMember extends PartialTextBasedChannel(Base) { public kick(reason?: string): Promise; public permissionsIn(channel: GuildChannelResolvable): Readonly; public setNickname(nickname: string | null, reason?: string): Promise; + public setFlags(flags: GuildMemberFlagsResolvable): Promise; public setAvatar(avatar: BufferResolvable | Base64Resolvable | null): Promise; public setBanner(banner: BufferResolvable | Base64Resolvable | null): Promise; public setAboutMe(bio: string | null): Promise; @@ -1620,6 +1622,11 @@ export class GuildMember extends PartialTextBasedChannel(Base) { public setThemeColors(primary?: ColorResolvable, accent?: ColorResolvable): GuildMember; } +export class GuildMemberFlags extends BitField { + public static FLAGS: Record; + public static resolve(bit?: BitFieldResolvable): number; +} + export class GuildPreview extends Base { private constructor(client: Client, data: RawGuildPreviewData); public approximateMemberCount: number; @@ -6215,11 +6222,20 @@ export interface GuildMemberEditData { deaf?: boolean; channel?: GuildVoiceChannelResolvable | null; communicationDisabledUntil?: DateResolvable | null; + flags?: GuildMemberFlagsResolvable; avatar?: BufferResolvable | Base64Resolvable | null; banner?: BufferResolvable | Base64Resolvable | null; bio?: string | null; } +export type GuildMemberFlagsString = + | 'DID_REJOIN' + | 'COMPLETED_ONBOARDING' + | 'BYPASSES_VERIFICATION' + | 'STARTED_ONBOARDING'; + +export type GuildMemberFlagsResolvable = BitFieldResolvable; + export type GuildMemberResolvable = GuildMember | UserResolvable; export type GuildResolvable = Guild | NonThreadGuildBasedChannel | GuildMember | GuildEmoji | Invite | Role | Snowflake;