3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-14 05:45:02 +00:00

Update djs & knub ()

* update pkgs

Signed-off-by: GitHub <noreply@github.com>

* new knub typings

Signed-off-by: GitHub <noreply@github.com>

* more pkg updates

Signed-off-by: GitHub <noreply@github.com>

* more fixes

Signed-off-by: GitHub <noreply@github.com>

* channel typings

Signed-off-by: GitHub <noreply@github.com>

* more message utils typings fixes

Signed-off-by: GitHub <noreply@github.com>

* migrate permissions

Signed-off-by: GitHub <noreply@github.com>

* fix: InternalPoster webhookables

Signed-off-by: GitHub <noreply@github.com>

* djs typings: Attachment & Util

Signed-off-by: GitHub <noreply@github.com>

* more typings

Signed-off-by: GitHub <noreply@github.com>

* fix: rename permissionNames

Signed-off-by: GitHub <noreply@github.com>

* more fixes

Signed-off-by: GitHub <noreply@github.com>

* half the number of errors

* knub commands => messageCommands

Signed-off-by: GitHub <noreply@github.com>

* configPreprocessor => configParser

Signed-off-by: GitHub <noreply@github.com>

* fix channel.messages

Signed-off-by: GitHub <noreply@github.com>

* revert automod any typing

Signed-off-by: GitHub <noreply@github.com>

* more configParser typings

Signed-off-by: GitHub <noreply@github.com>

* revert

Signed-off-by: GitHub <noreply@github.com>

* remove knub type params

Signed-off-by: GitHub <noreply@github.com>

* fix more MessageEmbed / MessageOptions

Signed-off-by: GitHub <noreply@github.com>

* dumb commit for @almeidx to see why this is stupid

Signed-off-by: GitHub <noreply@github.com>

* temp disable custom_events

Signed-off-by: GitHub <noreply@github.com>

* more minor typings fixes - 23 err left

Signed-off-by: GitHub <noreply@github.com>

* update djs dep

* +debug build method (revert this)

Signed-off-by: GitHub <noreply@github.com>

* Revert "+debug build method (revert this)"

This reverts commit a80af1e729.

* Redo +debug build (Revert this)

Signed-off-by: GitHub <noreply@github.com>

* uniform before/after Load shorthands

Signed-off-by: GitHub <noreply@github.com>

* remove unused imports & add prettier plugin

Signed-off-by: GitHub <noreply@github.com>

* env fixes for web platform hosting

Signed-off-by: GitHub <noreply@github.com>

* feat: knub v32-next; related fixes

* fix: allow legacy keys in change_perms action

* fix: request Message Content intent

* fix: use Knub's config validation logic in API

* fix(dashboard): fix error when there are no message and/or slash commands in a plugin

* fix(automod): start_thread action thread options

* fix(CustomEvents): message command types

* chore: remove unneeded type annotation

* feat: add forum channel icon; use thread icon for news threads

* chore: make tslint happy

* chore: fix formatting

---------

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: almeidx <almeidx@pm.me>
Co-authored-by: Dragory <2606411+Dragory@users.noreply.github.com>
This commit is contained in:
Tiago R 2023-04-01 12:58:17 +01:00 committed by GitHub
parent 293115af22
commit 06877e90cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
476 changed files with 2965 additions and 3251 deletions

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { configUtils, CooldownManager } from "knub";
import { ConfigPreprocessorFn } from "knub/dist/config/configTypes";
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
import { GuildArchives } from "../../data/GuildArchives";
import { GuildLogs } from "../../data/GuildLogs";
@ -9,11 +9,13 @@ import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
import { MINUTES, SECONDS } from "../../utils";
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap";
import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap";
import { StrictValidationError } from "../../validatorUtils";
import { StrictValidationError, validate } from "../../validatorUtils";
import { CountersPlugin } from "../Counters/CountersPlugin";
import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin";
import { LogsPlugin } from "../Logs/LogsPlugin";
import { ModActionsPlugin } from "../ModActions/ModActionsPlugin";
import { MutesPlugin } from "../Mutes/MutesPlugin";
import { PhishermanPlugin } from "../Phisherman/PhishermanPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { availableActions } from "./actions/availableActions";
import { AntiraidClearCmd } from "./commands/AntiraidClearCmd";
@ -35,8 +37,6 @@ import { clearOldRecentSpam } from "./functions/clearOldRecentSpam";
import { pluginInfo } from "./info";
import { availableTriggers } from "./triggers/availableTriggers";
import { AutomodPluginType, ConfigSchema } from "./types";
import { PhishermanPlugin } from "../Phisherman/PhishermanPlugin";
import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin";
const defaultOptions = {
config: {
@ -63,13 +63,15 @@ const defaultOptions = {
/**
* Config preprocessor to set default values for triggers and perform extra validation
* TODO: Separate input and output types
*/
const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = (options) => {
if (options.config?.rules) {
const configParser = (input: unknown) => {
const rules = (input as any).rules;
if (rules) {
// Loop through each rule
for (const [name, rule] of Object.entries(options.config.rules)) {
for (const [name, rule] of Object.entries(rules)) {
if (rule == null) {
delete options.config.rules[name];
delete rules[name];
continue;
}
@ -97,7 +99,7 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = (options) =>
for (const triggerObj of rule["triggers"]) {
for (const triggerName in triggerObj) {
if (!availableTriggers[triggerName]) {
throw new StrictValidationError([`Unknown trigger '${triggerName}' in rule '${rule.name}'`]);
throw new StrictValidationError([`Unknown trigger '${triggerName}' in rule '${rule["name"]}'`]);
}
const triggerBlueprint = availableTriggers[triggerName];
@ -117,11 +119,11 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = (options) =>
if (white && black) {
throw new StrictValidationError([
`Cannot have both blacklist and whitelist enabled at rule <${rule.name}/match_attachment_type>`,
`Cannot have both blacklist and whitelist enabled at rule <${rule["name"]}/match_attachment_type>`,
]);
} else if (!white && !black) {
throw new StrictValidationError([
`Must have either blacklist or whitelist enabled at rule <${rule.name}/match_attachment_type>`,
`Must have either blacklist or whitelist enabled at rule <${rule["name"]}/match_attachment_type>`,
]);
}
}
@ -132,11 +134,11 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = (options) =>
if (white && black) {
throw new StrictValidationError([
`Cannot have both blacklist and whitelist enabled at rule <${rule.name}/match_mime_type>`,
`Cannot have both blacklist and whitelist enabled at rule <${rule["name"]}/match_mime_type>`,
]);
} else if (!white && !black) {
throw new StrictValidationError([
`Must have either blacklist or whitelist enabled at rule <${rule.name}/match_mime_type>`,
`Must have either blacklist or whitelist enabled at rule <${rule["name"]}/match_mime_type>`,
]);
}
}
@ -147,7 +149,7 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = (options) =>
if (rule["actions"]) {
for (const actionName in rule["actions"]) {
if (!availableActions[actionName]) {
throw new StrictValidationError([`Unknown action '${actionName}' in rule '${rule.name}'`]);
throw new StrictValidationError([`Unknown action '${actionName}' in rule '${rule["name"]}'`]);
}
const actionBlueprint = availableActions[actionName];
@ -163,9 +165,9 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = (options) =>
// Enable logging of automod actions by default
if (rule["actions"]) {
for (const actionName in rule.actions) {
for (const actionName in rule["actions"]) {
if (!availableActions[actionName]) {
throw new StrictValidationError([`Unknown action '${actionName}' in rule '${rule.name}'`]);
throw new StrictValidationError([`Unknown action '${actionName}' in rule '${rule["name"]}'`]);
}
}
@ -173,13 +175,18 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = (options) =>
rule["actions"]["log"] = true;
}
if (rule["actions"]["clean"] && rule["actions"]["start_thread"]) {
throw new StrictValidationError([`Cannot have both clean and start_thread at rule '${rule.name}'`]);
throw new StrictValidationError([`Cannot have both clean and start_thread at rule '${rule["name"]}'`]);
}
}
}
}
return options;
const error = validate(ConfigSchema, input);
if (error) {
throw error;
}
return input as t.TypeOf<typeof ConfigSchema>;
};
export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
@ -197,9 +204,8 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
InternalPosterPlugin,
],
configSchema: ConfigSchema,
defaultOptions,
configPreprocessor,
configParser,
customOverrideCriteriaFunctions: {
antiraid_level: (pluginData, matchParams, value) => {
@ -218,136 +224,126 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
// Messages use message events from SavedMessages, see onLoad below
],
commands: [AntiraidClearCmd, SetAntiraidCmd, ViewAntiraidCmd],
messageCommands: [AntiraidClearCmd, SetAntiraidCmd, ViewAntiraidCmd],
async beforeLoad(pluginData) {
pluginData.state.queue = new Queue();
const { state, guild } = pluginData;
pluginData.state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`);
state.queue = new Queue();
pluginData.state.recentActions = [];
state.regexRunner = getRegExpRunner(`guild-${guild.id}`);
pluginData.state.recentSpam = [];
state.recentActions = [];
pluginData.state.recentNicknameChanges = new Map();
state.recentSpam = [];
pluginData.state.ignoredRoleChanges = new Set();
state.recentNicknameChanges = new Map();
pluginData.state.cooldownManager = new CooldownManager();
state.ignoredRoleChanges = new Set();
pluginData.state.logs = new GuildLogs(pluginData.guild.id);
pluginData.state.savedMessages = GuildSavedMessages.getGuildInstance(pluginData.guild.id);
pluginData.state.antiraidLevels = GuildAntiraidLevels.getGuildInstance(pluginData.guild.id);
pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id);
state.cooldownManager = new CooldownManager();
pluginData.state.cachedAntiraidLevel = await pluginData.state.antiraidLevels.get();
state.logs = new GuildLogs(guild.id);
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
state.antiraidLevels = GuildAntiraidLevels.getGuildInstance(guild.id);
state.archives = GuildArchives.getGuildInstance(guild.id);
state.cachedAntiraidLevel = await state.antiraidLevels.get();
},
async afterLoad(pluginData) {
pluginData.state.clearRecentActionsInterval = setInterval(() => clearOldRecentActions(pluginData), 1 * MINUTES);
pluginData.state.clearRecentSpamInterval = setInterval(() => clearOldRecentSpam(pluginData), 1 * SECONDS);
pluginData.state.clearRecentNicknameChangesInterval = setInterval(
const { state, guild } = pluginData;
state.clearRecentActionsInterval = setInterval(() => clearOldRecentActions(pluginData), 1 * MINUTES);
state.clearRecentSpamInterval = setInterval(() => clearOldRecentSpam(pluginData), 1 * SECONDS);
state.clearRecentNicknameChangesInterval = setInterval(
() => clearOldRecentNicknameChanges(pluginData),
30 * SECONDS,
);
pluginData.state.onMessageCreateFn = (message) => runAutomodOnMessage(pluginData, message, false);
pluginData.state.savedMessages.events.on("create", pluginData.state.onMessageCreateFn);
pluginData.state.onMessageUpdateFn = (message) => runAutomodOnMessage(pluginData, message, true);
pluginData.state.savedMessages.events.on("update", pluginData.state.onMessageUpdateFn);
state.onMessageCreateFn = (message) => runAutomodOnMessage(pluginData, message, false);
state.savedMessages.events.on("create", state.onMessageCreateFn);
state.onMessageUpdateFn = (message) => runAutomodOnMessage(pluginData, message, true);
state.savedMessages.events.on("update", state.onMessageUpdateFn);
const countersPlugin = pluginData.getPlugin(CountersPlugin);
pluginData.state.onCounterTrigger = (name, triggerName, channelId, userId) => {
state.onCounterTrigger = (name, triggerName, channelId, userId) => {
runAutomodOnCounterTrigger(pluginData, name, triggerName, channelId, userId, false);
};
pluginData.state.onCounterReverseTrigger = (name, triggerName, channelId, userId) => {
state.onCounterReverseTrigger = (name, triggerName, channelId, userId) => {
runAutomodOnCounterTrigger(pluginData, name, triggerName, channelId, userId, true);
};
countersPlugin.onCounterEvent("trigger", pluginData.state.onCounterTrigger);
countersPlugin.onCounterEvent("reverseTrigger", pluginData.state.onCounterReverseTrigger);
countersPlugin.onCounterEvent("trigger", state.onCounterTrigger);
countersPlugin.onCounterEvent("reverseTrigger", state.onCounterReverseTrigger);
const modActionsEvents = pluginData.getPlugin(ModActionsPlugin).getEventEmitter();
pluginData.state.modActionsListeners = new Map();
pluginData.state.modActionsListeners.set("note", (userId: string) =>
runAutomodOnModAction(pluginData, "note", userId),
state.modActionsListeners = new Map();
state.modActionsListeners.set("note", (userId: string) => runAutomodOnModAction(pluginData, "note", userId));
state.modActionsListeners.set("warn", (userId: string, reason: string | undefined, isAutomodAction: boolean) =>
runAutomodOnModAction(pluginData, "warn", userId, reason, isAutomodAction),
);
pluginData.state.modActionsListeners.set(
"warn",
(userId: string, reason: string | undefined, isAutomodAction: boolean) =>
runAutomodOnModAction(pluginData, "warn", userId, reason, isAutomodAction),
state.modActionsListeners.set("kick", (userId: string, reason: string | undefined, isAutomodAction: boolean) =>
runAutomodOnModAction(pluginData, "kick", userId, reason, isAutomodAction),
);
pluginData.state.modActionsListeners.set(
"kick",
(userId: string, reason: string | undefined, isAutomodAction: boolean) =>
runAutomodOnModAction(pluginData, "kick", userId, reason, isAutomodAction),
state.modActionsListeners.set("ban", (userId: string, reason: string | undefined, isAutomodAction: boolean) =>
runAutomodOnModAction(pluginData, "ban", userId, reason, isAutomodAction),
);
pluginData.state.modActionsListeners.set(
"ban",
(userId: string, reason: string | undefined, isAutomodAction: boolean) =>
runAutomodOnModAction(pluginData, "ban", userId, reason, isAutomodAction),
);
pluginData.state.modActionsListeners.set("unban", (userId: string) =>
runAutomodOnModAction(pluginData, "unban", userId),
);
registerEventListenersFromMap(modActionsEvents, pluginData.state.modActionsListeners);
state.modActionsListeners.set("unban", (userId: string) => runAutomodOnModAction(pluginData, "unban", userId));
registerEventListenersFromMap(modActionsEvents, state.modActionsListeners);
const mutesEvents = pluginData.getPlugin(MutesPlugin).getEventEmitter();
pluginData.state.mutesListeners = new Map();
pluginData.state.mutesListeners.set(
"mute",
(userId: string, reason: string | undefined, isAutomodAction: boolean) =>
runAutomodOnModAction(pluginData, "mute", userId, reason, isAutomodAction),
state.mutesListeners = new Map();
state.mutesListeners.set("mute", (userId: string, reason: string | undefined, isAutomodAction: boolean) =>
runAutomodOnModAction(pluginData, "mute", userId, reason, isAutomodAction),
);
pluginData.state.mutesListeners.set("unmute", (userId: string) =>
runAutomodOnModAction(pluginData, "unmute", userId),
);
registerEventListenersFromMap(mutesEvents, pluginData.state.mutesListeners);
state.mutesListeners.set("unmute", (userId: string) => runAutomodOnModAction(pluginData, "unmute", userId));
registerEventListenersFromMap(mutesEvents, state.mutesListeners);
},
async beforeUnload(pluginData) {
const { state, guild } = pluginData;
const countersPlugin = pluginData.getPlugin(CountersPlugin);
if (pluginData.state.onCounterTrigger) {
countersPlugin.offCounterEvent("trigger", pluginData.state.onCounterTrigger);
if (state.onCounterTrigger) {
countersPlugin.offCounterEvent("trigger", state.onCounterTrigger);
}
if (pluginData.state.onCounterReverseTrigger) {
countersPlugin.offCounterEvent("reverseTrigger", pluginData.state.onCounterReverseTrigger);
if (state.onCounterReverseTrigger) {
countersPlugin.offCounterEvent("reverseTrigger", state.onCounterReverseTrigger);
}
const modActionsEvents = pluginData.getPlugin(ModActionsPlugin).getEventEmitter();
if (pluginData.state.modActionsListeners) {
unregisterEventListenersFromMap(modActionsEvents, pluginData.state.modActionsListeners);
if (state.modActionsListeners) {
unregisterEventListenersFromMap(modActionsEvents, state.modActionsListeners);
}
const mutesEvents = pluginData.getPlugin(MutesPlugin).getEventEmitter();
if (pluginData.state.mutesListeners) {
unregisterEventListenersFromMap(mutesEvents, pluginData.state.mutesListeners);
if (state.mutesListeners) {
unregisterEventListenersFromMap(mutesEvents, state.mutesListeners);
}
pluginData.state.queue.clear();
state.queue.clear();
discardRegExpRunner(`guild-${pluginData.guild.id}`);
discardRegExpRunner(`guild-${guild.id}`);
if (pluginData.state.clearRecentActionsInterval) {
clearInterval(pluginData.state.clearRecentActionsInterval);
if (state.clearRecentActionsInterval) {
clearInterval(state.clearRecentActionsInterval);
}
if (pluginData.state.clearRecentSpamInterval) {
clearInterval(pluginData.state.clearRecentSpamInterval);
if (state.clearRecentSpamInterval) {
clearInterval(state.clearRecentSpamInterval);
}
if (pluginData.state.clearRecentNicknameChangesInterval) {
clearInterval(pluginData.state.clearRecentNicknameChangesInterval);
if (state.clearRecentNicknameChangesInterval) {
clearInterval(state.clearRecentNicknameChangesInterval);
}
if (pluginData.state.onMessageCreateFn) {
pluginData.state.savedMessages.events.off("create", pluginData.state.onMessageCreateFn);
if (state.onMessageCreateFn) {
state.savedMessages.events.off("create", state.onMessageCreateFn);
}
if (pluginData.state.onMessageUpdateFn) {
pluginData.state.savedMessages.events.off("update", pluginData.state.onMessageUpdateFn);
if (state.onMessageUpdateFn) {
state.savedMessages.events.off("update", state.onMessageUpdateFn);
}
},
});

View file

@ -1,6 +1,5 @@
import { Permissions, Snowflake } from "discord.js";
import { PermissionFlagsBits, Snowflake } from "discord.js";
import * as t from "io-ts";
import { LogType } from "../../../data/LogType";
import { nonNullish, unique } from "../../../utils";
import { canAssignRole } from "../../../utils/canAssignRole";
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
@ -10,7 +9,7 @@ import { LogsPlugin } from "../../Logs/LogsPlugin";
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
import { automodAction } from "../helpers";
const p = Permissions.FLAGS;
const p = PermissionFlagsBits;
export const AddRolesAction = automodAction({
configType: t.array(t.string),
@ -20,7 +19,7 @@ export const AddRolesAction = automodAction({
const members = unique(contexts.map((c) => c.member).filter(nonNullish));
const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!;
const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES);
const missingPermissions = getMissingPermissions(me.permissions, p.ManageRoles);
if (missingPermissions) {
const logs = pluginData.getPlugin(LogsPlugin);
logs.logBotAlert({

View file

@ -1,8 +1,7 @@
import * as t from "io-ts";
import { LogType } from "../../../data/LogType";
import { CountersPlugin } from "../../Counters/CountersPlugin";
import { automodAction } from "../helpers";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
export const AddToCounterAction = automodAction({
configType: t.type({

View file

@ -1,4 +1,4 @@
import { Snowflake, TextChannel, ThreadChannel } from "discord.js";
import { Snowflake } from "discord.js";
import * as t from "io-ts";
import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions";
import { LogType } from "../../../data/LogType";
@ -9,21 +9,19 @@ import {
TemplateSafeValueContainer,
} from "../../../templateFormatter";
import {
createChunkedMessage,
chunkMessageLines,
isTruthy,
messageLink,
stripObjectToScalars,
tAllowedMentions,
tNormalizedNullOptional,
isTruthy,
verboseChannelMention,
validateAndParseMessageContent,
chunkMessageLines,
verboseChannelMention,
} from "../../../utils";
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { InternalPosterPlugin } from "../../InternalPoster/InternalPosterPlugin";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
import { TemplateSafeUser, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
import { InternalPosterPlugin } from "../../InternalPoster/InternalPosterPlugin";
export const AlertAction = automodAction({
configType: t.type({
@ -38,7 +36,7 @@ export const AlertAction = automodAction({
const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake);
const logs = pluginData.getPlugin(LogsPlugin);
if (channel?.isText()) {
if (channel?.isTextBased()) {
const text = actionConfig.text;
const theMessageLink =
contexts[0].message && messageLink(pluginData.guild.id, contexts[0].message.channel_id, contexts[0].message.id);
@ -96,7 +94,7 @@ export const AlertAction = automodAction({
const chunks = chunkMessageLines(rendered);
for (const chunk of chunks) {
await poster.sendMessage(channel, {
content: rendered,
content: chunk,
allowedMentions: erisAllowedMentionsToDjsMentionOptions(actionConfig.allowed_mentions),
});
}

View file

@ -1,4 +1,4 @@
import { ThreadChannel } from "discord.js";
import { AnyThreadChannel } from "discord.js";
import * as t from "io-ts";
import { noop } from "../../../utils";
import { automodAction } from "../helpers";
@ -11,7 +11,7 @@ export const ArchiveThreadAction = automodAction({
const threads = contexts
.filter((c) => c.message?.channel_id)
.map((c) => pluginData.guild.channels.cache.get(c.message!.channel_id))
.filter((c): c is ThreadChannel => c?.isThread() ?? false);
.filter((c): c is AnyThreadChannel => c?.isThread() ?? false);
for (const thread of threads) {
await thread.setArchived().catch(noop);

View file

@ -1,5 +1,4 @@
import * as t from "io-ts";
import { LogType } from "../../../data/LogType";
import { nonNullish, unique } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";

View file

@ -1,20 +1,72 @@
import { Permissions, PermissionString } from "discord.js";
import { PermissionsBitField, PermissionsString } 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 { isValidSnowflake, noop, tNullable, tPartialDictionary } from "../../../utils";
import {
guildToTemplateSafeGuild,
savedMessageToTemplateSafeSavedMessage,
userToTemplateSafeUser,
} from "../../../utils/templateSafeObjects";
import { automodAction } from "../helpers";
type LegacyPermMap = Record<string, keyof (typeof PermissionsBitField)["Flags"]>;
const legacyPermMap = {
CREATE_INSTANT_INVITE: "CreateInstantInvite",
KICK_MEMBERS: "KickMembers",
BAN_MEMBERS: "BanMembers",
ADMINISTRATOR: "Administrator",
MANAGE_CHANNELS: "ManageChannels",
MANAGE_GUILD: "ManageGuild",
ADD_REACTIONS: "AddReactions",
VIEW_AUDIT_LOG: "ViewAuditLog",
PRIORITY_SPEAKER: "PrioritySpeaker",
STREAM: "Stream",
VIEW_CHANNEL: "ViewChannel",
SEND_MESSAGES: "SendMessages",
SEND_TTSMESSAGES: "SendTTSMessages",
MANAGE_MESSAGES: "ManageMessages",
EMBED_LINKS: "EmbedLinks",
ATTACH_FILES: "AttachFiles",
READ_MESSAGE_HISTORY: "ReadMessageHistory",
MENTION_EVERYONE: "MentionEveryone",
USE_EXTERNAL_EMOJIS: "UseExternalEmojis",
VIEW_GUILD_INSIGHTS: "ViewGuildInsights",
CONNECT: "Connect",
SPEAK: "Speak",
MUTE_MEMBERS: "MuteMembers",
DEAFEN_MEMBERS: "DeafenMembers",
MOVE_MEMBERS: "MoveMembers",
USE_VAD: "UseVAD",
CHANGE_NICKNAME: "ChangeNickname",
MANAGE_NICKNAMES: "ManageNicknames",
MANAGE_ROLES: "ManageRoles",
MANAGE_WEBHOOKS: "ManageWebhooks",
MANAGE_EMOJIS_AND_STICKERS: "ManageEmojisAndStickers",
USE_APPLICATION_COMMANDS: "UseApplicationCommands",
REQUEST_TO_SPEAK: "RequestToSpeak",
MANAGE_EVENTS: "ManageEvents",
MANAGE_THREADS: "ManageThreads",
CREATE_PUBLIC_THREADS: "CreatePublicThreads",
CREATE_PRIVATE_THREADS: "CreatePrivateThreads",
USE_EXTERNAL_STICKERS: "UseExternalStickers",
SEND_MESSAGES_IN_THREADS: "SendMessagesInThreads",
USE_EMBEDDED_ACTIVITIES: "UseEmbeddedActivities",
MODERATE_MEMBERS: "ModerateMembers",
} satisfies LegacyPermMap;
const realToLegacyMap = Object.entries(legacyPermMap).reduce((map, pair) => {
map[pair[1]] = pair[0];
return map;
}, {}) as Record<keyof typeof PermissionsBitField.Flags, keyof typeof legacyPermMap>;
export const ChangePermsAction = automodAction({
configType: t.type({
target: t.string,
channel: tNullable(t.string),
perms: tPartialDictionary(t.keyof(Permissions.FLAGS), tNullable(t.boolean)),
perms: tPartialDictionary(
t.union([t.keyof(PermissionsBitField.Flags), t.keyof(legacyPermMap)]),
tNullable(t.boolean),
),
}),
defaultConfig: {},
@ -52,13 +104,15 @@ export const ChangePermsAction = automodAction({
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<Record<PermissionString, boolean | null>> = {};
const allow = new PermissionsBitField(overwrite?.allow ?? 0n).serialize();
const deny = new PermissionsBitField(overwrite?.deny ?? 0n).serialize();
const newPerms: Partial<Record<PermissionsString, boolean | null>> = {};
for (const key in allow) {
if (typeof actionConfig.perms[key] !== "undefined") {
newPerms[key] = actionConfig.perms[key];
const legacyKey = realToLegacyMap[key];
const configEntry = actionConfig.perms[key] ?? actionConfig.perms[legacyKey];
if (typeof configEntry !== "undefined") {
newPerms[key] = configEntry;
continue;
}
if (allow[key]) {
@ -86,11 +140,12 @@ export const ChangePermsAction = automodAction({
if (!role) return;
const perms = new Permissions(role.permissions).serialize();
const perms = new PermissionsBitField(role.permissions).serialize();
for (const key in actionConfig.perms) {
perms[key] = actionConfig.perms[key];
const realKey = legacyPermMap[key] ?? key;
perms[realKey] = actionConfig.perms[key];
}
const permsArray = <PermissionString[]>Object.keys(perms).filter((key) => perms[key]);
await role.setPermissions(new Permissions(permsArray)).catch(noop);
const permsArray = <PermissionsString[]>Object.keys(perms).filter((key) => perms[key]);
await role.setPermissions(new PermissionsBitField(permsArray)).catch(noop);
},
});

View file

@ -1,4 +1,4 @@
import { Snowflake, TextChannel } from "discord.js";
import { GuildTextBasedChannel, Snowflake } from "discord.js";
import * as t from "io-ts";
import { LogType } from "../../../data/LogType";
import { noop } from "../../../utils";
@ -32,7 +32,7 @@ export const CleanAction = automodAction({
pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id);
}
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel;
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as GuildTextBasedChannel;
await channel.bulkDelete(messageIds as Snowflake[]).catch(noop);
}
},

View file

@ -1,9 +1,7 @@
import * as t from "io-ts";
import { LogType } from "../../../data/LogType";
import { isTruthy, stripObjectToScalars, unique } from "../../../utils";
import { isTruthy, unique } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
export const LogAction = automodAction({
configType: t.boolean,

View file

@ -1,5 +1,4 @@
import * as t from "io-ts";
import { LogType } from "../../../data/LogType";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils";
import { CaseArgs } from "../../Cases/types";

View file

@ -1,6 +1,5 @@
import { Permissions, Snowflake } from "discord.js";
import { PermissionFlagsBits, Snowflake } from "discord.js";
import * as t from "io-ts";
import { LogType } from "../../../data/LogType";
import { nonNullish, unique } from "../../../utils";
import { canAssignRole } from "../../../utils/canAssignRole";
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
@ -10,7 +9,7 @@ import { LogsPlugin } from "../../Logs/LogsPlugin";
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
import { automodAction } from "../helpers";
const p = Permissions.FLAGS;
const p = PermissionFlagsBits;
export const RemoveRolesAction = automodAction({
configType: t.array(t.string),
@ -21,7 +20,7 @@ export const RemoveRolesAction = automodAction({
const members = unique(contexts.map((c) => c.member).filter(nonNullish));
const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!;
const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES);
const missingPermissions = getMissingPermissions(me.permissions, p.ManageRoles);
if (missingPermissions) {
const logs = pluginData.getPlugin(LogsPlugin);
logs.logBotAlert({

View file

@ -1,6 +1,5 @@
import { MessageOptions, Permissions, Snowflake, TextChannel, ThreadChannel, User } from "discord.js";
import { GuildTextBasedChannel, MessageCreateOptions, PermissionsBitField, Snowflake, User } from "discord.js";
import * as t from "io-ts";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
import {
convertDelayStringToMS,
@ -14,10 +13,11 @@ import {
verboseChannelMention,
} from "../../../utils";
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
import { AutomodContext } from "../types";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
export const ReplyAction = automodAction({
configType: t.union([
@ -36,7 +36,7 @@ export const ReplyAction = automodAction({
.filter((c) => c.message?.channel_id)
.filter((c) => {
const channel = pluginData.guild.channels.cache.get(c.message!.channel_id as Snowflake);
return channel?.isText();
return channel?.isTextBased();
});
const contextsByChannelId = contextsWithTextChannels.reduce((map: Map<string, AutomodContext[]>, context) => {
@ -63,16 +63,16 @@ export const ReplyAction = automodAction({
const formatted =
typeof actionConfig === "string"
? await renderReplyText(actionConfig)
: ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageOptions);
: ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageCreateOptions);
if (formatted) {
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel;
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as GuildTextBasedChannel;
// Check for basic Send Messages and View Channel permissions
if (
!hasDiscordPermissions(
channel.permissionsFor(pluginData.client.user!.id),
Permissions.FLAGS.SEND_MESSAGES | Permissions.FLAGS.VIEW_CHANNEL,
PermissionsBitField.Flags.SendMessages | PermissionsBitField.Flags.ViewChannel,
)
) {
pluginData.getPlugin(LogsPlugin).logBotAlert({
@ -84,7 +84,10 @@ export const ReplyAction = automodAction({
// If the message is an embed, check for embed permissions
if (
typeof formatted !== "string" &&
!hasDiscordPermissions(channel.permissionsFor(pluginData.client.user!.id), Permissions.FLAGS.EMBED_LINKS)
!hasDiscordPermissions(
channel.permissionsFor(pluginData.client.user!.id),
PermissionsBitField.Flags.EmbedLinks,
)
) {
pluginData.getPlugin(LogsPlugin).logBotAlert({
body: `Missing permissions to reply **with an embed** in ${verboseChannelMention(
@ -96,7 +99,7 @@ export const ReplyAction = automodAction({
const messageContent = validateAndParseMessageContent(formatted);
const messageOpts: MessageOptions = {
const messageOpts: MessageCreateOptions = {
...messageContent,
allowedMentions: {
users: [user.id],
@ -118,7 +121,7 @@ export const ReplyAction = automodAction({
if (typeof actionConfig === "object" && actionConfig.auto_delete) {
const delay = convertDelayStringToMS(String(actionConfig.auto_delete))!;
setTimeout(() => !replyMsg.deleted && replyMsg.delete().catch(noop), delay);
setTimeout(() => replyMsg.deletable && replyMsg.delete().catch(noop), delay);
}
}
}

View file

@ -1,8 +1,7 @@
import * as t from "io-ts";
import { LogType } from "../../../data/LogType";
import { CountersPlugin } from "../../Counters/CountersPlugin";
import { automodAction } from "../helpers";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
export const SetCounterAction = automodAction({
configType: t.type({

View file

@ -1,10 +1,8 @@
import { Snowflake, TextChannel } from "discord.js";
import { ChannelType, GuildTextBasedChannel, Snowflake } from "discord.js";
import * as t from "io-ts";
import { ChannelTypeStrings } from "src/types";
import { LogType } from "../../../data/LogType";
import { convertDelayStringToMS, isDiscordAPIError, tDelayString, tNullable } from "../../../utils";
import { automodAction } from "../helpers";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
export const SetSlowmodeAction = automodAction({
configType: t.type({
@ -23,29 +21,27 @@ export const SetSlowmodeAction = automodAction({
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake);
// Only text channels and text channels within categories support slowmodes
if (!channel || !(channel.type === ChannelTypeStrings.TEXT || ChannelTypeStrings.CATEGORY)) {
if (!channel || (!channel.isTextBased() && channel.type !== ChannelType.GuildCategory)) {
continue;
}
const channelsToSlowmode: TextChannel[] = [];
if (channel.type === ChannelTypeStrings.CATEGORY) {
const channelsToSlowmode: GuildTextBasedChannel[] = [];
if (channel.type === ChannelType.GuildCategory) {
// Find all text channels within the category
for (const ch of pluginData.guild.channels.cache.values()) {
if (ch.parentId === channel.id && ch.type === ChannelTypeStrings.TEXT) {
channelsToSlowmode.push(ch as TextChannel);
if (ch.parentId === channel.id && ch.type === ChannelType.GuildText) {
channelsToSlowmode.push(ch);
}
}
} else {
channelsToSlowmode.push(channel as TextChannel);
channelsToSlowmode.push(channel);
}
const slowmodeSeconds = Math.ceil(slowmodeMs / 1000);
try {
for (const chan of channelsToSlowmode) {
await chan.edit({
rateLimitPerUser: slowmodeSeconds,
});
await chan.setRateLimitPerUser(slowmodeSeconds);
}
} catch (e) {
// Check for invalid form body -> indicates duration was too large

View file

@ -1,8 +1,12 @@
import { GuildFeature, ThreadAutoArchiveDuration } from "discord-api-types/v9";
import { TextChannel } from "discord.js";
import {
ChannelType,
GuildFeature,
GuildTextThreadCreateOptions,
ThreadAutoArchiveDuration,
ThreadChannel,
} from "discord.js";
import * as t from "io-ts";
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
import { ChannelTypeStrings } from "../../../types";
import { convertDelayStringToMS, MINUTES, noop, tDelayString, tNullable } from "../../../utils";
import { savedMessageToTemplateSafeSavedMessage, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { automodAction } from "../helpers";
@ -32,12 +36,11 @@ export const StartThreadAction = automodAction({
const threads = contexts.filter((c) => {
if (!c.message || !c.user) return false;
const channel = pluginData.guild.channels.cache.get(c.message.channel_id);
if (channel?.type !== ChannelTypeStrings.TEXT || !channel.isText()) return false; // for some reason the typing here for channel.type defaults to ThreadChannelTypes (?)
if (channel?.type !== ChannelType.GuildText || !channel.isTextBased()) return false; // for some reason the typing here for channel.type defaults to ThreadChannelTypes (?)
// check against max threads per channel
if (actionConfig.limit_per_channel && actionConfig.limit_per_channel > 0) {
const threadCount = channel.threads.cache.filter(
(tr) =>
tr.ownerId === pluginData.client.user!.id && !tr.deleted && !tr.archived && tr.parentId === channel.id,
(tr) => tr.ownerId === pluginData.client.user!.id && !tr.archived && tr.parentId === channel.id,
).size;
if (threadCount >= actionConfig.limit_per_channel) return false;
}
@ -53,7 +56,9 @@ export const StartThreadAction = automodAction({
: ThreadAutoArchiveDuration.OneHour;
for (const threadContext of threads) {
const channel = pluginData.guild.channels.cache.get(threadContext.message!.channel_id) as TextChannel;
const channel = pluginData.guild.channels.cache.get(threadContext.message!.channel_id);
if (!channel || !("threads" in channel) || channel.type === ChannelType.GuildForum) continue;
const renderThreadName = async (str: string) =>
renderTemplate(
str,
@ -63,20 +68,35 @@ export const StartThreadAction = automodAction({
}),
);
const threadName = await renderThreadName(actionConfig.name ?? "{user.tag}s thread");
const thread = await channel.threads
.create({
name: threadName,
autoArchiveDuration: autoArchive,
type:
actionConfig.private && guild.features.includes(GuildFeature.PrivateThreads)
? ChannelTypeStrings.PRIVATE_THREAD
: ChannelTypeStrings.PUBLIC_THREAD,
startMessage:
!actionConfig.private && guild.features.includes(GuildFeature.PrivateThreads)
? threadContext.message!.id
: undefined,
})
.catch(noop);
const threadOptions: GuildTextThreadCreateOptions<unknown> = {
name: threadName,
autoArchiveDuration: autoArchive,
startMessage:
!actionConfig.private && guild.features.includes(GuildFeature.PrivateThreads)
? threadContext.message!.id
: undefined,
};
let thread: ThreadChannel | undefined;
if (channel.type === ChannelType.GuildNews) {
thread = await channel.threads
.create({
...threadOptions,
type: ChannelType.AnnouncementThread,
})
.catch(() => undefined);
} else {
thread = await channel.threads
.create({
...threadOptions,
type: actionConfig.private ? ChannelType.PrivateThread : ChannelType.PublicThread,
startMessage:
!actionConfig.private && guild.features.includes(GuildFeature.PrivateThreads)
? threadContext.message!.id
: undefined,
})
.catch(() => undefined);
}
if (actionConfig.slowmode && thread) {
const dur = Math.ceil(Math.max(convertDelayStringToMS(actionConfig.slowmode) ?? 0, 0) / 1000);
if (dur > 0) {

View file

@ -1,9 +1,9 @@
import { typedGuildCommand } from "knub";
import { guildPluginMessageCommand } from "knub";
import { sendSuccessMessage } from "../../../pluginUtils";
import { setAntiraidLevel } from "../functions/setAntiraidLevel";
import { AutomodPluginType } from "../types";
export const AntiraidClearCmd = typedGuildCommand<AutomodPluginType>()({
export const AntiraidClearCmd = guildPluginMessageCommand<AutomodPluginType>()({
trigger: ["antiraid clear", "antiraid reset", "antiraid none", "antiraid off"],
permission: "can_set_antiraid",

View file

@ -1,10 +1,10 @@
import { typedGuildCommand } from "knub";
import { guildPluginMessageCommand } from "knub";
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { setAntiraidLevel } from "../functions/setAntiraidLevel";
import { AutomodPluginType } from "../types";
export const SetAntiraidCmd = typedGuildCommand<AutomodPluginType>()({
export const SetAntiraidCmd = guildPluginMessageCommand<AutomodPluginType>()({
trigger: "antiraid",
permission: "can_set_antiraid",

View file

@ -1,7 +1,7 @@
import { typedGuildCommand } from "knub";
import { guildPluginMessageCommand } from "knub";
import { AutomodPluginType } from "../types";
export const ViewAntiraidCmd = typedGuildCommand<AutomodPluginType>()({
export const ViewAntiraidCmd = guildPluginMessageCommand<AutomodPluginType>()({
trigger: "antiraid",
permission: "can_view_antiraid",

View file

@ -1,9 +1,9 @@
import { typedGuildEventListener } from "knub";
import { guildPluginEventListener } from "knub";
import { RecentActionType } from "../constants";
import { runAutomod } from "../functions/runAutomod";
import { AutomodContext, AutomodPluginType } from "../types";
export const RunAutomodOnJoinEvt = typedGuildEventListener<AutomodPluginType>()({
export const RunAutomodOnJoinEvt = guildPluginEventListener<AutomodPluginType>()({
event: "guildMemberAdd",
listener({ pluginData, args: { member } }) {
const context: AutomodContext = {
@ -26,7 +26,7 @@ export const RunAutomodOnJoinEvt = typedGuildEventListener<AutomodPluginType>()(
},
});
export const RunAutomodOnLeaveEvt = typedGuildEventListener<AutomodPluginType>()({
export const RunAutomodOnLeaveEvt = guildPluginEventListener<AutomodPluginType>()({
event: "guildMemberRemove",
listener({ pluginData, args: { member } }) {
const context: AutomodContext = {

View file

@ -1,10 +1,10 @@
import { typedGuildEventListener } from "knub";
import { guildPluginEventListener } from "knub";
import diff from "lodash.difference";
import isEqual from "lodash.isequal";
import { runAutomod } from "../functions/runAutomod";
import { AutomodContext, AutomodPluginType } from "../types";
export const RunAutomodOnMemberUpdate = typedGuildEventListener<AutomodPluginType>()({
export const RunAutomodOnMemberUpdate = guildPluginEventListener<AutomodPluginType>()({
event: "guildMemberUpdate",
listener({ pluginData, args: { oldMember, newMember } }) {
if (!oldMember) return;

View file

@ -14,7 +14,6 @@ export async function runAutomodOnCounterTrigger(
) {
const user = userId ? await resolveUser(pluginData.client, userId) : undefined;
const member = (userId && (await resolveMember(pluginData.client, pluginData.guild, userId))) || undefined;
const prettyCounterName = pluginData.getPlugin(CountersPlugin).getPrettyNameForCounter(counterName);
const prettyTriggerName = pluginData
.getPlugin(CountersPlugin)

View file

@ -1,13 +1,12 @@
import { Snowflake } from "discord.js";
import { GuildPluginData } from "knub";
import moment from "moment-timezone";
import { performance } from "perf_hooks";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { profilingEnabled } from "../../../utils/easyProfiler";
import { addRecentActionsFromMessage } from "../functions/addRecentActionsFromMessage";
import { clearRecentActionsForMessage } from "../functions/clearRecentActionsForMessage";
import { runAutomod } from "../functions/runAutomod";
import { AutomodContext, AutomodPluginType } from "../types";
import { performance } from "perf_hooks";
import { profilingEnabled } from "../../../utils/easyProfiler";
export async function runAutomodOnMessage(
pluginData: GuildPluginData<AutomodPluginType>,

View file

@ -1,9 +1,9 @@
import { typedGuildEventListener } from "knub";
import { guildPluginEventListener } from "knub";
import { RecentActionType } from "../constants";
import { runAutomod } from "../functions/runAutomod";
import { AutomodContext, AutomodPluginType } from "../types";
export const RunAutomodOnThreadCreate = typedGuildEventListener<AutomodPluginType>()({
export const RunAutomodOnThreadCreate = guildPluginEventListener<AutomodPluginType>()({
event: "threadCreate",
async listener({ pluginData, args: { thread } }) {
const user = thread.ownerId
@ -32,7 +32,7 @@ export const RunAutomodOnThreadCreate = typedGuildEventListener<AutomodPluginTyp
},
});
export const RunAutomodOnThreadDelete = typedGuildEventListener<AutomodPluginType>()({
export const RunAutomodOnThreadDelete = guildPluginEventListener<AutomodPluginType>()({
event: "threadDelete",
async listener({ pluginData, args: { thread } }) {
const user = thread.ownerId
@ -54,7 +54,7 @@ export const RunAutomodOnThreadDelete = typedGuildEventListener<AutomodPluginTyp
},
});
export const RunAutomodOnThreadUpdate = typedGuildEventListener<AutomodPluginType>()({
export const RunAutomodOnThreadUpdate = guildPluginEventListener<AutomodPluginType>()({
event: "threadUpdate",
async listener({ pluginData, args: { oldThread, newThread: thread } }) {
const user = thread.ownerId

View file

@ -1,7 +1,7 @@
import { GuildPluginData } from "knub";
import { startProfiling } from "../../../utils/easyProfiler";
import { RECENT_ACTION_EXPIRY_TIME } from "../constants";
import { AutomodPluginType } from "../types";
import { startProfiling } from "../../../utils/easyProfiler";
export function clearOldRecentActions(pluginData: GuildPluginData<AutomodPluginType>) {
const stopProfiling = startProfiling(pluginData.getKnubInstance().profiler, "automod:fns:clearOldRecentActions");

View file

@ -1,7 +1,7 @@
import { GuildPluginData } from "knub";
import { startProfiling } from "../../../utils/easyProfiler";
import { RECENT_SPAM_EXPIRY_TIME } from "../constants";
import { AutomodPluginType } from "../types";
import { startProfiling } from "../../../utils/easyProfiler";
export function clearOldRecentSpam(pluginData: GuildPluginData<AutomodPluginType>) {
const stopProfiling = startProfiling(pluginData.getKnubInstance().profiler, "automod:fns:clearOldRecentSpam");

View file

@ -1,6 +1,6 @@
import { GuildPluginData } from "knub";
import { AutomodContext, AutomodPluginType } from "../types";
import { startProfiling } from "../../../utils/easyProfiler";
import { AutomodContext, AutomodPluginType } from "../types";
export function clearRecentActionsForMessage(pluginData: GuildPluginData<AutomodPluginType>, context: AutomodContext) {
const stopProfiling = startProfiling(

View file

@ -1,7 +1,7 @@
import { GuildPluginData } from "knub";
import { startProfiling } from "../../../utils/easyProfiler";
import { RecentActionType } from "../constants";
import { AutomodPluginType } from "../types";
import { startProfiling } from "../../../utils/easyProfiler";
export function findRecentSpam(
pluginData: GuildPluginData<AutomodPluginType>,

View file

@ -1,10 +1,10 @@
import { GuildPluginData } from "knub";
import moment from "moment-timezone";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { startProfiling } from "../../../utils/easyProfiler";
import { RecentActionType } from "../constants";
import { AutomodPluginType } from "../types";
import { getMatchingRecentActions } from "./getMatchingRecentActions";
import { startProfiling } from "../../../utils/easyProfiler";
export function getMatchingMessageRecentActions(
pluginData: GuildPluginData<AutomodPluginType>,

View file

@ -1,7 +1,7 @@
import { GuildPluginData } from "knub";
import { startProfiling } from "../../../utils/easyProfiler";
import { RecentActionType } from "../constants";
import { AutomodPluginType } from "../types";
import { startProfiling } from "../../../utils/easyProfiler";
export function getMatchingRecentActions(
pluginData: GuildPluginData<AutomodPluginType>,

View file

@ -1,4 +1,4 @@
import { Snowflake, TextChannel } from "discord.js";
import { ActivityType, Snowflake } from "discord.js";
import { GuildPluginData } from "knub";
import { messageSummary, verboseChannelMention } from "../../../utils";
import { AutomodContext, AutomodPluginType } from "../types";
@ -11,13 +11,13 @@ export function getTextMatchPartialSummary(
) {
if (type === "message") {
const message = context.message!;
const channel = pluginData.guild.channels.cache.get(message.channel_id as Snowflake) as TextChannel;
const channel = pluginData.guild.channels.cache.get(message.channel_id as Snowflake);
const channelMention = channel ? verboseChannelMention(channel) : `\`#${message.channel_id}\``;
return `message in ${channelMention}:\n${messageSummary(message)}`;
} else if (type === "embed") {
const message = context.message!;
const channel = pluginData.guild.channels.cache.get(message.channel_id as Snowflake) as TextChannel;
const channel = pluginData.guild.channels.cache.get(message.channel_id as Snowflake);
const channelMention = channel ? verboseChannelMention(channel) : `\`#${message.channel_id}\``;
return `message embed in ${channelMention}:\n${messageSummary(message)}`;
@ -29,6 +29,6 @@ export function getTextMatchPartialSummary(
const visibleName = context.member?.nickname || context.user!.username;
return `visible name: ${visibleName}`;
} else if (type === "customstatus") {
return `custom status: ${context.member!.presence?.activities.find((a) => a.type === "CUSTOM")?.name}`;
return `custom status: ${context.member!.presence?.activities.find((a) => a.type === ActivityType.Custom)?.name}`;
}
}

View file

@ -1,7 +1,8 @@
import { Constants, MessageEmbed } from "discord.js";
import { ActivityType, Embed } from "discord.js";
import { GuildPluginData } from "knub";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { resolveMember } from "../../../utils";
import { DeepMutable } from "../../../utils/typeUtils.js";
import { AutomodPluginType } from "../types";
type TextTriggerWithMultipleMatchTypes = {
@ -33,7 +34,7 @@ export async function* matchMultipleTextTypesOnMessage(
}
if (trigger.match_embeds && msg.data.embeds?.length) {
const copiedEmbed: MessageEmbed = JSON.parse(JSON.stringify(msg.data.embeds[0]));
const copiedEmbed: DeepMutable<Embed> = JSON.parse(JSON.stringify(msg.data.embeds[0]));
if (copiedEmbed.video) {
copiedEmbed.description = ""; // The description is not rendered, hence it doesn't need to be matched
}
@ -53,7 +54,7 @@ export async function* matchMultipleTextTypesOnMessage(
}
for (const activity of member.presence?.activities ?? []) {
if (activity.type === Constants.ActivityTypes[4]) {
if (activity.type === ActivityType.Custom) {
yield ["customstatus", `${activity.emoji} ${activity.name}`];
break;
}

View file

@ -1,4 +1,4 @@
import { Snowflake, TextChannel, ThreadChannel } from "discord.js";
import { Snowflake } from "discord.js";
import { GuildPluginData } from "knub";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { disableUserNotificationStrings, UserNotificationMethod } from "../../../utils";
@ -19,7 +19,7 @@ export function resolveActionContactMethods(
}
const channel = pluginData.guild.channels.cache.get(actionConfig.notifyChannel as Snowflake);
if (!channel?.isText()) {
if (!channel?.isTextBased()) {
throw new RecoverablePluginError(ERRORS.INVALID_USER_NOTIFICATION_CHANNEL);
}

View file

@ -1,13 +1,13 @@
import { Snowflake, TextChannel, ThreadChannel } from "discord.js";
import { GuildTextBasedChannel, Snowflake } from "discord.js";
import { GuildPluginData } from "knub";
import { performance } from "perf_hooks";
import { calculateBlocking, profilingEnabled } from "../../../utils/easyProfiler";
import { availableActions } from "../actions/availableActions";
import { CleanAction } from "../actions/clean";
import { AutomodTriggerMatchResult } from "../helpers";
import { availableTriggers } from "../triggers/availableTriggers";
import { AutomodContext, AutomodPluginType } from "../types";
import { checkAndUpdateCooldown } from "./checkAndUpdateCooldown";
import { performance } from "perf_hooks";
import { calculateBlocking, profilingEnabled } from "../../../utils/easyProfiler";
export async function runAutomod(pluginData: GuildPluginData<AutomodPluginType>, context: AutomodContext) {
const userId = context.user?.id || context.member?.id || context.message?.user_id;
@ -18,7 +18,7 @@ export async function runAutomod(pluginData: GuildPluginData<AutomodPluginType>,
const channelOrThread =
context.channel ??
(channelIdOrThreadId
? (pluginData.guild.channels.cache.get(channelIdOrThreadId as Snowflake) as TextChannel | ThreadChannel)
? (pluginData.guild.channels.cache.get(channelIdOrThreadId as Snowflake) as GuildTextBasedChannel)
: null);
const channelId = channelOrThread?.isThread() ? channelOrThread.parent?.id : channelIdOrThreadId;
const threadId = channelOrThread?.isThread() ? channelOrThread.id : null;

View file

@ -1,7 +1,5 @@
import { User } from "discord.js";
import { GuildPluginData } from "knub";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { LogType } from "../../../data/LogType";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { runAutomodOnAntiraidLevel } from "../events/runAutomodOnAntiraidLevel";
import { AutomodPluginType } from "../types";

View file

@ -1,6 +1,6 @@
import * as t from "io-ts";
import { GuildPluginData } from "knub";
import { Awaitable } from "knub/dist/utils";
import { Awaitable } from "../../utils/typeUtils";
import { AutomodContext, AutomodPluginType } from "./types";
interface BaseAutomodTriggerMatchResult {

View file

@ -1,5 +1,6 @@
import { trimPluginDescription } from "../../utils";
import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint";
import { ConfigSchema } from "./types";
export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
prettyName: "Automod",
@ -99,4 +100,5 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
{matchSummary}
~~~
`),
configSchema: ConfigSchema,
};

View file

@ -1,4 +1,4 @@
import { Snowflake, TextChannel } from "discord.js";
import { Snowflake } from "discord.js";
import * as t from "io-ts";
import { verboseChannelMention } from "../../../utils";
import { automodTrigger } from "../helpers";
@ -22,7 +22,7 @@ export const AnyMessageTrigger = automodTrigger<AnyMessageResultType>()({
},
renderMatchInformation({ pluginData, contexts, matchResult }) {
const channel = pluginData.guild.channels.cache.get(contexts[0].message!.channel_id as Snowflake) as TextChannel;
const channel = pluginData.guild.channels.cache.get(contexts[0].message!.channel_id as Snowflake);
return `Matched message (\`${contexts[0].message!.id}\`) in ${
channel ? verboseChannelMention(channel) : "Unknown Channel"
}`;

View file

@ -11,9 +11,9 @@ import { KickTrigger } from "./kick";
import { LineSpamTrigger } from "./lineSpam";
import { LinkSpamTrigger } from "./linkSpam";
import { MatchAttachmentTypeTrigger } from "./matchAttachmentType";
import { MatchMimeTypeTrigger } from "./matchMimeType";
import { MatchInvitesTrigger } from "./matchInvites";
import { MatchLinksTrigger } from "./matchLinks";
import { MatchMimeTypeTrigger } from "./matchMimeType";
import { MatchRegexTrigger } from "./matchRegex";
import { MatchWordsTrigger } from "./matchWords";
import { MemberJoinTrigger } from "./memberJoin";
@ -26,14 +26,14 @@ import { NoteTrigger } from "./note";
import { RoleAddedTrigger } from "./roleAdded";
import { RoleRemovedTrigger } from "./roleRemoved";
import { StickerSpamTrigger } from "./stickerSpam";
import { ThreadArchiveTrigger } from "./threadArchive";
import { ThreadCreateTrigger } from "./threadCreate";
import { ThreadCreateSpamTrigger } from "./threadCreateSpam";
import { ThreadDeleteTrigger } from "./threadDelete";
import { ThreadUnarchiveTrigger } from "./threadUnarchive";
import { UnbanTrigger } from "./unban";
import { UnmuteTrigger } from "./unmute";
import { WarnTrigger } from "./warn";
import { ThreadArchiveTrigger } from "./threadArchive";
import { ThreadUnarchiveTrigger } from "./threadUnarchive";
export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>> = {
any_message: AnyMessageTrigger,

View file

@ -1,4 +1,4 @@
import { Snowflake, TextChannel, Util } from "discord.js";
import { escapeInlineCode, Snowflake } from "discord.js";
import * as t from "io-ts";
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
import { automodTrigger } from "../helpers";
@ -66,12 +66,12 @@ export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
},
renderMatchInformation({ pluginData, contexts, matchResult }) {
const channel = pluginData.guild.channels.cache.get(contexts[0].message!.channel_id as Snowflake) as TextChannel;
const channel = pluginData.guild.channels.cache.get(contexts[0].message!.channel_id as Snowflake)!;
const prettyChannel = verboseChannelMention(channel);
return (
asSingleLine(`
Matched attachment type \`${Util.escapeInlineCode(matchResult.extra.matchedType)}\`
Matched attachment type \`${escapeInlineCode(matchResult.extra.matchedType)}\`
(${matchResult.extra.mode === "blacklist" ? "blacklisted" : "not in whitelist"})
in message (\`${contexts[0].message!.id}\`) in ${prettyChannel}:
`) + messageSummary(contexts[0].message!)

View file

@ -1,16 +1,15 @@
import { Util } from "discord.js";
import escapeStringRegexp from "escape-string-regexp";
import { escapeInlineCode } from "discord.js";
import * as t from "io-ts";
import { phishermanDomainIsSafe } from "../../../data/Phisherman";
import { allowTimeout } from "../../../RegExpRunner";
import { getUrlsInString, tNullable } from "../../../utils";
import { mergeRegexes } from "../../../utils/mergeRegexes";
import { mergeWordsIntoRegex } from "../../../utils/mergeWordsIntoRegex";
import { TRegex } from "../../../validatorUtils";
import { PhishermanPlugin } from "../../Phisherman/PhishermanPlugin";
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
import { automodTrigger } from "../helpers";
import { mergeRegexes } from "../../../utils/mergeRegexes";
import { mergeWordsIntoRegex } from "../../../utils/mergeWordsIntoRegex";
import { PhishermanPlugin } from "../../Phisherman/PhishermanPlugin";
import { phishermanDomainIsSafe } from "../../../data/Phisherman";
interface MatchResultType {
type: MatchableTextType;
@ -186,7 +185,7 @@ export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
renderMatchInformation({ pluginData, contexts, matchResult }) {
const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]);
let information = `Matched link \`${Util.escapeInlineCode(matchResult.extra.link)}\``;
let information = `Matched link \`${escapeInlineCode(matchResult.extra.link)}\``;
if (matchResult.extra.details) {
information += ` ${matchResult.extra.details}`;
}

View file

@ -1,7 +1,7 @@
import { automodTrigger } from "../helpers";
import { escapeInlineCode } from "discord.js";
import * as t from "io-ts";
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
import { GuildChannel, Util } from "discord.js";
import { automodTrigger } from "../helpers";
interface MatchResultType {
matchedType: string;
@ -65,13 +65,13 @@ export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({
renderMatchInformation({ pluginData, contexts, matchResult }) {
const { message } = contexts[0];
const channel = pluginData.guild.channels.resolve(message!.channel_id);
const prettyChannel = verboseChannelMention(channel as GuildChannel);
const channel = pluginData.guild.channels.resolve(message!.channel_id)!;
const prettyChannel = verboseChannelMention(channel);
const { matchedType, mode } = matchResult.extra;
return (
asSingleLine(`
Matched MIME type \`${Util.escapeInlineCode(matchedType)}\`
Matched MIME type \`${escapeInlineCode(matchedType)}\`
(${mode === "blacklist" ? "blacklisted" : "not in whitelist"})
in message (\`${message!.id}\`) in ${prettyChannel}
`) + messageSummary(message!)

View file

@ -1,12 +1,12 @@
import * as t from "io-ts";
import { allowTimeout } from "../../../RegExpRunner";
import { mergeRegexes } from "../../../utils/mergeRegexes";
import { normalizeText } from "../../../utils/normalizeText";
import { stripMarkdown } from "../../../utils/stripMarkdown";
import { TRegex } from "../../../validatorUtils";
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
import { automodTrigger } from "../helpers";
import { mergeRegexes } from "../../../utils/mergeRegexes";
interface MatchResultType {
pattern: string;

View file

@ -1,4 +1,3 @@
import { Util } from "discord.js";
import escapeStringRegexp from "escape-string-regexp";
import * as t from "io-ts";
import { normalizeText } from "../../../utils/normalizeText";

View file

@ -1,5 +1,4 @@
import { Snowflake } from "discord-api-types/v9";
import { User, Util } from "discord.js";
import { escapeBold, User, type Snowflake } from "discord.js";
import * as t from "io-ts";
import { tNullable } from "../../../utils";
import { automodTrigger } from "../helpers";
@ -49,7 +48,7 @@ export const ThreadArchiveTrigger = automodTrigger<ThreadArchiveResult>()({
const parentName = matchResult.extra.matchedThreadParentName;
const base = `Thread **#${threadName}** (\`${threadId}\`) has been archived in the **#${parentName}** (\`${parentId}\`) channel`;
if (threadOwner) {
return `${base} by **${Util.escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
return `${base} by **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
}
return base;
},

View file

@ -1,5 +1,4 @@
import { Snowflake } from "discord-api-types/v9";
import { User, Util } from "discord.js";
import { escapeBold, User, type Snowflake } from "discord.js";
import * as t from "io-ts";
import { automodTrigger } from "../helpers";
@ -41,7 +40,7 @@ export const ThreadCreateTrigger = automodTrigger<ThreadCreateResult>()({
const parentName = matchResult.extra.matchedThreadParentName;
const base = `Thread **#${threadName}** (\`${threadId}\`) has been created in the **#${parentName}** (\`${parentId}\`) channel`;
if (threadOwner) {
return `${base} by **${Util.escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
return `${base} by **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
}
return base;
},

View file

@ -1,5 +1,4 @@
import { Snowflake } from "discord-api-types/v9";
import { User, Util } from "discord.js";
import { escapeBold, User, type Snowflake } from "discord.js";
import * as t from "io-ts";
import { automodTrigger } from "../helpers";
@ -40,7 +39,7 @@ export const ThreadDeleteTrigger = automodTrigger<ThreadDeleteResult>()({
const parentId = matchResult.extra.matchedThreadParentId;
const parentName = matchResult.extra.matchedThreadParentName;
if (threadOwner) {
return `Thread **#${threadName ?? "Unknown"}** (\`${threadId}\`) created by **${Util.escapeBold(
return `Thread **#${threadName ?? "Unknown"}** (\`${threadId}\`) created by **${escapeBold(
threadOwner.tag,
)}** (\`${threadOwner.id}\`) in the **#${parentName}** (\`${parentId}\`) channel has been deleted`;
}

View file

@ -1,5 +1,4 @@
import { Snowflake } from "discord-api-types/v9";
import { User, Util } from "discord.js";
import { escapeBold, User, type Snowflake } from "discord.js";
import * as t from "io-ts";
import { tNullable } from "../../../utils";
import { automodTrigger } from "../helpers";
@ -49,7 +48,7 @@ export const ThreadUnarchiveTrigger = automodTrigger<ThreadUnarchiveResult>()({
const parentName = matchResult.extra.matchedThreadParentName;
const base = `Thread **#${threadName}** (\`${threadId}\`) has been unarchived in the **#${parentName}** (\`${parentId}\`) channel`;
if (threadOwner) {
return `${base} by **${Util.escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
return `${base} by **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
}
return base;
},

View file

@ -1,4 +1,4 @@
import { GuildMember, PartialGuildMember, TextChannel, ThreadChannel, User } from "discord.js";
import { GuildMember, GuildTextBasedChannel, PartialGuildMember, ThreadChannel, User } from "discord.js";
import * as t from "io-ts";
import { BasePluginType, CooldownManager } from "knub";
import { SavedMessage } from "../../data/entities/SavedMessage";
@ -139,7 +139,7 @@ export interface AutomodContext {
locked?: ThreadChannel;
unlocked?: ThreadChannel;
};
channel?: TextChannel | ThreadChannel;
channel?: GuildTextBasedChannel;
}
export interface RecentAction {