diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 079ef5d8c..efc42df4a 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -39,6 +39,7 @@ class WebSocketPacketManager { this.register(Constants.WSEvents.USER_UPDATE, require('./handlers/UserUpdate')); this.register(Constants.WSEvents.USER_NOTE_UPDATE, require('./handlers/UserNoteUpdate')); this.register(Constants.WSEvents.USER_SETTINGS_UPDATE, require('./handlers/UserSettingsUpdate')); + this.register(Constants.WSEvents.USER_GUILD_SETTINGS_UPDATE, require('./handlers/UserGuildSettingsUpdate')); this.register(Constants.WSEvents.VOICE_STATE_UPDATE, require('./handlers/VoiceStateUpdate')); this.register(Constants.WSEvents.TYPING_START, require('./handlers/TypingStart')); this.register(Constants.WSEvents.MESSAGE_CREATE, require('./handlers/MessageCreate')); diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js index d7ee8ed11..8c2492abf 100644 --- a/src/client/websocket/packets/handlers/Ready.js +++ b/src/client/websocket/packets/handlers/Ready.js @@ -10,6 +10,7 @@ class ReadyHandler extends AbstractHandler { client.ws.heartbeat(); data.user.user_settings = data.user_settings; + data.user.user_guild_settings = data.user_guild_settings; const clientUser = new ClientUser(client, data.user); client.user = clientUser; diff --git a/src/client/websocket/packets/handlers/UserGuildSettingsUpdate.js b/src/client/websocket/packets/handlers/UserGuildSettingsUpdate.js new file mode 100644 index 000000000..db38a724b --- /dev/null +++ b/src/client/websocket/packets/handlers/UserGuildSettingsUpdate.js @@ -0,0 +1,18 @@ +const AbstractHandler = require('./AbstractHandler'); +const Constants = require('../../../../util/Constants'); + +class UserGuildSettingsUpdateHandler extends AbstractHandler { + handle(packet) { + const client = this.packetManager.client; + client.user.guildSettings.get(packet.d.guild_id).patch(packet.d); + client.emit(Constants.Events.USER_GUILD_SETTINGS_UPDATE, client.user.guildSettings.get(packet.d.guild_id)); + } +} + +/** + * Emitted whenever the client user's settings update. + * @event Client#clientUserGuildSettingsUpdate + * @param {ClientUserGuildSettings} clientUserGuildSettings The new client user guild settings + */ + +module.exports = UserGuildSettingsUpdateHandler; diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index e0a2f3ba2..476a6f5b0 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -1,6 +1,7 @@ const User = require('./User'); const Collection = require('../util/Collection'); const ClientUserSettings = require('./ClientUserSettings'); +const ClientUserGuildSettings = require('./ClientUserGuildSettings'); const Constants = require('../util/Constants'); const Util = require('../util/Util'); const Guild = require('./Guild'); @@ -78,6 +79,19 @@ class ClientUser extends User { * @type {?ClientUserSettings} */ if (data.user_settings) this.settings = new ClientUserSettings(this, data.user_settings); + + /** + * All of the user's guild settings + * @type {Collection} + * This is only filled when using a user account + */ + this.guildSettings = new Collection(); + if (data.user_guild_settings) { + for (const settings of data.user_guild_settings) { + const guild = this.client.guilds.get(settings.guild_id); + this.guildSettings.set(guild.id, new ClientUserGuildSettings(settings, guild)); + } + } } edit(data, password) { diff --git a/src/structures/ClientUserChannelOverride.js b/src/structures/ClientUserChannelOverride.js new file mode 100644 index 000000000..3ea594874 --- /dev/null +++ b/src/structures/ClientUserChannelOverride.js @@ -0,0 +1,29 @@ +const Constants = require('../util/Constants'); + +/** + * A wrapper around the ClientUser's channel overrides. + */ +class ClientUserChannelOverride { + constructor(user, data) { + this.user = user; + this.patch(data); + } + + /** + * Patch the data contained in this class with new partial data. + * @param {Object} data Data to patch this with + */ + patch(data) { + for (const key of Object.keys(Constants.UserChannelOverrideMap)) { + const value = Constants.UserChannelOverrideMap[key]; + if (!data.hasOwnProperty(key)) continue; + if (typeof value === 'function') { + this[value.name] = value(data[key]); + } else { + this[value] = data[key]; + } + } + } +} + +module.exports = ClientUserChannelOverride; diff --git a/src/structures/ClientUserGuildSettings.js b/src/structures/ClientUserGuildSettings.js new file mode 100644 index 000000000..960906379 --- /dev/null +++ b/src/structures/ClientUserGuildSettings.js @@ -0,0 +1,47 @@ +const Constants = require('../util/Constants'); +const Collection = require('../util/Collection'); +const ClientUserChannelOverride = require('./ClientUserChannelOverride'); + +/** + * A wrapper around the ClientUser's guild settings. + */ +class ClientUserGuildSettings { + constructor(data, guild) { + this.guild = guild; + this.channelOverrides = new Collection(); + this.patch(data); + } + + /** + * Patch the data contained in this class with new partial data. + * @param {Object} data Data to patch this with + */ + patch(data) { + for (const key of Object.keys(Constants.UserGuildSettingsMap)) { + const value = Constants.UserGuildSettingsMap[key]; + if (!data.hasOwnProperty(key)) continue; + if (key === 'channel_overrides') { + for (const channel of data[key]) { + this.channelOverrides.set(channel.channel_id, + new ClientUserChannelOverride(this.guild.client.user, channel)); + } + } else if (typeof value === 'function') { + this[value.name] = value(data[key]); + } else { + this[value] = data[key]; + } + } + } + + /** + * Update a specific property of the guild settings. + * @param {string} name Name of property + * @param {value} value Value to patch + * @returns {Promise} + */ + update(name, value) { + return this.guild.client.api.guilds(this.guild.id).settings.patch({ data: { [name]: value } }); + } +} + +module.exports = ClientUserGuildSettings; diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 1fe93e93c..db8795591 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -333,6 +333,62 @@ class Guild { } /** + * Whether the guild is muted + * This is only available when using a user account. + * @type {?boolean} + */ + get muted() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.id).muted; + } catch (err) { + return false; + } + } + + /** + * The type of message that should notify you + * one of `EVERYTHING`, `MENTIONS`, `NOTHING` + * This is only available when using a user account. + * @type {string} + */ + get messageNotifications() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.id).messageNotifications; + } catch (err) { + return null; + } + } + + /** + * Whether to receive mobile push notifications + * This is only available when using a user account. + * @type {boolean} + */ + get mobilePush() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.id).mobilePush; + } catch (err) { + return false; + } + } + + /** + * Whether to suppress everyone messages + * This is only available when using a user account. + * @type {boolean} + */ + get suppressEveryone() { + try { + return this.client.user.guildSettings.get(this.id).suppressEveryone; + } catch (err) { + return null; + } + } + + /* * The `@everyone` role of the guild * @type {Role} * @readonly diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 76dec642a..20b18376a 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -4,6 +4,7 @@ const Invite = require('./Invite'); const PermissionOverwrites = require('./PermissionOverwrites'); const Permissions = require('../util/Permissions'); const Collection = require('../util/Collection'); +const Constants = require('../util/Constants'); const { TypeError } = require('../errors'); /** @@ -363,6 +364,35 @@ class GuildChannel extends Channel { return this.client.api.channels(this.id).delete({ reason }).then(() => this); } + /** + * Whether the channel is muted + * This is only available when using a user account. + * @type {boolean} + */ + get muted() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.guild.id).channelOverrides.get(this.id).muted; + } catch (err) { + return false; + } + } + + /** + * The type of message that should notify you + * one of `EVERYTHING`, `MENTIONS`, `NOTHING`, `INHERIT` + * This is only available when using a user account. + * @type {string} + */ + get messageNotifications() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.guild.id).channelOverrides.get(this.id).messageNotifications; + } catch (err) { + return Constants.MessageNotificationTypes[3]; + } + } + /** * When concatenated with a string, this automatically returns the channel's mention instead of the Channel object. * @returns {string} diff --git a/src/util/Constants.js b/src/util/Constants.js index 7af384ba9..cbf6c6a73 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -231,6 +231,7 @@ exports.Events = { USER_UPDATE: 'userUpdate', USER_NOTE_UPDATE: 'userNoteUpdate', USER_SETTINGS_UPDATE: 'clientUserSettingsUpdate', + USER_GUILD_SETTINGS_UPDATE: 'clientUserGuildSettingsUpdate', PRESENCE_UPDATE: 'presenceUpdate', VOICE_STATE_UPDATE: 'voiceStateUpdate', TYPING_START: 'typingStart', @@ -312,6 +313,7 @@ exports.WSEvents = { USER_UPDATE: 'USER_UPDATE', USER_NOTE_UPDATE: 'USER_NOTE_UPDATE', USER_SETTINGS_UPDATE: 'USER_SETTINGS_UPDATE', + USER_GUILD_SETTINGS_UPDATE: 'USER_GUILD_SETTINGS_UPDATE', PRESENCE_UPDATE: 'PRESENCE_UPDATE', VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE', TYPING_START: 'TYPING_START', @@ -349,6 +351,13 @@ exports.ExplicitContentFilterTypes = [ 'FRIENDS_AND_NON_FRIENDS', ]; +exports.MessageNotificationTypes = [ + 'EVERYTHING', + 'MENTIONS', + 'NOTHING', + 'INHERIT', +]; + exports.UserSettingsMap = { /** * Automatically convert emoticons in your messages to emoji @@ -482,6 +491,60 @@ exports.UserSettingsMap = { }, }; +exports.UserGuildSettingsMap = { + message_notifications: function messageNotifications(type) { // eslint-disable-line func-name-matching + /** + * The type of message that should notify you + * one of `EVERYTHING`, `MENTIONS`, `NOTHING` + * @name ClientUserGuildSettings#messageNotifications + * @type {string} + */ + return exports.MessageNotificationTypes[type]; + }, + /** + * Whether to receive mobile push notifications + * @name ClientUserGuildSettings#mobilePush + * @type {boolean} + */ + mobile_push: 'mobilePush', + /** + * Whether the guild is muted or not + * @name ClientUserGuildSettings#muted + * @type {boolean} + */ + muted: 'muted', + /** + * Whether to suppress everyone messages + * @name ClientUserGuildSettings#suppressEveryone + * @type {boolean} + */ + suppress_everyone: 'suppressEveryone', + /** + * A collection containing all the channel overrides + * @name ClientUserGuildSettings#channelOverrides + * @type {Collection} + */ + channel_overrides: 'channelOverrides', +}; + +exports.UserChannelOverrideMap = { + message_notifications: function messageNotifications(type) { // eslint-disable-line func-name-matching + /** + * The type of message that should notify you + * one of `EVERYTHING`, `MENTIONS`, `NOTHING`, `INHERIT` + * @name ClientUserChannelOverrides#messageNotifications + * @type {string} + */ + return exports.MessageNotificationTypes[type]; + }, + /** + * Whether the guild is muted or not + * @name ClientUserChannelOverrides#muted + * @type {boolean} + */ + muted: 'muted', +}; + /** * All flags users can have: * - STAFF