Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(GuildMemberManager): extend API coverage #4872

Merged
merged 19 commits into from May 11, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
91 changes: 73 additions & 18 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} member The member to edit
monbrey marked this conversation as resolved.
Show resolved Hide resolved
* @param {GuildMemberEditData} data The data to edit the member with
* @param {string} [reason] Reason for editing this user
* @returns {Promise<GuildMember>}
*/
async edit(member, data, reason) {
monbrey marked this conversation as resolved.
Show resolved Hide resolved
const id = this.client.users.resolveID(member);
monbrey marked this conversation as resolved.
Show resolved Hide resolved
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 All @@ -222,23 +287,15 @@ class GuildMemberManager extends BaseManager {
* .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`))
* .catch(console.error);
*/
ban(user, options = { days: 0 }) {
async ban(user, options = { days: 0 }) {
if (typeof options !== 'object') return Promise.reject(new TypeError('INVALID_TYPE', 'options', 'object', true));
if (options.days) options.delete_message_days = options.days;
const id = this.client.users.resolveID(user);
if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID', true));
return this.client.api
.guilds(this.guild.id)
.bans[id].put({ data: options })
.then(() => {
if (user instanceof GuildMember) return user;
const _user = this.client.users.resolve(id);
if (_user) {
const member = this.resolve(_user);
return member || _user;
}
return id;
});

await this.client.api.guilds(this.guild.id).bans[id].put({ data: options });

return this.resolve(user) ?? this.client.users.resolve(user) ?? id;
}

/**
Expand All @@ -252,13 +309,11 @@ class GuildMemberManager extends BaseManager {
* .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
* .catch(console.error);
*/
unban(user, reason) {
async unban(user, reason) {
const id = this.client.users.resolveID(user);
if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID'));
return this.client.api
.guilds(this.guild.id)
.bans[id].delete({ reason })
.then(() => this.client.users.resolve(user));
await this.client.api.guilds(this.guild.id).bans[id].delete({ reason });
return this.client.users.resolve(user);
}

_fetchSingle({ user, cache, force = false }) {
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 @@ -2011,10 +2011,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