From 19235306f740d443d5da4ea3873b3da85bd50558 Mon Sep 17 00:00:00 2001 From: metal Date: Wed, 11 May 2022 14:35:05 +0000 Subject: [PATCH 01/11] idk kev Signed-off-by: GitHub --- backend/src/plugins/Cases/functions/getCaseEmbed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Cases/functions/getCaseEmbed.ts b/backend/src/plugins/Cases/functions/getCaseEmbed.ts index 93ee02ef..5a1b2ab8 100644 --- a/backend/src/plugins/Cases/functions/getCaseEmbed.ts +++ b/backend/src/plugins/Cases/functions/getCaseEmbed.ts @@ -1,4 +1,4 @@ -import { MessageEditOptions, MessageOptions } from "discord.js"; +import { MessageEditOptions, MessageOptions, Util } from "discord.js"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; import { CaseTypes } from "../../../data/CaseTypes"; @@ -67,7 +67,7 @@ export async function getCaseEmbed( if (theCase.notes.length) { for (const note of theCase.notes) { const noteDate = moment.utc(note.created_at); - let noteBody = note.body.trim(); + let noteBody = Util.escapeCodeBlock(note.body.trim()); if (noteBody === "") { noteBody = emptyEmbedValue; } From 662d9cda26da008614739e31b718df154503ec8f Mon Sep 17 00:00:00 2001 From: almeidx Date: Tue, 14 Jun 2022 18:23:36 +0100 Subject: [PATCH 02/11] fix: no response if user only has hidden cases in `!cases @user` --- backend/src/plugins/ModActions/commands/CasesUserCmd.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts index 90023026..c6646711 100644 --- a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts +++ b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts @@ -67,6 +67,12 @@ export const CasesUserCmd = modActionsCmd({ msg.channel.send(`No cases found for **${userName}**`); } else { const casesToDisplay = args.hidden ? cases : normalCases; + if (!casesToDisplay.length) { + msg.channel.send( + `No normal cases found for **${userName}**. Use "-hidden" to show ${cases.length} hidden cases.`, + ); + return; + } if (args.expand) { if (casesToDisplay.length > 8) { From 5c0d607af383dbfd1be1908a2ad885e76d67dbdc Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Tue, 21 Jun 2022 21:43:59 -0600 Subject: [PATCH 03/11] feat: Tag list search and improved readability --- .../src/plugins/Tags/commands/TagListCmd.ts | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Tags/commands/TagListCmd.ts b/backend/src/plugins/Tags/commands/TagListCmd.ts index c50baaa5..4bc4691e 100644 --- a/backend/src/plugins/Tags/commands/TagListCmd.ts +++ b/backend/src/plugins/Tags/commands/TagListCmd.ts @@ -1,3 +1,4 @@ +import { commandTypeHelpers as ct } from "../../../commandTypes"; import { createChunkedMessage } from "../../../utils"; import { tagsCmd } from "../types"; @@ -5,7 +6,11 @@ export const TagListCmd = tagsCmd({ trigger: ["tag list", "tags", "taglist"], permission: "can_list", - async run({ message: msg, pluginData }) { + signature: { + search: ct.string({ required: false }), + }, + + async run({ message: msg, args, pluginData }) { const tags = await pluginData.state.tags.all(); if (tags.length === 0) { msg.channel.send(`No tags created yet! Use \`tag create\` command to create one.`); @@ -15,6 +20,33 @@ export const TagListCmd = tagsCmd({ const prefix = (await pluginData.config.getForMessage(msg)).prefix; const tagNames = tags.map((tag) => tag.tag).sort(); - createChunkedMessage(msg.channel, `Available tags (use with ${prefix}tag): \`\`\`${tagNames.join(", ")}\`\`\``); + const filteredTags = args.search + ? tagNames.filter((tag) => + new RegExp( + args.search + .split("") + .map((char) => char.replace(/[.*+?^${}()|[\]\\]/, "\\$&")) + .join(".*") + ).test(tag) + ) + : tagNames; + + const tagGroups = filteredTags.reduce((acc, tag) => { + const obj = { ...acc }; + const tagUpper = tag.toUpperCase(); + const key = /[A-Z]/.test(tagUpper[0]) ? tagUpper[0] : "#"; + if (!(key in obj)) { + obj[key] = []; + } + obj[key].push(tag); + return obj; + }, {}); + + const tagList = Object.keys(tagGroups) + .sort() + .map((key) => `[${key}] ${tagGroups[key].join(", ")}`) + .join("\n"); + + createChunkedMessage(msg.channel, `Available tags (use with ${prefix}tag): \`\`\`${tagList}\`\`\``); }, }); From 265764866ed50e93845f28ade8fd63f2258d422e Mon Sep 17 00:00:00 2001 From: k200 <85361508+k200-1@users.noreply.github.com> Date: Sat, 13 Aug 2022 13:38:11 +0100 Subject: [PATCH 04/11] Change order of flags in command Fixes the order of the flags so that the command works --- PRODUCTION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRODUCTION.md b/PRODUCTION.md index e05d5cc3..f3bc8b6b 100644 --- a/PRODUCTION.md +++ b/PRODUCTION.md @@ -31,4 +31,4 @@ Only edit files in `/backend/src`, `/shared/src`, and `/dashboard/src`. Make sure to revert any hotfixes before updating the bot normally. ## View logs -To view real-time logs, run `docker compose -f docker-compose.production.yml -t -f logs` +To view real-time logs, run `docker compose -f docker-compose.production.yml logs -t -f` From d472fd4fa6a2111fe71f88715d269386f3f6fadc Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 13 Aug 2022 23:19:06 +0300 Subject: [PATCH 05/11] fix(logs): fix inconsistent thread/channel/category exclusions; add excluded_threads log channel option --- .../plugins/Logs/logFunctions/logCensor.ts | 4 +- .../Logs/logFunctions/logChannelCreate.ts | 4 +- .../Logs/logFunctions/logChannelDelete.ts | 4 +- .../Logs/logFunctions/logChannelUpdate.ts | 4 +- .../src/plugins/Logs/logFunctions/logClean.ts | 4 +- .../Logs/logFunctions/logMessageDelete.ts | 4 +- .../Logs/logFunctions/logMessageDeleteAuto.ts | 4 +- .../Logs/logFunctions/logMessageDeleteBare.ts | 4 +- .../Logs/logFunctions/logMessageDeleteBulk.ts | 4 +- .../Logs/logFunctions/logMessageEdit.ts | 3 +- .../logFunctions/logMessageSpamDetected.ts | 4 +- .../logFunctions/logPostedScheduledMessage.ts | 4 +- .../Logs/logFunctions/logRepeatedMessage.ts | 4 +- .../Logs/logFunctions/logScheduledMessage.ts | 4 +- .../logScheduledRepeatedMessage.ts | 4 +- .../logFunctions/logStageInstanceCreate.ts | 4 +- .../logFunctions/logStageInstanceDelete.ts | 4 +- .../logFunctions/logStageInstanceUpdate.ts | 4 +- .../Logs/logFunctions/logThreadCreate.ts | 4 +- .../Logs/logFunctions/logThreadDelete.ts | 4 +- .../Logs/logFunctions/logThreadUpdate.ts | 4 +- .../logVoiceChannelForceDisconnect.ts | 4 +- .../logFunctions/logVoiceChannelForceMove.ts | 4 +- .../Logs/logFunctions/logVoiceChannelJoin.ts | 4 +- .../Logs/logFunctions/logVoiceChannelLeave.ts | 4 +- .../Logs/logFunctions/logVoiceChannelMove.ts | 4 +- backend/src/plugins/Logs/types.ts | 1 + backend/src/plugins/Logs/util/log.ts | 5 ++ backend/src/utils/isDmChannel.ts | 6 +++ backend/src/utils/isGuildChannel.ts | 5 ++ backend/src/utils/isThreadChannel.ts | 10 ++++ backend/src/utils/resolveChannelIds.ts | 53 +++++++++++++++++++ 32 files changed, 132 insertions(+), 51 deletions(-) create mode 100644 backend/src/utils/isDmChannel.ts create mode 100644 backend/src/utils/isGuildChannel.ts create mode 100644 backend/src/utils/isThreadChannel.ts create mode 100644 backend/src/utils/resolveChannelIds.ts diff --git a/backend/src/plugins/Logs/logFunctions/logCensor.ts b/backend/src/plugins/Logs/logFunctions/logCensor.ts index b0131657..c5048730 100644 --- a/backend/src/plugins/Logs/logFunctions/logCensor.ts +++ b/backend/src/plugins/Logs/logFunctions/logCensor.ts @@ -12,6 +12,7 @@ import { import { SavedMessage } from "../../../data/entities/SavedMessage"; import { UnknownUser } from "../../../utils"; import { deactivateMentions, disableCodeBlocks } from "knub/dist/helpers"; +import { resolveChannelIds } from "src/utils/resolveChannelIds"; interface LogCensorData { user: User | UnknownUser; @@ -33,9 +34,8 @@ export function logCensor(pluginData: GuildPluginData, data: Log }), { userId: data.user.id, - channel: data.channel.id, - category: data.channel.parentId, bot: data.user instanceof User ? data.user.bot : false, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logChannelCreate.ts b/backend/src/plugins/Logs/logFunctions/logChannelCreate.ts index 02ccba3d..a95ffb48 100644 --- a/backend/src/plugins/Logs/logFunctions/logChannelCreate.ts +++ b/backend/src/plugins/Logs/logFunctions/logChannelCreate.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { GuildChannel, NewsChannel } from "discord.js"; import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogChannelCreateData { channel: GuildChannel | NewsChannel; @@ -18,8 +19,7 @@ export function logChannelCreate(pluginData: GuildPluginData, da channel: channelToTemplateSafeChannel(data.channel), }), { - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logChannelDelete.ts b/backend/src/plugins/Logs/logFunctions/logChannelDelete.ts index 547ed963..25bd4374 100644 --- a/backend/src/plugins/Logs/logFunctions/logChannelDelete.ts +++ b/backend/src/plugins/Logs/logFunctions/logChannelDelete.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { GuildChannel, NewsChannel } from "discord.js"; import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogChannelDeleteData { channel: GuildChannel | NewsChannel; @@ -18,8 +19,7 @@ export function logChannelDelete(pluginData: GuildPluginData, da channel: channelToTemplateSafeChannel(data.channel), }), { - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts b/backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts index ebe2ceb5..7942501e 100644 --- a/backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts +++ b/backend/src/plugins/Logs/logFunctions/logChannelUpdate.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { GuildChannel, NewsChannel } from "discord.js"; import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogChannelUpdateData { oldChannel: GuildChannel | NewsChannel; @@ -22,8 +23,7 @@ export function logChannelUpdate(pluginData: GuildPluginData, da differenceString: data.differenceString, }), { - channel: data.newChannel.id, - category: data.newChannel.parentId, + ...resolveChannelIds(data.newChannel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logClean.ts b/backend/src/plugins/Logs/logFunctions/logClean.ts index 4ef2077e..6486873f 100644 --- a/backend/src/plugins/Logs/logFunctions/logClean.ts +++ b/backend/src/plugins/Logs/logFunctions/logClean.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildTextChannel, User } from "discord.js"; import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogCleanData { mod: User; @@ -24,8 +25,7 @@ export function logClean(pluginData: GuildPluginData, data: LogC archiveUrl: data.archiveUrl, }), { - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts b/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts index 6a2d642c..e103b13b 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts @@ -13,6 +13,7 @@ import moment from "moment-timezone"; import { ISavedMessageAttachmentData, SavedMessage } from "../../../data/entities/SavedMessage"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { UnknownUser, useMediaUrls } from "../../../utils"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogMessageDeleteData { user: User | UnknownUser; @@ -47,10 +48,9 @@ export function logMessageDelete(pluginData: GuildPluginData, da }), { userId: data.user.id, - channel: data.channel.id, - category: data.channel.parentId, messageTextContent: data.message.data.content, bot: data.user instanceof User ? data.user.bot : false, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts b/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts index 4ce4694c..d4e66f28 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts @@ -11,6 +11,7 @@ import { } from "../../../utils/templateSafeObjects"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { UnknownUser } from "../../../utils"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogMessageDeleteAutoData { message: SavedMessage; @@ -32,8 +33,7 @@ export function logMessageDeleteAuto(pluginData: GuildPluginData { userId: data.user.id, bot: data.user instanceof User ? data.user.bot : false, - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts b/backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts index ed49d3e4..ad9320e3 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageDeleteBare.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildTextChannel, GuildTextBasedChannel, ThreadChannel } from "discord.js"; import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogMessageDeleteBareData { messageId: string; @@ -20,8 +21,7 @@ export function logMessageDeleteBare(pluginData: GuildPluginData channel: channelToTemplateSafeChannel(data.channel), }), { - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts b/backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts index b57b526b..2ffe36bf 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageDeleteBulk.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildTextChannel, GuildTextBasedChannel, ThreadChannel } from "discord.js"; import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogMessageDeleteBulkData { count: number; @@ -24,8 +25,7 @@ export function logMessageDeleteBulk(pluginData: GuildPluginData archiveUrl: data.archiveUrl, }), { - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logMessageEdit.ts b/backend/src/plugins/Logs/logFunctions/logMessageEdit.ts index 3aee8dbe..64f12e8f 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageEdit.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageEdit.ts @@ -11,6 +11,7 @@ import { } from "../../../utils/templateSafeObjects"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { UnknownUser } from "../../../utils"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogMessageEditData { user: User | UnknownUser; @@ -31,9 +32,9 @@ export function logMessageEdit(pluginData: GuildPluginData, data }), { userId: data.user.id, - channel: data.channel.id, messageTextContent: data.after.data.content, bot: data.user instanceof User ? data.user.bot : false, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts b/backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts index 94e41eda..a18f197c 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageSpamDetected.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildTextChannel, GuildChannel, GuildMember, ThreadChannel } from "discord.js"; import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogMessageSpamDetectedData { member: GuildMember; @@ -30,9 +31,8 @@ export function logMessageSpamDetected(pluginData: GuildPluginData, { userId: data.author.id, bot: data.author.bot, - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts b/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts index 54315e6f..8067dd6a 100644 --- a/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts +++ b/backend/src/plugins/Logs/logFunctions/logScheduledMessage.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildTextChannel, GuildTextBasedChannel, ThreadChannel, User } from "discord.js"; import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogScheduledMessageData { author: User; @@ -28,8 +29,7 @@ export function logScheduledMessage(pluginData: GuildPluginData, { userId: data.author.id, bot: data.author.bot, - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts b/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts index 03272f5f..849b1a4a 100644 --- a/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts +++ b/backend/src/plugins/Logs/logFunctions/logScheduledRepeatedMessage.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildTextChannel, GuildTextBasedChannel, ThreadChannel, User } from "discord.js"; import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogScheduledRepeatedMessageData { author: User; @@ -35,8 +36,7 @@ export function logScheduledRepeatedMessage( { userId: data.author.id, bot: data.author.bot, - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts b/backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts index f1602c3b..6172a2bc 100644 --- a/backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts +++ b/backend/src/plugins/Logs/logFunctions/logStageInstanceCreate.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { StageChannel, StageInstance } from "discord.js"; import { channelToTemplateSafeChannel, stageToTemplateSafeStage } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogStageInstanceCreateData { stageInstance: StageInstance; @@ -20,8 +21,7 @@ export function logStageInstanceCreate(pluginData: GuildPluginData, dat thread: channelToTemplateSafeChannel(data.thread), }), { - channel: data.thread.parentId, - category: data.thread.parent?.parentId, + ...resolveChannelIds(data.thread), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logThreadDelete.ts b/backend/src/plugins/Logs/logFunctions/logThreadDelete.ts index 2467682f..04827083 100644 --- a/backend/src/plugins/Logs/logFunctions/logThreadDelete.ts +++ b/backend/src/plugins/Logs/logFunctions/logThreadDelete.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { ThreadChannel } from "discord.js"; import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogThreadDeleteData { thread: ThreadChannel; @@ -18,8 +19,7 @@ export function logThreadDelete(pluginData: GuildPluginData, dat thread: channelToTemplateSafeChannel(data.thread), }), { - channel: data.thread.parentId, - category: data.thread.parent?.parentId, + ...resolveChannelIds(data.thread), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts b/backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts index 80e20acc..c359a683 100644 --- a/backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts +++ b/backend/src/plugins/Logs/logFunctions/logThreadUpdate.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { ThreadChannel } from "discord.js"; import { channelToTemplateSafeChannel } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogThreadUpdateData { oldThread: ThreadChannel; @@ -22,8 +23,7 @@ export function logThreadUpdate(pluginData: GuildPluginData, dat differenceString: data.differenceString, }), { - channel: data.newThread.parentId, - category: data.newThread.parent?.parentId, + ...resolveChannelIds(data.newThread), }, ); } diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts index 4bdca809..9940a1a3 100644 --- a/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceDisconnect.ts @@ -9,6 +9,7 @@ import { memberToTemplateSafeMember, userToTemplateSafeUser, } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogVoiceChannelForceDisconnectData { mod: User; @@ -31,8 +32,7 @@ export function logVoiceChannelForceDisconnect( { userId: data.member.id, roles: Array.from(data.member.roles.cache.keys()), - channel: data.oldChannel.id, - category: data.oldChannel.parentId, + ...resolveChannelIds(data.oldChannel), bot: data.member.user.bot, }, ); diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts index 5f5ec1e7..90b5478c 100644 --- a/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelForceMove.ts @@ -9,6 +9,7 @@ import { memberToTemplateSafeMember, userToTemplateSafeUser, } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogVoiceChannelForceMoveData { mod: User; @@ -33,8 +34,7 @@ export function logVoiceChannelForceMove( { userId: data.member.id, roles: Array.from(data.member.roles.cache.keys()), - channel: data.newChannel.id, - category: data.newChannel.parentId, + ...resolveChannelIds(data.newChannel), bot: data.member.user.bot, }, ); diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts index 07c44309..71d5ea0c 100644 --- a/backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelJoin.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildVoiceChannel, GuildMember } from "discord.js"; import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogVoiceChannelJoinData { member: GuildMember; @@ -22,8 +23,7 @@ export function logVoiceChannelJoin(pluginData: GuildPluginData, { userId: data.member.id, roles: Array.from(data.member.roles.cache.keys()), - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), bot: data.member.user.bot, }, ); diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts index 3fc67abb..509494fc 100644 --- a/backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelLeave.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildVoiceChannel, GuildMember } from "discord.js"; import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogVoiceChannelLeaveData { member: GuildMember; @@ -22,8 +23,7 @@ export function logVoiceChannelLeave(pluginData: GuildPluginData { userId: data.member.id, roles: Array.from(data.member.roles.cache.keys()), - channel: data.channel.id, - category: data.channel.parentId, + ...resolveChannelIds(data.channel), bot: data.member.user.bot, }, ); diff --git a/backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts b/backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts index 14e47ccd..56090907 100644 --- a/backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts +++ b/backend/src/plugins/Logs/logFunctions/logVoiceChannelMove.ts @@ -5,6 +5,7 @@ import { log } from "../util/log"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter"; import { BaseGuildVoiceChannel, GuildMember } from "discord.js"; import { channelToTemplateSafeChannel, memberToTemplateSafeMember } from "../../../utils/templateSafeObjects"; +import { resolveChannelIds } from "../../../utils/resolveChannelIds"; interface LogVoiceChannelMoveData { member: GuildMember; @@ -24,8 +25,7 @@ export function logVoiceChannelMove(pluginData: GuildPluginData, { userId: data.member.id, roles: Array.from(data.member.roles.cache.keys()), - channel: data.newChannel.id, - category: data.newChannel.parentId, + ...resolveChannelIds(data.newChannel), bot: data.member.user.bot, }, ); diff --git a/backend/src/plugins/Logs/types.ts b/backend/src/plugins/Logs/types.ts index d1905b8e..511636a6 100644 --- a/backend/src/plugins/Logs/types.ts +++ b/backend/src/plugins/Logs/types.ts @@ -42,6 +42,7 @@ const LogChannel = t.partial({ excluded_message_regexes: t.array(TRegex), excluded_channels: t.array(t.string), excluded_categories: t.array(t.string), + excluded_threads: t.array(t.string), exclude_bots: t.boolean, excluded_roles: t.array(t.string), format: tNullable(tLogFormats), diff --git a/backend/src/plugins/Logs/util/log.ts b/backend/src/plugins/Logs/util/log.ts index f95e1f07..26be9585 100644 --- a/backend/src/plugins/Logs/util/log.ts +++ b/backend/src/plugins/Logs/util/log.ts @@ -22,6 +22,7 @@ interface ExclusionData { roles?: Snowflake[] | null; channel?: Snowflake | null; category?: Snowflake | null; + thread?: Snowflake | null; messageTextContent?: string | null; } @@ -58,6 +59,10 @@ async function shouldExclude( return true; } + if (opts.excluded_threads && exclusionData.thread && opts.excluded_threads.includes(exclusionData.thread)) { + return true; + } + if (opts.excluded_message_regexes && exclusionData.messageTextContent) { for (const regex of opts.excluded_message_regexes) { const matches = await pluginData.state.regexRunner diff --git a/backend/src/utils/isDmChannel.ts b/backend/src/utils/isDmChannel.ts new file mode 100644 index 00000000..ad1a5781 --- /dev/null +++ b/backend/src/utils/isDmChannel.ts @@ -0,0 +1,6 @@ +import { Channel, DMChannel } from "discord.js"; +import { ChannelTypeStrings } from "src/types"; + +export function isDmChannel(channel: Channel): channel is DMChannel { + return channel.type === ChannelTypeStrings.DM || channel.type === ChannelTypeStrings.GROUP; +} diff --git a/backend/src/utils/isGuildChannel.ts b/backend/src/utils/isGuildChannel.ts new file mode 100644 index 00000000..fa5a5199 --- /dev/null +++ b/backend/src/utils/isGuildChannel.ts @@ -0,0 +1,5 @@ +import { Channel, GuildChannel } from "discord.js"; + +export function isGuildChannel(channel: Channel): channel is GuildChannel { + return channel.type.startsWith("GUILD_"); +} diff --git a/backend/src/utils/isThreadChannel.ts b/backend/src/utils/isThreadChannel.ts new file mode 100644 index 00000000..6a1060df --- /dev/null +++ b/backend/src/utils/isThreadChannel.ts @@ -0,0 +1,10 @@ +import { Channel, ThreadChannel } from "discord.js"; +import { ChannelTypeStrings } from "src/types"; + +export function isThreadChannel(channel: Channel): channel is ThreadChannel { + return ( + channel.type === ChannelTypeStrings.NEWS_THREAD || + channel.type === ChannelTypeStrings.PUBLIC_THREAD || + channel.type === ChannelTypeStrings.PRIVATE_THREAD + ); +} diff --git a/backend/src/utils/resolveChannelIds.ts b/backend/src/utils/resolveChannelIds.ts new file mode 100644 index 00000000..bc7493e9 --- /dev/null +++ b/backend/src/utils/resolveChannelIds.ts @@ -0,0 +1,53 @@ +import { ChannelType } from "discord-api-types/v9"; +import { CategoryChannel, Channel } from "discord.js"; +import { ChannelTypes } from "discord.js/typings/enums"; +import { ChannelTypeStrings } from "src/types"; +import { isDmChannel } from "./isDmChannel"; +import { isGuildChannel } from "./isGuildChannel"; +import { isThreadChannel } from "./isThreadChannel"; + +type ResolvedChannelIds = { + category: string | null; + channel: string | null; + thread: string | null; +}; + +export function resolveChannelIds(channel: Channel): ResolvedChannelIds { + if (isDmChannel(channel)) { + return { + category: null, + channel: channel.id, + thread: null, + }; + } + + if (isThreadChannel(channel)) { + return { + category: channel.parent?.parentId || null, + channel: channel.parentId, + thread: channel.id, + }; + } + + if (channel instanceof CategoryChannel) { + return { + category: channel.id, + channel: null, + thread: null, + }; + } + + if (isGuildChannel(channel)) { + return { + category: channel.parentId, + channel: channel.id, + thread: null, + }; + } + + return { + category: null, + channel: channel.id, + thread: null, + }; +} From ec8523ce751891affafb376380c40facc4e89cac Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 13 Aug 2022 23:23:44 +0300 Subject: [PATCH 06/11] fix(docker): add mysql container health check This makes the prepare_backend container (which runs migrations) wait until the mysql server is actually running, not just the container. Thanks to Skyz on Discord for the implementation. --- docker-compose.production.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 6ab79342..9b6a477d 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -24,6 +24,11 @@ services: volumes: - ./docker/production/data/mysql:/var/lib/mysql command: --authentication-policy=mysql_native_password + healthcheck: + test: "/usr/bin/mysql --user=root --password=\"${DOCKER_PROD_MYSQL_ROOT_PASSWORD}\" --execute \"SHOW DATABASES;\"" + interval: 5s + timeout: 300s + retries: 60 prepare_backend: build: @@ -32,7 +37,8 @@ services: DOCKER_USER_UID: ${DOCKER_USER_UID:?Missing DOCKER_USER_UID} DOCKER_USER_GID: ${DOCKER_USER_GID:?Missing DOCKER_USER_GID} depends_on: - - mysql + mysql: + condition: service_healthy volumes: - ./:/zeppelin command: |- From 3f16214ae00bc0f39eb1659ee27543fd4573d288 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 13 Aug 2022 23:29:15 +0300 Subject: [PATCH 07/11] chore: update .clabot contributors list --- .clabot | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.clabot b/.clabot index e4625a4a..6ca4d958 100644 --- a/.clabot +++ b/.clabot @@ -1,19 +1,22 @@ { "contributors": [ - "dependabot", + "BanTheNons", "CleverSource", "DarkView", + "DenverCoder1", "Jernik", - "WeebHiroyuki", + "Rstar284", "almeidx", "axisiscool", "dexbiobot", "greenbigfrog", + "k200-1", "metal0", + "paolojpa", "roflmaoqwerty", + "thewilloftheshadow", "usoka", - "vcokltfre", - "Rstar284" + "vcokltfre" ], "message": "Thank you for contributing to Zeppelin! We require contributors to sign our Contributor License Agreement (CLA). To let us review and merge your code, please visit https://github.com/ZeppelinBot/CLA to sign the CLA!" } From 00591510ffcf300ac393e850b8190cc8eac32fd9 Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 13 Aug 2022 21:47:24 +0100 Subject: [PATCH 08/11] Automod add changeperms action (#309) * initial * fix typings UwU * check no perms for overrides * cleanup & add template rendering * remove defaults Co-authored-by: Almeida * Update backend/src/plugins/Automod/actions/changePerms.ts Co-authored-by: Almeida * Update backend/src/plugins/Automod/actions/changePerms.ts Co-authored-by: Almeida * Update backend/src/plugins/Automod/actions/changePerms.ts Co-authored-by: Almeida * .resolve instead of .fetch Co-authored-by: Almeida * fix * add more template variables * rename msg to message * .edit instead of .create Signed-off-by: GitHub Signed-off-by: GitHub Co-authored-by: metal Co-authored-by: Almeida --- .../Automod/actions/availableActions.ts | 3 + .../plugins/Automod/actions/changePerms.ts | 96 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 backend/src/plugins/Automod/actions/changePerms.ts diff --git a/backend/src/plugins/Automod/actions/availableActions.ts b/backend/src/plugins/Automod/actions/availableActions.ts index c37f3a39..3f253bc0 100644 --- a/backend/src/plugins/Automod/actions/availableActions.ts +++ b/backend/src/plugins/Automod/actions/availableActions.ts @@ -6,6 +6,7 @@ import { AlertAction } from "./alert"; import { ArchiveThreadAction } from "./archiveThread"; import { BanAction } from "./ban"; import { ChangeNicknameAction } from "./changeNickname"; +import { ChangePermsAction } from "./changePerms"; import { CleanAction } from "./clean"; import { KickAction } from "./kick"; import { LogAction } from "./log"; @@ -36,6 +37,7 @@ export const availableActions: Record> = { set_slowmode: SetSlowmodeAction, start_thread: StartThreadAction, archive_thread: ArchiveThreadAction, + change_perms: ChangePermsAction, }; export const AvailableActions = t.type({ @@ -56,4 +58,5 @@ export const AvailableActions = t.type({ set_slowmode: SetSlowmodeAction.configType, start_thread: StartThreadAction.configType, archive_thread: ArchiveThreadAction.configType, + change_perms: ChangePermsAction.configType, }); diff --git a/backend/src/plugins/Automod/actions/changePerms.ts b/backend/src/plugins/Automod/actions/changePerms.ts new file mode 100644 index 00000000..ad060879 --- /dev/null +++ b/backend/src/plugins/Automod/actions/changePerms.ts @@ -0,0 +1,96 @@ +import { Permissions, PermissionString } from "discord.js"; +import * as t from "io-ts"; +import { automodAction } from "../helpers"; +import { tNullable, isValidSnowflake, tPartialDictionary } from "../../../utils"; +import { noop } from "knub/dist/utils"; +import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; +import { + guildToTemplateSafeGuild, + savedMessageToTemplateSafeSavedMessage, + userToTemplateSafeUser, +} from "../../../utils/templateSafeObjects"; + +export const ChangePermsAction = automodAction({ + configType: t.type({ + target: t.string, + channel: tNullable(t.string), + perms: tPartialDictionary(t.keyof(Permissions.FLAGS), tNullable(t.boolean)), + }), + defaultConfig: {}, + + async apply({ pluginData, contexts, actionConfig, ruleName }) { + const user = contexts.find((c) => c.user)?.user; + const message = contexts.find((c) => c.message)?.message; + + const renderTarget = async (str: string) => + renderTemplate( + str, + new TemplateSafeValueContainer({ + user: user ? userToTemplateSafeUser(user) : null, + guild: guildToTemplateSafeGuild(pluginData.guild), + message: message ? savedMessageToTemplateSafeSavedMessage(message) : null, + }), + ); + const renderChannel = async (str: string) => + renderTemplate( + str, + new TemplateSafeValueContainer({ + user: user ? userToTemplateSafeUser(user) : null, + guild: guildToTemplateSafeGuild(pluginData.guild), + message: message ? savedMessageToTemplateSafeSavedMessage(message) : null, + }), + ); + const target = await renderTarget(actionConfig.target); + const channelId = actionConfig.channel ? await renderChannel(actionConfig.channel) : null; + const role = pluginData.guild.roles.resolve(target); + if (!role) { + const member = await pluginData.guild.members.fetch(target).catch(noop); + if (!member) return; + } + + if (channelId && isValidSnowflake(channelId)) { + const channel = pluginData.guild.channels.resolve(channelId); + if (!channel || channel.isThread()) return; + const overwrite = channel.permissionOverwrites.cache.find((pw) => pw.id === target); + const allow = new Permissions(overwrite?.allow ?? 0n).serialize(); + const deny = new Permissions(overwrite?.deny ?? 0n).serialize(); + const newPerms: Partial> = {}; + + for (const key in allow) { + if (typeof actionConfig.perms[key] !== "undefined") { + newPerms[key] = actionConfig.perms[key]; + continue; + } + if (allow[key]) { + newPerms[key] = true; + } else if (deny[key]) { + newPerms[key] = false; + } + } + + // takes more code lines but looks cleaner imo + let hasPerms = false; + for (const key in newPerms) { + if (typeof newPerms[key] === "boolean") { + hasPerms = true; + break; + } + } + if (overwrite && !hasPerms) { + await channel.permissionOverwrites.delete(target).catch(noop); + return; + } + await channel.permissionOverwrites.edit(target, newPerms).catch(noop); + return; + } + + if (!role) return; + + const perms = new Permissions(role.permissions).serialize(); + for (const key in actionConfig.perms) { + perms[key] = actionConfig.perms[key]; + } + const permsArray = Object.keys(perms).filter((key) => perms[key]); + await role.setPermissions(new Permissions(permsArray)).catch(noop); + }, +}); From d846231855792bc426a0acebe12c4f9256490468 Mon Sep 17 00:00:00 2001 From: metal Date: Sat, 13 Aug 2022 21:47:47 +0100 Subject: [PATCH 09/11] Fix automod alert in news channels (#290) * yeah yeah * my prettier was f'ed up Co-authored-by: metal From 94802a665c66cc6946c8cea1586095ae0640bf5a Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 14 Aug 2022 00:06:20 +0300 Subject: [PATCH 10/11] fix: fix expired api permissions not being deleted --- backend/src/data/ApiPermissionAssignments.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/data/ApiPermissionAssignments.ts b/backend/src/data/ApiPermissionAssignments.ts index d6cf11db..a83a91f9 100644 --- a/backend/src/data/ApiPermissionAssignments.ts +++ b/backend/src/data/ApiPermissionAssignments.ts @@ -80,7 +80,8 @@ export class ApiPermissionAssignments extends BaseRepository { .createQueryBuilder() .where("expires_at IS NOT NULL") .andWhere("expires_at <= NOW()") - .delete(); + .delete() + .execute(); } async applyOwnerChange(guildId: string, newOwnerId: string) { From a15b2f02a91c8ef2a5410888d5f8665bcc3a6c96 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 14 Aug 2022 00:21:21 +0300 Subject: [PATCH 11/11] feat: small tweaks to tag list search --- .../src/plugins/Tags/commands/TagListCmd.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/backend/src/plugins/Tags/commands/TagListCmd.ts b/backend/src/plugins/Tags/commands/TagListCmd.ts index 4bc4691e..aaa42ec4 100644 --- a/backend/src/plugins/Tags/commands/TagListCmd.ts +++ b/backend/src/plugins/Tags/commands/TagListCmd.ts @@ -1,6 +1,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes"; import { createChunkedMessage } from "../../../utils"; import { tagsCmd } from "../types"; +import escapeStringRegexp from "escape-string-regexp"; export const TagListCmd = tagsCmd({ trigger: ["tag list", "tags", "taglist"], @@ -19,20 +20,16 @@ export const TagListCmd = tagsCmd({ const prefix = (await pluginData.config.getForMessage(msg)).prefix; const tagNames = tags.map((tag) => tag.tag).sort(); + const searchRegex = args.search ? new RegExp([...args.search].map((s) => escapeStringRegexp(s)).join(".*")) : null; - const filteredTags = args.search - ? tagNames.filter((tag) => - new RegExp( - args.search - .split("") - .map((char) => char.replace(/[.*+?^${}()|[\]\\]/, "\\$&")) - .join(".*") - ).test(tag) - ) - : tagNames; + const filteredTags = args.search ? tagNames.filter((tag) => searchRegex!.test(tag)) : tagNames; - const tagGroups = filteredTags.reduce((acc, tag) => { - const obj = { ...acc }; + if (filteredTags.length === 0) { + msg.channel.send("No tags matched the filter"); + return; + } + + const tagGroups = filteredTags.reduce((obj, tag) => { const tagUpper = tag.toUpperCase(); const key = /[A-Z]/.test(tagUpper[0]) ? tagUpper[0] : "#"; if (!(key in obj)) {