diff --git a/packages/discord.js/src/managers/GuildMemberRoleManager.js b/packages/discord.js/src/managers/GuildMemberRoleManager.js index e5302683d0f9..1c86d464d74a 100644 --- a/packages/discord.js/src/managers/GuildMemberRoleManager.js +++ b/packages/discord.js/src/managers/GuildMemberRoleManager.js @@ -107,17 +107,7 @@ class GuildMemberRoleManager extends DataManager { */ async add(roleOrRoles, reason) { if (roleOrRoles instanceof Collection || Array.isArray(roleOrRoles)) { - const resolvedRoles = []; - for (const role of roleOrRoles.values()) { - const resolvedRole = this.guild.roles.resolveId(role); - if (!resolvedRole) { - throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role); - } - resolvedRoles.push(resolvedRole); - } - - const newRoles = [...new Set(resolvedRoles.concat(...this.cache.keys()))]; - return this.set(newRoles, reason); + return this.modify({ rolesToAdd: roleOrRoles, reason }); } else { roleOrRoles = this.guild.roles.resolveId(roleOrRoles); if (roleOrRoles === null) { @@ -144,17 +134,7 @@ class GuildMemberRoleManager extends DataManager { */ async remove(roleOrRoles, reason) { if (roleOrRoles instanceof Collection || Array.isArray(roleOrRoles)) { - const resolvedRoles = []; - for (const role of roleOrRoles.values()) { - const resolvedRole = this.guild.roles.resolveId(role); - if (!resolvedRole) { - throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role); - } - resolvedRoles.push(resolvedRole); - } - - const newRoles = this.cache.filter(role => !resolvedRoles.includes(role.id)); - return this.set(newRoles, reason); + return this.modify({ rolesToRemove: roleOrRoles, reason }); } else { roleOrRoles = this.guild.roles.resolveId(roleOrRoles); if (roleOrRoles === null) { @@ -174,6 +154,53 @@ class GuildMemberRoleManager extends DataManager { } } + /** + * @typedef {Object} ModifyGuildMemberRolesOptions + * @property {Readonly | ReadonlyCollection} [rolesToAdd] The roles to add + * @property {Readonly | ReadonlyCollection} [rolesToRemove] The roles to remove + * @property {Readonly | ReadonlyCollection} [reason] Reason for modifying + * the roles + */ + + /** + * Modifies the roles of the member. + * @param {ModifyGuildMemberRolesOptions} [options] Options for modifying the roles + * @returns {GuildMember} + */ + modify(options) { + const resolvedRolesToAdd = this.resolveRoles(options.rolesToAdd); + const resolvedRolesToRemove = this.resolveRoles(options.rolesToRemove); + + const currentRoles = new Set(this.member.roles.cache.keys()); + for (const role of resolvedRolesToAdd) { + currentRoles.add(role.id); + } + for (const role of resolvedRolesToRemove) { + currentRoles.delete(role.id); + } + + return this.member.roles.set([...currentRoles], options.reason); + } + + /** + * Resolves roles from the input. + * @param {Readonly | ReadonlyCollection} rolesToResolve The roles to resolve + * @returns {Role[]} The resolved roles + * @private + */ + resolveRoles(rolesToResolve) { + if (!rolesToResolve) return []; + const resolvedRoles = []; + for (const role of rolesToResolve.values()) { + const resolvedRole = this.guild.roles.resolveId(role); + if (!resolvedRole) { + throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role); + } + resolvedRoles.push(resolvedRole); + } + return resolvedRoles; + } + /** * Sets the roles applied to the member. * @param {Collection|RoleResolvable[]} roles The roles or role ids to apply diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index ae236f758fb6..e231a7a00fc1 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -4393,6 +4393,12 @@ export class GuildStickerManager extends CachedManager; } +export interface ModifyGuildMemberRolesOptions { + rolesToAdd?: readonly RoleResolvable[] | ReadonlyCollection; + rolesToRemove?: readonly RoleResolvable[] | ReadonlyCollection; + reason?: string; +} + export class GuildMemberRoleManager extends DataManager { private constructor(member: GuildMember); public get hoist(): Role | null; @@ -4408,6 +4414,7 @@ export class GuildMemberRoleManager extends DataManager, reason?: string, ): Promise; + public modify(options: ModifyGuildMemberRolesOptions): GuildMember; public set( roles: readonly RoleResolvable[] | ReadonlyCollection, reason?: string, @@ -4416,6 +4423,7 @@ export class GuildMemberRoleManager extends DataManager, reason?: string, ): Promise; + private resolveRoles(rolesToResolve: readonly RoleResolvable[] | ReadonlyCollection): Role[]; } export interface FetchPollAnswerVotersOptions extends BaseFetchPollAnswerVotersOptions {