Skip to content

Commit

Permalink
feat(GuildMemberManager): extend API coverage (#4872)
Browse files Browse the repository at this point in the history
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
  • Loading branch information
3 people committed May 11, 2021
1 parent 668cd47 commit 2e2464b
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 34 deletions.
65 changes: 65 additions & 0 deletions src/managers/GuildMemberManager.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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.
* <info>The user must be a member of the guild</info>
* @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<GuildMember>}
*/
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.
* <info>It's recommended to set options.count to `false` for large guilds.</info>
Expand Down Expand Up @@ -207,6 +249,29 @@ class GuildMemberManager extends BaseManager {
.then(data => data.pruned);
}

/**
* Kicks a user from the guild.
* <info>The user must be a member of the guild</info>
* @param {UserResolvable} user The member to kick
* @param {string} [reason] Reason for kicking
* @returns {Promise<GuildMember|User|Snowflake>} 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
Expand Down
37 changes: 3 additions & 34 deletions 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');
Expand Down Expand Up @@ -283,34 +282,8 @@ class GuildMember extends Base {
* @param {string} [reason] Reason for editing this user
* @returns {Promise<GuildMember>}
*/
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);
}

/**
Expand Down Expand Up @@ -345,11 +318,7 @@ class GuildMember extends Base {
* @returns {Promise<GuildMember>}
*/
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);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions typings/index.d.ts
Expand Up @@ -2145,10 +2145,12 @@ declare module 'discord.js' {
constructor(guild: Guild, iterable?: Iterable<any>);
public guild: Guild;
public ban(user: UserResolvable, options?: BanOptions): Promise<GuildMember | User | Snowflake>;
public edit(user: UserResolvable, data: GuildMemberEditData, reason?: string): Promise<void>;
public fetch(
options: UserResolvable | FetchMemberOptions | (FetchMembersOptions & { user: UserResolvable }),
): Promise<GuildMember>;
public fetch(options?: FetchMembersOptions): Promise<Collection<Snowflake, GuildMember>>;
public kick(user: UserResolvable, reason?: string): Promise<GuildMember | User | Snowflake>;
public prune(options: GuildPruneMembersOptions & { dry?: false; count: false }): Promise<null>;
public prune(options?: GuildPruneMembersOptions): Promise<number>;
public search(options: GuildSearchMembersOptions): Promise<Collection<Snowflake, GuildMember>>;
Expand Down

0 comments on commit 2e2464b

Please sign in to comment.