diff --git a/src/managers/GuildMemberManager.js b/src/managers/GuildMemberManager.js
index f4ac65ac9..2c80c99f3 100644
--- a/src/managers/GuildMemberManager.js
+++ b/src/managers/GuildMemberManager.js
@@ -3,6 +3,7 @@
const BaseManager = require('./BaseManager');
const { Error, TypeError, RangeError } = require('../errors');
const GuildMember = require('../structures/GuildMember');
+const Role = require('../structures/Role');
const Collection = require('../util/Collection');
const { Events, OPCodes } = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil');
@@ -149,6 +150,47 @@ class GuildMemberManager extends BaseManager {
return data.reduce((col, member) => col.set(member.user.id, this.add(member, cache)), new Collection());
}
+ /**
+ * Edits a member of the guild.
+ * The user must be a member of the guild
+ * @param {UserResolvable} user The member to edit
+ * @param {GuildMemberEditData} data The data to edit the member with
+ * @param {string} [reason] Reason for editing this user
+ * @returns {Promise}
+ */
+ async edit(user, data, reason) {
+ const id = this.client.users.resolveID(user);
+ if (!id) throw new TypeError('INVALID_TYPE', 'user', 'UserResolvable');
+
+ // Clone the data object for immutability
+ const _data = { ...data };
+ if (_data.channel) {
+ _data.channel = this.guild.channels.resolve(_data.channel);
+ if (!_data.channel || _data.channel.type !== 'voice') {
+ throw new Error('GUILD_VOICE_CHANNEL_RESOLVE');
+ }
+ _data.channel_id = _data.channel.id;
+ _data.channel = undefined;
+ } else if (_data.channel === null) {
+ _data.channel_id = null;
+ _data.channel = undefined;
+ }
+ if (_data.roles) _data.roles = _data.roles.map(role => (role instanceof Role ? role.id : role));
+ let endpoint = this.client.api.guilds(this.guild.id);
+ if (id === this.client.user.id) {
+ const keys = Object.keys(_data);
+ if (keys.length === 1 && keys[0] === 'nick') endpoint = endpoint.members('@me').nick;
+ else endpoint = endpoint.members(id);
+ } else {
+ endpoint = endpoint.members(id);
+ }
+ const d = await endpoint.patch({ data: _data, reason });
+
+ const clone = this.cache.get(id)?._clone();
+ clone?.patch(d);
+ return clone ?? this.add(d, false);
+ }
+
/**
* Prunes members from the guild based on how long they have been inactive.
* It's recommended to set options.count to `false` for large guilds.
@@ -207,6 +249,29 @@ class GuildMemberManager extends BaseManager {
.then(data => data.pruned);
}
+ /**
+ * Kicks a user from the guild.
+ * The user must be a member of the guild
+ * @param {UserResolvable} user The member to kick
+ * @param {string} [reason] Reason for kicking
+ * @returns {Promise} Result object will be resolved as specifically as possible.
+ * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
+ * be resolved, the user ID will be the result.
+ * @example
+ * // Kick a user by ID (or with a user/guild member object)
+ * guild.members.kick('84484653687267328')
+ * .then(user => console.log(`Kicked ${user.username || user.id || user} from ${guild.name}`))
+ * .catch(console.error);
+ */
+ async kick(user, reason) {
+ const id = this.client.users.resolveID(user);
+ if (!id) return Promise.reject(new TypeError('INVALID_TYPE', 'user', 'UserResolvable'));
+
+ await this.client.api.guilds(this.guild.id).members(id).delete({ reason });
+
+ return this.resolve(user) ?? this.client.users.resolve(user) ?? id;
+ }
+
/**
* Bans a user from the guild.
* @param {UserResolvable} user The user to ban
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index f00af7f01..53c33e387 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -1,7 +1,6 @@
'use strict';
const Base = require('./Base');
-const Role = require('./Role');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { Error } = require('../errors');
const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager');
@@ -283,34 +282,8 @@ class GuildMember extends Base {
* @param {string} [reason] Reason for editing this user
* @returns {Promise}
*/
- async edit(data, reason) {
- if (data.channel) {
- const voiceChannelID = this.guild.channels.resolveID(data.channel);
- const voiceChannel = this.guild.channels.cache.get(voiceChannelID);
- if (!voiceChannelID || (voiceChannel && voiceChannel?.type !== 'voice')) {
- throw new Error('GUILD_VOICE_CHANNEL_RESOLVE');
- }
- data.channel_id = voiceChannelID;
- data.channel = undefined;
- } else if (data.channel === null) {
- data.channel_id = null;
- data.channel = undefined;
- }
- if (data.roles) data.roles = data.roles.map(role => (role instanceof Role ? role.id : role));
- let endpoint = this.client.api.guilds(this.guild.id);
- if (this.user.id === this.client.user.id) {
- const keys = Object.keys(data);
- if (keys.length === 1 && keys[0] === 'nick') endpoint = endpoint.members('@me').nick;
- else endpoint = endpoint.members(this.id);
- } else {
- endpoint = endpoint.members(this.id);
- }
- await endpoint.patch({ data, reason });
-
- const clone = this._clone();
- data.user = this.user;
- clone._patch(data);
- return clone;
+ edit(data, reason) {
+ return this.guild.members.edit(this, data, reason);
}
/**
@@ -345,11 +318,7 @@ class GuildMember extends Base {
* @returns {Promise}
*/
kick(reason) {
- return this.client.api
- .guilds(this.guild.id)
- .members(this.user.id)
- .delete({ reason })
- .then(() => this);
+ return this.guild.members.kick(this, reason);
}
/**
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 3f56237bc..619e39d69 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -2145,10 +2145,12 @@ declare module 'discord.js' {
constructor(guild: Guild, iterable?: Iterable);
public guild: Guild;
public ban(user: UserResolvable, options?: BanOptions): Promise;
+ public edit(user: UserResolvable, data: GuildMemberEditData, reason?: string): Promise;
public fetch(
options: UserResolvable | FetchMemberOptions | (FetchMembersOptions & { user: UserResolvable }),
): Promise;
public fetch(options?: FetchMembersOptions): Promise>;
+ public kick(user: UserResolvable, reason?: string): Promise;
public prune(options: GuildPruneMembersOptions & { dry?: false; count: false }): Promise;
public prune(options?: GuildPruneMembersOptions): Promise;
public search(options: GuildSearchMembersOptions): Promise>;