From 76b21162aca7cd4897826437da3063524e1e7553 Mon Sep 17 00:00:00 2001 From: Almeida Date: Fri, 17 Feb 2023 21:09:49 +0000 Subject: [PATCH] feat(GuildMember): add `flags` (#9087) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- packages/discord.js/src/index.js | 1 + .../src/managers/GuildMemberManager.js | 6 +++ .../discord.js/src/structures/GuildMember.js | 22 ++++++++++ packages/discord.js/src/util/APITypes.js | 5 +++ .../src/util/GuildMemberFlagsBitField.js | 41 +++++++++++++++++++ packages/discord.js/typings/index.d.ts | 13 ++++++ packages/discord.js/typings/index.test-d.ts | 3 ++ 7 files changed, 91 insertions(+) create mode 100644 packages/discord.js/src/util/GuildMemberFlagsBitField.js diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index fd7372f5d..d5a585f67 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -28,6 +28,7 @@ exports.Colors = require('./util/Colors'); exports.DataResolver = require('./util/DataResolver'); exports.Events = require('./util/Events'); exports.Formatters = require('./util/Formatters'); +exports.GuildMemberFlagsBitField = require('./util/GuildMemberFlagsBitField').GuildMemberFlagsBitField; exports.IntentsBitField = require('./util/IntentsBitField'); exports.LimitedCollection = require('./util/LimitedCollection'); exports.MessageFlagsBitField = require('./util/MessageFlagsBitField'); diff --git a/packages/discord.js/src/managers/GuildMemberManager.js b/packages/discord.js/src/managers/GuildMemberManager.js index 704cd56b6..d99ce2a8d 100644 --- a/packages/discord.js/src/managers/GuildMemberManager.js +++ b/packages/discord.js/src/managers/GuildMemberManager.js @@ -11,6 +11,7 @@ const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel'); const { GuildMember } = require('../structures/GuildMember'); const { Role } = require('../structures/Role'); const Events = require('../util/Events'); +const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField'); const Partials = require('../util/Partials'); /** @@ -279,6 +280,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 {string} [reason] Reason for editing this user */ @@ -314,6 +316,10 @@ class GuildMemberManager extends CachedManager { : options.communicationDisabledUntil; } + if (typeof options.flags !== 'undefined') { + options.flags = GuildMemberFlagsBitField.resolve(options.flags); + } + let endpoint; if (id === this.client.user.id) { const keys = Object.keys(options); diff --git a/packages/discord.js/src/structures/GuildMember.js b/packages/discord.js/src/structures/GuildMember.js index e4f953a97..f6d10d1f3 100644 --- a/packages/discord.js/src/structures/GuildMember.js +++ b/packages/discord.js/src/structures/GuildMember.js @@ -6,6 +6,7 @@ const VoiceState = require('./VoiceState'); const TextBasedChannel = require('./interfaces/TextBasedChannel'); const { DiscordjsError, ErrorCodes } = require('../errors'); const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager'); +const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField'); const PermissionsBitField = require('../util/PermissionsBitField'); /** @@ -93,6 +94,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 GuildMemberFlagsBitField(data.flags).freeze(); + } else { + this.flags ??= new GuildMemberFlagsBitField().freeze(); + } } _clone() { @@ -314,6 +325,16 @@ class GuildMember extends Base { return this.guild.members.edit(this, options); } + /** + * 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 nickname for this member. * @param {?string} nick The nickname for the guild member, or `null` if you want to reset their nickname @@ -423,6 +444,7 @@ class GuildMember extends Base { this.avatar === member.avatar && this.pending === member.pending && this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp && + this.flags.bitfield === member.flags.bitfield && (this._roles === member._roles || (this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i]))) ); diff --git a/packages/discord.js/src/util/APITypes.js b/packages/discord.js/src/util/APITypes.js index 7189eeff0..64ca186a8 100644 --- a/packages/discord.js/src/util/APITypes.js +++ b/packages/discord.js/src/util/APITypes.js @@ -273,6 +273,11 @@ * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildMFALevel} */ +/** + * @external GuildMemberFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildMemberFlags} + */ + /** * @external GuildNSFWLevel * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildNSFWLevel} diff --git a/packages/discord.js/src/util/GuildMemberFlagsBitField.js b/packages/discord.js/src/util/GuildMemberFlagsBitField.js new file mode 100644 index 000000000..84ce6b903 --- /dev/null +++ b/packages/discord.js/src/util/GuildMemberFlagsBitField.js @@ -0,0 +1,41 @@ +'use strict'; + +const { GuildMemberFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link GuildMember#flags} bitfield. + * @extends {BitField} + */ +class GuildMemberFlagsBitField extends BitField { + /** + * Numeric guild guild member flags. + * @type {GuildMemberFlags} + * @memberof GuildMemberFlagsBitField + */ + static Flags = GuildMemberFlags; +} + +/** + * @name GuildMemberFlagsBitField + * @kind constructor + * @memberof GuildMemberFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Bitfield of the packed bits + * @type {number} + * @name GuildMemberFlagsBitField#bitfield + */ + +/** + * Data that can be resolved to give a guild member flag bitfield. This can be: + * * A string (see {@link GuildMemberFlagsBitField.Flags}) + * * A guild member flag + * * An instance of GuildMemberFlagsBitField + * * An Array of GuildMemberFlagsResolvable + * @typedef {string|number|GuildMemberFlagsBitField|GuildMemberFlagsResolvable[]} GuildMemberFlagsResolvable + */ + +exports.GuildMemberFlagsBitField = GuildMemberFlagsBitField; diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 1c36f7f0c..6ddad6d32 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -154,6 +154,7 @@ import { ApplicationRoleConnectionMetadataType, APIApplicationRoleConnectionMetadata, ImageFormat, + GuildMemberFlags, } from 'discord-api-types/v10'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -1474,6 +1475,15 @@ export class GuildEmoji extends BaseGuildEmoji { public setName(name: string, reason?: string): Promise; } +export type GuildMemberFlagsString = keyof typeof GuildMemberFlags; + +export type GuildMemberFlagsResolvable = BitFieldResolvable; + +export class GuildMemberFlagsBitField extends BitField { + public static Flags: GuildMemberFlags; + public static resolve(bit?: BitFieldResolvable): number; +} + export class GuildMember extends PartialTextBasedChannel(Base) { private constructor(client: Client, data: RawGuildMemberData, guild: Guild); public avatar: string | null; @@ -1487,6 +1497,7 @@ export class GuildMember extends PartialTextBasedChannel(Base) { public pending: boolean; public get communicationDisabledUntil(): Date | null; public communicationDisabledUntilTimestamp: number | null; + public flags: Readonly; public get joinedAt(): Date | null; public joinedTimestamp: number | null; public get kickable(): boolean; @@ -1516,6 +1527,7 @@ export class GuildMember extends PartialTextBasedChannel(Base) { }; public kick(reason?: string): Promise; public permissionsIn(channel: GuildChannelResolvable): Readonly; + public setFlags(flags: GuildMemberFlagsResolvable, reason?: string): Promise; public setNickname(nickname: string | null, reason?: string): Promise; public toJSON(): unknown; public toString(): UserMention; @@ -5482,6 +5494,7 @@ export interface GuildMemberEditOptions { deaf?: boolean; channel?: GuildVoiceChannelResolvable | null; communicationDisabledUntil?: DateResolvable | null; + flags?: GuildMemberFlagsResolvable; reason?: string; } diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index b5a4b4652..0432fa6d8 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -158,6 +158,7 @@ import { AutoModerationRuleManager, PrivateThreadChannel, PublicThreadChannel, + GuildMemberFlagsBitField, } from '.'; import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders'; @@ -2143,3 +2144,5 @@ client.on('guildAuditLogEntryCreate', (auditLogEntry, guild) => { expectType(auditLogEntry); expectType(guild); }); + +expectType>(guildMember.flags);