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.'); }