From f288293fd5929e004448985aa283894b82803c50 Mon Sep 17 00:00:00 2001 From: D3vision Date: Fri, 15 Dec 2023 21:23:12 +0100 Subject: [PATCH] adding tracking functionality to kick command --- @types/Client.ts | 1 + @types/Events.d.ts | 10 +- @types/Guild.d.ts | 3 + @types/Settings.ts | 5 + options.json | 2 +- src/client/DiscordClient.ts | 5 + .../components/commands/moderation/Kick.ts | 18 +- .../components/observers/GuildLogging.ts | 60 ++- .../components/settings/logging/Rejoin.ts | 83 ++++ src/client/interfaces/Infraction.ts | 2 + .../storage/providers/MariaDBProvider.ts | 2 +- .../en_gb/observers/en_gb_guildLogging.lang | 363 +++++++++--------- .../en_gb/settings/en_gb_logging.lang | 92 ++--- 13 files changed, 416 insertions(+), 230 deletions(-) create mode 100644 src/client/components/settings/logging/Rejoin.ts diff --git a/@types/Client.ts b/@types/Client.ts index 7652380..10547c9 100644 --- a/@types/Client.ts +++ b/@types/Client.ts @@ -332,6 +332,7 @@ export type InfractionArguments = { } export type AdditionalInfractionData = { + track?: boolean; muteType?: MuteType, roles?: Role[] roleIds?: Snowflake[], diff --git a/@types/Events.d.ts b/@types/Events.d.ts index c90b3ad..c140edf 100644 --- a/@types/Events.d.ts +++ b/@types/Events.d.ts @@ -1,10 +1,17 @@ -import { Component } from '../src/client/interfaces/index.ts'; +import { Component, Infraction } from '../src/client/interfaces/index.ts'; import { ClientEvents as CE, InvalidRequestWarningData, RateLimitData, ResponseLike } from 'discord.js'; import { ExtendedGuildBan, ExtendedMessage } from './Client.ts'; import { APIRequest } from '@discordjs/rest'; import { AutomodErrorProps, LinkFilterWarnProps, LogErrorProps, MissingPermsProps, UtilityErrorProps, WordWatcherErrorProps } from './Moderation.js'; type ComponentUpdate = { component: Component, type: 'ENABLE' | 'DISABLE' | 'LOAD' | 'UNLOAD' | 'RELOAD' } + +export type RejoinLogEntry = { + userId: string; + guildId: string; + caseId: string; + notify: string; +} export interface ClientEvents extends CE { componentUpdate: [data: ComponentUpdate], rateLimit: [rateLimitInfo: RateLimitData]; @@ -20,4 +27,5 @@ export interface ClientEvents extends CE { utilityError: [UtilityErrorProps]; linkFilterWarn: [LinkFilterWarnProps]; filterMissingPermissions: [MissingPermsProps]; + infraction: [Infraction]; } \ No newline at end of file diff --git a/@types/Guild.d.ts b/@types/Guild.d.ts index 26d8d41..0cd36a6 100644 --- a/@types/Guild.d.ts +++ b/@types/Guild.d.ts @@ -21,6 +21,7 @@ import { PermissionSettings, ProtectionSettings, RaidprotectionSettings, + RejoinSettings, SelfroleSettings, SilenceSettings, StaffSettings, @@ -63,6 +64,7 @@ export type GuildSettingTypes = | WordFilterSettings | WordWatcherSettings | LocaleSettings + | RejoinSettings export type PartialGuildSettings = Partial // { @@ -101,6 +103,7 @@ export type GuildSettings = { invitefilter: InviteFilterSettings, mentionfilter: MentionFilterSettings, raidprotection: RaidprotectionSettings, + rejoin: RejoinSettings } export type PermissionSet = { diff --git a/@types/Settings.ts b/@types/Settings.ts index 91d26b2..24808fb 100644 --- a/@types/Settings.ts +++ b/@types/Settings.ts @@ -192,4 +192,9 @@ export type LocaleSettings = { export type RaidprotectionSettings = { // +} & Setting + +export type RejoinSettings = { + channel: string | null, + message: string | null } & Setting \ No newline at end of file diff --git a/options.json b/options.json index 1ca8ac1..41443c8 100644 --- a/options.json +++ b/options.json @@ -76,7 +76,7 @@ ] }, "mariadb": { - "load": false, + "load": true, "tables": [] } }, diff --git a/src/client/DiscordClient.ts b/src/client/DiscordClient.ts index c16a563..55a86d2 100644 --- a/src/client/DiscordClient.ts +++ b/src/client/DiscordClient.ts @@ -595,6 +595,11 @@ class DiscordClient extends Client return this.#storageManager.mongodb; } + get mariadb () + { + return this.#storageManager.mariadb; + } + get moderation () { return this.#moderationManager; diff --git a/src/client/components/commands/moderation/Kick.ts b/src/client/components/commands/moderation/Kick.ts index 151dae3..3da7fa1 100644 --- a/src/client/components/commands/moderation/Kick.ts +++ b/src/client/components/commands/moderation/Kick.ts @@ -1,4 +1,4 @@ -import { CommandParams } from '../../../../../@types/Client.js'; +import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js'; import DiscordClient from '../../../DiscordClient.js'; import { Kick } from '../../../infractions/index.js'; import { ModerationCommand } from '../../../interfaces/index.js'; @@ -13,7 +13,16 @@ class KickCommand extends ModerationCommand name: 'kick', description: 'Kick people.', moduleName: 'moderation', - options: [], + options: [ + { + name: 'track', + description: 'Whether to ping you if the user rejoins', + type: CommandOptionType.BOOLEAN, + flag: true, + valueOptional: true, + defaultValue: false + } + ], guildOnly: true, showUsage: true, memberPermissions: [ 'KickMembers' ], @@ -21,11 +30,14 @@ class KickCommand extends ModerationCommand }); } - async execute (invoker: InvokerWrapper, { users, ...args }: CommandParams) + async execute (invoker: InvokerWrapper, { users, track, ...args }: CommandParams) { const wrappers = await Promise.all(users!.asUsers.map(user => invoker.guild.memberWrapper(user))); return this.client.moderation.handleInfraction(Kick, invoker, { targets: wrappers.filter(Boolean) as MemberWrapper[], + data: { + track: track?.asBool + }, args }); } diff --git a/src/client/components/observers/GuildLogging.ts b/src/client/components/observers/GuildLogging.ts index 5f2e225..5df9f25 100644 --- a/src/client/components/observers/GuildLogging.ts +++ b/src/client/components/observers/GuildLogging.ts @@ -9,6 +9,8 @@ import { AttachmentData, MessageLogEntry } from '../../../../@types/Guild.js'; import { stripIndents } from 'common-tags'; import moment from 'moment'; import { inspect } from 'util'; +import Infraction from '../../interfaces/Infraction.js'; +import { RejoinLogEntry } from '../../../../@types/Events.js'; /* eslint-disable no-labels */ const CONSTANTS: { @@ -60,7 +62,9 @@ class GuildLogger extends Observer [ 'guildMemberUpdate', this.memberUpdate.bind(this) ], [ 'threadCreate', this.threadCreate.bind(this) ], [ 'threadDelete', this.threadDelete.bind(this) ], - [ 'threadUpdate', this.threadUpdate.bind(this) ] + [ 'threadUpdate', this.threadUpdate.bind(this) ], + [ 'infraction', this.infraction.bind(this) ], + [ 'guildMemberAdd', this.rejoinTracking.bind(this) ], ]; if (!process.env.MODERATION_WEHBHOOK_ID || !process.env.MODERATION_WEHBHOOK_TOKEN) @@ -924,6 +928,60 @@ class GuildLogger extends Observer }; await logChannel.send({ embeds: [ embed ] }); } + + async infraction (inf: Infraction) + { + if (inf.type !== 'KICK') + return; + + const { guild: wrapper } = inf; + const settings = await wrapper.settings(); + const setting = settings.rejoin; + if (!setting.channel || !setting.enabled) + return; + + if (!inf.targetId || !inf.guildId || !inf.id) + throw new Error('Missing ID for target, guild, infraction'); + + const notifyArray = []; + if (inf.data.track) + notifyArray.push(inf.executorId); + await this.client.mariadb.query('INSERT INTO `kickLog` (`userId`, `guildId`, `caseId`, `notify`) VALUES (?);', [[ inf.targetId, inf.guildId, inf.id, JSON.stringify(notifyArray) ]]); + } + + async rejoinTracking (member: ExtendedGuildMember) + { + const { guildWrapper: wrapper } = member; + const settings = await wrapper.settings(); + const setting = settings.rejoin; + if (!setting.channel || !setting.enabled) + return; + + const logChannel = await wrapper.resolveChannel(setting.channel); + if (!logChannel) + return; + + const missing = logChannel.permissionsFor(this.client.user!)?.missing([ 'ViewChannel', 'SendMessages' ]); + if (!missing || missing.length) + return this.client.emit('logError', { guild: wrapper, logger: 'rejoinLogger', reason: 'REJOINLOG_NO_PERMS', params: { missing: missing?.join(', ') ?? 'ALL' } }); + + const [ result ] = await this.client.mariadb.query('DELETE FROM `kickLog` WHERE `guildId` = ? AND `userId` = ? RETURNING *;', [ member.guild.id, member.id ]) as RejoinLogEntry[]; + if (!result) + return; + + const infraction = await this.client.mongodb.infractions.findOne({ id: result.caseId }); + + let { message } = setting; + message = this._replaceTags(message!, member); + const notifyArray = JSON.parse(result.notify); + if (notifyArray.length > 0) + { + const notifyString = notifyArray.map((id: string) => `<@${id}>`).join(', '); + message = notifyString + '\n' + message; + } + message += (infraction ? `\n**Reason:** \`${infraction.reason}\`` : '') + `\n**Case:** \`${result.caseId.split(':')[1]}\``; + await this.client.rateLimiter.queueSend(logChannel, message); + } } export default GuildLogger; \ No newline at end of file diff --git a/src/client/components/settings/logging/Rejoin.ts b/src/client/components/settings/logging/Rejoin.ts new file mode 100644 index 0000000..48703e7 --- /dev/null +++ b/src/client/components/settings/logging/Rejoin.ts @@ -0,0 +1,83 @@ +import { TextChannel } from 'discord.js'; +import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js'; +import { RejoinSettings } from '../../../../../@types/Settings.js'; +import DiscordClient from '../../../DiscordClient.js'; +import Setting from '../../../interfaces/Setting.js'; +import InvokerWrapper from '../../wrappers/InvokerWrapper.js'; +import CommandError from '../../../interfaces/CommandError.js'; + +class Rejoin extends Setting +{ + constructor (client: DiscordClient) + { + super(client, { + name: 'rejoin', + description: 'Configure if and where the bot will send a message when a kicked user rejoins the server', + display: 'Rejoin Logging', + moduleName: 'logging', + default: { + enabled: false, + channel: null, + message: 'The user **{mention}** ({id}) has rejoined the server after being kicked.', + }, + definitions: { + enabled: 'BOOLEAN', + channel: 'GUILD_TEXT', + message: 'STRING' + }, + commandOptions: [ + { + name: 'enabled', + description: 'Toggle logging on or off', + type: CommandOptionType.BOOLEAN, + flag: true, + valueOptional: true, + defaultValue: true + }, + { + name: 'channel', + description: 'Channel in which to output logs', + type: CommandOptionType.TEXT_CHANNEL + }, + { + name: 'message', + description: 'Message to send when a user rejoins', + type: CommandOptionType.STRING + } + ] + }); + } + + async execute (invoker: InvokerWrapper, opts: CommandParams, setting: RejoinSettings) + { + const channel = opts.channel?.asChannel; + const message = opts.message?.asString; + + const index = 'SETTING_SUCCESS_ALT'; + + if (opts.enabled) + setting.enabled = opts.enabled.asBool; + + if (opts.channel) + { + if (!(channel instanceof TextChannel)) + throw new CommandError(invoker, { index: 'ERR_INVALID_CHANNEL_TYPE' }); + const perms = channel.permissionsFor(this.client.user!); + const missingPerms = perms?.missing([ 'ViewChannel', 'SendMessages' ]); + if (!missingPerms || missingPerms.length) + return { + error: true, + index: 'ERR_CHANNEL_PERMS', + params: { channel: channel.name, perms: missingPerms?.join(', ') ?? 'ALL' } + }; + setting.channel = channel.id; + } + + if (opts.message) + setting.message = message ?? null; + + return { index }; + } +} + +export default Rejoin; \ No newline at end of file diff --git a/src/client/interfaces/Infraction.ts b/src/client/interfaces/Infraction.ts index c0d38a1..bb75e4f 100644 --- a/src/client/interfaces/Infraction.ts +++ b/src/client/interfaces/Infraction.ts @@ -235,6 +235,8 @@ class Infraction if (this.#data.roles) delete this.#data.roles; + this.client.emit('infraction', this); + return this.save(); } diff --git a/src/client/storage/providers/MariaDBProvider.ts b/src/client/storage/providers/MariaDBProvider.ts index f22b847..2d1cd1f 100644 --- a/src/client/storage/providers/MariaDBProvider.ts +++ b/src/client/storage/providers/MariaDBProvider.ts @@ -187,7 +187,7 @@ class MariaDBProvider extends Provider { const result = await new Promise((resolve, reject) => { - const q = connection.query({ timeout, sql: query }, [ values ], (err, results, fields) => + const q = connection.query({ timeout, sql: query }, values, (err, results, fields) => { if (err) reject(err); diff --git a/src/localization/en_gb/observers/en_gb_guildLogging.lang b/src/localization/en_gb/observers/en_gb_guildLogging.lang index d4b6d64..23f346f 100644 --- a/src/localization/en_gb/observers/en_gb_guildLogging.lang +++ b/src/localization/en_gb/observers/en_gb_guildLogging.lang @@ -1,180 +1,185 @@ -// Message logs -[MSGLOG_DELETE_TITLE] -{emoji_trash} {author}'s message was deleted in #{channel} - -[MSGLOG_NOCONTENT] -**__NO TEXT CONTENT__** - -[MSGLOG_REPLY] -Message was in reply to user {tag} ({id}): - -[MSGLOG_REPLY_VALUE] -**[Jump]({link})** -{content} - -[MSGLOG_REPLY_NOCONTENT] -**__Missing content.__** - -[MSGLOG_FILTERED] -The message was filtered: - -[MSGLOG_FILTERED_VALUE_WORD] -Filter: **{matcher}** -Matched: **{match}** - -[MSGLOG_FILTERED_VALUE_INVITE] -Filter: **{matcher}** -Matched: **{match}** - -[MSGLOG_FILTERED_VALUE_LINK] -Filter: **{matcher}** -Matched: **{match}** - -[MSGLOG_FILTERED_VALUE_MENTION] -Filter: **{filter}** -Amount: **{amount}** - -[MSGLOG_FILTERED_VALUE_WORDWATCHER] -Filter: **{filter}** -Action: **{action}** - -[MSGLOG_FILTERED_SANCTIONED] -__User was sanctioned.__ {emoji_hammer} - -[MSGLOG_DELETE_FOOTER] -Message ID: {msgID} | User ID: {userID} - -[MSGLOG_EDIT_TITLE] -{emoji_note} {author} edited their message in #{channel} - -[MSGLOG_EDIT_FOOTER] -Message ID: {msgID} | User ID: {userID} - -[MSGLOG_EDIT_OLD] -Old message - -[MSGLOG_EDIT_NEW] -New message - -[MSGLOG_EDIT_OLD_CUTOFF] -The original message was cut off at 1024 characters. - -[MSGLOG_EDIT_NEW_CUTOFF] -The new message is cut off at 1024 characters. - -[MSGLOG_EDIT_JUMP] -**[Jump to message](https://discord.com/channels/{guild}/{channel}/{message})** - -[MSGLOG_PINNED_TITLE] -{emoji_pin} {author}'s message was {pinned} in #{channel} - -[PIN_TOGGLE] -switch({toggle}) { - case true: - 'pinned'; - break; - default: - 'unpinned'; - break; -} - -[THREAD_SWITCH] -switch('{type}') { - case 'CREATE': - 'created'; - break; - case 'DELETE': - 'deleted'; - break; - case 'ARCHIVE': - 'archived'; - break; - case 'UNARCHIVE': - 'unarchived'; - break; -} - -[MSGLOG_THREAD_TITLE] -A thread was {action} in #{channel} - -[MSGLOG_THREAD_DESC_DELETE] -**Thread:** {name} ({id}) -**Owner:** {owner} -**Deleted by:** {actor} - -[MSGLOG_THREAD_DESC_CREATE] -**Thread:** {name} <#{id}> -**Owner:** {owner} - -[MSGLOG_THREAD_DESC_ARCHIVE] -**Thread:** {name} <#{id}> -**Owner:** {owner} -**Archived by:** {actor} - -[MSGLOG_THREAD_DESC_UNARCHIVE] -**Thread:** {name} <#{id}> -**Owner:** {owner} -**Unarchived by:** {actor} - -[MSGLOG_THREAD_FOOTER] -Channel ID: {channelId} • Thread ID: {threadId} • Owner ID: {ownerId} - -[MSGLOG_THREAD_LOCKED] -**Thread was locked** - -[MSGLOG_NO_PERMS] -Bot missing permissions in message log channel -Missing: {missing} - -[MSGLOG_NO_HOOK] -Webhook missing in message log channel. -Reassign the message log channel to have the bot recreate the webhook. - -//Voice Logs -[VCLOG_JOIN] -{nickname} **{tag}** ({id}) joined channel {emoji_voice-channel} **{newChannel}**. - -[VCLOG_SWITCH] -{nickname} **{tag}** ({id}) switched from {emoji_voice-channel} **{oldChannel}** to {emoji_voice-channel} **{newChannel}**. - -[VCLOG_LEAVE] -{nickname} **{tag}** ({id}) left channel {emoji_voice-channel} **{oldChannel}** - -[VCLOG_NO_PERMS] -Bot missing permissions in voice join/leave log channel -Missing: {missing} - -[MEMBERLOG_NO_PERMS] -Bot missing permissions in member log channel -Missing: {missing} - -//Nickname Logs -[NICKLOG_TITLE] -{user} updated their nickname - -[NICKLOG_DESCRIPTION] -**Old nickname:** `{oldNick}` -**New nickname:** `{newNick}` - -[NICKLOG_FOOTER] -User ID: {id} - -[NICKLOG_NO_PERMS] -Bot missing permissions in nickname log channel -Missing: {missing} - -//Bulk Delete Logs -[BULK_DELETE_TITLE] -Bulk message delete log [{embedNr}] in **#{channel}** - -[BULK_DELETE_FOOTER] -Showing messages {rangeStart}-{rangeEnd} of {total} messages - -[BULK_DELETE_NO_CONTENT] -No content. - -[BULK_DELETE_ATTACHMENTS] -Message attachments: - -[BULK_DELETE_ATTACHMENTS_VALUE] +// Message logs +[MSGLOG_DELETE_TITLE] +{emoji_trash} {author}'s message was deleted in #{channel} + +[MSGLOG_NOCONTENT] +**__NO TEXT CONTENT__** + +[MSGLOG_REPLY] +Message was in reply to user {tag} ({id}): + +[MSGLOG_REPLY_VALUE] +**[Jump]({link})** +{content} + +[MSGLOG_REPLY_NOCONTENT] +**__Missing content.__** + +[MSGLOG_FILTERED] +The message was filtered: + +[MSGLOG_FILTERED_VALUE_WORD] +Filter: **{matcher}** +Matched: **{match}** + +[MSGLOG_FILTERED_VALUE_INVITE] +Filter: **{matcher}** +Matched: **{match}** + +[MSGLOG_FILTERED_VALUE_LINK] +Filter: **{matcher}** +Matched: **{match}** + +[MSGLOG_FILTERED_VALUE_MENTION] +Filter: **{filter}** +Amount: **{amount}** + +[MSGLOG_FILTERED_VALUE_WORDWATCHER] +Filter: **{filter}** +Action: **{action}** + +[MSGLOG_FILTERED_SANCTIONED] +__User was sanctioned.__ {emoji_hammer} + +[MSGLOG_DELETE_FOOTER] +Message ID: {msgID} | User ID: {userID} + +[MSGLOG_EDIT_TITLE] +{emoji_note} {author} edited their message in #{channel} + +[MSGLOG_EDIT_FOOTER] +Message ID: {msgID} | User ID: {userID} + +[MSGLOG_EDIT_OLD] +Old message + +[MSGLOG_EDIT_NEW] +New message + +[MSGLOG_EDIT_OLD_CUTOFF] +The original message was cut off at 1024 characters. + +[MSGLOG_EDIT_NEW_CUTOFF] +The new message is cut off at 1024 characters. + +[MSGLOG_EDIT_JUMP] +**[Jump to message](https://discord.com/channels/{guild}/{channel}/{message})** + +[MSGLOG_PINNED_TITLE] +{emoji_pin} {author}'s message was {pinned} in #{channel} + +[PIN_TOGGLE] +switch({toggle}) { + case true: + 'pinned'; + break; + default: + 'unpinned'; + break; +} + +[THREAD_SWITCH] +switch('{type}') { + case 'CREATE': + 'created'; + break; + case 'DELETE': + 'deleted'; + break; + case 'ARCHIVE': + 'archived'; + break; + case 'UNARCHIVE': + 'unarchived'; + break; +} + +[MSGLOG_THREAD_TITLE] +A thread was {action} in #{channel} + +[MSGLOG_THREAD_DESC_DELETE] +**Thread:** {name} ({id}) +**Owner:** {owner} +**Deleted by:** {actor} + +[MSGLOG_THREAD_DESC_CREATE] +**Thread:** {name} <#{id}> +**Owner:** {owner} + +[MSGLOG_THREAD_DESC_ARCHIVE] +**Thread:** {name} <#{id}> +**Owner:** {owner} +**Archived by:** {actor} + +[MSGLOG_THREAD_DESC_UNARCHIVE] +**Thread:** {name} <#{id}> +**Owner:** {owner} +**Unarchived by:** {actor} + +[MSGLOG_THREAD_FOOTER] +Channel ID: {channelId} • Thread ID: {threadId} • Owner ID: {ownerId} + +[MSGLOG_THREAD_LOCKED] +**Thread was locked** + +[MSGLOG_NO_PERMS] +Bot missing permissions in message log channel +Missing: {missing} + +[MSGLOG_NO_HOOK] +Webhook missing in message log channel. +Reassign the message log channel to have the bot recreate the webhook. + +//Voice Logs +[VCLOG_JOIN] +{nickname} **{tag}** ({id}) joined channel {emoji_voice-channel} **{newChannel}**. + +[VCLOG_SWITCH] +{nickname} **{tag}** ({id}) switched from {emoji_voice-channel} **{oldChannel}** to {emoji_voice-channel} **{newChannel}**. + +[VCLOG_LEAVE] +{nickname} **{tag}** ({id}) left channel {emoji_voice-channel} **{oldChannel}** + +[VCLOG_NO_PERMS] +Bot missing permissions in voice join/leave log channel +Missing: {missing} + +[MEMBERLOG_NO_PERMS] +Bot missing permissions in member log channel +Missing: {missing} + +//Nickname Logs +[NICKLOG_TITLE] +{user} updated their nickname + +[NICKLOG_DESCRIPTION] +**Old nickname:** `{oldNick}` +**New nickname:** `{newNick}` + +[NICKLOG_FOOTER] +User ID: {id} + +[NICKLOG_NO_PERMS] +Bot missing permissions in nickname log channel +Missing: {missing} + +//Rejoin Tracking Logs +[REJOINLOG_NO_PERMS] +Bot missing permissions in rejoin log channel +Missing: {missing} + +//Bulk Delete Logs +[BULK_DELETE_TITLE] +Bulk message delete log [{embedNr}] in **#{channel}** + +[BULK_DELETE_FOOTER] +Showing messages {rangeStart}-{rangeEnd} of {total} messages + +[BULK_DELETE_NO_CONTENT] +No content. + +[BULK_DELETE_ATTACHMENTS] +Message attachments: + +[BULK_DELETE_ATTACHMENTS_VALUE] {links} \ No newline at end of file diff --git a/src/localization/en_gb/settings/en_gb_logging.lang b/src/localization/en_gb/settings/en_gb_logging.lang index 629577d..0cfaf71 100644 --- a/src/localization/en_gb/settings/en_gb_logging.lang +++ b/src/localization/en_gb/settings/en_gb_logging.lang @@ -1,44 +1,48 @@ -[SETTING_MESSAGES_HELP] -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 `ManageWebhooks` 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. - -[SETTING_MEMBERS_HELP] -Configure member logging for your server. - -**Usable tags:** -`{mention}` - mentions the user -`{tag}` - username#discriminator -`{username}` - username -`{guildsize}` - member count of the server -`{guildname}` - name of the server -`{accage}` - age of the account -`{id}` - ID of the account - -[SETTING_DMINFRACTION_HELP] -Configure if messages get sent to users after they're infracted. -Setup custom messages and customize what infractions you want to send to the user. - -[SETTING_DMINFRACTION_VALID] -Valid choices are **{valid}**. - -// Memberlogs -[SETTING_MEMBERLOG_JOIN] -》 Join Message - -[SETTING_MEMBERLOG_LEAVE] -》 Leave Message - -[SETTING_ERRORS_HELP] -Log issues that arise due to configuration. -Most common outputs will be from automod. - -[SETTING_MODERATION_HELP] -Define the channel to which moderation logs are sent. -The setting also allows you to exclude/include types of actions from being logged in the channel. - -[SETTING_NICKNAMES_HELP] -Configure member nickname logging for your server. - -[SETTING_VOICE_HELP] -Configure logging of voice joins and leaves for your server. \ No newline at end of file +[SETTING_MESSAGES_HELP] +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 `ManageWebhooks` 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. + +[SETTING_MEMBERS_HELP] +Configure member logging for your server. + +**Usable tags:** +`{mention}` - mentions the user +`{tag}` - username#discriminator +`{username}` - username +`{guildsize}` - member count of the server +`{guildname}` - name of the server +`{accage}` - age of the account +`{id}` - ID of the account + +[SETTING_DMINFRACTION_HELP] +Configure if messages get sent to users after they're infracted. +Setup custom messages and customize what infractions you want to send to the user. + +[SETTING_DMINFRACTION_VALID] +Valid choices are **{valid}**. + +// Memberlogs +[SETTING_MEMBERLOG_JOIN] +》 Join Message + +[SETTING_MEMBERLOG_LEAVE] +》 Leave Message + +[SETTING_ERRORS_HELP] +Log issues that arise due to configuration. +Most common outputs will be from automod. + +[SETTING_MODERATION_HELP] +Define the channel to which moderation logs are sent. +The setting also allows you to exclude/include types of actions from being logged in the channel. + +[SETTING_NICKNAMES_HELP] +Configure member nickname logging for your server. + +[SETTING_VOICE_HELP] +Configure logging of voice joins and leaves for your server. + +//Rejoin logs +[SETTING_REJOIN_HELP] +Configure if and where a message is sent when a user who has been kicked rejoins the server. \ No newline at end of file