diff --git a/language/languages/en_us/settings/en_us_moderation.lang b/language/languages/en_us/settings/en_us_moderation.lang index bfb16b7..d25096b 100644 --- a/language/languages/en_us/settings/en_us_moderation.lang +++ b/language/languages/en_us/settings/en_us_moderation.lang @@ -134,6 +134,8 @@ Successfully set voice joins and leaves to log to **#{changed}**. [S_MESSAGELOG_DESCRIPTION] Configure message logging for your server. +Message logging utilizes webhooks for bulk message deletions, so if you wish to log those, make sure the bot has the `MANAGE_WEBHOOKS` permission in the logging channel! +If you've given the permission retroactively, make sure to reconfigure the channel to ensure the bot creates the webhook. [S_MESSAGELOG_ROLES_LIST] The following roles are ignored by chatlogs: @@ -160,6 +162,11 @@ Successfully reset the chatlogs setting. [S_MESSAGELOG_TOGGLE] Successfully turned message logging **{changed}**. +[S_MESSAGELOG_TOGGLE_PERM] +Successfully turned message logging **{changed}**. + +**Note:** The bot lacks the `MANAGE_WEBHOOKS` permission and is as such unable to configure the webhook for message logging. + [S_MESSAGELOG_CHANNEL] Successfully set messages to log to **#{changed}**. diff --git a/structure/client/components/commands/moderation/Mute.js b/structure/client/components/commands/moderation/Mute.js index 6c8a06d..3a70086 100644 --- a/structure/client/components/commands/moderation/Mute.js +++ b/structure/client/components/commands/moderation/Mute.js @@ -98,7 +98,7 @@ class MuteCommand extends Command { } if(time > 0) { - if(time < 60 || time > 2629746) { + if(time < 60 || time > 2629746) { // approx. 1 month return message.respond(message.format('C_MUTE_DURATIONEXCEPTION'), { emoji: 'failure' }); } } diff --git a/structure/client/components/observers/GuildLogging.js b/structure/client/components/observers/GuildLogging.js new file mode 100644 index 0000000..cb81aa3 --- /dev/null +++ b/structure/client/components/observers/GuildLogging.js @@ -0,0 +1,102 @@ +const { Observer } = require('../../../interfaces/'); + +const CONSTANTS = {}; + +class GuildLogger extends Observer { + + constructor(client) { + + super(client, { + name: 'guildLogger', + priority: 3 + }); + + this.hooks = [ + ['message', this.storeAttachment.bind(this)], //Attachment logging + ['messageDelete', this.messageDelete.bind(this)], + ['messageDeleteBulk', this.messageDeleteBulk.bind(this)], + ['messageUpdate', this.messageEdit.bind(this)], + ['voiceStateUpdate', this.voiceState.bind(this)], + ['guildBanAdd', this.ban.bind(this)], + ['guildBanRemove', this.unban.bind(this)], + ['guildMemberAdd', this.memberJoin.bind(this)], + ['guildMemberRemove', this.memberLeave.bind(this)], + ['guildMemberUpdate', this.memberUpdate.bind(this)] + ]; + + } + + async storeAttachment(message) { + + } + + //TODO: Figure this thing out, this should be called from messageDelete and rawMessageDelete if any attachments are present + //data should be an object containing the necessary information to query for the attachment from the db, and the relevant information to log it + //Will figure this out once I get to that point + async logAttachment(data) { + + } + + async messageDelete(message) { + + const { guild } = message; + if (!guild) return; + + if (!message.member) message.member = await guild.members.fetch(message.author); + const { member, channel } = message; + + const settings = await guild.settings(); + const chatlogs = settings.messageLog; + if (!chatlogs || !chatlogs.channel) return; + + const { ignoredRoles, ignoredChannels } = chatlogs; + const logChannel = guild.resolveChannel(chatlogs.channel); + if (!logChannel) return; + + if (ignoredRoles && member._roles.length) + for (const role of ignoredRoles) + if (member._roles.includes(role)) return; + + if (ignoredChannels && ignoredChannels.includes(channel.id)) return; + + if (message.attachments.size) return this.logAttachment(); + + + + } + + async messageDeleteBulk(messages) { + + } + + async messageEdit(oldMessage, newMessage) { + + } + + async voiceState(oldState, newState) { + + } + + async ban(guild, user) { + + } + + async unban(guild, user) { + + } + + async memberJoin(member) { + + } + + async memberLeave(member) { + + } + + async memberUpdate(oldMember, newMember) { + + } + +} + +module.exports = GuildLogger; \ No newline at end of file diff --git a/structure/client/components/observers/UserLogging.js b/structure/client/components/observers/UserLogging.js new file mode 100644 index 0000000..14536aa --- /dev/null +++ b/structure/client/components/observers/UserLogging.js @@ -0,0 +1,18 @@ +const { Observer } = require('../../../interfaces/'); + +const CONSTANTS = {}; + +class UserLogger extends Observer { + + constructor(client) { + + super(client, { + name: 'userLogger', + priority: 3 + }); + + } + +} + +module.exports = UserLogger; \ No newline at end of file diff --git a/structure/client/components/settings/moderation/MessageLog.js b/structure/client/components/settings/moderation/MessageLog.js index b7ce4c6..c0f509d 100644 --- a/structure/client/components/settings/moderation/MessageLog.js +++ b/structure/client/components/settings/moderation/MessageLog.js @@ -30,7 +30,8 @@ class MessageLogsSetting extends Setting { ignoredChannels: [], ignoredRoles: [], attachments: false, - enabled: false + enabled: false, + webhook: null } } }); @@ -170,11 +171,29 @@ class MessageLogsSetting extends Setting { } else if (this.client.resolver.resolveBoolean(method) === false) { - setting.channel = null; index = 'S_MESSAGELOG_TOGGLE' changes = message.format('ON_OFF_TOGGLE', { toggle: false }, true); - } else { + if (setting.webhook) { //Clean up - remove webhook since logs were disabled + const channel = guild.resolveChannel(setting.channel); + if (channel) { //Channel might have been deleted + const hooks = channel.fetchWebhooks(); + const hook = hooks.filter(hook => hook.id === setting.webhook).first(); + if (hook && !channel.permissionsFor(guild.me).has('MANAGE_WEBHOOKS')) index = 'S_MESSAGELOG_TOGGLE_PERM' //missing perms + else try { //If necessary perms and a hook exists, remove it and uncache it from the main collection + if (hook) await hook.delete('Removing message logging hook, as message logs were disabled.'); + guild.webhooks.delete(setting.webhook); + setting.webhook = null; + } catch (err) { + this.client.logger.error(err); + if (err.code === 10015) setting.webhook = null; // webhook is somehow missing prior to the bot deleting it? + } + } + } + + setting.channel = null; + + } else { //Set the channel & configure webhook const channel = guild.resolveChannel(method); if (!channel) return { @@ -182,6 +201,18 @@ class MessageLogsSetting extends Setting { error: true }; + //Handle old webhook if one exists + const oldChannel = guild.resolveChannel(setting.channel); + if(oldChannel ) + if (oldChannel && chatlogs.webhook && oldChannel.id !== channel.id) { + const hooks = await oldChannel.fetchWebhooks(); + const hook = hooks.filter(hook => hook.id === setting.webhook).first(); + if (hook) { + guild.webhooks.delete(hook.id); + hook.delete('Removing old webhook').catch(this.client.logger.error); + } + } + index = 'S_MESSAGELOG_CHANNEL'; changes = channel.name; setting.channel = channel.id; diff --git a/structure/extensions/Guild.js b/structure/extensions/Guild.js index 75f9764..be54bda 100644 --- a/structure/extensions/Guild.js +++ b/structure/extensions/Guild.js @@ -1,5 +1,6 @@ const escapeRegex = require('escape-string-regexp'); const { Structures } = require('discord.js'); +const { Collection } = require('../../util'); const Guild = Structures.extend('Guild', (Guild) => { @@ -13,6 +14,7 @@ const Guild = Structures.extend('Guild', (Guild) => { this._permissions = null; //internal cache, should always match database. this.callbacks = []; + this.webhooks = new Collection(); }