diff --git a/src/client/Client.js b/src/client/Client.js
index 4a68f7735..687cc06a6 100644
--- a/src/client/Client.js
+++ b/src/client/Client.js
@@ -287,11 +287,12 @@ class Client extends EventEmitter {
* Caches a user, or obtains it from the cache if it's already cached.
* This is only available when using a bot account.
* @param {string} id The ID of the user to obtain
+ * @param {boolean} [cache=true] Insert the user into the users cache
* @returns {Promise}
*/
- fetchUser(id) {
+ fetchUser(id, cache = true) {
if (this.users.has(id)) return Promise.resolve(this.users.get(id));
- return this.rest.methods.getUser(id);
+ return this.rest.methods.getUser(id, cache);
}
/**
diff --git a/src/client/actions/GuildDelete.js b/src/client/actions/GuildDelete.js
index 56896b63b..de34437d4 100644
--- a/src/client/actions/GuildDelete.js
+++ b/src/client/actions/GuildDelete.js
@@ -12,7 +12,9 @@ class GuildDeleteAction extends Action {
let guild = client.guilds.get(data.id);
if (guild) {
- for (const channel of guild.channels.values()) channel.stopTyping(true);
+ for (const channel of guild.channels.values()) {
+ if (channel.type === 'text') channel.stopTyping(true);
+ }
if (guild.available && data.unavailable) {
// guild is unavailable
diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js
index 414959582..b5a22a10e 100644
--- a/src/client/rest/RESTMethods.js
+++ b/src/client/rest/RESTMethods.js
@@ -5,6 +5,7 @@ const splitMessage = require('../../util/SplitMessage');
const parseEmoji = require('../../util/ParseEmoji');
const escapeMarkdown = require('../../util/EscapeMarkdown');
const transformSearchOptions = require('../../util/TransformSearchOptions');
+const Snowflake = require('../../util/Snowflake');
const User = require('../../structures/User');
const GuildMember = require('../../structures/GuildMember');
@@ -136,7 +137,12 @@ class RESTMethods {
);
}
- bulkDeleteMessages(channel, messages) {
+ bulkDeleteMessages(channel, messages, filterOld) {
+ if (filterOld) {
+ messages = messages.filter(id =>
+ Date.now() - Snowflake.deconstruct(id).date.getTime() < 1209600000
+ );
+ }
return this.rest.makeRequest('post', `${Constants.Endpoints.channelMessages(channel.id)}/bulk_delete`, true, {
messages,
}).then(() =>
@@ -258,10 +264,14 @@ class RESTMethods {
);
}
- getUser(userID) {
- return this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then(data =>
- this.client.actions.UserGet.handle(data).user
- );
+ getUser(userID, cache) {
+ return this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then(data => {
+ if (cache) {
+ return this.client.actions.UserGet.handle(data).user;
+ } else {
+ return new User(this.client, data);
+ }
+ });
}
updateCurrentUser(_data, password) {
@@ -351,10 +361,14 @@ class RESTMethods {
return this.rest.makeRequest('get', Constants.Endpoints.channelMessage(channel.id, messageID), true);
}
- getGuildMember(guild, user) {
- return this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then(data =>
- this.client.actions.GuildMemberGet.handle(guild, data).member
- );
+ getGuildMember(guild, user, cache) {
+ return this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then(data => {
+ if (cache) {
+ return this.client.actions.GuildMemberGet.handle(guild, data).member;
+ } else {
+ return new GuildMember(guild, data);
+ }
+ });
}
updateGuildMember(member, data) {
@@ -701,6 +715,25 @@ class RESTMethods {
setNote(user, note) {
return this.rest.makeRequest('put', Constants.Endpoints.note(user.id), true, { note }).then(() => user);
}
+
+ acceptInvite(code) {
+ if (code.id) code = code.id;
+ return new Promise((resolve, reject) =>
+ this.rest.makeRequest('post', Constants.Endpoints.invite(code), true).then((res) => {
+ const handler = guild => {
+ if (guild.id === res.id) {
+ resolve(guild);
+ this.client.removeListener('guildCreate', handler);
+ }
+ };
+ this.client.on('guildCreate', handler);
+ this.client.setTimeout(() => {
+ this.client.removeListener('guildCreate', handler);
+ reject(new Error('Accepting invite timed out'));
+ }, 120e3);
+ })
+ );
+ }
}
module.exports = RESTMethods;
diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js
index ea8374540..80725de01 100644
--- a/src/client/voice/ClientVoiceManager.js
+++ b/src/client/voice/ClientVoiceManager.js
@@ -79,7 +79,13 @@ class ClientVoiceManager {
joinChannel(channel) {
return new Promise((resolve, reject) => {
if (this.pending.get(channel.guild.id)) throw new Error('Already connecting to this guild\'s voice server.');
- if (!channel.joinable) throw new Error('You do not have permission to join this voice channel.');
+ if (!channel.joinable) {
+ if (channel.full) {
+ throw new Error('You do not have permission to join this voice channel; it is full.');
+ } else {
+ throw new Error('You do not have permission to join this voice channel.');
+ }
+ }
const existingConnection = this.connections.get(channel.guild.id);
if (existingConnection) {
diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js
index 5ff0057df..43eddb7c7 100644
--- a/src/structures/ClientUser.js
+++ b/src/structures/ClientUser.js
@@ -297,6 +297,14 @@ class ClientUser extends User {
);
}
}
+
+ /**
+ * @param {Invite|string} invite Invite or code to accept
+ * @returns {Promise} Joined guild
+ */
+ acceptInvite(invite) {
+ return this.client.rest.methods.acceptInvite(invite);
+ }
}
module.exports = ClientUser;
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index c9de61847..fef675a0a 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -325,14 +325,15 @@ class Guild {
/**
* Fetch a single guild member from a user.
* @param {UserResolvable} user The user to fetch the member for
+ * @param {boolean} [cache=true] Insert the user into the users cache
* @returns {Promise}
*/
- fetchMember(user) {
+ fetchMember(user, cache = true) {
if (this._fetchWaiter) return Promise.reject(new Error('Already fetching guild members.'));
user = this.client.resolver.resolveUser(user);
if (!user) return Promise.reject(new Error('User is not cached. Use Client.fetchUser first.'));
if (this.members.has(user.id)) return Promise.resolve(this.members.get(user.id));
- return this.client.rest.methods.getGuildMember(this, user);
+ return this.client.rest.methods.getGuildMember(this, user, cache);
}
/**
@@ -343,7 +344,7 @@ class Guild {
*/
fetchMembers(query = '') {
return new Promise((resolve, reject) => {
- if (this._fetchWaiter) throw new Error('Already fetching guild members in ${this.id}.');
+ if (this._fetchWaiter) throw new Error(`Already fetching guild members in ${this.id}.`);
if (this.memberCount === this.members.size) {
resolve(this);
return;
diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js
index ad18a94b0..e6d5b637f 100644
--- a/src/structures/VoiceChannel.js
+++ b/src/structures/VoiceChannel.js
@@ -45,13 +45,23 @@ class VoiceChannel extends GuildChannel {
return null;
}
+ /**
+ * Checks if the voice channel is full
+ * @type {boolean}
+ */
+ get full() {
+ return this.members.size >= this.userLimit;
+ }
+
/**
* Checks if the client has permission join the voice channel
* @type {boolean}
*/
get joinable() {
if (this.client.browser) return false;
- return this.permissionsFor(this.client.user).hasPermission('CONNECT');
+ if (!this.permissionsFor(this.client.user).hasPermission('CONNECT')) return false;
+ if (this.full && !this.permissionsFor(this.client.user).hasPermission('MOVE_MEMBERS')) return false;
+ return true;
}
/**
diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js
index be1844d9d..ee21764e6 100644
--- a/src/structures/interface/TextBasedChannel.js
+++ b/src/structures/interface/TextBasedChannel.js
@@ -353,16 +353,17 @@ class TextBasedChannel {
}
/**
- * Bulk delete given messages.
+ * Bulk delete given messages that are newer than two weeks
* This is only available when using a bot account.
* @param {Collection|Message[]|number} messages Messages to delete, or number of messages to delete
+ * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
* @returns {Promise>} Deleted messages
*/
- bulkDelete(messages) {
+ bulkDelete(messages, filterOld = false) {
if (!isNaN(messages)) return this.fetchMessages({ limit: messages }).then(msgs => this.bulkDelete(msgs));
if (messages instanceof Array || messages instanceof Collection) {
const messageIDs = messages instanceof Collection ? messages.keyArray() : messages.map(m => m.id);
- return this.client.rest.methods.bulkDeleteMessages(this, messageIDs);
+ return this.client.rest.methods.bulkDeleteMessages(this, messageIDs, filterOld);
}
throw new TypeError('The messages must be an Array, Collection, or number.');
}