mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-03-15 05:41:51 +00:00
Merge branch 'master' of github.com:ZeppelinBot/Zeppelin into feat/application-commands
This commit is contained in:
commit
2c0e4b37ca
235 changed files with 3464 additions and 4799 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -82,3 +82,6 @@ npm-audit.txt
|
|||
*.debug.js
|
||||
|
||||
.vscode/
|
||||
|
||||
config-errors.txt
|
||||
/config-schema.json
|
||||
|
|
3449
backend/package-lock.json
generated
3449
backend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@zeppelin/backend",
|
||||
"name": "@zeppelinbot/backend",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"private": true,
|
||||
|
@ -24,6 +24,8 @@
|
|||
"migrate-rollback": "npm run typeorm -- migration:revert -d dist/backend/src/data/dataSource.js",
|
||||
"migrate-rollback-prod": "cross-env NODE_ENV=production npm run migrate",
|
||||
"migrate-rollback-dev": "cross-env NODE_ENV=development npm run build && npm run migrate",
|
||||
"validate-active-configs": "cross-env NODE_ENV=development node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --enable-source-maps dist/backend/src/validateActiveConfigs.js > ../config-errors.txt",
|
||||
"export-config-json-schema": "cross-env NODE_ENV=development node -r ./register-tsconfig-paths.js --unhandled-rejections=strict --enable-source-maps dist/backend/src/exportSchemas.js > ../config-schema.json",
|
||||
"test": "npm run build && npm run run-tests",
|
||||
"run-tests": "ava",
|
||||
"test-watch": "tsc-watch --onSuccess \"npx ava\""
|
||||
|
@ -35,14 +37,13 @@
|
|||
"cors": "^2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"deep-diff": "^1.0.2",
|
||||
"discord.js": "^14.11.0",
|
||||
"discord.js": "^14.14.1",
|
||||
"dotenv": "^4.0.0",
|
||||
"emoji-regex": "^8.0.0",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"express": "^4.17.0",
|
||||
"fp-ts": "^2.0.1",
|
||||
"humanize-duration": "^3.15.0",
|
||||
"io-ts": "^2.0.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"knub": "^32.0.0-next.16",
|
||||
"knub-command-manager": "^9.1.0",
|
||||
|
@ -96,7 +97,8 @@
|
|||
"@types/uuid": "^9.0.2",
|
||||
"ava": "^5.3.1",
|
||||
"rimraf": "^2.6.2",
|
||||
"source-map-support": "^0.5.16"
|
||||
"source-map-support": "^0.5.16",
|
||||
"zod-to-json-schema": "^3.22.3"
|
||||
},
|
||||
"ava": {
|
||||
"files": [
|
||||
|
|
|
@ -11,6 +11,7 @@ export enum ERRORS {
|
|||
MUTE_ROLE_ABOVE_ZEP,
|
||||
USER_ABOVE_ZEP,
|
||||
USER_NOT_MODERATABLE,
|
||||
TEMPLATE_PARSE_ERROR,
|
||||
}
|
||||
|
||||
export const RECOVERABLE_PLUGIN_ERROR_MESSAGES = {
|
||||
|
@ -24,6 +25,7 @@ export const RECOVERABLE_PLUGIN_ERROR_MESSAGES = {
|
|||
[ERRORS.MUTE_ROLE_ABOVE_ZEP]: "Specified mute role is above Zeppelin in the role hierarchy",
|
||||
[ERRORS.USER_ABOVE_ZEP]: "Cannot mute user, specified user is above Zeppelin in the role hierarchy",
|
||||
[ERRORS.USER_NOT_MODERATABLE]: "Cannot mute user, specified user is not moderatable",
|
||||
[ERRORS.TEMPLATE_PARSE_ERROR]: "Template parse error",
|
||||
};
|
||||
|
||||
export class RecoverablePluginError extends Error {
|
||||
|
|
|
@ -1,34 +1,99 @@
|
|||
import express from "express";
|
||||
import z from "zod";
|
||||
import { guildPlugins } from "../plugins/availablePlugins";
|
||||
import { indentLines } from "../utils";
|
||||
import { notFound } from "./responses";
|
||||
|
||||
function formatConfigSchema(schema) {
|
||||
if (schema._tag === "InterfaceType" || schema._tag === "PartialType") {
|
||||
function isZodObject(schema: z.ZodTypeAny): schema is z.ZodObject<any> {
|
||||
return schema._def.typeName === "ZodObject";
|
||||
}
|
||||
|
||||
function isZodRecord(schema: z.ZodTypeAny): schema is z.ZodRecord<any> {
|
||||
return schema._def.typeName === "ZodRecord";
|
||||
}
|
||||
|
||||
function isZodEffects(schema: z.ZodTypeAny): schema is z.ZodEffects<any, any> {
|
||||
return schema._def.typeName === "ZodEffects";
|
||||
}
|
||||
|
||||
function isZodOptional(schema: z.ZodTypeAny): schema is z.ZodOptional<any> {
|
||||
return schema._def.typeName === "ZodOptional";
|
||||
}
|
||||
|
||||
function isZodArray(schema: z.ZodTypeAny): schema is z.ZodArray<any> {
|
||||
return schema._def.typeName === "ZodArray";
|
||||
}
|
||||
|
||||
function isZodUnion(schema: z.ZodTypeAny): schema is z.ZodUnion<any> {
|
||||
return schema._def.typeName === "ZodUnion";
|
||||
}
|
||||
|
||||
function isZodNullable(schema: z.ZodTypeAny): schema is z.ZodNullable<any> {
|
||||
return schema._def.typeName === "ZodNullable";
|
||||
}
|
||||
|
||||
function isZodDefault(schema: z.ZodTypeAny): schema is z.ZodDefault<any> {
|
||||
return schema._def.typeName === "ZodDefault";
|
||||
}
|
||||
|
||||
function isZodLiteral(schema: z.ZodTypeAny): schema is z.ZodLiteral<any> {
|
||||
return schema._def.typeName === "ZodLiteral";
|
||||
}
|
||||
|
||||
function isZodIntersection(schema: z.ZodTypeAny): schema is z.ZodIntersection<any, any> {
|
||||
return schema._def.typeName === "ZodIntersection";
|
||||
}
|
||||
|
||||
function formatZodConfigSchema(schema: z.ZodTypeAny) {
|
||||
if (isZodObject(schema)) {
|
||||
return (
|
||||
`{\n` +
|
||||
Object.entries(schema.props)
|
||||
.map(([k, value]) => indentLines(`${k}: ${formatConfigSchema(value)}`, 2))
|
||||
Object.entries(schema._def.shape())
|
||||
.map(([k, value]) => indentLines(`${k}: ${formatZodConfigSchema(value as z.ZodTypeAny)}`, 2))
|
||||
.join("\n") +
|
||||
"\n}"
|
||||
);
|
||||
} else if (schema._tag === "DictionaryType") {
|
||||
return "{\n" + indentLines(`[string]: ${formatConfigSchema(schema.codomain)}`, 2) + "\n}";
|
||||
} else if (schema._tag === "ArrayType") {
|
||||
return `Array<${formatConfigSchema(schema.type)}>`;
|
||||
} else if (schema._tag === "UnionType") {
|
||||
if (schema.name.startsWith("Nullable<")) {
|
||||
return `Nullable<${formatConfigSchema(schema.types[0])}>`;
|
||||
} else if (schema.name.startsWith("Optional<")) {
|
||||
return `Optional<${formatConfigSchema(schema.types[0])}>`;
|
||||
} else {
|
||||
return schema.types.map((t) => formatConfigSchema(t)).join(" | ");
|
||||
}
|
||||
} else if (schema._tag === "IntersectionType") {
|
||||
return schema.types.map((t) => formatConfigSchema(t)).join(" & ");
|
||||
} else {
|
||||
return schema.name;
|
||||
}
|
||||
if (isZodRecord(schema)) {
|
||||
return "{\n" + indentLines(`[string]: ${formatZodConfigSchema(schema._def.valueType)}`, 2) + "\n}";
|
||||
}
|
||||
if (isZodEffects(schema)) {
|
||||
return formatZodConfigSchema(schema._def.schema);
|
||||
}
|
||||
if (isZodOptional(schema)) {
|
||||
return `Optional<${formatZodConfigSchema(schema._def.innerType)}>`;
|
||||
}
|
||||
if (isZodArray(schema)) {
|
||||
return `Array<${formatZodConfigSchema(schema._def.type)}>`;
|
||||
}
|
||||
if (isZodUnion(schema)) {
|
||||
return schema._def.options.map((t) => formatZodConfigSchema(t)).join(" | ");
|
||||
}
|
||||
if (isZodNullable(schema)) {
|
||||
return `Nullable<${formatZodConfigSchema(schema._def.innerType)}>`;
|
||||
}
|
||||
if (isZodDefault(schema)) {
|
||||
return formatZodConfigSchema(schema._def.innerType);
|
||||
}
|
||||
if (isZodLiteral(schema)) {
|
||||
return schema._def.value;
|
||||
}
|
||||
if (isZodIntersection(schema)) {
|
||||
return [formatZodConfigSchema(schema._def.left), formatZodConfigSchema(schema._def.right)].join(" & ");
|
||||
}
|
||||
if (schema._def.typeName === "ZodString") {
|
||||
return "string";
|
||||
}
|
||||
if (schema._def.typeName === "ZodNumber") {
|
||||
return "number";
|
||||
}
|
||||
if (schema._def.typeName === "ZodBoolean") {
|
||||
return "boolean";
|
||||
}
|
||||
if (schema._def.typeName === "ZodNever") {
|
||||
return "never";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
export function initDocs(app: express.Express) {
|
||||
|
@ -67,7 +132,7 @@ export function initDocs(app: express.Express) {
|
|||
}));
|
||||
|
||||
const defaultOptions = plugin.defaultOptions || {};
|
||||
const configSchema = plugin.info?.configSchema && formatConfigSchema(plugin.info.configSchema);
|
||||
const configSchema = plugin.info?.configSchema && formatZodConfigSchema(plugin.info.configSchema);
|
||||
|
||||
res.json({
|
||||
name,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { createTypeHelper } from "knub-command-manager";
|
|||
import {
|
||||
channelMentionRegex,
|
||||
convertDelayStringToMS,
|
||||
inputPatternToRegExp,
|
||||
isValidSnowflake,
|
||||
resolveMember,
|
||||
resolveUser,
|
||||
|
@ -26,7 +27,6 @@ import {
|
|||
} from "./utils";
|
||||
import { isValidTimezone } from "./utils/isValidTimezone";
|
||||
import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget";
|
||||
import { inputPatternToRegExp } from "./validatorUtils";
|
||||
|
||||
export const commandTypes = {
|
||||
...messageCommandBaseTypeConverters,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { ConfigValidationError, PluginConfigManager } from "knub";
|
||||
import moment from "moment-timezone";
|
||||
import { ZodError } from "zod";
|
||||
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
|
||||
import { guildPlugins } from "./plugins/availablePlugins";
|
||||
import { PartialZeppelinGuildConfigSchema, ZeppelinGuildConfig } from "./types";
|
||||
import { StrictValidationError, decodeAndValidateStrict } from "./validatorUtils";
|
||||
import { ZeppelinGuildConfig, zZeppelinGuildConfig } from "./types";
|
||||
import { formatZodIssue } from "./utils/formatZodIssue";
|
||||
|
||||
const pluginNameToPlugin = new Map<string, ZeppelinPlugin>();
|
||||
for (const plugin of guildPlugins) {
|
||||
|
@ -11,8 +12,10 @@ for (const plugin of guildPlugins) {
|
|||
}
|
||||
|
||||
export async function validateGuildConfig(config: any): Promise<string | null> {
|
||||
const validationResult = decodeAndValidateStrict(PartialZeppelinGuildConfigSchema, config);
|
||||
if (validationResult instanceof StrictValidationError) return validationResult.getErrors();
|
||||
const validationResult = zZeppelinGuildConfig.safeParse(config);
|
||||
if (!validationResult.success) {
|
||||
return validationResult.error.issues.map(formatZodIssue).join("\n");
|
||||
}
|
||||
|
||||
const guildConfig = config as ZeppelinGuildConfig;
|
||||
|
||||
|
@ -41,7 +44,10 @@ export async function validateGuildConfig(config: any): Promise<string | null> {
|
|||
try {
|
||||
await configManager.init();
|
||||
} catch (err) {
|
||||
if (err instanceof ConfigValidationError || err instanceof StrictValidationError) {
|
||||
if (err instanceof ZodError) {
|
||||
return `${pluginName}: ${err.issues.map(formatZodIssue).join("\n")}`;
|
||||
}
|
||||
if (err instanceof ConfigValidationError) {
|
||||
return `${pluginName}: ${err.message}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,12 @@ export class Configs extends BaseRepository {
|
|||
this.configs = dataSource.getRepository(Config);
|
||||
}
|
||||
|
||||
getActive() {
|
||||
return this.configs.find({
|
||||
where: { is_active: true },
|
||||
});
|
||||
}
|
||||
|
||||
getActiveByKey(key) {
|
||||
return this.configs.findOne({
|
||||
where: {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Guild, Snowflake } from "discord.js";
|
||||
import moment from "moment-timezone";
|
||||
import { isDefaultSticker } from "src/utils/isDefaultSticker";
|
||||
import { Repository } from "typeorm";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../templateFormatter";
|
||||
import { renderUsername, trimLines } from "../utils";
|
||||
import { decrypt, encrypt } from "../utils/crypt";
|
||||
import { isDefaultSticker } from "../utils/isDefaultSticker";
|
||||
import { channelToTemplateSafeChannel, guildToTemplateSafeGuild } from "../utils/templateSafeObjects";
|
||||
import { BaseGuildRepository } from "./BaseGuildRepository";
|
||||
import { dataSource } from "./dataSource";
|
||||
|
|
|
@ -12,7 +12,8 @@ import { CounterValue } from "./entities/CounterValue";
|
|||
const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS;
|
||||
const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS;
|
||||
|
||||
const MAX_COUNTER_VALUE = 2147483647; // 2^31-1, for MySQL INT
|
||||
export const MIN_COUNTER_VALUE = 0;
|
||||
export const MAX_COUNTER_VALUE = 2147483647; // 2^31-1, for MySQL INT
|
||||
|
||||
const decayQueue = new Queue();
|
||||
|
||||
|
@ -115,7 +116,9 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
userId = userId || "0";
|
||||
|
||||
const rawUpdate =
|
||||
change >= 0 ? `value = LEAST(value + ${change}, ${MAX_COUNTER_VALUE})` : `value = GREATEST(value ${change}, 0)`;
|
||||
change >= 0
|
||||
? `value = LEAST(value + ${change}, ${MAX_COUNTER_VALUE})`
|
||||
: `value = GREATEST(value ${change}, ${MIN_COUNTER_VALUE})`;
|
||||
|
||||
await this.counterValues.query(
|
||||
`
|
||||
|
@ -173,7 +176,7 @@ export class GuildCounters extends BaseGuildRepository {
|
|||
|
||||
const rawUpdate =
|
||||
decayAmountToApply >= 0
|
||||
? `GREATEST(value - ${decayAmountToApply}, 0)`
|
||||
? `GREATEST(value - ${decayAmountToApply}, ${MIN_COUNTER_VALUE})`
|
||||
: `LEAST(value + ${Math.abs(decayAmountToApply)}, ${MAX_COUNTER_VALUE})`;
|
||||
|
||||
// Using an UPDATE with ORDER BY in an attempt to avoid deadlocks from simultaneous decays
|
||||
|
|
|
@ -5,7 +5,7 @@ import { LogType } from "./LogType";
|
|||
const guildInstances: Map<string, GuildLogs> = new Map();
|
||||
|
||||
interface IIgnoredLog {
|
||||
type: LogType;
|
||||
type: keyof typeof LogType;
|
||||
ignoreId: any;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ export class GuildLogs extends events.EventEmitter {
|
|||
guildInstances.set(guildId, this);
|
||||
}
|
||||
|
||||
log(type: LogType, data: any, ignoreId?: string) {
|
||||
log(type: keyof typeof LogType, data: any, ignoreId?: string) {
|
||||
if (ignoreId && this.isLogIgnored(type, ignoreId)) {
|
||||
this.clearIgnoredLog(type, ignoreId);
|
||||
return;
|
||||
|
@ -36,7 +36,7 @@ export class GuildLogs extends events.EventEmitter {
|
|||
this.emit("log", { type, data });
|
||||
}
|
||||
|
||||
ignoreLog(type: LogType, ignoreId: any, timeout?: number) {
|
||||
ignoreLog(type: keyof typeof LogType, ignoreId: any, timeout?: number) {
|
||||
this.ignoredLogs.push({ type, ignoreId });
|
||||
|
||||
// Clear after expiry (15sec by default)
|
||||
|
@ -45,11 +45,11 @@ export class GuildLogs extends events.EventEmitter {
|
|||
}, timeout || 1000 * 15);
|
||||
}
|
||||
|
||||
isLogIgnored(type: LogType, ignoreId: any) {
|
||||
isLogIgnored(type: keyof typeof LogType, ignoreId: any) {
|
||||
return this.ignoredLogs.some((info) => type === info.type && ignoreId === info.ignoreId);
|
||||
}
|
||||
|
||||
clearIgnoredLog(type: LogType, ignoreId: any) {
|
||||
clearIgnoredLog(type: keyof typeof LogType, ignoreId: any) {
|
||||
this.ignoredLogs.splice(
|
||||
this.ignoredLogs.findIndex((info) => type === info.type && ignoreId === info.ignoreId),
|
||||
1,
|
||||
|
|
|
@ -1,102 +1,74 @@
|
|||
export enum LogType {
|
||||
MEMBER_WARN = 1,
|
||||
MEMBER_MUTE,
|
||||
MEMBER_UNMUTE,
|
||||
MEMBER_MUTE_EXPIRED,
|
||||
MEMBER_KICK,
|
||||
MEMBER_BAN,
|
||||
MEMBER_UNBAN,
|
||||
MEMBER_FORCEBAN,
|
||||
MEMBER_SOFTBAN,
|
||||
MEMBER_JOIN,
|
||||
MEMBER_LEAVE,
|
||||
MEMBER_ROLE_ADD,
|
||||
MEMBER_ROLE_REMOVE,
|
||||
MEMBER_NICK_CHANGE,
|
||||
MEMBER_USERNAME_CHANGE,
|
||||
MEMBER_RESTORE,
|
||||
|
||||
CHANNEL_CREATE,
|
||||
CHANNEL_DELETE,
|
||||
CHANNEL_UPDATE,
|
||||
|
||||
THREAD_CREATE,
|
||||
THREAD_DELETE,
|
||||
THREAD_UPDATE,
|
||||
|
||||
ROLE_CREATE,
|
||||
ROLE_DELETE,
|
||||
ROLE_UPDATE,
|
||||
|
||||
MESSAGE_EDIT,
|
||||
MESSAGE_DELETE,
|
||||
MESSAGE_DELETE_BULK,
|
||||
MESSAGE_DELETE_BARE,
|
||||
|
||||
VOICE_CHANNEL_JOIN,
|
||||
VOICE_CHANNEL_LEAVE,
|
||||
VOICE_CHANNEL_MOVE,
|
||||
|
||||
STAGE_INSTANCE_CREATE,
|
||||
STAGE_INSTANCE_DELETE,
|
||||
STAGE_INSTANCE_UPDATE,
|
||||
|
||||
EMOJI_CREATE,
|
||||
EMOJI_DELETE,
|
||||
EMOJI_UPDATE,
|
||||
|
||||
STICKER_CREATE,
|
||||
STICKER_DELETE,
|
||||
STICKER_UPDATE,
|
||||
|
||||
COMMAND,
|
||||
|
||||
MESSAGE_SPAM_DETECTED,
|
||||
CENSOR,
|
||||
CLEAN,
|
||||
|
||||
CASE_CREATE,
|
||||
|
||||
MASSUNBAN,
|
||||
MASSBAN,
|
||||
MASSMUTE,
|
||||
|
||||
MEMBER_TIMED_MUTE,
|
||||
MEMBER_TIMED_UNMUTE,
|
||||
MEMBER_TIMED_BAN,
|
||||
MEMBER_TIMED_UNBAN,
|
||||
|
||||
MEMBER_JOIN_WITH_PRIOR_RECORDS,
|
||||
OTHER_SPAM_DETECTED,
|
||||
|
||||
MEMBER_ROLE_CHANGES,
|
||||
VOICE_CHANNEL_FORCE_MOVE,
|
||||
VOICE_CHANNEL_FORCE_DISCONNECT,
|
||||
|
||||
CASE_UPDATE,
|
||||
|
||||
MEMBER_MUTE_REJOIN,
|
||||
|
||||
SCHEDULED_MESSAGE,
|
||||
POSTED_SCHEDULED_MESSAGE,
|
||||
|
||||
BOT_ALERT,
|
||||
AUTOMOD_ACTION,
|
||||
|
||||
SCHEDULED_REPEATED_MESSAGE,
|
||||
REPEATED_MESSAGE,
|
||||
|
||||
MESSAGE_DELETE_AUTO,
|
||||
|
||||
SET_ANTIRAID_USER,
|
||||
SET_ANTIRAID_AUTO,
|
||||
|
||||
MASS_ASSIGN_ROLES,
|
||||
MASS_UNASSIGN_ROLES,
|
||||
|
||||
MEMBER_NOTE,
|
||||
|
||||
CASE_DELETE,
|
||||
|
||||
DM_FAILED,
|
||||
}
|
||||
export const LogType = {
|
||||
MEMBER_WARN: "MEMBER_WARN",
|
||||
MEMBER_MUTE: "MEMBER_MUTE",
|
||||
MEMBER_UNMUTE: "MEMBER_UNMUTE",
|
||||
MEMBER_MUTE_EXPIRED: "MEMBER_MUTE_EXPIRED",
|
||||
MEMBER_KICK: "MEMBER_KICK",
|
||||
MEMBER_BAN: "MEMBER_BAN",
|
||||
MEMBER_UNBAN: "MEMBER_UNBAN",
|
||||
MEMBER_FORCEBAN: "MEMBER_FORCEBAN",
|
||||
MEMBER_SOFTBAN: "MEMBER_SOFTBAN",
|
||||
MEMBER_JOIN: "MEMBER_JOIN",
|
||||
MEMBER_LEAVE: "MEMBER_LEAVE",
|
||||
MEMBER_ROLE_ADD: "MEMBER_ROLE_ADD",
|
||||
MEMBER_ROLE_REMOVE: "MEMBER_ROLE_REMOVE",
|
||||
MEMBER_NICK_CHANGE: "MEMBER_NICK_CHANGE",
|
||||
MEMBER_USERNAME_CHANGE: "MEMBER_USERNAME_CHANGE",
|
||||
MEMBER_RESTORE: "MEMBER_RESTORE",
|
||||
CHANNEL_CREATE: "CHANNEL_CREATE",
|
||||
CHANNEL_DELETE: "CHANNEL_DELETE",
|
||||
CHANNEL_UPDATE: "CHANNEL_UPDATE",
|
||||
THREAD_CREATE: "THREAD_CREATE",
|
||||
THREAD_DELETE: "THREAD_DELETE",
|
||||
THREAD_UPDATE: "THREAD_UPDATE",
|
||||
ROLE_CREATE: "ROLE_CREATE",
|
||||
ROLE_DELETE: "ROLE_DELETE",
|
||||
ROLE_UPDATE: "ROLE_UPDATE",
|
||||
MESSAGE_EDIT: "MESSAGE_EDIT",
|
||||
MESSAGE_DELETE: "MESSAGE_DELETE",
|
||||
MESSAGE_DELETE_BULK: "MESSAGE_DELETE_BULK",
|
||||
MESSAGE_DELETE_BARE: "MESSAGE_DELETE_BARE",
|
||||
VOICE_CHANNEL_JOIN: "VOICE_CHANNEL_JOIN",
|
||||
VOICE_CHANNEL_LEAVE: "VOICE_CHANNEL_LEAVE",
|
||||
VOICE_CHANNEL_MOVE: "VOICE_CHANNEL_MOVE",
|
||||
STAGE_INSTANCE_CREATE: "STAGE_INSTANCE_CREATE",
|
||||
STAGE_INSTANCE_DELETE: "STAGE_INSTANCE_DELETE",
|
||||
STAGE_INSTANCE_UPDATE: "STAGE_INSTANCE_UPDATE",
|
||||
EMOJI_CREATE: "EMOJI_CREATE",
|
||||
EMOJI_DELETE: "EMOJI_DELETE",
|
||||
EMOJI_UPDATE: "EMOJI_UPDATE",
|
||||
STICKER_CREATE: "STICKER_CREATE",
|
||||
STICKER_DELETE: "STICKER_DELETE",
|
||||
STICKER_UPDATE: "STICKER_UPDATE",
|
||||
COMMAND: "COMMAND",
|
||||
MESSAGE_SPAM_DETECTED: "MESSAGE_SPAM_DETECTED",
|
||||
CENSOR: "CENSOR",
|
||||
CLEAN: "CLEAN",
|
||||
CASE_CREATE: "CASE_CREATE",
|
||||
MASSUNBAN: "MASSUNBAN",
|
||||
MASSBAN: "MASSBAN",
|
||||
MASSMUTE: "MASSMUTE",
|
||||
MEMBER_TIMED_MUTE: "MEMBER_TIMED_MUTE",
|
||||
MEMBER_TIMED_UNMUTE: "MEMBER_TIMED_UNMUTE",
|
||||
MEMBER_TIMED_BAN: "MEMBER_TIMED_BAN",
|
||||
MEMBER_TIMED_UNBAN: "MEMBER_TIMED_UNBAN",
|
||||
MEMBER_JOIN_WITH_PRIOR_RECORDS: "MEMBER_JOIN_WITH_PRIOR_RECORDS",
|
||||
OTHER_SPAM_DETECTED: "OTHER_SPAM_DETECTED",
|
||||
MEMBER_ROLE_CHANGES: "MEMBER_ROLE_CHANGES",
|
||||
VOICE_CHANNEL_FORCE_MOVE: "VOICE_CHANNEL_FORCE_MOVE",
|
||||
VOICE_CHANNEL_FORCE_DISCONNECT: "VOICE_CHANNEL_FORCE_DISCONNECT",
|
||||
CASE_UPDATE: "CASE_UPDATE",
|
||||
MEMBER_MUTE_REJOIN: "MEMBER_MUTE_REJOIN",
|
||||
SCHEDULED_MESSAGE: "SCHEDULED_MESSAGE",
|
||||
POSTED_SCHEDULED_MESSAGE: "POSTED_SCHEDULED_MESSAGE",
|
||||
BOT_ALERT: "BOT_ALERT",
|
||||
AUTOMOD_ACTION: "AUTOMOD_ACTION",
|
||||
SCHEDULED_REPEATED_MESSAGE: "SCHEDULED_REPEATED_MESSAGE",
|
||||
REPEATED_MESSAGE: "REPEATED_MESSAGE",
|
||||
MESSAGE_DELETE_AUTO: "MESSAGE_DELETE_AUTO",
|
||||
SET_ANTIRAID_USER: "SET_ANTIRAID_USER",
|
||||
SET_ANTIRAID_AUTO: "SET_ANTIRAID_AUTO",
|
||||
MEMBER_NOTE: "MEMBER_NOTE",
|
||||
CASE_DELETE: "CASE_DELETE",
|
||||
DM_FAILED: "DM_FAILED",
|
||||
} as const;
|
||||
|
|
|
@ -15,3 +15,9 @@ export function connect() {
|
|||
|
||||
return connectionPromise;
|
||||
}
|
||||
|
||||
export function disconnect() {
|
||||
if (connectionPromise) {
|
||||
connectionPromise.then(() => dataSource.destroy());
|
||||
}
|
||||
}
|
||||
|
|
24
backend/src/exportSchemas.ts
Normal file
24
backend/src/exportSchemas.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { z } from "zod";
|
||||
import zodToJsonSchema from "zod-to-json-schema";
|
||||
import { guildPlugins } from "./plugins/availablePlugins";
|
||||
import { zZeppelinGuildConfig } from "./types";
|
||||
|
||||
const pluginSchemaMap = guildPlugins.reduce((map, plugin) => {
|
||||
if (!plugin.info) {
|
||||
return map;
|
||||
}
|
||||
map[plugin.name] = plugin.info.configSchema;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
const fullSchema = zZeppelinGuildConfig.omit({ plugins: true }).merge(
|
||||
z.strictObject({
|
||||
plugins: z.strictObject(pluginSchemaMap).partial(),
|
||||
}),
|
||||
);
|
||||
|
||||
const jsonSchema = zodToJsonSchema(fullSchema);
|
||||
|
||||
console.log(JSON.stringify(jsonSchema, null, 2));
|
||||
|
||||
process.exit(0);
|
|
@ -11,21 +11,10 @@ import {
|
|||
TextBasedChannel,
|
||||
User,
|
||||
} from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import {
|
||||
AnyPluginData,
|
||||
CommandContext,
|
||||
ConfigValidationError,
|
||||
ExtendedMatchParams,
|
||||
GuildPluginData,
|
||||
PluginOverrideCriteria,
|
||||
helpers,
|
||||
} from "knub";
|
||||
import { AnyPluginData, CommandContext, ExtendedMatchParams, GuildPluginData, helpers } from "knub";
|
||||
import { isStaff } from "./staff";
|
||||
import { TZeppelinKnub } from "./types";
|
||||
import { tNullable } from "./utils";
|
||||
import { Tail } from "./utils/typeUtils";
|
||||
import { StrictValidationError, parseIoTsSchema } from "./validatorUtils";
|
||||
|
||||
const { getMemberLevel } = helpers;
|
||||
|
||||
|
@ -59,46 +48,6 @@ export async function hasPermission(
|
|||
return helpers.hasPermission(config, permission);
|
||||
}
|
||||
|
||||
const PluginOverrideCriteriaType: t.Type<PluginOverrideCriteria<unknown>> = t.recursion(
|
||||
"PluginOverrideCriteriaType",
|
||||
() =>
|
||||
t.partial({
|
||||
channel: tNullable(t.union([t.string, t.array(t.string)])),
|
||||
category: tNullable(t.union([t.string, t.array(t.string)])),
|
||||
level: tNullable(t.union([t.string, t.array(t.string)])),
|
||||
user: tNullable(t.union([t.string, t.array(t.string)])),
|
||||
role: tNullable(t.union([t.string, t.array(t.string)])),
|
||||
|
||||
all: tNullable(t.array(PluginOverrideCriteriaType)),
|
||||
any: tNullable(t.array(PluginOverrideCriteriaType)),
|
||||
not: tNullable(PluginOverrideCriteriaType),
|
||||
|
||||
extra: t.unknown,
|
||||
}),
|
||||
);
|
||||
|
||||
export function strictValidationErrorToConfigValidationError(err: StrictValidationError) {
|
||||
return new ConfigValidationError(
|
||||
err
|
||||
.getErrors()
|
||||
.map((e) => e.toString())
|
||||
.join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
export function makeIoTsConfigParser<Schema extends t.Type<any>>(schema: Schema): (input: unknown) => t.TypeOf<Schema> {
|
||||
return (input: unknown) => {
|
||||
try {
|
||||
return parseIoTsSchema(schema, input);
|
||||
} catch (err) {
|
||||
if (err instanceof StrictValidationError) {
|
||||
throw strictValidationErrorToConfigValidationError(err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function isContextInteraction(
|
||||
context: TextBasedChannel | Message | User | ChatInputCommandInteraction,
|
||||
): context is ChatInputCommandInteraction {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { PluginOptions } from "knub";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { makeIoTsConfigParser } from "../../pluginUtils";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin";
|
||||
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { AutoDeletePluginType, ConfigSchema } from "./types";
|
||||
import { AutoDeletePluginType, zAutoDeleteConfig } from "./types";
|
||||
import { onMessageCreate } from "./util/onMessageCreate";
|
||||
import { onMessageDelete } from "./util/onMessageDelete";
|
||||
import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk";
|
||||
|
@ -24,11 +23,11 @@ export const AutoDeletePlugin = zeppelinGuildPlugin<AutoDeletePluginType>()({
|
|||
prettyName: "Auto-delete",
|
||||
description: "Allows Zeppelin to auto-delete messages from a channel after a delay",
|
||||
configurationGuide: "Maximum deletion delay is currently 5 minutes",
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zAutoDeleteConfig,
|
||||
},
|
||||
|
||||
dependencies: () => [TimeAndDatePlugin, LogsPlugin],
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
configParser: (input) => zAutoDeleteConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
beforeLoad(pluginData) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { SavedMessage } from "../../data/entities/SavedMessage";
|
||||
import { MINUTES, tDelayString } from "../../utils";
|
||||
import { MINUTES, zDelayString } from "../../utils";
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
export const MAX_DELAY = 5 * MINUTES;
|
||||
|
@ -13,14 +13,13 @@ export interface IDeletionQueueItem {
|
|||
message: SavedMessage;
|
||||
}
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
enabled: t.boolean,
|
||||
delay: tDelayString,
|
||||
export const zAutoDeleteConfig = z.strictObject({
|
||||
enabled: z.boolean(),
|
||||
delay: zDelayString,
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface AutoDeletePluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.output<typeof zAutoDeleteConfig>;
|
||||
state: {
|
||||
guildSavedMessages: GuildSavedMessages;
|
||||
guildLogs: GuildLogs;
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { PluginOptions } from "knub";
|
||||
import { GuildAutoReactions } from "../../data/GuildAutoReactions";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { makeIoTsConfigParser } from "../../pluginUtils";
|
||||
import { trimPluginDescription } from "../../utils";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd";
|
||||
import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd";
|
||||
import { AddReactionsEvt } from "./events/AddReactionsEvt";
|
||||
import { AutoReactionsPluginType, ConfigSchema } from "./types";
|
||||
import { AutoReactionsPluginType, zAutoReactionsConfig } from "./types";
|
||||
|
||||
const defaultOptions: PluginOptions<AutoReactionsPluginType> = {
|
||||
config: {
|
||||
|
@ -32,7 +31,7 @@ export const AutoReactionsPlugin = zeppelinGuildPlugin<AutoReactionsPluginType>(
|
|||
description: trimPluginDescription(`
|
||||
Allows setting up automatic reactions to all new messages on a channel
|
||||
`),
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zAutoReactionsConfig,
|
||||
},
|
||||
|
||||
// prettier-ignore
|
||||
|
@ -40,7 +39,7 @@ export const AutoReactionsPlugin = zeppelinGuildPlugin<AutoReactionsPluginType>(
|
|||
LogsPlugin,
|
||||
],
|
||||
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
configParser: (input) => zAutoReactionsConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
// prettier-ignore
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildAutoReactions } from "../../data/GuildAutoReactions";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { AutoReaction } from "../../data/entities/AutoReaction";
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
can_manage: t.boolean,
|
||||
export const zAutoReactionsConfig = z.strictObject({
|
||||
can_manage: z.boolean(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface AutoReactionsPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.output<typeof zAutoReactionsConfig>;
|
||||
state: {
|
||||
logs: GuildLogs;
|
||||
savedMessages: GuildSavedMessages;
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { configUtils, CooldownManager } from "knub";
|
||||
import { CooldownManager } from "knub";
|
||||
import { Queue } from "../../Queue";
|
||||
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
|
||||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { Queue } from "../../Queue";
|
||||
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
||||
import { MINUTES, SECONDS } from "../../utils";
|
||||
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap";
|
||||
import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap";
|
||||
import { parseIoTsSchema, StrictValidationError } from "../../validatorUtils";
|
||||
import { CountersPlugin } from "../Counters/CountersPlugin";
|
||||
import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin";
|
||||
|
@ -17,13 +16,12 @@ import { MutesPlugin } from "../Mutes/MutesPlugin";
|
|||
import { PhishermanPlugin } from "../Phisherman/PhishermanPlugin";
|
||||
import { RoleManagerPlugin } from "../RoleManager/RoleManagerPlugin";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { availableActions } from "./actions/availableActions";
|
||||
import { AntiraidClearCmd } from "./commands/AntiraidClearCmd";
|
||||
import { SetAntiraidCmd } from "./commands/SetAntiraidCmd";
|
||||
import { ViewAntiraidCmd } from "./commands/ViewAntiraidCmd";
|
||||
import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger";
|
||||
import { RunAutomodOnJoinEvt, RunAutomodOnLeaveEvt } from "./events/RunAutomodOnJoinLeaveEvt";
|
||||
import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate";
|
||||
import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger";
|
||||
import { runAutomodOnMessage } from "./events/runAutomodOnMessage";
|
||||
import { runAutomodOnModAction } from "./events/runAutomodOnModAction";
|
||||
import {
|
||||
|
@ -35,8 +33,7 @@ import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChang
|
|||
import { clearOldRecentActions } from "./functions/clearOldRecentActions";
|
||||
import { clearOldRecentSpam } from "./functions/clearOldRecentSpam";
|
||||
import { pluginInfo } from "./info";
|
||||
import { availableTriggers } from "./triggers/availableTriggers";
|
||||
import { AutomodPluginType, ConfigSchema } from "./types";
|
||||
import { AutomodPluginType, zAutomodConfig } from "./types";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -61,129 +58,6 @@ const defaultOptions = {
|
|||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Config preprocessor to set default values for triggers and perform extra validation
|
||||
* TODO: Separate input and output types
|
||||
*/
|
||||
const configParser = (input: unknown) => {
|
||||
const rules = (input as any).rules;
|
||||
if (rules) {
|
||||
// Loop through each rule
|
||||
for (const [name, rule] of Object.entries(rules)) {
|
||||
if (rule == null) {
|
||||
delete rules[name];
|
||||
continue;
|
||||
}
|
||||
|
||||
rule["name"] = name;
|
||||
|
||||
// If the rule doesn't have an explicitly set "enabled" property, set it to true
|
||||
if (rule["enabled"] == null) {
|
||||
rule["enabled"] = true;
|
||||
}
|
||||
|
||||
if (rule["allow_further_rules"] == null) {
|
||||
rule["allow_further_rules"] = false;
|
||||
}
|
||||
|
||||
if (rule["affects_bots"] == null) {
|
||||
rule["affects_bots"] = false;
|
||||
}
|
||||
|
||||
if (rule["affects_self"] == null) {
|
||||
rule["affects_self"] = false;
|
||||
}
|
||||
|
||||
// Loop through the rule's triggers
|
||||
if (rule["triggers"]) {
|
||||
for (const triggerObj of rule["triggers"]) {
|
||||
for (const triggerName in triggerObj) {
|
||||
if (!availableTriggers[triggerName]) {
|
||||
throw new StrictValidationError([`Unknown trigger '${triggerName}' in rule '${rule["name"]}'`]);
|
||||
}
|
||||
|
||||
const triggerBlueprint = availableTriggers[triggerName];
|
||||
|
||||
if (typeof triggerBlueprint.defaultConfig === "object" && triggerBlueprint.defaultConfig != null) {
|
||||
triggerObj[triggerName] = configUtils.mergeConfig(
|
||||
triggerBlueprint.defaultConfig,
|
||||
triggerObj[triggerName] || {},
|
||||
);
|
||||
} else {
|
||||
triggerObj[triggerName] = triggerObj[triggerName] || triggerBlueprint.defaultConfig;
|
||||
}
|
||||
|
||||
if (triggerObj[triggerName].match_attachment_type) {
|
||||
const white = triggerObj[triggerName].match_attachment_type.whitelist_enabled;
|
||||
const black = triggerObj[triggerName].match_attachment_type.blacklist_enabled;
|
||||
|
||||
if (white && black) {
|
||||
throw new StrictValidationError([
|
||||
`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>`,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerObj[triggerName].match_mime_type) {
|
||||
const white = triggerObj[triggerName].match_mime_type.whitelist_enabled;
|
||||
const black = triggerObj[triggerName].match_mime_type.blacklist_enabled;
|
||||
|
||||
if (white && black) {
|
||||
throw new StrictValidationError([
|
||||
`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>`,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rule["actions"]) {
|
||||
for (const actionName in rule["actions"]) {
|
||||
if (!availableActions[actionName]) {
|
||||
throw new StrictValidationError([`Unknown action '${actionName}' in rule '${rule["name"]}'`]);
|
||||
}
|
||||
|
||||
const actionBlueprint = availableActions[actionName];
|
||||
const actionConfig = rule["actions"][actionName];
|
||||
|
||||
if (typeof actionConfig !== "object" || Array.isArray(actionConfig) || actionConfig == null) {
|
||||
rule["actions"][actionName] = actionConfig;
|
||||
} else {
|
||||
rule["actions"][actionName] = configUtils.mergeConfig(actionBlueprint.defaultConfig, actionConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable logging of automod actions by default
|
||||
if (rule["actions"]) {
|
||||
for (const actionName in rule["actions"]) {
|
||||
if (!availableActions[actionName]) {
|
||||
throw new StrictValidationError([`Unknown action '${actionName}' in rule '${rule["name"]}'`]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rule["actions"]["log"] == null) {
|
||||
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"]}'`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parseIoTsSchema(ConfigSchema, input);
|
||||
};
|
||||
|
||||
export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
|
||||
name: "automod",
|
||||
showInDocs: true,
|
||||
|
@ -201,7 +75,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
|
|||
],
|
||||
|
||||
defaultOptions,
|
||||
configParser,
|
||||
configParser: (input) => zAutomodConfig.parse(input),
|
||||
|
||||
customOverrideCriteriaFunctions: {
|
||||
antiraid_level: (pluginData, matchParams, value) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { PermissionFlagsBits, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { nonNullish, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { nonNullish, unique, zSnowflake } from "../../../utils";
|
||||
import { canAssignRole } from "../../../utils/canAssignRole";
|
||||
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
|
||||
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
||||
|
@ -11,9 +11,10 @@ import { automodAction } from "../helpers";
|
|||
|
||||
const p = PermissionFlagsBits;
|
||||
|
||||
const configSchema = z.array(zSnowflake);
|
||||
|
||||
export const AddRolesAction = automodAction({
|
||||
configType: t.array(t.string),
|
||||
defaultConfig: [],
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const members = unique(contexts.map((c) => c.member).filter(nonNullish));
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { zBoundedCharacters } from "../../../utils";
|
||||
import { CountersPlugin } from "../../Counters/CountersPlugin";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const AddToCounterAction = automodAction({
|
||||
configType: t.type({
|
||||
counter: t.string,
|
||||
amount: t.number,
|
||||
}),
|
||||
const configSchema = z.object({
|
||||
counter: zBoundedCharacters(0, 100),
|
||||
amount: z.number(),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const AddToCounterAction = automodAction({
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const countersPlugin = pluginData.getPlugin(CountersPlugin);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions";
|
||||
import z from "zod";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import {
|
||||
createTypedTemplateSafeValueContainer,
|
||||
|
@ -12,25 +11,28 @@ import {
|
|||
chunkMessageLines,
|
||||
isTruthy,
|
||||
messageLink,
|
||||
tAllowedMentions,
|
||||
tNormalizedNullOptional,
|
||||
validateAndParseMessageContent,
|
||||
verboseChannelMention,
|
||||
zAllowedMentions,
|
||||
zBoundedCharacters,
|
||||
zNullishToUndefined,
|
||||
zSnowflake,
|
||||
} from "../../../utils";
|
||||
import { erisAllowedMentionsToDjsMentionOptions } from "../../../utils/erisAllowedMentionsToDjsMentionOptions";
|
||||
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
|
||||
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||
import { InternalPosterPlugin } from "../../InternalPoster/InternalPosterPlugin";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const AlertAction = automodAction({
|
||||
configType: t.type({
|
||||
channel: t.string,
|
||||
text: t.string,
|
||||
allowed_mentions: tNormalizedNullOptional(tAllowedMentions),
|
||||
}),
|
||||
const configSchema = z.object({
|
||||
channel: zSnowflake,
|
||||
text: zBoundedCharacters(0, 4000),
|
||||
allowed_mentions: zNullishToUndefined(zAllowedMentions.nullable().default(null)),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const AlertAction = automodAction({
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
|
||||
const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake);
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { AnyThreadChannel } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { noop } from "../../../utils";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const ArchiveThreadAction = automodAction({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts }) {
|
||||
const threads = contexts
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import * as t from "io-ts";
|
||||
import { AutomodActionBlueprint } from "../helpers";
|
||||
import { AddRolesAction } from "./addRoles";
|
||||
import { AddToCounterAction } from "./addToCounter";
|
||||
|
@ -20,7 +19,7 @@ import { SetSlowmodeAction } from "./setSlowmode";
|
|||
import { StartThreadAction } from "./startThread";
|
||||
import { WarnAction } from "./warn";
|
||||
|
||||
export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
|
||||
export const availableActions = {
|
||||
clean: CleanAction,
|
||||
warn: WarnAction,
|
||||
mute: MuteAction,
|
||||
|
@ -40,26 +39,4 @@ export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
|
|||
archive_thread: ArchiveThreadAction,
|
||||
change_perms: ChangePermsAction,
|
||||
pause_invites: PauseInvitesAction,
|
||||
};
|
||||
|
||||
export const AvailableActions = t.type({
|
||||
clean: CleanAction.configType,
|
||||
warn: WarnAction.configType,
|
||||
mute: MuteAction.configType,
|
||||
kick: KickAction.configType,
|
||||
ban: BanAction.configType,
|
||||
alert: AlertAction.configType,
|
||||
change_nickname: ChangeNicknameAction.configType,
|
||||
log: LogAction.configType,
|
||||
add_roles: AddRolesAction.configType,
|
||||
remove_roles: RemoveRolesAction.configType,
|
||||
set_antiraid_level: SetAntiraidLevelAction.configType,
|
||||
reply: ReplyAction.configType,
|
||||
add_to_counter: AddToCounterAction.configType,
|
||||
set_counter: SetCounterAction.configType,
|
||||
set_slowmode: SetSlowmodeAction.configType,
|
||||
start_thread: StartThreadAction.configType,
|
||||
archive_thread: ArchiveThreadAction.configType,
|
||||
change_perms: ChangePermsAction.configType,
|
||||
pause_invites: PauseInvitesAction.configType,
|
||||
});
|
||||
} satisfies Record<string, AutomodActionBlueprint<any>>;
|
||||
|
|
|
@ -1,25 +1,30 @@
|
|||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import {
|
||||
convertDelayStringToMS,
|
||||
nonNullish,
|
||||
unique,
|
||||
zBoundedCharacters,
|
||||
zDelayString,
|
||||
zSnowflake,
|
||||
} from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { zNotify } from "../constants";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const BanAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
duration: tNullable(tDelayString),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
deleteMessageDays: tNullable(t.number),
|
||||
postInCaseLog: tNullable(t.boolean),
|
||||
hide_case: tNullable(t.boolean),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
reason: zBoundedCharacters(0, 4000).nullable().default(null),
|
||||
duration: zDelayString.nullable().default(null),
|
||||
notify: zNotify.nullable().default(null),
|
||||
notifyChannel: zSnowflake.nullable().default(null),
|
||||
deleteMessageDays: z.number().nullable().default(null),
|
||||
postInCaseLog: z.boolean().nullable().default(null),
|
||||
hide_case: z.boolean().nullable().default(false),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
notify: null, // Use defaults from ModActions
|
||||
hide_case: false,
|
||||
},
|
||||
export const BanAction = automodAction({
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, matchResult }) {
|
||||
const reason = actionConfig.reason || "Kicked automatically";
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import { nonNullish, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { nonNullish, unique, zBoundedCharacters } from "../../../utils";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const ChangeNicknameAction = automodAction({
|
||||
configType: t.union([
|
||||
t.string,
|
||||
t.type({
|
||||
name: t.string,
|
||||
configSchema: z.union([
|
||||
zBoundedCharacters(0, 32),
|
||||
z.strictObject({
|
||||
name: zBoundedCharacters(0, 32),
|
||||
}),
|
||||
]),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const members = unique(contexts.map((c) => c.member).filter(nonNullish));
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { PermissionsBitField, PermissionsString } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { isValidSnowflake, noop, tNullable, tPartialDictionary } from "../../../utils";
|
||||
import { U } from "ts-toolbelt";
|
||||
import z from "zod";
|
||||
import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { isValidSnowflake, keys, noop, zBoundedCharacters } from "../../../utils";
|
||||
import {
|
||||
guildToTemplateSafeGuild,
|
||||
savedMessageToTemplateSafeSavedMessage,
|
||||
userToTemplateSafeUser,
|
||||
} from "../../../utils/templateSafeObjects";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
type LegacyPermMap = Record<string, keyof (typeof PermissionsBitField)["Flags"]>;
|
||||
|
@ -59,41 +61,63 @@ const realToLegacyMap = Object.entries(legacyPermMap).reduce((map, pair) => {
|
|||
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.union([t.keyof(PermissionsBitField.Flags), t.keyof(legacyPermMap)]),
|
||||
tNullable(t.boolean),
|
||||
),
|
||||
}),
|
||||
defaultConfig: {},
|
||||
const permissionNames = keys(PermissionsBitField.Flags) as U.ListOf<keyof typeof PermissionsBitField.Flags>;
|
||||
const legacyPermissionNames = keys(legacyPermMap) as U.ListOf<keyof typeof legacyPermMap>;
|
||||
const allPermissionNames = [...permissionNames, ...legacyPermissionNames] as const;
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
export const ChangePermsAction = automodAction({
|
||||
configSchema: z.strictObject({
|
||||
target: zBoundedCharacters(1, 2000),
|
||||
channel: zBoundedCharacters(1, 2000).nullable().default(null),
|
||||
perms: z.record(z.enum(allPermissionNames), z.boolean().nullable()),
|
||||
}),
|
||||
|
||||
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,
|
||||
let target: string;
|
||||
try {
|
||||
target = await renderTemplate(
|
||||
actionConfig.target,
|
||||
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;
|
||||
} catch (err) {
|
||||
if (err instanceof TemplateParseError) {
|
||||
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
||||
body: `Error in target format of automod rule ${ruleName}: ${err.message}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
let channelId: string | null = null;
|
||||
if (actionConfig.channel) {
|
||||
try {
|
||||
channelId = await renderTemplate(
|
||||
actionConfig.channel,
|
||||
new TemplateSafeValueContainer({
|
||||
user: user ? userToTemplateSafeUser(user) : null,
|
||||
guild: guildToTemplateSafeGuild(pluginData.guild),
|
||||
message: message ? savedMessageToTemplateSafeSavedMessage(message) : null,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof TemplateParseError) {
|
||||
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
||||
body: `Error in channel format of automod rule ${ruleName}: ${err.message}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const role = pluginData.guild.roles.resolve(target);
|
||||
if (!role) {
|
||||
const member = await pluginData.guild.members.fetch(target).catch(noop);
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { GuildTextBasedChannel, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { noop } from "../../../utils";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const CleanAction = automodAction({
|
||||
configType: t.boolean,
|
||||
defaultConfig: false,
|
||||
configSchema: z.boolean().default(false),
|
||||
|
||||
async apply({ pluginData, contexts, ruleName }) {
|
||||
const messageIdsToDeleteByChannelId: Map<string, string[]> = new Map();
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { zBoundedCharacters } from "../../../utils";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const ExampleAction = automodAction({
|
||||
configType: t.type({
|
||||
someValue: t.string,
|
||||
configSchema: z.strictObject({
|
||||
someValue: zBoundedCharacters(0, 1000),
|
||||
}),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
// TODO: Everything
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import * as t from "io-ts";
|
||||
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { asyncMap, nonNullish, resolveMember, unique, zBoundedCharacters, zSnowflake } from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { zNotify } from "../constants";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const KickAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
postInCaseLog: tNullable(t.boolean),
|
||||
hide_case: tNullable(t.boolean),
|
||||
configSchema: z.strictObject({
|
||||
reason: zBoundedCharacters(0, 4000).nullable().default(null),
|
||||
notify: zNotify.nullable().default(null),
|
||||
notifyChannel: zSnowflake.nullable().default(null),
|
||||
postInCaseLog: z.boolean().nullable().default(null),
|
||||
hide_case: z.boolean().nullable().default(false),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
notify: null, // Use defaults from ModActions
|
||||
hide_case: false,
|
||||
},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, matchResult }) {
|
||||
const reason = actionConfig.reason || "Kicked automatically";
|
||||
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { isTruthy, unique } from "../../../utils";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const LogAction = automodAction({
|
||||
configType: t.boolean,
|
||||
defaultConfig: true,
|
||||
configSchema: z.boolean().default(true),
|
||||
|
||||
async apply({ pluginData, contexts, ruleName, matchResult }) {
|
||||
const users = unique(contexts.map((c) => c.user)).filter(isTruthy);
|
||||
|
|
|
@ -1,29 +1,38 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
|
||||
import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils";
|
||||
import {
|
||||
convertDelayStringToMS,
|
||||
nonNullish,
|
||||
unique,
|
||||
zBoundedCharacters,
|
||||
zDelayString,
|
||||
zSnowflake,
|
||||
} from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { MutesPlugin } from "../../Mutes/MutesPlugin";
|
||||
import { zNotify } from "../constants";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const MuteAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
duration: tNullable(tDelayString),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
remove_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])),
|
||||
restore_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])),
|
||||
postInCaseLog: tNullable(t.boolean),
|
||||
hide_case: tNullable(t.boolean),
|
||||
configSchema: z.strictObject({
|
||||
reason: zBoundedCharacters(0, 4000).nullable().default(null),
|
||||
duration: zDelayString.nullable().default(null),
|
||||
notify: zNotify.nullable().default(null),
|
||||
notifyChannel: zSnowflake.nullable().default(null),
|
||||
remove_roles_on_mute: z
|
||||
.union([z.boolean(), z.array(zSnowflake)])
|
||||
.nullable()
|
||||
.default(null),
|
||||
restore_roles_on_mute: z
|
||||
.union([z.boolean(), z.array(zSnowflake)])
|
||||
.nullable()
|
||||
.default(null),
|
||||
postInCaseLog: z.boolean().nullable().default(null),
|
||||
hide_case: z.boolean().nullable().default(false),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
notify: null, // Use defaults from ModActions
|
||||
hide_case: false,
|
||||
},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
|
||||
const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : undefined;
|
||||
const reason = actionConfig.reason || "Muted automatically";
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import { GuildFeature } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const PauseInvitesAction = automodAction({
|
||||
configType: t.type({
|
||||
paused: t.boolean,
|
||||
configSchema: z.strictObject({
|
||||
paused: z.boolean(),
|
||||
}),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
async apply({ pluginData, actionConfig }) {
|
||||
const hasInvitesDisabled = pluginData.guild.features.includes(GuildFeature.InvitesDisabled);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { PermissionFlagsBits, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { nonNullish, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { nonNullish, unique, zSnowflake } from "../../../utils";
|
||||
import { canAssignRole } from "../../../utils/canAssignRole";
|
||||
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
|
||||
import { memberRolesLock } from "../../../utils/lockNameHelpers";
|
||||
|
@ -12,9 +12,7 @@ import { automodAction } from "../helpers";
|
|||
const p = PermissionFlagsBits;
|
||||
|
||||
export const RemoveRolesAction = automodAction({
|
||||
configType: t.array(t.string),
|
||||
|
||||
defaultConfig: [],
|
||||
configSchema: z.array(zSnowflake).default([]),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const members = unique(contexts.map((c) => c.member).filter(nonNullish));
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { GuildTextBasedChannel, MessageCreateOptions, PermissionsBitField, Snowflake, User } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import z from "zod";
|
||||
import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import {
|
||||
convertDelayStringToMS,
|
||||
noop,
|
||||
renderRecursively,
|
||||
tDelayString,
|
||||
tMessageContent,
|
||||
tNullable,
|
||||
unique,
|
||||
validateAndParseMessageContent,
|
||||
verboseChannelMention,
|
||||
zBoundedCharacters,
|
||||
zDelayString,
|
||||
zMessageContent,
|
||||
} from "../../../utils";
|
||||
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
|
||||
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
|
||||
|
@ -20,17 +20,15 @@ import { automodAction } from "../helpers";
|
|||
import { AutomodContext } from "../types";
|
||||
|
||||
export const ReplyAction = automodAction({
|
||||
configType: t.union([
|
||||
t.string,
|
||||
t.type({
|
||||
text: tMessageContent,
|
||||
auto_delete: tNullable(t.union([tDelayString, t.number])),
|
||||
inline: tNullable(t.boolean),
|
||||
configSchema: z.union([
|
||||
zBoundedCharacters(0, 4000),
|
||||
z.strictObject({
|
||||
text: zMessageContent,
|
||||
auto_delete: z.union([zDelayString, z.number()]).nullable().default(null),
|
||||
inline: z.boolean().default(false),
|
||||
}),
|
||||
]),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const contextsWithTextChannels = contexts
|
||||
.filter((c) => c.message?.channel_id)
|
||||
|
@ -60,10 +58,21 @@ export const ReplyAction = automodAction({
|
|||
}),
|
||||
);
|
||||
|
||||
const formatted =
|
||||
typeof actionConfig === "string"
|
||||
? await renderReplyText(actionConfig)
|
||||
: ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageCreateOptions);
|
||||
let formatted: string | MessageCreateOptions;
|
||||
try {
|
||||
formatted =
|
||||
typeof actionConfig === "string"
|
||||
? await renderReplyText(actionConfig)
|
||||
: ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageCreateOptions);
|
||||
} catch (err) {
|
||||
if (err instanceof TemplateParseError) {
|
||||
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
||||
body: `Error in reply format of automod rule \`${ruleName}\`: ${err.message}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (formatted) {
|
||||
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as GuildTextBasedChannel;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import { zBoundedCharacters } from "../../../utils";
|
||||
import { setAntiraidLevel } from "../functions/setAntiraidLevel";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const SetAntiraidLevelAction = automodAction({
|
||||
configType: tNullable(t.string),
|
||||
defaultConfig: "",
|
||||
configSchema: zBoundedCharacters(0, 100).nullable(),
|
||||
|
||||
async apply({ pluginData, actionConfig }) {
|
||||
setAntiraidLevel(pluginData, actionConfig ?? null);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { MAX_COUNTER_VALUE, MIN_COUNTER_VALUE } from "../../../data/GuildCounters";
|
||||
import { zBoundedCharacters } from "../../../utils";
|
||||
import { CountersPlugin } from "../../Counters/CountersPlugin";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const SetCounterAction = automodAction({
|
||||
configType: t.type({
|
||||
counter: t.string,
|
||||
value: t.number,
|
||||
configSchema: z.strictObject({
|
||||
counter: zBoundedCharacters(0, 100),
|
||||
value: z.number().min(MIN_COUNTER_VALUE).max(MAX_COUNTER_VALUE),
|
||||
}),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const countersPlugin = pluginData.getPlugin(CountersPlugin);
|
||||
if (!countersPlugin.counterExists(actionConfig.counter)) {
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import { ChannelType, GuildTextBasedChannel, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, isDiscordAPIError, tDelayString, tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, isDiscordAPIError, zDelayString, zSnowflake } from "../../../utils";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const SetSlowmodeAction = automodAction({
|
||||
configType: t.type({
|
||||
channels: tNullable(t.array(t.string)),
|
||||
duration: tNullable(tDelayString),
|
||||
configSchema: z.strictObject({
|
||||
channels: z.array(zSnowflake),
|
||||
duration: zDelayString.nullable().default("10s"),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
duration: "10s",
|
||||
},
|
||||
|
||||
async apply({ pluginData, actionConfig, contexts }) {
|
||||
const slowmodeMs = Math.max(actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : 0, 0);
|
||||
const channels: Snowflake[] = actionConfig.channels ?? [];
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
import {
|
||||
ChannelType,
|
||||
GuildFeature,
|
||||
GuildTextThreadCreateOptions,
|
||||
ThreadAutoArchiveDuration,
|
||||
ThreadChannel,
|
||||
} from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { MINUTES, convertDelayStringToMS, noop, tDelayString, tNullable } from "../../../utils";
|
||||
import { ChannelType, GuildTextThreadCreateOptions, ThreadAutoArchiveDuration, ThreadChannel } from "discord.js";
|
||||
import z from "zod";
|
||||
import { TemplateParseError, TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { MINUTES, convertDelayStringToMS, noop, zBoundedCharacters, zDelayString } from "../../../utils";
|
||||
import { savedMessageToTemplateSafeSavedMessage, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
const validThreadAutoArchiveDurations: ThreadAutoArchiveDuration[] = [
|
||||
|
@ -19,19 +14,15 @@ const validThreadAutoArchiveDurations: ThreadAutoArchiveDuration[] = [
|
|||
];
|
||||
|
||||
export const StartThreadAction = automodAction({
|
||||
configType: t.type({
|
||||
name: tNullable(t.string),
|
||||
auto_archive: tDelayString,
|
||||
private: tNullable(t.boolean),
|
||||
slowmode: tNullable(tDelayString),
|
||||
limit_per_channel: tNullable(t.number),
|
||||
configSchema: z.strictObject({
|
||||
name: zBoundedCharacters(1, 100).nullable(),
|
||||
auto_archive: zDelayString,
|
||||
private: z.boolean().default(false),
|
||||
slowmode: zDelayString.nullable().default(null),
|
||||
limit_per_channel: z.number().nullable().default(5),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
limit_per_channel: 5,
|
||||
},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
// check if the message still exists, we don't want to create threads for deleted messages
|
||||
const threads = contexts.filter((c) => {
|
||||
if (!c.message || !c.user) return false;
|
||||
|
@ -47,7 +38,6 @@ export const StartThreadAction = automodAction({
|
|||
return true;
|
||||
});
|
||||
|
||||
const guild = pluginData.guild;
|
||||
const archiveSet = actionConfig.auto_archive
|
||||
? Math.ceil(Math.max(convertDelayStringToMS(actionConfig.auto_archive) ?? 0, 0) / MINUTES)
|
||||
: ThreadAutoArchiveDuration.OneDay;
|
||||
|
@ -57,24 +47,31 @@ export const StartThreadAction = automodAction({
|
|||
|
||||
for (const threadContext of threads) {
|
||||
const channel = pluginData.guild.channels.cache.get(threadContext.message!.channel_id);
|
||||
if (!channel || !("threads" in channel) || channel.type === ChannelType.GuildForum) continue;
|
||||
if (!channel || !("threads" in channel) || channel.isThreadOnly()) continue;
|
||||
|
||||
const renderThreadName = async (str: string) =>
|
||||
renderTemplate(
|
||||
str,
|
||||
let threadName: string;
|
||||
try {
|
||||
threadName = await renderTemplate(
|
||||
actionConfig.name ?? "{user.renderedUsername}'s thread",
|
||||
new TemplateSafeValueContainer({
|
||||
user: userToTemplateSafeUser(threadContext.user!),
|
||||
msg: savedMessageToTemplateSafeSavedMessage(threadContext.message!),
|
||||
}),
|
||||
);
|
||||
const threadName = await renderThreadName(actionConfig.name ?? "{user.renderedUsername}'s thread");
|
||||
} catch (err) {
|
||||
if (err instanceof TemplateParseError) {
|
||||
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
||||
body: `Error in thread name format of automod rule ${ruleName}: ${err.message}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
const threadOptions: GuildTextThreadCreateOptions<unknown> = {
|
||||
name: threadName,
|
||||
autoArchiveDuration: autoArchive,
|
||||
startMessage:
|
||||
!actionConfig.private && guild.features.includes(GuildFeature.PrivateThreads)
|
||||
? threadContext.message!.id
|
||||
: undefined,
|
||||
startMessage: !actionConfig.private ? threadContext.message!.id : undefined,
|
||||
};
|
||||
|
||||
let thread: ThreadChannel | undefined;
|
||||
|
@ -90,10 +87,7 @@ export const StartThreadAction = automodAction({
|
|||
.create({
|
||||
...threadOptions,
|
||||
type: actionConfig.private ? ChannelType.PrivateThread : ChannelType.PublicThread,
|
||||
startMessage:
|
||||
!actionConfig.private && guild.features.includes(GuildFeature.PrivateThreads)
|
||||
? threadContext.message!.id
|
||||
: undefined,
|
||||
startMessage: !actionConfig.private ? threadContext.message!.id : undefined,
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import * as t from "io-ts";
|
||||
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { asyncMap, nonNullish, resolveMember, unique, zBoundedCharacters, zSnowflake } from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { zNotify } from "../constants";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const WarnAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
postInCaseLog: tNullable(t.boolean),
|
||||
hide_case: tNullable(t.boolean),
|
||||
configSchema: z.strictObject({
|
||||
reason: zBoundedCharacters(0, 4000).nullable().default(null),
|
||||
notify: zNotify.nullable().default(null),
|
||||
notifyChannel: zSnowflake.nullable().default(null),
|
||||
postInCaseLog: z.boolean().nullable().default(null),
|
||||
hide_case: z.boolean().nullable().default(false),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
notify: null, // Use defaults from ModActions
|
||||
hide_case: false,
|
||||
},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, matchResult }) {
|
||||
const reason = actionConfig.reason || "Warned automatically";
|
||||
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import z from "zod";
|
||||
import { MINUTES, SECONDS } from "../../utils";
|
||||
|
||||
export const RECENT_SPAM_EXPIRY_TIME = 10 * SECONDS;
|
||||
|
@ -18,3 +19,5 @@ export enum RecentActionType {
|
|||
MemberLeave,
|
||||
ThreadCreate,
|
||||
}
|
||||
|
||||
export const zNotify = z.union([z.literal("dm"), z.literal("channel")]);
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
||||
import { humanizeDurationShort } from "../../../humanizeDurationShort";
|
||||
import { getBaseUrl } from "../../../pluginUtils";
|
||||
import { convertDelayStringToMS, sorter, tDelayString, tNullable } from "../../../utils";
|
||||
import { convertDelayStringToMS, sorter, zDelayString } from "../../../utils";
|
||||
import { RecentActionType } from "../constants";
|
||||
import { automodTrigger } from "../helpers";
|
||||
import { findRecentSpam } from "./findRecentSpam";
|
||||
import { getMatchingMessageRecentActions } from "./getMatchingMessageRecentActions";
|
||||
import { getMessageSpamIdentifier } from "./getSpamIdentifier";
|
||||
|
||||
const MessageSpamTriggerConfig = t.type({
|
||||
amount: t.number,
|
||||
within: tDelayString,
|
||||
per_channel: tNullable(t.boolean),
|
||||
});
|
||||
|
||||
interface TMessageSpamMatchResultType {
|
||||
archiveId: string;
|
||||
}
|
||||
|
||||
const configSchema = z.strictObject({
|
||||
amount: z.number().int(),
|
||||
within: zDelayString,
|
||||
per_channel: z.boolean().nullable().default(false),
|
||||
});
|
||||
|
||||
export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: string) {
|
||||
return automodTrigger<TMessageSpamMatchResultType>()({
|
||||
configType: MessageSpamTriggerConfig,
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -42,7 +42,7 @@ export async function* matchMultipleTextTypesOnMessage(
|
|||
}
|
||||
|
||||
if (trigger.match_visible_names) {
|
||||
yield ["visiblename", member.nickname || msg.data.author.username];
|
||||
yield ["visiblename", member.displayName || msg.data.author.username];
|
||||
}
|
||||
|
||||
if (trigger.match_usernames) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as t from "io-ts";
|
||||
import { GuildPluginData } from "knub";
|
||||
import z, { ZodTypeAny } from "zod";
|
||||
import { Awaitable } from "../../utils/typeUtils";
|
||||
import { AutomodContext, AutomodPluginType } from "./types";
|
||||
|
||||
|
@ -31,21 +31,19 @@ type AutomodTriggerRenderMatchInformationFn<TConfigType, TMatchResultExtra> = (m
|
|||
matchResult: AutomodTriggerMatchResult<TMatchResultExtra>;
|
||||
}) => Awaitable<string>;
|
||||
|
||||
export interface AutomodTriggerBlueprint<TConfigType extends t.Any, TMatchResultExtra> {
|
||||
configType: TConfigType;
|
||||
defaultConfig: Partial<t.TypeOf<TConfigType>>;
|
||||
|
||||
match: AutomodTriggerMatchFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
|
||||
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
|
||||
export interface AutomodTriggerBlueprint<TConfigSchema extends ZodTypeAny, TMatchResultExtra> {
|
||||
configSchema: TConfigSchema;
|
||||
match: AutomodTriggerMatchFn<z.output<TConfigSchema>, TMatchResultExtra>;
|
||||
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<z.output<TConfigSchema>, TMatchResultExtra>;
|
||||
}
|
||||
|
||||
export function automodTrigger<TMatchResultExtra>(): <TConfigType extends t.Any>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>,
|
||||
) => AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>;
|
||||
export function automodTrigger<TMatchResultExtra>(): <TConfigSchema extends ZodTypeAny>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigSchema, TMatchResultExtra>,
|
||||
) => AutomodTriggerBlueprint<TConfigSchema, TMatchResultExtra>;
|
||||
|
||||
export function automodTrigger<TConfigType extends t.Any>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigType, unknown>,
|
||||
): AutomodTriggerBlueprint<TConfigType, unknown>;
|
||||
export function automodTrigger<TConfigSchema extends ZodTypeAny>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigSchema, unknown>,
|
||||
): AutomodTriggerBlueprint<TConfigSchema, unknown>;
|
||||
|
||||
export function automodTrigger(...args) {
|
||||
if (args.length) {
|
||||
|
@ -63,15 +61,13 @@ type AutomodActionApplyFn<TConfigType> = (meta: {
|
|||
matchResult: AutomodTriggerMatchResult;
|
||||
}) => Awaitable<void>;
|
||||
|
||||
export interface AutomodActionBlueprint<TConfigType extends t.Any> {
|
||||
configType: TConfigType;
|
||||
defaultConfig: Partial<t.TypeOf<TConfigType>>;
|
||||
|
||||
apply: AutomodActionApplyFn<t.TypeOf<TConfigType>>;
|
||||
export interface AutomodActionBlueprint<TConfigSchema extends ZodTypeAny> {
|
||||
configSchema: TConfigSchema;
|
||||
apply: AutomodActionApplyFn<z.output<TConfigSchema>>;
|
||||
}
|
||||
|
||||
export function automodAction<TConfigType extends t.Any>(
|
||||
blueprint: AutomodActionBlueprint<TConfigType>,
|
||||
): AutomodActionBlueprint<TConfigType> {
|
||||
export function automodAction<TConfigSchema extends ZodTypeAny>(
|
||||
blueprint: AutomodActionBlueprint<TConfigSchema>,
|
||||
): AutomodActionBlueprint<TConfigSchema> {
|
||||
return blueprint;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { trimPluginDescription } from "../../utils";
|
||||
import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint";
|
||||
import { ConfigSchema } from "./types";
|
||||
import { zAutomodConfig } from "./types";
|
||||
|
||||
export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
|
||||
prettyName: "Automod",
|
||||
|
@ -100,5 +100,5 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
|
|||
{matchSummary}
|
||||
~~~
|
||||
`),
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zAutomodConfig,
|
||||
};
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface AntiraidLevelTriggerResult {}
|
||||
|
||||
export const AntiraidLevelTrigger = automodTrigger<AntiraidLevelTriggerResult>()({
|
||||
configType: t.type({
|
||||
level: tNullable(t.string),
|
||||
only_on_change: tNullable(t.boolean),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
level: z.nullable(z.string().max(100)),
|
||||
only_on_change: z.nullable(z.boolean()),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const AntiraidLevelTrigger = automodTrigger<AntiraidLevelTriggerResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context }) {
|
||||
if (!context.antiraid) {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { verboseChannelMention } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface AnyMessageResultType {}
|
||||
|
||||
export const AnyMessageTrigger = automodTrigger<AnyMessageResultType>()({
|
||||
configType: t.type({}),
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
defaultConfig: {},
|
||||
export const AnyMessageTrigger = automodTrigger<AnyMessageResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import * as t from "io-ts";
|
||||
import { AutomodTriggerBlueprint } from "../helpers";
|
||||
import { AntiraidLevelTrigger } from "./antiraidLevel";
|
||||
import { AnyMessageTrigger } from "./anyMessage";
|
||||
|
@ -45,6 +44,7 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
|
|||
match_attachment_type: MatchAttachmentTypeTrigger,
|
||||
match_mime_type: MatchMimeTypeTrigger,
|
||||
member_join: MemberJoinTrigger,
|
||||
member_leave: MemberLeaveTrigger,
|
||||
role_added: RoleAddedTrigger,
|
||||
role_removed: RoleRemovedTrigger,
|
||||
|
||||
|
@ -76,46 +76,3 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
|
|||
thread_archive: ThreadArchiveTrigger,
|
||||
thread_unarchive: ThreadUnarchiveTrigger,
|
||||
};
|
||||
|
||||
export const AvailableTriggers = t.type({
|
||||
any_message: AnyMessageTrigger.configType,
|
||||
|
||||
match_words: MatchWordsTrigger.configType,
|
||||
match_regex: MatchRegexTrigger.configType,
|
||||
match_invites: MatchInvitesTrigger.configType,
|
||||
match_links: MatchLinksTrigger.configType,
|
||||
match_attachment_type: MatchAttachmentTypeTrigger.configType,
|
||||
match_mime_type: MatchMimeTypeTrigger.configType,
|
||||
member_join: MemberJoinTrigger.configType,
|
||||
member_leave: MemberLeaveTrigger.configType,
|
||||
role_added: RoleAddedTrigger.configType,
|
||||
role_removed: RoleRemovedTrigger.configType,
|
||||
|
||||
message_spam: MessageSpamTrigger.configType,
|
||||
mention_spam: MentionSpamTrigger.configType,
|
||||
link_spam: LinkSpamTrigger.configType,
|
||||
attachment_spam: AttachmentSpamTrigger.configType,
|
||||
emoji_spam: EmojiSpamTrigger.configType,
|
||||
line_spam: LineSpamTrigger.configType,
|
||||
character_spam: CharacterSpamTrigger.configType,
|
||||
member_join_spam: MemberJoinSpamTrigger.configType,
|
||||
sticker_spam: StickerSpamTrigger.configType,
|
||||
thread_create_spam: ThreadCreateSpamTrigger.configType,
|
||||
|
||||
counter_trigger: CounterTrigger.configType,
|
||||
|
||||
note: NoteTrigger.configType,
|
||||
warn: WarnTrigger.configType,
|
||||
mute: MuteTrigger.configType,
|
||||
unmute: UnmuteTrigger.configType,
|
||||
kick: KickTrigger.configType,
|
||||
ban: BanTrigger.configType,
|
||||
unban: UnbanTrigger.configType,
|
||||
|
||||
antiraid_level: AntiraidLevelTrigger.configType,
|
||||
|
||||
thread_create: ThreadCreateTrigger.configType,
|
||||
thread_delete: ThreadDeleteTrigger.configType,
|
||||
thread_archive: ThreadArchiveTrigger.configType,
|
||||
thread_unarchive: ThreadUnarchiveTrigger.configType,
|
||||
});
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface BanTriggerResultType {}
|
||||
|
||||
export const BanTrigger = automodTrigger<BanTriggerResultType>()({
|
||||
configType: t.type({
|
||||
manual: t.boolean,
|
||||
automatic: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
manual: z.boolean().default(true),
|
||||
automatic: z.boolean().default(true),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
manual: true,
|
||||
automatic: true,
|
||||
},
|
||||
export const BanTrigger = automodTrigger<BanTriggerResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (context.modAction?.type !== "ban") {
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line
|
||||
interface CounterTriggerResult {}
|
||||
|
||||
export const CounterTrigger = automodTrigger<CounterTriggerResult>()({
|
||||
configType: t.type({
|
||||
counter: t.string,
|
||||
trigger: t.string,
|
||||
reverse: tNullable(t.boolean),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
counter: z.string().max(100),
|
||||
trigger: z.string().max(100),
|
||||
reverse: z.boolean().optional(),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const CounterTrigger = automodTrigger<CounterTriggerResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context }) {
|
||||
if (!context.counterTrigger) {
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ExampleMatchResultType {
|
||||
isBanana: boolean;
|
||||
}
|
||||
|
||||
export const ExampleTrigger = automodTrigger<ExampleMatchResultType>()({
|
||||
configType: t.type({
|
||||
allowedFruits: t.array(t.string),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
allowedFruits: z.array(z.string().max(100)).max(50).default(["peach", "banana"]),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
allowedFruits: ["peach", "banana"],
|
||||
},
|
||||
export const ExampleTrigger = automodTrigger<ExampleMatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context }) {
|
||||
const foundFruit = triggerConfig.allowedFruits.find((fruit) => context.message?.data.content === fruit);
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface KickTriggerResultType {}
|
||||
|
||||
export const KickTrigger = automodTrigger<KickTriggerResultType>()({
|
||||
configType: t.type({
|
||||
manual: t.boolean,
|
||||
automatic: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
manual: z.boolean().default(true),
|
||||
automatic: z.boolean().default(true),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
manual: true,
|
||||
automatic: true,
|
||||
},
|
||||
export const KickTrigger = automodTrigger<KickTriggerResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (context.modAction?.type !== "kick") {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { escapeInlineCode, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { extname } from "path";
|
||||
import z from "zod";
|
||||
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
|
@ -9,20 +9,33 @@ interface MatchResultType {
|
|||
mode: "blacklist" | "whitelist";
|
||||
}
|
||||
|
||||
export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
filetype_blacklist: t.array(t.string),
|
||||
blacklist_enabled: t.boolean,
|
||||
filetype_whitelist: t.array(t.string),
|
||||
whitelist_enabled: t.boolean,
|
||||
}),
|
||||
const configSchema = z
|
||||
.strictObject({
|
||||
filetype_blacklist: z.array(z.string().max(32)).max(255).default([]),
|
||||
blacklist_enabled: z.boolean().default(false),
|
||||
filetype_whitelist: z.array(z.string().max(32)).max(255).default([]),
|
||||
whitelist_enabled: z.boolean().default(false),
|
||||
})
|
||||
.transform((parsed, ctx) => {
|
||||
if (parsed.blacklist_enabled && parsed.whitelist_enabled) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Cannot have both blacklist and whitelist enabled",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
if (!parsed.blacklist_enabled && !parsed.whitelist_enabled) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Must have either blacklist or whitelist enabled",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
filetype_blacklist: [],
|
||||
blacklist_enabled: false,
|
||||
filetype_whitelist: [],
|
||||
whitelist_enabled: false,
|
||||
},
|
||||
export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as t from "io-ts";
|
||||
import { getInviteCodesInString, GuildInvite, isGuildInvite, resolveInvite, tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { getInviteCodesInString, GuildInvite, isGuildInvite, resolveInvite, zSnowflake } from "../../../utils";
|
||||
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
@ -10,30 +10,22 @@ interface MatchResultType {
|
|||
invite?: GuildInvite;
|
||||
}
|
||||
|
||||
export const MatchInvitesTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
include_guilds: tNullable(t.array(t.string)),
|
||||
exclude_guilds: tNullable(t.array(t.string)),
|
||||
include_invite_codes: tNullable(t.array(t.string)),
|
||||
exclude_invite_codes: tNullable(t.array(t.string)),
|
||||
allow_group_dm_invites: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
include_guilds: z.array(zSnowflake).max(255).optional(),
|
||||
exclude_guilds: z.array(zSnowflake).max(255).optional(),
|
||||
include_invite_codes: z.array(z.string().max(32)).max(255).optional(),
|
||||
exclude_invite_codes: z.array(z.string().max(32)).max(255).optional(),
|
||||
allow_group_dm_invites: z.boolean().default(false),
|
||||
match_messages: z.boolean().default(true),
|
||||
match_embeds: z.boolean().default(false),
|
||||
match_visible_names: z.boolean().default(false),
|
||||
match_usernames: z.boolean().default(false),
|
||||
match_nicknames: z.boolean().default(false),
|
||||
match_custom_status: z.boolean().default(false),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
allow_group_dm_invites: false,
|
||||
match_messages: true,
|
||||
match_embeds: false,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
},
|
||||
export const MatchInvitesTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { escapeInlineCode } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { allowTimeout } from "../../../RegExpRunner";
|
||||
import { phishermanDomainIsSafe } from "../../../data/Phisherman";
|
||||
import { getUrlsInString, tNullable } from "../../../utils";
|
||||
import { getUrlsInString, zRegex } 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";
|
||||
|
@ -21,40 +20,37 @@ const regexCache = new WeakMap<any, RegExp[]>();
|
|||
|
||||
const quickLinkCheck = /^https?:\/\//i;
|
||||
|
||||
export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
include_domains: tNullable(t.array(t.string)),
|
||||
exclude_domains: tNullable(t.array(t.string)),
|
||||
include_subdomains: t.boolean,
|
||||
include_words: tNullable(t.array(t.string)),
|
||||
exclude_words: tNullable(t.array(t.string)),
|
||||
include_regex: tNullable(t.array(TRegex)),
|
||||
exclude_regex: tNullable(t.array(TRegex)),
|
||||
phisherman: tNullable(
|
||||
t.type({
|
||||
include_suspected: tNullable(t.boolean),
|
||||
include_verified: tNullable(t.boolean),
|
||||
}),
|
||||
),
|
||||
only_real_links: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
include_domains: z.array(z.string().max(255)).max(700).optional(),
|
||||
exclude_domains: z.array(z.string().max(255)).max(700).optional(),
|
||||
include_subdomains: z.boolean().default(true),
|
||||
include_words: z.array(z.string().max(2000)).max(700).optional(),
|
||||
exclude_words: z.array(z.string().max(2000)).max(700).optional(),
|
||||
include_regex: z
|
||||
.array(zRegex(z.string().max(2000)))
|
||||
.max(512)
|
||||
.optional(),
|
||||
exclude_regex: z
|
||||
.array(zRegex(z.string().max(2000)))
|
||||
.max(512)
|
||||
.optional(),
|
||||
phisherman: z
|
||||
.strictObject({
|
||||
include_suspected: z.boolean().optional(),
|
||||
include_verified: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
only_real_links: z.boolean().default(true),
|
||||
match_messages: z.boolean().default(true),
|
||||
match_embeds: z.boolean().default(true),
|
||||
match_visible_names: z.boolean().default(false),
|
||||
match_usernames: z.boolean().default(false),
|
||||
match_nicknames: z.boolean().default(false),
|
||||
match_custom_status: z.boolean().default(false),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
include_subdomains: true,
|
||||
match_messages: true,
|
||||
match_embeds: false,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
only_real_links: true,
|
||||
},
|
||||
export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { escapeInlineCode } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
|
@ -8,20 +8,33 @@ interface MatchResultType {
|
|||
mode: "blacklist" | "whitelist";
|
||||
}
|
||||
|
||||
export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
mime_type_blacklist: t.array(t.string),
|
||||
blacklist_enabled: t.boolean,
|
||||
mime_type_whitelist: t.array(t.string),
|
||||
whitelist_enabled: t.boolean,
|
||||
}),
|
||||
const configSchema = z
|
||||
.strictObject({
|
||||
mime_type_blacklist: z.array(z.string().max(255)).max(255).default([]),
|
||||
blacklist_enabled: z.boolean().default(false),
|
||||
mime_type_whitelist: z.array(z.string().max(255)).max(255).default([]),
|
||||
whitelist_enabled: z.boolean().default(false),
|
||||
})
|
||||
.transform((parsed, ctx) => {
|
||||
if (parsed.blacklist_enabled && parsed.whitelist_enabled) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Cannot have both blacklist and whitelist enabled",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
if (!parsed.blacklist_enabled && !parsed.whitelist_enabled) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Must have either blacklist or whitelist enabled",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
mime_type_blacklist: [],
|
||||
blacklist_enabled: false,
|
||||
mime_type_whitelist: [],
|
||||
whitelist_enabled: false,
|
||||
},
|
||||
export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig: trigger }) {
|
||||
if (!context.message) return;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { allowTimeout } from "../../../RegExpRunner";
|
||||
import { zRegex } from "../../../utils";
|
||||
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";
|
||||
|
@ -13,33 +13,23 @@ interface MatchResultType {
|
|||
type: MatchableTextType;
|
||||
}
|
||||
|
||||
const configSchema = z.strictObject({
|
||||
patterns: z.array(zRegex(z.string().max(2000))).max(512),
|
||||
case_sensitive: z.boolean().default(false),
|
||||
normalize: z.boolean().default(false),
|
||||
strip_markdown: z.boolean().default(false),
|
||||
match_messages: z.boolean().default(true),
|
||||
match_embeds: z.boolean().default(false),
|
||||
match_visible_names: z.boolean().default(false),
|
||||
match_usernames: z.boolean().default(false),
|
||||
match_nicknames: z.boolean().default(false),
|
||||
match_custom_status: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const regexCache = new WeakMap<any, RegExp[]>();
|
||||
|
||||
export const MatchRegexTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
patterns: t.array(TRegex),
|
||||
case_sensitive: t.boolean,
|
||||
normalize: t.boolean,
|
||||
strip_markdown: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
case_sensitive: false,
|
||||
normalize: false,
|
||||
strip_markdown: false,
|
||||
match_messages: true,
|
||||
match_embeds: false,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
},
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { normalizeText } from "../../../utils/normalizeText";
|
||||
import { stripMarkdown } from "../../../utils/stripMarkdown";
|
||||
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
||||
|
@ -13,37 +13,24 @@ interface MatchResultType {
|
|||
|
||||
const regexCache = new WeakMap<any, RegExp[]>();
|
||||
|
||||
export const MatchWordsTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
words: t.array(t.string),
|
||||
case_sensitive: t.boolean,
|
||||
only_full_words: t.boolean,
|
||||
normalize: t.boolean,
|
||||
loose_matching: t.boolean,
|
||||
loose_matching_threshold: t.number,
|
||||
strip_markdown: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
words: z.array(z.string().max(2000)).max(1024),
|
||||
case_sensitive: z.boolean().default(false),
|
||||
only_full_words: z.boolean().default(true),
|
||||
normalize: z.boolean().default(false),
|
||||
loose_matching: z.boolean().default(false),
|
||||
loose_matching_threshold: z.number().int().default(4),
|
||||
strip_markdown: z.boolean().default(false),
|
||||
match_messages: z.boolean().default(true),
|
||||
match_embeds: z.boolean().default(false),
|
||||
match_visible_names: z.boolean().default(false),
|
||||
match_usernames: z.boolean().default(false),
|
||||
match_nicknames: z.boolean().default(false),
|
||||
match_custom_status: z.boolean().default(false),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
case_sensitive: false,
|
||||
only_full_words: true,
|
||||
normalize: false,
|
||||
loose_matching: false,
|
||||
loose_matching_threshold: 4,
|
||||
strip_markdown: false,
|
||||
match_messages: true,
|
||||
match_embeds: false,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
},
|
||||
export const MatchWordsTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, tDelayString } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, zDelayString } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const MemberJoinTrigger = automodTrigger<unknown>()({
|
||||
configType: t.type({
|
||||
only_new: t.boolean,
|
||||
new_threshold: tDelayString,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
only_new: z.boolean().default(false),
|
||||
new_threshold: zDelayString.default("1h"),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
only_new: false,
|
||||
new_threshold: "1h",
|
||||
},
|
||||
export const MemberJoinTrigger = automodTrigger<unknown>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (!context.joined || !context.member) {
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, tDelayString } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, zDelayString } from "../../../utils";
|
||||
import { RecentActionType } from "../constants";
|
||||
import { findRecentSpam } from "../functions/findRecentSpam";
|
||||
import { getMatchingRecentActions } from "../functions/getMatchingRecentActions";
|
||||
import { sumRecentActionCounts } from "../functions/sumRecentActionCounts";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const MemberJoinSpamTrigger = automodTrigger<unknown>()({
|
||||
configType: t.type({
|
||||
amount: t.number,
|
||||
within: tDelayString,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
amount: z.number().int(),
|
||||
within: zDelayString,
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const MemberJoinSpamTrigger = automodTrigger<unknown>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig }) {
|
||||
if (!context.joined || !context.member) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const MemberLeaveTrigger = automodTrigger<unknown>()({
|
||||
configType: t.type({}),
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
defaultConfig: {},
|
||||
export const MemberLeaveTrigger = automodTrigger<unknown>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (!context.joined || !context.member) {
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface MuteTriggerResultType {}
|
||||
|
||||
export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({
|
||||
configType: t.type({
|
||||
manual: t.boolean,
|
||||
automatic: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
manual: z.boolean().default(true),
|
||||
automatic: z.boolean().default(true),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
manual: true,
|
||||
automatic: true,
|
||||
},
|
||||
export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (context.modAction?.type !== "mute") {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface NoteTriggerResultType {}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const NoteTrigger = automodTrigger<NoteTriggerResultType>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (context.modAction?.type !== "note") {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { renderUserUsername } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { renderUsername, zSnowflake } from "../../../utils";
|
||||
import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
|
@ -8,10 +8,10 @@ interface RoleAddedMatchResult {
|
|||
matchedRoleId: string;
|
||||
}
|
||||
|
||||
export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||
configType: t.union([t.string, t.array(t.string)]),
|
||||
const configSchema = z.union([zSnowflake, z.array(zSnowflake).max(255)]).default([]);
|
||||
|
||||
defaultConfig: "",
|
||||
export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context, pluginData }) {
|
||||
if (!context.member || !context.rolesChanged || context.rolesChanged.added!.length === 0) {
|
||||
|
@ -38,7 +38,7 @@ export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
|||
const role = pluginData.guild.roles.cache.get(matchResult.extra.matchedRoleId as Snowflake);
|
||||
const roleName = role?.name || "Unknown";
|
||||
const member = contexts[0].member!;
|
||||
const memberName = `**${renderUserUsername(member.user)}** (\`${member.id}\`)`;
|
||||
const memberName = `**${renderUsername(member)}** (\`${member.id}\`)`;
|
||||
return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was added to ${memberName}`;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { renderUserUsername } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { renderUsername, zSnowflake } from "../../../utils";
|
||||
import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
|
@ -8,10 +8,10 @@ interface RoleAddedMatchResult {
|
|||
matchedRoleId: string;
|
||||
}
|
||||
|
||||
export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||
configType: t.union([t.string, t.array(t.string)]),
|
||||
const configSchema = z.union([zSnowflake, z.array(zSnowflake).max(255)]).default([]);
|
||||
|
||||
defaultConfig: "",
|
||||
export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context, pluginData }) {
|
||||
if (!context.member || !context.rolesChanged || context.rolesChanged.removed!.length === 0) {
|
||||
|
@ -38,7 +38,7 @@ export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
|||
const role = pluginData.guild.roles.cache.get(matchResult.extra.matchedRoleId as Snowflake);
|
||||
const roleName = role?.name || "Unknown";
|
||||
const member = contexts[0].member!;
|
||||
const memberName = `**${renderUserUsername(member.user)}** (\`${member.id}\`)`;
|
||||
const memberName = `**${renderUsername(member)}** (\`${member.id}\`)`;
|
||||
return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was removed from ${memberName}`;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { User, escapeBold, type Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { renderUsername } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ThreadArchiveResult {
|
||||
|
@ -11,12 +11,12 @@ interface ThreadArchiveResult {
|
|||
matchedThreadOwner: User | undefined;
|
||||
}
|
||||
|
||||
export const ThreadArchiveTrigger = automodTrigger<ThreadArchiveResult>()({
|
||||
configType: t.type({
|
||||
locked: tNullable(t.boolean),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
locked: z.boolean().optional(),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const ThreadArchiveTrigger = automodTrigger<ThreadArchiveResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (!context.threadChange?.archived) {
|
||||
|
@ -48,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 **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
|
||||
return `${base} by **${escapeBold(renderUsername(threadOwner))}** (\`${threadOwner.id}\`)`;
|
||||
}
|
||||
return base;
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { User, escapeBold, type Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { renderUsername } from "../../../utils.js";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ThreadCreateResult {
|
||||
|
@ -10,9 +11,10 @@ interface ThreadCreateResult {
|
|||
matchedThreadOwner: User | undefined;
|
||||
}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const ThreadCreateTrigger = automodTrigger<ThreadCreateResult>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (!context.threadChange?.created) {
|
||||
|
@ -40,7 +42,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 **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
|
||||
return `${base} by **${escapeBold(renderUsername(threadOwner))}** (\`${threadOwner.id}\`)`;
|
||||
}
|
||||
return base;
|
||||
},
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, tDelayString } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, zDelayString } from "../../../utils";
|
||||
import { RecentActionType } from "../constants";
|
||||
import { findRecentSpam } from "../functions/findRecentSpam";
|
||||
import { getMatchingRecentActions } from "../functions/getMatchingRecentActions";
|
||||
import { sumRecentActionCounts } from "../functions/sumRecentActionCounts";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const ThreadCreateSpamTrigger = automodTrigger<unknown>()({
|
||||
configType: t.type({
|
||||
amount: t.number,
|
||||
within: tDelayString,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
amount: z.number().int(),
|
||||
within: zDelayString,
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const ThreadCreateSpamTrigger = automodTrigger<unknown>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig }) {
|
||||
if (!context.threadChange?.created) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { User, escapeBold, type Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { renderUsername } from "../../../utils.js";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ThreadDeleteResult {
|
||||
|
@ -10,9 +11,10 @@ interface ThreadDeleteResult {
|
|||
matchedThreadOwner: User | undefined;
|
||||
}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const ThreadDeleteTrigger = automodTrigger<ThreadDeleteResult>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (!context.threadChange?.deleted) {
|
||||
|
@ -40,7 +42,7 @@ export const ThreadDeleteTrigger = automodTrigger<ThreadDeleteResult>()({
|
|||
const parentName = matchResult.extra.matchedThreadParentName;
|
||||
if (threadOwner) {
|
||||
return `Thread **#${threadName ?? "Unknown"}** (\`${threadId}\`) created by **${escapeBold(
|
||||
threadOwner.tag,
|
||||
renderUsername(threadOwner),
|
||||
)}** (\`${threadOwner.id}\`) in the **#${parentName}** (\`${parentId}\`) channel has been deleted`;
|
||||
}
|
||||
return `Thread **#${
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { User, escapeBold, type Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { renderUsername } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ThreadUnarchiveResult {
|
||||
|
@ -11,12 +11,12 @@ interface ThreadUnarchiveResult {
|
|||
matchedThreadOwner: User | undefined;
|
||||
}
|
||||
|
||||
export const ThreadUnarchiveTrigger = automodTrigger<ThreadUnarchiveResult>()({
|
||||
configType: t.type({
|
||||
locked: tNullable(t.boolean),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
locked: z.boolean().optional(),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const ThreadUnarchiveTrigger = automodTrigger<ThreadUnarchiveResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (!context.threadChange?.unarchived) {
|
||||
|
@ -48,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 **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
|
||||
return `${base} by **${escapeBold(renderUsername(threadOwner))}** (\`${threadOwner.id}\`)`;
|
||||
}
|
||||
return base;
|
||||
},
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface UnbanTriggerResultType {}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const UnbanTrigger = automodTrigger<UnbanTriggerResultType>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (context.modAction?.type !== "unban") {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface UnmuteTriggerResultType {}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const UnmuteTrigger = automodTrigger<UnmuteTriggerResultType>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (context.modAction?.type !== "unmute") {
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface WarnTriggerResultType {}
|
||||
|
||||
export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({
|
||||
configType: t.type({
|
||||
manual: t.boolean,
|
||||
automatic: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
manual: z.boolean().default(true),
|
||||
automatic: z.boolean().default(true),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
manual: true,
|
||||
automatic: true,
|
||||
},
|
||||
export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (context.modAction?.type !== "warn") {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { GuildMember, GuildTextBasedChannel, PartialGuildMember, ThreadChannel, User } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { BasePluginType, CooldownManager } from "knub";
|
||||
import z from "zod";
|
||||
import { Queue } from "../../Queue";
|
||||
import { RegExpRunner } from "../../RegExpRunner";
|
||||
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
|
||||
|
@ -8,39 +8,80 @@ import { GuildArchives } from "../../data/GuildArchives";
|
|||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { SavedMessage } from "../../data/entities/SavedMessage";
|
||||
import { tNullable } from "../../utils";
|
||||
import { entries, zBoundedRecord, zDelayString } from "../../utils";
|
||||
import { CounterEvents } from "../Counters/types";
|
||||
import { ModActionType, ModActionsEvents } from "../ModActions/types";
|
||||
import { MutesEvents } from "../Mutes/types";
|
||||
import { AvailableActions } from "./actions/availableActions";
|
||||
import { availableActions } from "./actions/availableActions";
|
||||
import { RecentActionType } from "./constants";
|
||||
import { AvailableTriggers } from "./triggers/availableTriggers";
|
||||
import { availableTriggers } from "./triggers/availableTriggers";
|
||||
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
export const Rule = t.type({
|
||||
enabled: t.boolean,
|
||||
name: t.string,
|
||||
presets: tNullable(t.array(t.string)),
|
||||
affects_bots: t.boolean,
|
||||
affects_self: t.boolean,
|
||||
triggers: t.array(t.partial(AvailableTriggers.props)),
|
||||
actions: t.partial(AvailableActions.props),
|
||||
cooldown: tNullable(t.string),
|
||||
allow_further_rules: t.boolean,
|
||||
});
|
||||
export type TRule = t.TypeOf<typeof Rule>;
|
||||
export type ZTriggersMapHelper = {
|
||||
[TriggerName in keyof typeof availableTriggers]: (typeof availableTriggers)[TriggerName]["configSchema"];
|
||||
};
|
||||
const zTriggersMap = z
|
||||
.strictObject(
|
||||
entries(availableTriggers).reduce((map, [triggerName, trigger]) => {
|
||||
map[triggerName] = trigger.configSchema;
|
||||
return map;
|
||||
}, {} as ZTriggersMapHelper),
|
||||
)
|
||||
.partial();
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
rules: t.record(t.string, Rule),
|
||||
antiraid_levels: t.array(t.string),
|
||||
can_set_antiraid: t.boolean,
|
||||
can_view_antiraid: t.boolean,
|
||||
type ZActionsMapHelper = {
|
||||
[ActionName in keyof typeof availableActions]: (typeof availableActions)[ActionName]["configSchema"];
|
||||
};
|
||||
const zActionsMap = z
|
||||
.strictObject(
|
||||
entries(availableActions).reduce((map, [actionName, action]) => {
|
||||
// @ts-expect-error TS can't infer this properly but it works fine thanks to our helper
|
||||
map[actionName] = action.configSchema;
|
||||
return map;
|
||||
}, {} as ZActionsMapHelper),
|
||||
)
|
||||
.partial();
|
||||
|
||||
const zRule = z.strictObject({
|
||||
enabled: z.boolean().default(true),
|
||||
// Typed as "never" because you are not expected to supply this directly.
|
||||
// The transform instead picks it up from the property key and the output type is a string.
|
||||
name: z
|
||||
.never()
|
||||
.optional()
|
||||
.transform((_, ctx) => {
|
||||
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
|
||||
if (!ruleName) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Automod rules must have names",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return ruleName;
|
||||
}),
|
||||
presets: z.array(z.string().max(100)).max(25).default([]),
|
||||
affects_bots: z.boolean().default(false),
|
||||
affects_self: z.boolean().default(false),
|
||||
cooldown: zDelayString.nullable().default(null),
|
||||
allow_further_rules: z.boolean().default(false),
|
||||
triggers: z.array(zTriggersMap),
|
||||
actions: zActionsMap.refine((v) => !(v.clean && v.start_thread), {
|
||||
message: "Cannot have both clean and start_thread active at the same time",
|
||||
}),
|
||||
});
|
||||
export type TRule = z.infer<typeof zRule>;
|
||||
|
||||
export const zAutomodConfig = z.strictObject({
|
||||
rules: zBoundedRecord(z.record(z.string().max(100), zRule), 0, 255),
|
||||
antiraid_levels: z.array(z.string().max(100)).max(10),
|
||||
can_set_antiraid: z.boolean(),
|
||||
can_view_antiraid: z.boolean(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface AutomodPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.output<typeof zAutomodConfig>;
|
||||
|
||||
customOverrideCriteria: {
|
||||
antiraid_level?: string;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { AllowedGuilds } from "../../data/AllowedGuilds";
|
|||
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
|
||||
import { Configs } from "../../data/Configs";
|
||||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
import { makeIoTsConfigParser } from "../../pluginUtils";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin";
|
||||
import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { getActiveReload, resetActiveReload } from "./activeReload";
|
||||
|
@ -23,7 +22,7 @@ import { ReloadServerCmd } from "./commands/ReloadServerCmd";
|
|||
import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd";
|
||||
import { RestPerformanceCmd } from "./commands/RestPerformanceCmd";
|
||||
import { ServersCmd } from "./commands/ServersCmd";
|
||||
import { BotControlPluginType, ConfigSchema } from "./types";
|
||||
import { BotControlPluginType, zBotControlConfig } from "./types";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -38,7 +37,7 @@ const defaultOptions = {
|
|||
|
||||
export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()({
|
||||
name: "bot_control",
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
configParser: (input) => zBotControlConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
// prettier-ignore
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ApiPermissions } from "@shared/apiPermissions";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils";
|
||||
import { renderUserUsername } from "../../../utils";
|
||||
import { renderUsername } from "../../../utils";
|
||||
import { CommonPlugin } from "../../Common/CommonPlugin";
|
||||
import { botControlCmd } from "../types";
|
||||
|
||||
|
@ -36,7 +36,8 @@ export const AddDashboardUserCmd = botControlCmd({
|
|||
await pluginData.state.apiPermissionAssignments.addUser(args.guildId, user.id, [ApiPermissions.EditConfig]);
|
||||
}
|
||||
|
||||
const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUserUsername(user)}**, \`${user.id}\`)`);
|
||||
const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`)`);
|
||||
|
||||
pluginData
|
||||
.getPlugin(CommonPlugin)
|
||||
.sendSuccessMessage(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { AllowedGuild } from "../../../data/entities/AllowedGuild";
|
||||
import { ApiPermissionAssignment } from "../../../data/entities/ApiPermissionAssignment";
|
||||
import { renderUserUsername, resolveUser } from "../../../utils";
|
||||
import { renderUsername, resolveUser } from "../../../utils";
|
||||
import { CommonPlugin } from "../../Common/CommonPlugin";
|
||||
import { botControlCmd } from "../types";
|
||||
|
||||
|
@ -42,7 +42,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
|||
|
||||
// If we have user, always display which guilds they have permissions in (or only specified guild permissions)
|
||||
if (args.user) {
|
||||
const userInfo = `**${renderUserUsername(args.user)}** (\`${args.user.id}\`)`;
|
||||
const userInfo = `**${renderUsername(args.user)}** (\`${args.user.id}\`)`;
|
||||
|
||||
for (const assignment of existingUserAssignment!) {
|
||||
if (guild != null && assignment.guild_id !== args.guildId) continue;
|
||||
|
@ -74,7 +74,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
|||
finalMessage += `The server ${guildInfo} has the following assigned permissions:\n`; // Double \n for consistency with AddDashboardUserCmd
|
||||
for (const assignment of existingGuildAssignment) {
|
||||
const user = await resolveUser(pluginData.client, assignment.target_id);
|
||||
finalMessage += `\n**${renderUserUsername(user)}**, \`${assignment.target_id}\`: ${assignment.permissions.join(
|
||||
finalMessage += `\n**${renderUsername(user)}**, \`${assignment.target_id}\`: ${assignment.permissions.join(
|
||||
", ",
|
||||
)}`;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { renderUserUsername, resolveUser } from "../../../utils";
|
||||
import { renderUsername, resolveUser } from "../../../utils";
|
||||
import { CommonPlugin } from "../../Common/CommonPlugin";
|
||||
import { botControlCmd } from "../types";
|
||||
|
||||
|
@ -27,7 +27,7 @@ export const ListDashboardUsersCmd = botControlCmd({
|
|||
);
|
||||
const userNameList = users.map(
|
||||
({ user, permission }) =>
|
||||
`<@!${user.id}> (**${renderUserUsername(user)}**, \`${user.id}\`): ${permission.permissions.join(", ")}`,
|
||||
`<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`): ${permission.permissions.join(", ")}`,
|
||||
);
|
||||
|
||||
pluginData
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils";
|
||||
import { renderUserUsername } from "../../../utils";
|
||||
import { renderUsername } from "../../../utils";
|
||||
import { CommonPlugin } from "../../Common/CommonPlugin";
|
||||
import { botControlCmd } from "../types";
|
||||
|
||||
|
@ -35,7 +35,8 @@ export const RemoveDashboardUserCmd = botControlCmd({
|
|||
await pluginData.state.apiPermissionAssignments.removeUser(args.guildId, user.id);
|
||||
}
|
||||
|
||||
const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUserUsername(user)}**, \`${user.id}\`)`);
|
||||
const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`)`);
|
||||
|
||||
pluginData
|
||||
.getPlugin(CommonPlugin)
|
||||
.sendSuccessMessage(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils";
|
||||
import { createChunkedMessage, getUser, sorter } from "../../../utils";
|
||||
import { createChunkedMessage, getUser, renderUsername, sorter } from "../../../utils";
|
||||
import { botControlCmd } from "../types";
|
||||
|
||||
export const ServersCmd = botControlCmd({
|
||||
|
@ -48,7 +48,9 @@ export const ServersCmd = botControlCmd({
|
|||
const lines = filteredGuilds.map((g) => {
|
||||
const paddedId = g.id.padEnd(longestId, " ");
|
||||
const owner = getUser(pluginData.client, g.ownerId);
|
||||
return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${owner.tag}** \`${owner.id}\`)`;
|
||||
return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${renderUsername(owner)}** \`${
|
||||
owner.id
|
||||
}\`)`;
|
||||
});
|
||||
createChunkedMessage(msg.channel, lines.join("\n"));
|
||||
} else {
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType, globalPluginEventListener, globalPluginMessageCommand } from "knub";
|
||||
import z from "zod";
|
||||
import { AllowedGuilds } from "../../data/AllowedGuilds";
|
||||
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
|
||||
import { Configs } from "../../data/Configs";
|
||||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
import { tNullable } from "../../utils";
|
||||
import { zBoundedCharacters } from "../../utils";
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
can_use: t.boolean,
|
||||
can_eligible: t.boolean,
|
||||
can_performance: t.boolean,
|
||||
can_add_server_from_invite: t.boolean,
|
||||
can_list_dashboard_perms: t.boolean,
|
||||
update_cmd: tNullable(t.string),
|
||||
export const zBotControlConfig = z.strictObject({
|
||||
can_use: z.boolean(),
|
||||
can_eligible: z.boolean(),
|
||||
can_performance: z.boolean(),
|
||||
can_add_server_from_invite: z.boolean(),
|
||||
can_list_dashboard_perms: z.boolean(),
|
||||
update_cmd: zBoundedCharacters(0, 2000).nullable(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface BotControlPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.output<typeof zBotControlConfig>;
|
||||
state: {
|
||||
archives: GuildArchives;
|
||||
allowedGuilds: AllowedGuilds;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Case } from "../../data/entities/Case";
|
|||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
import { GuildCases } from "../../data/GuildCases";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { makeIoTsConfigParser, mapToPublicFn } from "../../pluginUtils";
|
||||
import { mapToPublicFn } from "../../pluginUtils";
|
||||
import { trimPluginDescription } from "../../utils";
|
||||
import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin";
|
||||
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
|
||||
|
@ -16,7 +16,7 @@ import { getCaseTypeAmountForUserId } from "./functions/getCaseTypeAmountForUser
|
|||
import { getRecentCasesByMod } from "./functions/getRecentCasesByMod";
|
||||
import { getTotalCasesByMod } from "./functions/getTotalCasesByMod";
|
||||
import { postCaseToCaseLogChannel } from "./functions/postToCaseLogChannel";
|
||||
import { CaseArgs, CaseNoteArgs, CasesPluginType, ConfigSchema } from "./types";
|
||||
import { CaseArgs, CaseNoteArgs, CasesPluginType, zCasesConfig } from "./types";
|
||||
|
||||
// The `any` cast here is to prevent TypeScript from locking up from the circular dependency
|
||||
function getLogsPlugin(): Promise<any> {
|
||||
|
@ -42,11 +42,11 @@ export const CasesPlugin = zeppelinGuildPlugin<CasesPluginType>()({
|
|||
description: trimPluginDescription(`
|
||||
This plugin contains basic configuration for cases created by other plugins
|
||||
`),
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zCasesConfig,
|
||||
},
|
||||
|
||||
dependencies: async () => [TimeAndDatePlugin, InternalPosterPlugin, (await getLogsPlugin()).LogsPlugin],
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
configParser: (input) => zCasesConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
public: {
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import type { Snowflake } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { logger } from "../../../logger";
|
||||
import { renderUserUsername, resolveUser } from "../../../utils";
|
||||
import { renderUsername, resolveUser } from "../../../utils";
|
||||
import { CaseArgs, CasesPluginType } from "../types";
|
||||
import { createCaseNote } from "./createCaseNote";
|
||||
import { postCaseToCaseLogChannel } from "./postToCaseLogChannel";
|
||||
|
||||
export async function createCase(pluginData: GuildPluginData<CasesPluginType>, args: CaseArgs) {
|
||||
const user = await resolveUser(pluginData.client, args.userId);
|
||||
const userName = renderUserUsername(user);
|
||||
const name = renderUsername(user);
|
||||
|
||||
const mod = await resolveUser(pluginData.client, args.modId);
|
||||
const modName = mod.tag;
|
||||
const modName = renderUsername(mod);
|
||||
|
||||
let ppName: string | null = null;
|
||||
let ppId: Snowflake | null = null;
|
||||
if (args.ppId) {
|
||||
const pp = await resolveUser(pluginData.client, args.ppId);
|
||||
ppName = pp.tag;
|
||||
ppName = renderUsername(pp);
|
||||
ppId = pp.id;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ export async function createCase(pluginData: GuildPluginData<CasesPluginType>, a
|
|||
const createdCase = await pluginData.state.cases.create({
|
||||
type: args.type,
|
||||
user_id: user.id,
|
||||
user_name: userName,
|
||||
user_name: name,
|
||||
mod_id: mod.id,
|
||||
mod_name: modName,
|
||||
audit_log_id: args.auditLogId,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
|
||||
import { UnknownUser, resolveUser } from "../../../utils";
|
||||
import { UnknownUser, renderUsername, resolveUser } from "../../../utils";
|
||||
import { CaseNoteArgs, CasesPluginType } from "../types";
|
||||
import { postCaseToCaseLogChannel } from "./postToCaseLogChannel";
|
||||
import { resolveCaseId } from "./resolveCaseId";
|
||||
|
@ -16,7 +16,7 @@ export async function createCaseNote(pluginData: GuildPluginData<CasesPluginType
|
|||
throw new RecoverablePluginError(ERRORS.INVALID_USER);
|
||||
}
|
||||
|
||||
const modName = mod.tag;
|
||||
const modName = renderUsername(mod);
|
||||
|
||||
let body = args.body;
|
||||
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType } from "knub";
|
||||
import { U } from "ts-toolbelt";
|
||||
import z from "zod";
|
||||
import { CaseNameToType, CaseTypes } from "../../data/CaseTypes";
|
||||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
import { GuildCases } from "../../data/GuildCases";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { tDelayString, tNullable, tPartialDictionary } from "../../utils";
|
||||
import { tColor } from "../../utils/tColor";
|
||||
import { keys, zBoundedCharacters, zDelayString, zSnowflake } from "../../utils";
|
||||
import { zColor } from "../../utils/zColor";
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
log_automatic_actions: t.boolean,
|
||||
case_log_channel: tNullable(t.string),
|
||||
show_relative_times: t.boolean,
|
||||
relative_time_cutoff: tDelayString,
|
||||
case_colors: tNullable(tPartialDictionary(t.keyof(CaseNameToType), tColor)),
|
||||
case_icons: tNullable(tPartialDictionary(t.keyof(CaseNameToType), t.string)),
|
||||
const caseKeys = keys(CaseNameToType) as U.ListOf<keyof typeof CaseNameToType>;
|
||||
|
||||
export const zCasesConfig = z.strictObject({
|
||||
log_automatic_actions: z.boolean(),
|
||||
case_log_channel: zSnowflake.nullable(),
|
||||
show_relative_times: z.boolean(),
|
||||
relative_time_cutoff: zDelayString.default("1w"),
|
||||
case_colors: z.record(z.enum(caseKeys), zColor).nullable(),
|
||||
case_icons: z.record(z.enum(caseKeys), zBoundedCharacters(0, 100)).nullable(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface CasesPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.infer<typeof zCasesConfig>;
|
||||
state: {
|
||||
logs: GuildLogs;
|
||||
cases: GuildCases;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { PluginOptions } from "knub";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { makeIoTsConfigParser } from "../../pluginUtils";
|
||||
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
||||
import { trimPluginDescription } from "../../utils";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { CensorPluginType, ConfigSchema } from "./types";
|
||||
import { CensorPluginType, zCensorConfig } from "./types";
|
||||
import { onMessageCreate } from "./util/onMessageCreate";
|
||||
import { onMessageUpdate } from "./util/onMessageUpdate";
|
||||
|
||||
|
@ -54,11 +53,11 @@ export const CensorPlugin = zeppelinGuildPlugin<CensorPluginType>()({
|
|||
For more advanced filtering, check out the Automod plugin!
|
||||
`),
|
||||
legacy: true,
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zCensorConfig,
|
||||
},
|
||||
|
||||
dependencies: () => [LogsPlugin],
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
configParser: (input) => zCensorConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
beforeLoad(pluginData) {
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType } from "knub";
|
||||
import z from "zod";
|
||||
import { RegExpRunner } from "../../RegExpRunner";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { tNullable } from "../../utils";
|
||||
import { TRegex } from "../../validatorUtils";
|
||||
import { zBoundedCharacters, zRegex, zSnowflake } from "../../utils";
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
filter_zalgo: t.boolean,
|
||||
filter_invites: t.boolean,
|
||||
invite_guild_whitelist: tNullable(t.array(t.string)),
|
||||
invite_guild_blacklist: tNullable(t.array(t.string)),
|
||||
invite_code_whitelist: tNullable(t.array(t.string)),
|
||||
invite_code_blacklist: tNullable(t.array(t.string)),
|
||||
allow_group_dm_invites: t.boolean,
|
||||
filter_domains: t.boolean,
|
||||
domain_whitelist: tNullable(t.array(t.string)),
|
||||
domain_blacklist: tNullable(t.array(t.string)),
|
||||
blocked_tokens: tNullable(t.array(t.string)),
|
||||
blocked_words: tNullable(t.array(t.string)),
|
||||
blocked_regex: tNullable(t.array(TRegex)),
|
||||
export const zCensorConfig = z.strictObject({
|
||||
filter_zalgo: z.boolean(),
|
||||
filter_invites: z.boolean(),
|
||||
invite_guild_whitelist: z.array(zSnowflake).nullable(),
|
||||
invite_guild_blacklist: z.array(zSnowflake).nullable(),
|
||||
invite_code_whitelist: z.array(zBoundedCharacters(0, 16)).nullable(),
|
||||
invite_code_blacklist: z.array(zBoundedCharacters(0, 16)).nullable(),
|
||||
allow_group_dm_invites: z.boolean(),
|
||||
filter_domains: z.boolean(),
|
||||
domain_whitelist: z.array(zBoundedCharacters(0, 255)).nullable(),
|
||||
domain_blacklist: z.array(zBoundedCharacters(0, 255)).nullable(),
|
||||
blocked_tokens: z.array(zBoundedCharacters(0, 2000)).nullable(),
|
||||
blocked_words: z.array(zBoundedCharacters(0, 2000)).nullable(),
|
||||
blocked_regex: z.array(zRegex(z.string().max(1000))).nullable(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface CensorPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.infer<typeof zCensorConfig>;
|
||||
state: {
|
||||
serverLogs: GuildLogs;
|
||||
savedMessages: GuildSavedMessages;
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
import * as t from "io-ts";
|
||||
import { makeIoTsConfigParser } from "../../pluginUtils";
|
||||
import z from "zod";
|
||||
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd";
|
||||
import { ChannelArchiverPluginType } from "./types";
|
||||
|
||||
const ConfigSchema = t.type({});
|
||||
|
||||
export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginType>()({
|
||||
name: "channel_archiver",
|
||||
showInDocs: false,
|
||||
|
||||
dependencies: () => [TimeAndDatePlugin],
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
configParser: (input) => z.strictObject({}).parse(input),
|
||||
|
||||
// prettier-ignore
|
||||
messageCommands: [
|
||||
|
|
|
@ -69,10 +69,9 @@ export const ArchiveChannelCmd = channelArchiverCmd({
|
|||
|
||||
for (const message of messages.values()) {
|
||||
const ts = moment.utc(message.createdTimestamp).format("YYYY-MM-DD HH:mm:ss");
|
||||
let content = `[${ts}] [${message.author.id}] [${renderUsername(
|
||||
message.author.username,
|
||||
message.author.discriminator,
|
||||
)}]: ${message.content || "<no text content>"}`;
|
||||
let content = `[${ts}] [${message.author.id}] [${renderUsername(message.author)}]: ${
|
||||
message.content || "<no text content>"
|
||||
}`;
|
||||
|
||||
if (message.attachments.size) {
|
||||
if (args["attachment-channel"]) {
|
||||
|
|
|
@ -9,11 +9,11 @@ import {
|
|||
} from "discord.js";
|
||||
import { PluginOptions } from "knub";
|
||||
import { logger } from "../../logger";
|
||||
import { isContextInteraction, makeIoTsConfigParser, sendContextResponse } from "../../pluginUtils";
|
||||
import { isContextInteraction, sendContextResponse } from "../../pluginUtils";
|
||||
import { errorMessage, successMessage } from "../../utils";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { getErrorEmoji, getSuccessEmoji } from "./functions/getEmoji";
|
||||
import { CommonPluginType, ConfigSchema } from "./types";
|
||||
import { CommonPluginType, zCommonConfig } from "./types";
|
||||
|
||||
const defaultOptions: PluginOptions<CommonPluginType> = {
|
||||
config: {
|
||||
|
@ -30,7 +30,7 @@ export const CommonPlugin = zeppelinGuildPlugin<CommonPluginType>()({
|
|||
},
|
||||
|
||||
dependencies: () => [],
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
configParser: (input) => zCommonConfig.parse(input),
|
||||
defaultOptions,
|
||||
public: {
|
||||
getSuccessEmoji(pluginData) {
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType } from "knub";
|
||||
import z from "zod";
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
success_emoji: t.string,
|
||||
error_emoji: t.string,
|
||||
export const zCommonConfig = z.strictObject({
|
||||
success_emoji: z.string(),
|
||||
error_emoji: z.string(),
|
||||
});
|
||||
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface CommonPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.output<typeof zCommonConfig>;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { CooldownManager } from "knub";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { makeIoTsConfigParser } from "../../pluginUtils";
|
||||
import { trimPluginDescription } from "../../utils";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { VoiceStateUpdateEvt } from "./events/VoiceStateUpdateEvt";
|
||||
import { CompanionChannelsPluginType, ConfigSchema } from "./types";
|
||||
import { CompanionChannelsPluginType, zCompanionChannelsConfig } from "./types";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -23,11 +22,11 @@ export const CompanionChannelsPlugin = zeppelinGuildPlugin<CompanionChannelsPlug
|
|||
Once set up, any time a user joins one of the specified voice channels,
|
||||
they'll get channel permissions applied to them for the text channels.
|
||||
`),
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zCompanionChannelsConfig,
|
||||
},
|
||||
|
||||
dependencies: () => [LogsPlugin],
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
configParser: (input) => zCompanionChannelsConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
events: [VoiceStateUpdateEvt],
|
||||
|
|
|
@ -1,28 +1,23 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType, CooldownManager, guildPluginEventListener } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { tNullable } from "../../utils";
|
||||
import { zBoundedCharacters, zSnowflake } from "../../utils";
|
||||
|
||||
// Permissions using these numbers: https://abal.moe/Eris/docs/reference (add all allowed/denied ones up)
|
||||
export const CompanionChannelOpts = t.type({
|
||||
voice_channel_ids: t.array(t.string),
|
||||
text_channel_ids: t.array(t.string),
|
||||
permissions: t.number,
|
||||
enabled: tNullable(t.boolean),
|
||||
export const zCompanionChannelOpts = z.strictObject({
|
||||
voice_channel_ids: z.array(zSnowflake),
|
||||
text_channel_ids: z.array(zSnowflake),
|
||||
// See https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
|
||||
permissions: z.number(),
|
||||
enabled: z.boolean().nullable().default(true),
|
||||
});
|
||||
export type TCompanionChannelOpts = t.TypeOf<typeof CompanionChannelOpts>;
|
||||
export type TCompanionChannelOpts = z.infer<typeof zCompanionChannelOpts>;
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
entries: t.record(t.string, CompanionChannelOpts),
|
||||
export const zCompanionChannelsConfig = z.strictObject({
|
||||
entries: z.record(zBoundedCharacters(0, 100), zCompanionChannelOpts),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface ICompanionChannelMap {
|
||||
[channelId: string]: TCompanionChannelOpts;
|
||||
}
|
||||
|
||||
export interface CompanionChannelsPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.infer<typeof zCompanionChannelsConfig>;
|
||||
state: {
|
||||
errorCooldownManager: CooldownManager;
|
||||
serverLogs: GuildLogs;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { PluginOptions } from "knub";
|
||||
import { GuildCases } from "../../data/GuildCases";
|
||||
import { makeIoTsConfigParser } from "../../pluginUtils";
|
||||
import { trimPluginDescription } from "../../utils";
|
||||
import { CasesPlugin } from "../Cases/CasesPlugin";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin";
|
||||
|
@ -14,7 +13,7 @@ import { ModMenuCmd } from "./commands/ModMenuUserCtxCmd";
|
|||
import { MuteCmd } from "./commands/MuteUserCtxCmd";
|
||||
import { NoteCmd } from "./commands/NoteUserCtxCmd";
|
||||
import { WarnCmd } from "./commands/WarnUserCtxCmd";
|
||||
import { ConfigSchema, ContextMenuPluginType } from "./types";
|
||||
import { ContextMenuPluginType, zContextMenusConfig } from "./types";
|
||||
|
||||
const defaultOptions: PluginOptions<ContextMenuPluginType> = {
|
||||
config: {
|
||||
|
@ -42,12 +41,11 @@ export const ContextMenuPlugin = zeppelinGuildPlugin<ContextMenuPluginType>()({
|
|||
description: trimPluginDescription(`
|
||||
This plugin provides command shortcuts via context menus
|
||||
`),
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zContextMenusConfig,
|
||||
},
|
||||
|
||||
dependencies: () => [CasesPlugin, MutesPlugin, ModActionsPlugin, LogsPlugin, UtilityPlugin],
|
||||
configParser: makeIoTsConfigParser(ConfigSchema),
|
||||
|
||||
configParser: (input) => zContextMenusConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
contextMenuCommands: [ModMenuCmd, NoteCmd, WarnCmd, MuteCmd, BanCmd, CleanCmd],
|
||||
|
|
|
@ -9,13 +9,13 @@ import {
|
|||
} from "discord.js";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { canActOn } from "src/pluginUtils";
|
||||
import { ModActionsPlugin } from "src/plugins/ModActions/ModActionsPlugin";
|
||||
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
|
||||
import { logger } from "../../../logger";
|
||||
import { canActOn } from "../../../pluginUtils";
|
||||
import { convertDelayStringToMS } from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { MutesPlugin } from "../../Mutes/MutesPlugin";
|
||||
import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd";
|
||||
import { ContextMenuPluginType, ModMenuActionType } from "../types";
|
||||
|
@ -71,12 +71,12 @@ async function muteAction(
|
|||
const result = await mutes.muteUser(target, durationMs, reason, reason, { caseArgs });
|
||||
|
||||
const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : "";
|
||||
const muteMessage = `Muted **${result.case.user_name}** ${
|
||||
const muteMessage = `Muted **${result.case!.user_name}** ${
|
||||
durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely"
|
||||
} (Case #${result.case.case_number})${messageResultText}`;
|
||||
} (Case #${result.case!.case_number})${messageResultText}`;
|
||||
|
||||
if (evidence) {
|
||||
await updateAction(pluginData, executingMember, result.case, evidence);
|
||||
await updateAction(pluginData, executingMember, result.case!, evidence);
|
||||
}
|
||||
|
||||
await interactionToReply
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import { APIEmbed, Awaitable } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { BasePluginType } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildCases } from "../../data/GuildCases";
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
can_use: t.boolean,
|
||||
|
||||
can_open_mod_menu: t.boolean,
|
||||
export const zContextMenusConfig = z.strictObject({
|
||||
can_use: z.boolean(),
|
||||
can_open_mod_menu: z.boolean(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface ContextMenuPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.infer<typeof zContextMenusConfig>;
|
||||
state: {
|
||||
cases: GuildCases;
|
||||
};
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
import { EventEmitter } from "events";
|
||||
import { PluginOptions } from "knub";
|
||||
import {
|
||||
buildCounterConditionString,
|
||||
CounterTrigger,
|
||||
getReverseCounterComparisonOp,
|
||||
parseCounterConditionString,
|
||||
} from "../../data/entities/CounterTrigger";
|
||||
import { GuildCounters } from "../../data/GuildCounters";
|
||||
import { CounterTrigger, parseCounterConditionString } from "../../data/entities/CounterTrigger";
|
||||
import { mapToPublicFn } from "../../pluginUtils";
|
||||
import { convertDelayStringToMS, MINUTES } from "../../utils";
|
||||
import { parseIoTsSchema, StrictValidationError } from "../../validatorUtils";
|
||||
import { MINUTES, convertDelayStringToMS, values } from "../../utils";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { AddCounterCmd } from "./commands/AddCounterCmd";
|
||||
import { CountersListCmd } from "./commands/CountersListCmd";
|
||||
|
@ -25,10 +19,8 @@ import { getPrettyNameForCounterTrigger } from "./functions/getPrettyNameForCoun
|
|||
import { offCounterEvent } from "./functions/offCounterEvent";
|
||||
import { onCounterEvent } from "./functions/onCounterEvent";
|
||||
import { setCounterValue } from "./functions/setCounterValue";
|
||||
import { ConfigSchema, CountersPluginType, TTrigger } from "./types";
|
||||
import { CountersPluginType, zCountersConfig } from "./types";
|
||||
|
||||
const MAX_COUNTERS = 5;
|
||||
const MAX_TRIGGERS_PER_COUNTER = 5;
|
||||
const DECAY_APPLY_INTERVAL = 5 * MINUTES;
|
||||
|
||||
const defaultOptions: PluginOptions<CountersPluginType> = {
|
||||
|
@ -72,50 +64,12 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()({
|
|||
description:
|
||||
"Keep track of per-user, per-channel, or global numbers and trigger specific actions based on this number",
|
||||
configurationGuide: "See <a href='/docs/setup-guides/counters'>Counters setup guide</a>",
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zCountersConfig,
|
||||
},
|
||||
|
||||
defaultOptions,
|
||||
// TODO: Separate input and output types
|
||||
configParser: (input) => {
|
||||
for (const [counterName, counter] of Object.entries<any>((input as any).counters || {})) {
|
||||
counter.name = counterName;
|
||||
counter.per_user = counter.per_user ?? false;
|
||||
counter.per_channel = counter.per_channel ?? false;
|
||||
counter.initial_value = counter.initial_value ?? 0;
|
||||
counter.triggers = counter.triggers || {};
|
||||
|
||||
if (Object.values(counter.triggers).length > MAX_TRIGGERS_PER_COUNTER) {
|
||||
throw new StrictValidationError([`You can only have at most ${MAX_TRIGGERS_PER_COUNTER} triggers per counter`]);
|
||||
}
|
||||
|
||||
// Normalize triggers
|
||||
for (const [triggerName, trigger] of Object.entries(counter.triggers)) {
|
||||
const triggerObj = (typeof trigger === "string" ? { condition: trigger } : trigger) as Partial<TTrigger>;
|
||||
|
||||
triggerObj.name = triggerName;
|
||||
const parsedCondition = parseCounterConditionString(triggerObj.condition || "");
|
||||
if (!parsedCondition) {
|
||||
throw new StrictValidationError([
|
||||
`Invalid comparison in counter trigger ${counterName}/${triggerName}: "${triggerObj.condition}"`,
|
||||
]);
|
||||
}
|
||||
|
||||
triggerObj.condition = buildCounterConditionString(parsedCondition[0], parsedCondition[1]);
|
||||
triggerObj.reverse_condition =
|
||||
triggerObj.reverse_condition ||
|
||||
buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]);
|
||||
|
||||
counter.triggers[triggerName] = triggerObj as TTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.values((input as any).counters || {}).length > MAX_COUNTERS) {
|
||||
throw new StrictValidationError([`You can only have at most ${MAX_COUNTERS} counters`]);
|
||||
}
|
||||
|
||||
return parseIoTsSchema(ConfigSchema, input);
|
||||
},
|
||||
configParser: (input) => zCountersConfig.parse(input),
|
||||
|
||||
public: {
|
||||
counterExists: mapToPublicFn(counterExists),
|
||||
|
@ -163,13 +117,12 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()({
|
|||
state.counterTriggersByCounterId.set(dbCounter.id, thisCounterTriggers);
|
||||
|
||||
// Initialize triggers
|
||||
for (const trigger of Object.values(counter.triggers)) {
|
||||
const theTrigger = trigger as TTrigger;
|
||||
const parsedCondition = parseCounterConditionString(theTrigger.condition)!;
|
||||
const parsedReverseCondition = parseCounterConditionString(theTrigger.reverse_condition)!;
|
||||
for (const trigger of values(counter.triggers)) {
|
||||
const parsedCondition = parseCounterConditionString(trigger.condition)!;
|
||||
const parsedReverseCondition = parseCounterConditionString(trigger.reverse_condition)!;
|
||||
const counterTrigger = await state.counters.initCounterTrigger(
|
||||
dbCounter.id,
|
||||
theTrigger.name,
|
||||
trigger.name,
|
||||
parsedCondition[0],
|
||||
parsedCondition[1],
|
||||
parsedReverseCondition[0],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType, TTrigger } from "../types";
|
||||
import { CountersPluginType } from "../types";
|
||||
|
||||
export function getPrettyNameForCounterTrigger(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
|
@ -12,6 +12,6 @@ export function getPrettyNameForCounterTrigger(
|
|||
return "Unknown Counter Trigger";
|
||||
}
|
||||
|
||||
const trigger = counter.triggers[triggerName] as TTrigger | undefined;
|
||||
const trigger = counter.triggers[triggerName];
|
||||
return trigger ? trigger.pretty_name || trigger.name : "Unknown Counter Trigger";
|
||||
}
|
||||
|
|
|
@ -1,45 +1,118 @@
|
|||
import { EventEmitter } from "events";
|
||||
import * as t from "io-ts";
|
||||
import { BasePluginType } from "knub";
|
||||
import { GuildCounters } from "../../data/GuildCounters";
|
||||
import { CounterTrigger } from "../../data/entities/CounterTrigger";
|
||||
import { tDelayString, tNullable } from "../../utils";
|
||||
import z from "zod";
|
||||
import { GuildCounters, MAX_COUNTER_VALUE, MIN_COUNTER_VALUE } from "../../data/GuildCounters";
|
||||
import {
|
||||
CounterTrigger,
|
||||
buildCounterConditionString,
|
||||
getReverseCounterComparisonOp,
|
||||
parseCounterConditionString,
|
||||
} from "../../data/entities/CounterTrigger";
|
||||
import { zBoundedCharacters, zBoundedRecord, zDelayString } from "../../utils";
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
export const Trigger = t.type({
|
||||
name: t.string,
|
||||
pretty_name: tNullable(t.string),
|
||||
condition: t.string,
|
||||
reverse_condition: t.string,
|
||||
});
|
||||
export type TTrigger = t.TypeOf<typeof Trigger>;
|
||||
const MAX_COUNTERS = 5;
|
||||
const MAX_TRIGGERS_PER_COUNTER = 5;
|
||||
|
||||
export const Counter = t.type({
|
||||
name: t.string,
|
||||
pretty_name: tNullable(t.string),
|
||||
per_channel: t.boolean,
|
||||
per_user: t.boolean,
|
||||
initial_value: t.number,
|
||||
triggers: t.record(t.string, t.union([t.string, Trigger])),
|
||||
decay: tNullable(
|
||||
t.type({
|
||||
amount: t.number,
|
||||
every: tDelayString,
|
||||
export const zTrigger = z
|
||||
.strictObject({
|
||||
// Dummy type because name gets replaced by the property key in transform()
|
||||
name: z
|
||||
.never()
|
||||
.optional()
|
||||
.transform(() => ""),
|
||||
pretty_name: zBoundedCharacters(0, 100).nullable().default(null),
|
||||
condition: zBoundedCharacters(1, 64).refine((str) => parseCounterConditionString(str) !== null, {
|
||||
message: "Invalid counter trigger condition",
|
||||
}),
|
||||
),
|
||||
can_view: tNullable(t.boolean),
|
||||
can_edit: tNullable(t.boolean),
|
||||
can_reset_all: tNullable(t.boolean),
|
||||
});
|
||||
export type TCounter = t.TypeOf<typeof Counter>;
|
||||
reverse_condition: zBoundedCharacters(1, 64)
|
||||
.refine((str) => parseCounterConditionString(str) !== null, {
|
||||
message: "Invalid counter trigger reverse condition",
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.transform((val, ctx) => {
|
||||
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
counters: t.record(t.string, Counter),
|
||||
can_view: t.boolean,
|
||||
can_edit: t.boolean,
|
||||
can_reset_all: t.boolean,
|
||||
let reverseCondition = val.reverse_condition;
|
||||
if (!reverseCondition) {
|
||||
const parsedCondition = parseCounterConditionString(val.condition)!;
|
||||
reverseCondition = buildCounterConditionString(
|
||||
getReverseCounterComparisonOp(parsedCondition[0]),
|
||||
parsedCondition[1],
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...val,
|
||||
name: ruleName,
|
||||
reverse_condition: reverseCondition,
|
||||
};
|
||||
});
|
||||
|
||||
const zTriggerFromString = zBoundedCharacters(0, 100).transform((val, ctx) => {
|
||||
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
|
||||
const parsedCondition = parseCounterConditionString(val);
|
||||
if (!parsedCondition) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Invalid counter trigger condition",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return {
|
||||
name: ruleName,
|
||||
pretty_name: null,
|
||||
condition: buildCounterConditionString(parsedCondition[0], parsedCondition[1]),
|
||||
reverse_condition: buildCounterConditionString(
|
||||
getReverseCounterComparisonOp(parsedCondition[0]),
|
||||
parsedCondition[1],
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const zTriggerInput = z.union([zTrigger, zTriggerFromString]);
|
||||
|
||||
export const zCounter = z.strictObject({
|
||||
// Typed as "never" because you are not expected to supply this directly.
|
||||
// The transform instead picks it up from the property key and the output type is a string.
|
||||
name: z
|
||||
.never()
|
||||
.optional()
|
||||
.transform((_, ctx) => {
|
||||
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
|
||||
if (!ruleName) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Counters must have names",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return ruleName;
|
||||
}),
|
||||
pretty_name: zBoundedCharacters(0, 100).nullable().default(null),
|
||||
per_channel: z.boolean().default(false),
|
||||
per_user: z.boolean().default(false),
|
||||
initial_value: z.number().min(MIN_COUNTER_VALUE).max(MAX_COUNTER_VALUE).default(0),
|
||||
triggers: zBoundedRecord(z.record(zBoundedCharacters(0, 100), zTriggerInput), 1, MAX_TRIGGERS_PER_COUNTER),
|
||||
decay: z
|
||||
.strictObject({
|
||||
amount: z.number(),
|
||||
every: zDelayString,
|
||||
})
|
||||
.nullable()
|
||||
.default(null),
|
||||
can_view: z.boolean().default(false),
|
||||
can_edit: z.boolean().default(false),
|
||||
can_reset_all: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export const zCountersConfig = z.strictObject({
|
||||
counters: zBoundedRecord(z.record(zBoundedCharacters(0, 100), zCounter), 0, MAX_COUNTERS),
|
||||
can_view: z.boolean(),
|
||||
can_edit: z.boolean(),
|
||||
can_reset_all: z.boolean(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface CounterEvents {
|
||||
trigger: (counterName: string, triggerName: string, channelId: string | null, userId: string | null) => void;
|
||||
|
@ -52,7 +125,7 @@ export interface CounterEventEmitter extends EventEmitter {
|
|||
}
|
||||
|
||||
export interface CountersPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.infer<typeof zCountersConfig>;
|
||||
state: {
|
||||
counters: GuildCounters;
|
||||
counterIds: Record<string, number>;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue