From ab8ea2e7e59f80c0fcfc840985d99a8954961200 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 2 Apr 2021 16:36:45 +0300 Subject: [PATCH 01/58] counters: move triggers to counters plugin; architectural tweaks --- backend/src/data/GuildCounters.ts | 148 ++++++++++-------- backend/src/data/entities/CounterTrigger.ts | 40 +++++ .../1617363975046-UpdateCounterTriggers.ts | 90 +++++++++++ backend/src/plugins/Automod/AutomodPlugin.ts | 27 +--- .../events/runAutomodOnCounterTrigger.ts | 16 +- .../src/plugins/Automod/triggers/counter.ts | 18 ++- backend/src/plugins/Automod/types.ts | 6 +- .../src/plugins/Counters/CountersPlugin.ts | 90 ++++++++--- .../checkAllValuesForReverseTrigger.ts | 10 +- .../functions/checkAllValuesForTrigger.ts | 10 +- .../Counters/functions/checkCounterTrigger.ts | 10 +- .../functions/checkReverseCounterTrigger.ts | 10 +- .../functions/getPrettyNameForCounter.ts | 8 + .../getPrettyNameForCounterTrigger.ts | 17 ++ .../Counters/functions/initCounterTrigger.ts | 31 ---- .../Counters/functions/validateCondition.ts | 8 - backend/src/plugins/Counters/types.ts | 18 ++- 17 files changed, 357 insertions(+), 200 deletions(-) create mode 100644 backend/src/migrations/1617363975046-UpdateCounterTriggers.ts create mode 100644 backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts create mode 100644 backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts delete mode 100644 backend/src/plugins/Counters/functions/initCounterTrigger.ts delete mode 100644 backend/src/plugins/Counters/functions/validateCondition.ts diff --git a/backend/src/data/GuildCounters.ts b/backend/src/data/GuildCounters.ts index 3f05972e..234c481f 100644 --- a/backend/src/data/GuildCounters.ts +++ b/backend/src/data/GuildCounters.ts @@ -1,44 +1,18 @@ import { BaseGuildRepository } from "./BaseGuildRepository"; -import { getRepository, In, IsNull, LessThan, Not, Repository } from "typeorm"; +import { FindConditions, getRepository, In, IsNull, LessThan, Not, Repository } from "typeorm"; import { Counter } from "./entities/Counter"; import { CounterValue } from "./entities/CounterValue"; -import { CounterTrigger, TRIGGER_COMPARISON_OPS, TriggerComparisonOp } from "./entities/CounterTrigger"; +import { + CounterTrigger, + isValidCounterComparisonOp, + TRIGGER_COMPARISON_OPS, + TriggerComparisonOp, +} from "./entities/CounterTrigger"; import { CounterTriggerState } from "./entities/CounterTriggerState"; import moment from "moment-timezone"; import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils"; import { connection } from "./db"; -const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})([1-9]\\d*)$`); - -/** - * @return Parsed comparison op and value, or null if the comparison string was invalid - */ -export function parseCondition(str: string): [TriggerComparisonOp, number] | null { - const matches = str.match(comparisonStringRegex); - return matches ? [matches[1] as TriggerComparisonOp, parseInt(matches[2], 10)] : null; -} - -export function buildConditionString(comparisonOp: TriggerComparisonOp, comparisonValue: number): string { - return `${comparisonOp}${comparisonValue}`; -} - -function isValidComparisonOp(op: string): boolean { - return TRIGGER_COMPARISON_OPS.includes(op as any); -} - -const REVERSE_OPS: Record = { - "=": "!=", - "!=": "=", - ">": "<=", - "<": ">=", - ">=": "<", - "<=": ">", -}; - -function getReverseComparisonOp(op: TriggerComparisonOp): TriggerComparisonOp { - return REVERSE_OPS[op]; -} - const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS; const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS; @@ -92,6 +66,8 @@ export class GuildCounters extends BaseGuildRepository { // If the existing counter's properties match the ones we're looking for, return it. // Otherwise, delete the existing counter and re-create it with the proper properties. if (existing.per_channel === perChannel && existing.per_user === perUser) { + await this.counters.update({ id: existing.id }, { delete_at: null }); + return existing; } @@ -114,24 +90,23 @@ export class GuildCounters extends BaseGuildRepository { } async markUnusedCountersToBeDeleted(idsToKeep: number[]): Promise { - if (idsToKeep.length === 0) { - return; + const criteria: FindConditions = { + guild_id: this.guildId, + delete_at: IsNull(), + }; + + if (idsToKeep.length) { + criteria.id = Not(In(idsToKeep)); } const deleteAt = moment .utc() .add(DELETE_UNUSED_COUNTERS_AFTER, "ms") .format(DBDateFormat); - await this.counters.update( - { - guild_id: this.guildId, - id: Not(In(idsToKeep)), - delete_at: IsNull(), - }, - { - delete_at: deleteAt, - }, - ); + + await this.counters.update(criteria, { + delete_at: deleteAt, + }); } async deleteCountersMarkedToBeDeleted(): Promise { @@ -230,17 +205,37 @@ export class GuildCounters extends BaseGuildRepository { ); } - async markAllTriggersTobeDeleted() { - const deleteAt = moment - .utc() - .add(DELETE_UNUSED_COUNTER_TRIGGERS_AFTER, "ms") - .format(DBDateFormat); - await this.counterTriggers.update( - {}, - { - delete_at: deleteAt, - }, - ); + async markUnusedTriggersToBeDeleted(triggerIdsToKeep: number[]) { + let triggersToMarkQuery = this.counterTriggers + .createQueryBuilder("counterTriggers") + .innerJoin(Counter, "counters", "counters.id = counterTriggers.counter_id") + .where("counters.guild_id = :guildId", { guildId: this.guildId }); + + // If there are no active triggers, we just mark all triggers from the guild to be deleted. + // Otherwise, we mark all but the active triggers in the guild. + if (triggerIdsToKeep.length) { + triggersToMarkQuery = triggersToMarkQuery.andWhere("counterTriggers.id NOT IN (:...triggerIds)", { + triggerIds: triggerIdsToKeep, + }); + } + + const triggersToMark = await triggersToMarkQuery.getMany(); + + if (triggersToMark.length) { + const deleteAt = moment + .utc() + .add(DELETE_UNUSED_COUNTER_TRIGGERS_AFTER, "ms") + .format(DBDateFormat); + + await this.counterTriggers.update( + { + id: In(triggersToMark.map(t => t.id)), + }, + { + delete_at: deleteAt, + }, + ); + } } async deleteTriggersMarkedToBeDeleted(): Promise { @@ -253,34 +248,53 @@ export class GuildCounters extends BaseGuildRepository { async initCounterTrigger( counterId: number, + triggerName: string, comparisonOp: TriggerComparisonOp, comparisonValue: number, + reverseComparisonOp: TriggerComparisonOp, + reverseComparisonValue: number, ): Promise { - if (!isValidComparisonOp(comparisonOp)) { + if (!isValidCounterComparisonOp(comparisonOp)) { throw new Error(`Invalid comparison op: ${comparisonOp}`); } + if (!isValidCounterComparisonOp(reverseComparisonOp)) { + throw new Error(`Invalid comparison op: ${reverseComparisonOp}`); + } + if (typeof comparisonValue !== "number") { throw new Error(`Invalid comparison value: ${comparisonValue}`); } + if (typeof reverseComparisonValue !== "number") { + throw new Error(`Invalid comparison value: ${reverseComparisonValue}`); + } + return connection.transaction(async entityManager => { const existing = await entityManager.findOne(CounterTrigger, { counter_id: counterId, - comparison_op: comparisonOp, - comparison_value: comparisonValue, + name: triggerName, }); if (existing) { // Since all existing triggers are marked as to-be-deleted before they are re-initialized, this needs to be reset - await entityManager.update(CounterTrigger, existing.id, { delete_at: null }); + await entityManager.update(CounterTrigger, existing.id, { + comparison_op: comparisonOp, + comparison_value: comparisonValue, + reverse_comparison_op: reverseComparisonOp, + reverse_comparison_value: reverseComparisonValue, + delete_at: null, + }); return existing; } const insertResult = await entityManager.insert(CounterTrigger, { counter_id: counterId, + name: triggerName, comparison_op: comparisonOp, comparison_value: comparisonValue, + reverse_comparison_op: reverseComparisonOp, + reverse_comparison_value: reverseComparisonValue, }); return (await entityManager.findOne(CounterTrigger, insertResult.identifiers[0].id))!; @@ -375,8 +389,8 @@ export class GuildCounters extends BaseGuildRepository { CounterTriggerState, matchingValues.map(row => ({ trigger_id: counterTrigger.id, - channelId: row.channel_id, - userId: row.user_id, + channel_id: row.channel_id, + user_id: row.user_id, })), ); } @@ -408,7 +422,6 @@ export class GuildCounters extends BaseGuildRepository { userId = userId || "0"; return connection.transaction(async entityManager => { - const reverseOp = getReverseComparisonOp(counterTrigger.comparison_op); const matchingValue = await entityManager .createQueryBuilder(CounterValue, "cv") .innerJoin( @@ -417,7 +430,9 @@ export class GuildCounters extends BaseGuildRepository { "triggerStates.trigger_id = :triggerId AND triggerStates.user_id = cv.user_id AND triggerStates.channel_id = cv.channel_id", { triggerId: counterTrigger.id }, ) - .where(`cv.value ${reverseOp} :value`, { value: counterTrigger.comparison_value }) + .where(`cv.value ${counterTrigger.reverse_comparison_op} :value`, { + value: counterTrigger.reverse_comparison_value, + }) .andWhere(`cv.counter_id = :counterId`, { counterId: counterTrigger.counter_id }) .andWhere(`cv.channel_id = :channelId AND cv.user_id = :userId`, { channelId, userId }) .getOne(); @@ -446,7 +461,6 @@ export class GuildCounters extends BaseGuildRepository { counterTrigger: CounterTrigger, ): Promise> { return connection.transaction(async entityManager => { - const reverseOp = getReverseComparisonOp(counterTrigger.comparison_op); const matchingValues: Array<{ id: string; triggerStateId: string; @@ -460,7 +474,9 @@ export class GuildCounters extends BaseGuildRepository { "triggerStates.trigger_id = :triggerId AND triggerStates.user_id = cv.user_id AND triggerStates.channel_id = cv.channel_id", { triggerId: counterTrigger.id }, ) - .where(`cv.value ${reverseOp} :value`, { value: counterTrigger.comparison_value }) + .where(`cv.value ${counterTrigger.reverse_comparison_op} :value`, { + value: counterTrigger.reverse_comparison_value, + }) .andWhere(`cv.counter_id = :counterId`, { counterId: counterTrigger.counter_id }) .select([ "cv.id AS id", diff --git a/backend/src/data/entities/CounterTrigger.ts b/backend/src/data/entities/CounterTrigger.ts index 7cf20700..71b6bf52 100644 --- a/backend/src/data/entities/CounterTrigger.ts +++ b/backend/src/data/entities/CounterTrigger.ts @@ -4,6 +4,37 @@ export const TRIGGER_COMPARISON_OPS = ["=", "!=", ">", "<", ">=", "<="] as const export type TriggerComparisonOp = typeof TRIGGER_COMPARISON_OPS[number]; +const REVERSE_OPS: Record = { + "=": "!=", + "!=": "=", + ">": "<=", + "<": ">=", + ">=": "<", + "<=": ">", +}; + +export function getReverseCounterComparisonOp(op: TriggerComparisonOp): TriggerComparisonOp { + return REVERSE_OPS[op]; +} + +const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})([1-9]\\d*)$`); + +/** + * @return Parsed comparison op and value, or null if the comparison string was invalid + */ +export function parseCounterConditionString(str: string): [TriggerComparisonOp, number] | null { + const matches = str.match(comparisonStringRegex); + return matches ? [matches[1] as TriggerComparisonOp, parseInt(matches[2], 10)] : null; +} + +export function buildCounterConditionString(comparisonOp: TriggerComparisonOp, comparisonValue: number): string { + return `${comparisonOp}${comparisonValue}`; +} + +export function isValidCounterComparisonOp(op: string): boolean { + return TRIGGER_COMPARISON_OPS.includes(op as any); +} + @Entity("counter_triggers") export class CounterTrigger { @PrimaryGeneratedColumn() @@ -12,12 +43,21 @@ export class CounterTrigger { @Column() counter_id: number; + @Column() + name: string; + @Column({ type: "varchar" }) comparison_op: TriggerComparisonOp; @Column() comparison_value: number; + @Column({ type: "varchar" }) + reverse_comparison_op: TriggerComparisonOp; + + @Column() + reverse_comparison_value: number; + @Column({ type: "datetime", nullable: true }) delete_at: string | null; } diff --git a/backend/src/migrations/1617363975046-UpdateCounterTriggers.ts b/backend/src/migrations/1617363975046-UpdateCounterTriggers.ts new file mode 100644 index 00000000..2e1e8664 --- /dev/null +++ b/backend/src/migrations/1617363975046-UpdateCounterTriggers.ts @@ -0,0 +1,90 @@ +import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from "typeorm"; +import { TableForeignKey } from "typeorm/index"; + +export class UpdateCounterTriggers1617363975046 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // Since we're adding a non-nullable unique name column and existing triggers won't have that, clear the table first + await queryRunner.query("DELETE FROM counter_triggers"); + + await queryRunner.addColumns("counter_triggers", [ + new TableColumn({ + name: "name", + type: "varchar", + length: "255", + }), + + new TableColumn({ + name: "reverse_comparison_op", + type: "varchar", + length: "16", + }), + + new TableColumn({ + name: "reverse_comparison_value", + type: "int", + }), + ]); + + // Drop foreign key for counter_id -- needed to be able to drop the following unique index + await queryRunner.dropForeignKey("counter_triggers", "FK_6bb47849ec95c87e58c5d3e6ae1"); + + // Index for ["counter_id", "comparison_op", "comparison_value"] + await queryRunner.dropIndex("counter_triggers", "IDX_ddc8a6701f1234b926d35aebf3"); + + await queryRunner.createIndex( + "counter_triggers", + new TableIndex({ + columnNames: ["counter_id", "name"], + isUnique: true, + }), + ); + + // Recreate foreign key for counter_id + await queryRunner.createForeignKey( + "counter_triggers", + new TableForeignKey({ + columnNames: ["counter_id"], + referencedTableName: "counters", + referencedColumnNames: ["id"], + onDelete: "CASCADE", + onUpdate: "CASCADE", + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + // Since we're going back to unique comparison op and comparison value in this reverse-migration, + // clear table contents first so we don't run into any conflicts with triggers with different names but identical comparison op and comparison value + await queryRunner.query("DELETE FROM counter_triggers"); + + // Drop foreign key for counter_id -- needed to be able to drop the following unique index + await queryRunner.dropForeignKey("counter_triggers", "FK_6bb47849ec95c87e58c5d3e6ae1"); + + // Index for ["counter_id", "name"] + await queryRunner.dropIndex("counter_triggers", "IDX_2ec128e1d74bedd0288b60cdd1"); + + await queryRunner.createIndex( + "counter_triggers", + new TableIndex({ + columnNames: ["counter_id", "comparison_op", "comparison_value"], + isUnique: true, + }), + ); + + // Recreate foreign key for counter_id + await queryRunner.createForeignKey( + "counter_triggers", + new TableForeignKey({ + columnNames: ["counter_id"], + referencedTableName: "counters", + referencedColumnNames: ["id"], + onDelete: "CASCADE", + onUpdate: "CASCADE", + }), + ); + + await queryRunner.dropColumn("counter_triggers", "reverse_comparison_value"); + await queryRunner.dropColumn("counter_triggers", "reverse_comparison_op"); + await queryRunner.dropColumn("counter_triggers", "name"); + } +} diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts index 9cd380c2..7bebd606 100644 --- a/backend/src/plugins/Automod/AutomodPlugin.ts +++ b/backend/src/plugins/Automod/AutomodPlugin.ts @@ -29,7 +29,6 @@ import { logger } from "../../logger"; import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate"; import { CountersPlugin } from "../Counters/CountersPlugin"; -import { parseCondition } from "../../data/GuildCounters"; import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger"; import { runAutomodOnModAction } from "./events/runAutomodOnModAction"; import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap"; @@ -114,15 +113,6 @@ const configPreprocessor: ConfigPreprocessorFn = options => { ]); } } - - if (triggerName === "counter") { - const parsedCondition = parseCondition(triggerObj[triggerName]!.condition); - if (parsedCondition == null) { - throw new StrictValidationError([ - `Invalid counter condition '${triggerObj[triggerName]!.condition}' in rule <${rule.name}>`, - ]); - } - } } } } @@ -229,23 +219,14 @@ export const AutomodPlugin = zeppelinGuildPlugin()("automod", async onAfterLoad(pluginData) { const countersPlugin = pluginData.getPlugin(CountersPlugin); - pluginData.state.onCounterTrigger = (name, condition, channelId, userId) => { - runAutomodOnCounterTrigger(pluginData, name, condition, channelId, userId, false); + pluginData.state.onCounterTrigger = (name, triggerName, channelId, userId) => { + runAutomodOnCounterTrigger(pluginData, name, triggerName, channelId, userId, false); }; - pluginData.state.onCounterReverseTrigger = (name, condition, channelId, userId) => { - runAutomodOnCounterTrigger(pluginData, name, condition, channelId, userId, true); + pluginData.state.onCounterReverseTrigger = (name, triggerName, channelId, userId) => { + runAutomodOnCounterTrigger(pluginData, name, triggerName, channelId, userId, true); }; - const config = pluginData.config.get(); - for (const rule of Object.values(config.rules)) { - for (const trigger of rule.triggers) { - if (trigger.counter) { - await countersPlugin.initCounterTrigger(trigger.counter.name, trigger.counter.condition); - } - } - } - countersPlugin.onCounterEvent("trigger", pluginData.state.onCounterTrigger); countersPlugin.onCounterEvent("reverseTrigger", pluginData.state.onCounterReverseTrigger); diff --git a/backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts b/backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts index 50939d80..2b1b974d 100644 --- a/backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts +++ b/backend/src/plugins/Automod/events/runAutomodOnCounterTrigger.ts @@ -2,30 +2,38 @@ import { GuildPluginData } from "knub"; import { AutomodContext, AutomodPluginType } from "../types"; import { runAutomod } from "../functions/runAutomod"; import { resolveMember, resolveUser, UnknownUser } from "../../../utils"; +import { CountersPlugin } from "../../Counters/CountersPlugin"; export async function runAutomodOnCounterTrigger( pluginData: GuildPluginData, counterName: string, - condition: string, + triggerName: string, channelId: string | null, userId: string | null, reverse: boolean, ) { const user = userId ? await resolveUser(pluginData.client, userId) : undefined; - const member = (userId && (await resolveMember(pluginData.client, pluginData.guild, userId))) || undefined; + const prettyCounterName = pluginData.getPlugin(CountersPlugin).getPrettyNameForCounter(counterName); + const prettyTriggerName = pluginData + .getPlugin(CountersPlugin) + .getPrettyNameForCounterTrigger(counterName, triggerName); + const context: AutomodContext = { timestamp: Date.now(), counterTrigger: { - name: counterName, - condition, + counter: counterName, + trigger: triggerName, + prettyCounter: prettyCounterName, + prettyTrigger: prettyTriggerName, channelId, userId, reverse, }, user: user instanceof UnknownUser ? undefined : user, member, + // TODO: Channel }; pluginData.state.queue.add(async () => { diff --git a/backend/src/plugins/Automod/triggers/counter.ts b/backend/src/plugins/Automod/triggers/counter.ts index 6b3c3288..ddfda013 100644 --- a/backend/src/plugins/Automod/triggers/counter.ts +++ b/backend/src/plugins/Automod/triggers/counter.ts @@ -9,8 +9,8 @@ interface CounterTriggerResult {} export const CounterTrigger = automodTrigger()({ configType: t.type({ - name: t.string, - condition: t.string, + counter: t.string, + trigger: t.string, reverse: tNullable(t.boolean), }), @@ -21,11 +21,11 @@ export const CounterTrigger = automodTrigger()({ return; } - if (context.counterTrigger.name !== triggerConfig.name) { + if (context.counterTrigger.counter !== triggerConfig.counter) { return; } - if (context.counterTrigger.condition !== triggerConfig.condition) { + if (context.counterTrigger.trigger !== triggerConfig.trigger) { return; } @@ -40,7 +40,13 @@ export const CounterTrigger = automodTrigger()({ }, renderMatchInformation({ matchResult, pluginData, contexts, triggerConfig }) { - // TODO: Show user, channel, reverse - return `Matched counter \`${triggerConfig.name} ${triggerConfig.condition}\``; + let str = `Matched counter trigger \`${contexts[0].counterTrigger!.prettyCounter} / ${ + contexts[0].counterTrigger!.prettyTrigger + }\``; + if (contexts[0].counterTrigger!.reverse) { + str += " (reverse)"; + } + + return str; }, }); diff --git a/backend/src/plugins/Automod/types.ts b/backend/src/plugins/Automod/types.ts index f6e273cb..37202143 100644 --- a/backend/src/plugins/Automod/types.ts +++ b/backend/src/plugins/Automod/types.ts @@ -103,8 +103,10 @@ export interface AutomodContext { actioned?: boolean; counterTrigger?: { - name: string; - condition: string; + counter: string; + trigger: string; + prettyCounter: string; + prettyTrigger: string; channelId: string | null; userId: string | null; reverse: boolean; diff --git a/backend/src/plugins/Counters/CountersPlugin.ts b/backend/src/plugins/Counters/CountersPlugin.ts index 56af0f96..b6789e81 100644 --- a/backend/src/plugins/Counters/CountersPlugin.ts +++ b/backend/src/plugins/Counters/CountersPlugin.ts @@ -1,5 +1,5 @@ import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; -import { ConfigSchema, CountersPluginType } from "./types"; +import { ConfigSchema, CountersPluginType, TTrigger } from "./types"; import { GuildCounters } from "../../data/GuildCounters"; import { mapToPublicFn } from "../../pluginUtils"; import { changeCounterValue } from "./functions/changeCounterValue"; @@ -10,16 +10,23 @@ import { onCounterEvent } from "./functions/onCounterEvent"; import { offCounterEvent } from "./functions/offCounterEvent"; import { emitCounterEvent } from "./functions/emitCounterEvent"; import { ConfigPreprocessorFn } from "knub/dist/config/configTypes"; -import { initCounterTrigger } from "./functions/initCounterTrigger"; import { decayCounter } from "./functions/decayCounter"; -import { validateCondition } from "./functions/validateCondition"; import { StrictValidationError } from "../../validatorUtils"; import { PluginOptions } from "knub"; import { ViewCounterCmd } from "./commands/ViewCounterCmd"; import { AddCounterCmd } from "./commands/AddCounterCmd"; import { SetCounterCmd } from "./commands/SetCounterCmd"; +import { + buildCounterConditionString, + CounterTrigger, + getReverseCounterComparisonOp, + parseCounterConditionString, +} from "../../data/entities/CounterTrigger"; +import { getPrettyNameForCounter } from "./functions/getPrettyNameForCounter"; +import { getPrettyNameForCounterTrigger } from "./functions/getPrettyNameForCounterTrigger"; const MAX_COUNTERS = 5; +const MAX_TRIGGERS_PER_COUNTER = 5; const DECAY_APPLY_INTERVAL = 5 * MINUTES; const defaultOptions: PluginOptions = { @@ -45,14 +52,40 @@ const defaultOptions: PluginOptions = { }; const configPreprocessor: ConfigPreprocessorFn = options => { - for (const counter of Object.values(options.config?.counters || {})) { + for (const [counterName, counter] of Object.entries(options.config?.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: Partial = typeof trigger === "string" ? { condition: trigger } : trigger; + + 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(options.config?.counters || {}).length > MAX_COUNTERS) { - throw new StrictValidationError([`You can only have at most ${MAX_COUNTERS} active counters`]); + throw new StrictValidationError([`You can only have at most ${MAX_COUNTERS} counters`]); } return options; @@ -76,14 +109,12 @@ export const CountersPlugin = zeppelinGuildPlugin()("counter public: { // Change a counter's value by a relative amount, e.g. +5 changeCounterValue: mapToPublicFn(changeCounterValue), + // Set a counter's value to an absolute value setCounterValue: mapToPublicFn(setCounterValue), - // Initialize a trigger. Once initialized, events will be fired when this trigger is triggered. - initCounterTrigger: mapToPublicFn(initCounterTrigger), - - // Validate a trigger's condition string - validateCondition: mapToPublicFn(validateCondition), + getPrettyNameForCounter: mapToPublicFn(getPrettyNameForCounter), + getPrettyNameForCounterTrigger: mapToPublicFn(getPrettyNameForCounterTrigger), onCounterEvent: mapToPublicFn(onCounterEvent), offCounterEvent: mapToPublicFn(offCounterEvent), @@ -99,22 +130,48 @@ export const CountersPlugin = zeppelinGuildPlugin()("counter async onLoad(pluginData) { pluginData.state.counters = new GuildCounters(pluginData.guild.id); pluginData.state.events = new EventEmitter(); + pluginData.state.counterTriggersByCounterId = new Map(); + + const activeTriggerIds: number[] = []; // Initialize and store the IDs of each of the counters internally pluginData.state.counterIds = {}; const config = pluginData.config.get(); - for (const [counterName, counter] of Object.entries(config.counters)) { + for (const counter of Object.values(config.counters)) { const dbCounter = await pluginData.state.counters.findOrCreateCounter( - counterName, + counter.name, counter.per_channel, counter.per_user, ); - pluginData.state.counterIds[counterName] = dbCounter.id; + pluginData.state.counterIds[counter.name] = dbCounter.id; + + const thisCounterTriggers: CounterTrigger[] = []; + pluginData.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)!; + const counterTrigger = await pluginData.state.counters.initCounterTrigger( + dbCounter.id, + theTrigger.name, + parsedCondition[0], + parsedCondition[1], + parsedReverseCondition[0], + parsedReverseCondition[1], + ); + activeTriggerIds.push(counterTrigger.id); + thisCounterTriggers.push(counterTrigger); + } } // Mark old/unused counters to be deleted later await pluginData.state.counters.markUnusedCountersToBeDeleted([...Object.values(pluginData.state.counterIds)]); + // Mark old/unused triggers to be deleted later + await pluginData.state.counters.markUnusedTriggersToBeDeleted(activeTriggerIds); + // Start decay timers pluginData.state.decayTimers = []; for (const [counterName, counter] of Object.entries(config.counters)) { @@ -130,13 +187,6 @@ export const CountersPlugin = zeppelinGuildPlugin()("counter }, DECAY_APPLY_INTERVAL), ); } - - // Initially set the counter trigger map to just an empty map - // The actual triggers are added by other plugins via initCounterTrigger() - pluginData.state.counterTriggersByCounterId = new Map(); - - // Mark all triggers to be deleted later. This is cancelled/reset when a plugin adds the trigger again via initCounterTrigger(). - await pluginData.state.counters.markAllTriggersTobeDeleted(); }, onUnload(pluginData) { diff --git a/backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts b/backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts index 9336906e..19998225 100644 --- a/backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts +++ b/backend/src/plugins/Counters/functions/checkAllValuesForReverseTrigger.ts @@ -1,6 +1,5 @@ import { GuildPluginData } from "knub"; import { CountersPluginType } from "../types"; -import { buildConditionString } from "../../../data/GuildCounters"; import { CounterTrigger } from "../../../data/entities/CounterTrigger"; import { emitCounterEvent } from "./emitCounterEvent"; @@ -11,13 +10,6 @@ export async function checkAllValuesForReverseTrigger( ) { const triggeredContexts = await pluginData.state.counters.checkAllValuesForReverseTrigger(counterTrigger); for (const context of triggeredContexts) { - emitCounterEvent( - pluginData, - "reverseTrigger", - counterName, - buildConditionString(counterTrigger.comparison_op, counterTrigger.comparison_value), - context.channelId, - context.userId, - ); + emitCounterEvent(pluginData, "reverseTrigger", counterName, counterTrigger.name, context.channelId, context.userId); } } diff --git a/backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts b/backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts index 277cff15..02b02e5f 100644 --- a/backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts +++ b/backend/src/plugins/Counters/functions/checkAllValuesForTrigger.ts @@ -1,6 +1,5 @@ import { GuildPluginData } from "knub"; import { CountersPluginType } from "../types"; -import { buildConditionString } from "../../../data/GuildCounters"; import { CounterTrigger } from "../../../data/entities/CounterTrigger"; import { emitCounterEvent } from "./emitCounterEvent"; @@ -11,13 +10,6 @@ export async function checkAllValuesForTrigger( ) { const triggeredContexts = await pluginData.state.counters.checkAllValuesForTrigger(counterTrigger); for (const context of triggeredContexts) { - emitCounterEvent( - pluginData, - "trigger", - counterName, - buildConditionString(counterTrigger.comparison_op, counterTrigger.comparison_value), - context.channelId, - context.userId, - ); + emitCounterEvent(pluginData, "trigger", counterName, counterTrigger.name, context.channelId, context.userId); } } diff --git a/backend/src/plugins/Counters/functions/checkCounterTrigger.ts b/backend/src/plugins/Counters/functions/checkCounterTrigger.ts index e45cf28d..5bdd1b05 100644 --- a/backend/src/plugins/Counters/functions/checkCounterTrigger.ts +++ b/backend/src/plugins/Counters/functions/checkCounterTrigger.ts @@ -1,6 +1,5 @@ import { GuildPluginData } from "knub"; import { CountersPluginType } from "../types"; -import { buildConditionString } from "../../../data/GuildCounters"; import { CounterTrigger } from "../../../data/entities/CounterTrigger"; import { emitCounterEvent } from "./emitCounterEvent"; @@ -13,13 +12,6 @@ export async function checkCounterTrigger( ) { const triggered = await pluginData.state.counters.checkForTrigger(counterTrigger, channelId, userId); if (triggered) { - await emitCounterEvent( - pluginData, - "trigger", - counterName, - buildConditionString(counterTrigger.comparison_op, counterTrigger.comparison_value), - channelId, - userId, - ); + await emitCounterEvent(pluginData, "trigger", counterName, counterTrigger.name, channelId, userId); } } diff --git a/backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts b/backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts index 5ed9b3d7..e601dd9d 100644 --- a/backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts +++ b/backend/src/plugins/Counters/functions/checkReverseCounterTrigger.ts @@ -1,6 +1,5 @@ import { GuildPluginData } from "knub"; import { CountersPluginType } from "../types"; -import { buildConditionString } from "../../../data/GuildCounters"; import { CounterTrigger } from "../../../data/entities/CounterTrigger"; import { emitCounterEvent } from "./emitCounterEvent"; @@ -13,13 +12,6 @@ export async function checkReverseCounterTrigger( ) { const triggered = await pluginData.state.counters.checkForReverseTrigger(counterTrigger, channelId, userId); if (triggered) { - await emitCounterEvent( - pluginData, - "reverseTrigger", - counterName, - buildConditionString(counterTrigger.comparison_op, counterTrigger.comparison_value), - channelId, - userId, - ); + await emitCounterEvent(pluginData, "reverseTrigger", counterName, counterTrigger.name, channelId, userId); } } diff --git a/backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts b/backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts new file mode 100644 index 00000000..766b4ef3 --- /dev/null +++ b/backend/src/plugins/Counters/functions/getPrettyNameForCounter.ts @@ -0,0 +1,8 @@ +import { CountersPluginType } from "../types"; +import { GuildPluginData } from "knub"; + +export function getPrettyNameForCounter(pluginData: GuildPluginData, counterName: string) { + const config = pluginData.config.get(); + const counter = config.counters[counterName]; + return counter ? counter.pretty_name || counter.name : "Unknown Counter"; +} diff --git a/backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts b/backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts new file mode 100644 index 00000000..d7a2b923 --- /dev/null +++ b/backend/src/plugins/Counters/functions/getPrettyNameForCounterTrigger.ts @@ -0,0 +1,17 @@ +import { CountersPluginType, TTrigger } from "../types"; +import { GuildPluginData } from "knub"; + +export function getPrettyNameForCounterTrigger( + pluginData: GuildPluginData, + counterName: string, + triggerName: string, +) { + const config = pluginData.config.get(); + const counter = config.counters[counterName]; + if (!counter) { + return "Unknown Counter Trigger"; + } + + const trigger = counter.triggers[triggerName] as TTrigger | undefined; + return trigger ? trigger.pretty_name || trigger.name : "Unknown Counter Trigger"; +} diff --git a/backend/src/plugins/Counters/functions/initCounterTrigger.ts b/backend/src/plugins/Counters/functions/initCounterTrigger.ts deleted file mode 100644 index afc5e9c9..00000000 --- a/backend/src/plugins/Counters/functions/initCounterTrigger.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { GuildPluginData } from "knub"; -import { CountersPluginType } from "../types"; -import { parseCondition } from "../../../data/GuildCounters"; - -/** - * Initialize a counter trigger. - * After a counter trigger has been initialized, it will be checked against whenever the counter's values change. - * If the trigger is triggered, an event is emitted. - */ -export async function initCounterTrigger( - pluginData: GuildPluginData, - counterName: string, - condition: string, -) { - const counterId = pluginData.state.counterIds[counterName]; - if (!counterId) { - throw new Error(`Unknown counter: ${counterName}`); - } - - const parsedComparison = parseCondition(condition); - if (!parsedComparison) { - throw new Error(`Invalid comparison string: ${condition}`); - } - - const [comparisonOp, comparisonValue] = parsedComparison; - const counterTrigger = await pluginData.state.counters.initCounterTrigger(counterId, comparisonOp, comparisonValue); - if (!pluginData.state.counterTriggersByCounterId.has(counterId)) { - pluginData.state.counterTriggersByCounterId.set(counterId, new Map()); - } - pluginData.state.counterTriggersByCounterId.get(counterId)!.set(counterTrigger.id, counterTrigger); -} diff --git a/backend/src/plugins/Counters/functions/validateCondition.ts b/backend/src/plugins/Counters/functions/validateCondition.ts deleted file mode 100644 index ca5e2e88..00000000 --- a/backend/src/plugins/Counters/functions/validateCondition.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { GuildPluginData } from "knub"; -import { CountersPluginType } from "../types"; -import { parseCondition } from "../../../data/GuildCounters"; - -export function validateCondition(pluginData: GuildPluginData, condition: string) { - const parsed = parseCondition(condition); - return parsed != null; -} diff --git a/backend/src/plugins/Counters/types.ts b/backend/src/plugins/Counters/types.ts index 3102e463..ec534452 100644 --- a/backend/src/plugins/Counters/types.ts +++ b/backend/src/plugins/Counters/types.ts @@ -6,11 +6,21 @@ import { EventEmitter } from "events"; import { CounterTrigger } from "../../data/entities/CounterTrigger"; 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; + export const Counter = t.type({ - name: tNullable(t.string), + 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, @@ -30,8 +40,8 @@ export const ConfigSchema = t.type({ export type TConfigSchema = t.TypeOf; export interface CounterEvents { - trigger: (name: string, condition: string, channelId: string | null, userId: string | null) => void; - reverseTrigger: (name: string, condition: string, channelId: string | null, userId: string | null) => void; + trigger: (counterName: string, triggerName: string, channelId: string | null, userId: string | null) => void; + reverseTrigger: (counterName: string, triggerName: string, channelId: string | null, userId: string | null) => void; } export interface CounterEventEmitter extends EventEmitter { @@ -46,6 +56,6 @@ export interface CountersPluginType extends BasePluginType { counterIds: Record; decayTimers: Timeout[]; events: CounterEventEmitter; - counterTriggersByCounterId: Map>; + counterTriggersByCounterId: Map; }; } From fb4979ff00dfcc094429b457625ce3351216f676 Mon Sep 17 00:00:00 2001 From: Jonathan <54381371+yaboyaxis@users.noreply.github.com> Date: Fri, 2 Apr 2021 09:38:24 -0400 Subject: [PATCH 02/58] Add !reason alias for !update (#141) --- backend/src/plugins/ModActions/commands/UpdateCmd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/ModActions/commands/UpdateCmd.ts b/backend/src/plugins/ModActions/commands/UpdateCmd.ts index 3a55463a..3a048b42 100644 --- a/backend/src/plugins/ModActions/commands/UpdateCmd.ts +++ b/backend/src/plugins/ModActions/commands/UpdateCmd.ts @@ -8,7 +8,7 @@ import { LogType } from "../../../data/LogType"; import { CaseTypes } from "../../../data/CaseTypes"; export const UpdateCmd = modActionsCmd({ - trigger: "update", + trigger: ["update", "reason"], permission: "can_note", description: "Update the specified case (or, if case number is omitted, your latest case) by adding more notes/details to it", From 2fc8cffd80a8a17a003f0932d54b4d5ce28b884e Mon Sep 17 00:00:00 2001 From: Nils <7890309+DarkView@users.noreply.github.com> Date: Fri, 2 Apr 2021 15:39:22 +0200 Subject: [PATCH 03/58] Allow kicking the user from VC on mute (#156) * Allow kicking the user from VC on mute If any non-id string is entered in `move_to_voice_channel`, the user is kicked from the VC instead of being moved. We do not automatically kick if the option is set to null in order to not make this a breaking change for old, intended behavior * Add explicit config option for kicking instead of kicking on any non-id Kicking takes precedent in this case and will take effect instead of moving to voice id --- backend/src/plugins/Mutes/MutesPlugin.ts | 1 + backend/src/plugins/Mutes/functions/muteUser.ts | 7 ++++--- backend/src/plugins/Mutes/types.ts | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/plugins/Mutes/MutesPlugin.ts b/backend/src/plugins/Mutes/MutesPlugin.ts index ac55acc6..36fb425e 100644 --- a/backend/src/plugins/Mutes/MutesPlugin.ts +++ b/backend/src/plugins/Mutes/MutesPlugin.ts @@ -25,6 +25,7 @@ const defaultOptions = { config: { mute_role: null, move_to_voice_channel: null, + kick_from_voice_channel: false, dm_on_mute: false, dm_on_update: false, diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index 2f8e4e28..393a00e1 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -120,11 +120,12 @@ export async function muteUser( } // If enabled, move the user to the mute voice channel (e.g. afk - just to apply the voice perms from the mute role) - const moveToVoiceChannelId = pluginData.config.get().move_to_voice_channel; - if (moveToVoiceChannelId) { + const cfg = pluginData.config.get(); + const moveToVoiceChannel = cfg.kick_from_voice_channel ? null : cfg.move_to_voice_channel; + if (moveToVoiceChannel || cfg.kick_from_voice_channel) { // TODO: Add back the voiceState check once we figure out how to get voice state for guild members that are loaded on-demand try { - await member.edit({ channelID: moveToVoiceChannelId }); + await member.edit({ channelID: moveToVoiceChannel }); } catch (e) {} // tslint:disable-line } } diff --git a/backend/src/plugins/Mutes/types.ts b/backend/src/plugins/Mutes/types.ts index 72ea52c2..7da2cb0d 100644 --- a/backend/src/plugins/Mutes/types.ts +++ b/backend/src/plugins/Mutes/types.ts @@ -15,6 +15,7 @@ import { EventEmitter } from "events"; export const ConfigSchema = t.type({ mute_role: tNullable(t.string), move_to_voice_channel: tNullable(t.string), + kick_from_voice_channel: t.boolean, dm_on_mute: t.boolean, dm_on_update: t.boolean, From 2e50fa763078a07d27847d5b72c80118cfbf8dde Mon Sep 17 00:00:00 2001 From: Nils <7890309+DarkView@users.noreply.github.com> Date: Fri, 2 Apr 2021 15:40:20 +0200 Subject: [PATCH 04/58] Add allow_mentions option to enable tags mentioning someone (#160) Checked when a tag is posted - so a tag can have for example pings enabled if a mod executes that tag, but not if anyone below mod does it. --- backend/src/plugins/Tags/TagsPlugin.ts | 1 + backend/src/plugins/Tags/types.ts | 2 ++ backend/src/plugins/Tags/util/onMessageCreate.ts | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/plugins/Tags/TagsPlugin.ts b/backend/src/plugins/Tags/TagsPlugin.ts index e8bb8541..f6aa89e1 100644 --- a/backend/src/plugins/Tags/TagsPlugin.ts +++ b/backend/src/plugins/Tags/TagsPlugin.ts @@ -29,6 +29,7 @@ const defaultOptions: PluginOptions = { user_tag_cooldown: null, global_tag_cooldown: null, user_cooldown: null, + allow_mentions: false, global_cooldown: null, auto_delete_command: false, diff --git a/backend/src/plugins/Tags/types.ts b/backend/src/plugins/Tags/types.ts index 15b0ce5b..21324be0 100644 --- a/backend/src/plugins/Tags/types.ts +++ b/backend/src/plugins/Tags/types.ts @@ -15,6 +15,7 @@ export const TagCategory = t.type({ user_tag_cooldown: tNullable(t.union([t.string, t.number])), // Per user, per tag user_category_cooldown: tNullable(t.union([t.string, t.number])), // Per user, per tag category global_tag_cooldown: tNullable(t.union([t.string, t.number])), // Any user, per tag + allow_mentions: tNullable(t.boolean), // Per user, per category global_category_cooldown: tNullable(t.union([t.string, t.number])), // Any user, per category auto_delete_command: tNullable(t.boolean), // Any tag, per tag category @@ -31,6 +32,7 @@ export const ConfigSchema = t.type({ user_tag_cooldown: tNullable(t.union([t.string, t.number])), // Per user, per tag global_tag_cooldown: tNullable(t.union([t.string, t.number])), // Any user, per tag user_cooldown: tNullable(t.union([t.string, t.number])), // Per user + allow_mentions: t.boolean, // Per user global_cooldown: tNullable(t.union([t.string, t.number])), // Any tag use auto_delete_command: t.boolean, // Any tag diff --git a/backend/src/plugins/Tags/util/onMessageCreate.ts b/backend/src/plugins/Tags/util/onMessageCreate.ts index d7d8530b..c3e92a83 100644 --- a/backend/src/plugins/Tags/util/onMessageCreate.ts +++ b/backend/src/plugins/Tags/util/onMessageCreate.ts @@ -99,7 +99,11 @@ export async function onMessageCreate(pluginData: GuildPluginData Date: Fri, 2 Apr 2021 14:42:25 +0100 Subject: [PATCH 05/58] feat: add color option to starboard (#163) Co-authored-by: Almeida <42935195+almeidx@users.noreply.github.com> --- backend/src/plugins/Starboard/StarboardPlugin.ts | 13 +++++++++++++ backend/src/plugins/Starboard/types.ts | 2 ++ .../util/createStarboardEmbedFromMessage.ts | 3 ++- .../Starboard/util/saveMessageToStarboard.ts | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Starboard/StarboardPlugin.ts b/backend/src/plugins/Starboard/StarboardPlugin.ts index c3374e56..8767d7e9 100644 --- a/backend/src/plugins/Starboard/StarboardPlugin.ts +++ b/backend/src/plugins/Starboard/StarboardPlugin.ts @@ -57,6 +57,19 @@ export const StarboardPlugin = zeppelinGuildPlugin()("starb stars_required: 5 ~~~ + ### Basic starboard with custom color + Any message on the server that gets 5 star reactions will be posted into the starboard channel (604342689038729226), with the given color (0x87CEEB). + + ~~~yml + starboard: + config: + boards: + basic: + channel_id: "604342689038729226" + stars_required: 5 + color: 0x87CEEB + ~~~ + ### Custom star emoji This is identical to the basic starboard above, but accepts two emoji: the regular star and a custom :mrvnSmile: emoji diff --git a/backend/src/plugins/Starboard/types.ts b/backend/src/plugins/Starboard/types.ts index 8c94f415..b2118f55 100644 --- a/backend/src/plugins/Starboard/types.ts +++ b/backend/src/plugins/Starboard/types.ts @@ -12,6 +12,7 @@ const StarboardOpts = t.type({ copy_full_embed: tNullable(t.boolean), enabled: tNullable(t.boolean), show_star_count: t.boolean, + color: t.number, }); export type TStarboardOpts = t.TypeOf; @@ -27,6 +28,7 @@ export const defaultStarboardOpts: Partial = { star_emoji: ["⭐"], enabled: true, show_star_count: true, + color: 0, }; export interface StarboardPluginType extends BasePluginType { diff --git a/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts b/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts index d63b519c..8ea900e2 100644 --- a/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts +++ b/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts @@ -8,7 +8,7 @@ const videoAttachmentExtensions = ["mp4", "mkv", "mov"]; type StarboardEmbed = EmbedWith<"footer" | "author" | "fields" | "timestamp">; -export function createStarboardEmbedFromMessage(msg: Message, copyFullEmbed: boolean): StarboardEmbed { +export function createStarboardEmbedFromMessage(msg: Message, copyFullEmbed: boolean, color: number): StarboardEmbed { const embed: StarboardEmbed = { footer: { text: `#${(msg.channel as GuildChannel).name}`, @@ -18,6 +18,7 @@ export function createStarboardEmbedFromMessage(msg: Message, copyFullEmbed: boo }, fields: [], timestamp: new Date(msg.timestamp).toISOString(), + color, }; if (msg.author.avatarURL) { diff --git a/backend/src/plugins/Starboard/util/saveMessageToStarboard.ts b/backend/src/plugins/Starboard/util/saveMessageToStarboard.ts index f30beffc..e7587abd 100644 --- a/backend/src/plugins/Starboard/util/saveMessageToStarboard.ts +++ b/backend/src/plugins/Starboard/util/saveMessageToStarboard.ts @@ -16,7 +16,7 @@ export async function saveMessageToStarboard( if (!channel) return; const starCount = (await pluginData.state.starboardReactions.getAllReactionsForMessageId(msg.id)).length; - const embed = createStarboardEmbedFromMessage(msg, Boolean(starboard.copy_full_embed)); + const embed = createStarboardEmbedFromMessage(msg, Boolean(starboard.copy_full_embed), starboard.color); embed.fields!.push(createStarboardPseudoFooterForMessage(starboard, msg, starboard.star_emoji![0], starCount)); const starboardMessage = await (channel as TextChannel).createMessage({ embed }); From c5d68650a3ba52b22f5b8235d5589bd364744f44 Mon Sep 17 00:00:00 2001 From: Nils <7890309+DarkView@users.noreply.github.com> Date: Fri, 2 Apr 2021 15:43:13 +0200 Subject: [PATCH 06/58] Enforce unified lock names by using functions to generate lock keys (#165) --- .../src/plugins/Automod/actions/addRoles.ts | 5 ++-- .../plugins/Automod/actions/removeRoles.ts | 5 ++-- .../plugins/Censor/util/onMessageCreate.ts | 3 ++- .../plugins/Censor/util/onMessageUpdate.ts | 3 ++- .../Counters/functions/changeCounterValue.ts | 3 ++- .../Counters/functions/decayCounter.ts | 3 ++- .../Counters/functions/setCounterValue.ts | 3 ++- .../src/plugins/ModActions/commands/BanCmd.ts | 9 ++++--- .../events/ReapplyActiveMuteOnJoinEvt.ts | 5 ++-- .../src/plugins/Mutes/functions/muteUser.ts | 6 ++--- .../src/plugins/Persist/events/LoadDataEvt.ts | 7 ++--- .../util/addMemberPendingRoleChange.ts | 3 ++- .../SelfGrantableRoles/commands/RoleAddCmd.ts | 3 ++- .../commands/RoleRemoveCmd.ts | 3 ++- .../plugins/Slowmode/util/onMessageCreate.ts | 3 ++- .../events/StarboardReactionAddEvt.ts | 3 ++- .../events/StarboardReactionRemoveEvts.ts | 5 ++-- backend/src/utils/lockNameHelpers.ts | 26 +++++++++++++++++++ 18 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 backend/src/utils/lockNameHelpers.ts diff --git a/backend/src/plugins/Automod/actions/addRoles.ts b/backend/src/plugins/Automod/actions/addRoles.ts index 4da915e7..976300a5 100644 --- a/backend/src/plugins/Automod/actions/addRoles.ts +++ b/backend/src/plugins/Automod/actions/addRoles.ts @@ -9,6 +9,7 @@ import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { canAssignRole } from "../../../utils/canAssignRole"; import { missingPermissionError } from "../../../utils/missingPermissionError"; import { ignoreRoleChange } from "../functions/ignoredRoleChanges"; +import { memberRolesLock } from "../../../utils/lockNameHelpers"; const p = Constants.Permissions; @@ -64,7 +65,7 @@ export const AddRolesAction = automodAction({ return; } - const memberRolesLock = await pluginData.locks.acquire(`member-roles-${member.id}`); + const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member)); const rolesArr = Array.from(memberRoles.values()); await member.edit({ @@ -72,7 +73,7 @@ export const AddRolesAction = automodAction({ }); member.roles = rolesArr; // Make sure we know of the new roles internally as well - memberRolesLock.unlock(); + memberRoleLock.unlock(); }), ); }, diff --git a/backend/src/plugins/Automod/actions/removeRoles.ts b/backend/src/plugins/Automod/actions/removeRoles.ts index 6049cbf2..c6c74e49 100644 --- a/backend/src/plugins/Automod/actions/removeRoles.ts +++ b/backend/src/plugins/Automod/actions/removeRoles.ts @@ -10,6 +10,7 @@ import { missingPermissionError } from "../../../utils/missingPermissionError"; import { canAssignRole } from "../../../utils/canAssignRole"; import { Constants } from "eris"; import { ignoreRoleChange } from "../functions/ignoredRoleChanges"; +import { memberRolesLock } from "../../../utils/lockNameHelpers"; const p = Constants.Permissions; @@ -66,7 +67,7 @@ export const RemoveRolesAction = automodAction({ return; } - const memberRolesLock = await pluginData.locks.acquire(`member-roles-${member.id}`); + const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member)); const rolesArr = Array.from(memberRoles.values()); await member.edit({ @@ -74,7 +75,7 @@ export const RemoveRolesAction = automodAction({ }); member.roles = rolesArr; // Make sure we know of the new roles internally as well - memberRolesLock.unlock(); + memberRoleLock.unlock(); }), ); }, diff --git a/backend/src/plugins/Censor/util/onMessageCreate.ts b/backend/src/plugins/Censor/util/onMessageCreate.ts index 2042126d..fb9cfaeb 100644 --- a/backend/src/plugins/Censor/util/onMessageCreate.ts +++ b/backend/src/plugins/Censor/util/onMessageCreate.ts @@ -2,10 +2,11 @@ import { GuildPluginData } from "knub"; import { CensorPluginType } from "../types"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { applyFiltersToMsg } from "./applyFiltersToMsg"; +import { messageLock } from "../../../utils/lockNameHelpers"; export async function onMessageCreate(pluginData: GuildPluginData, savedMessage: SavedMessage) { if (savedMessage.is_bot) return; - const lock = await pluginData.locks.acquire(`message-${savedMessage.id}`); + const lock = await pluginData.locks.acquire(messageLock(savedMessage)); const wasDeleted = await applyFiltersToMsg(pluginData, savedMessage); diff --git a/backend/src/plugins/Censor/util/onMessageUpdate.ts b/backend/src/plugins/Censor/util/onMessageUpdate.ts index 4c279caa..7afd4c17 100644 --- a/backend/src/plugins/Censor/util/onMessageUpdate.ts +++ b/backend/src/plugins/Censor/util/onMessageUpdate.ts @@ -2,10 +2,11 @@ import { GuildPluginData } from "knub"; import { CensorPluginType } from "../types"; import { SavedMessage } from "../../../data/entities/SavedMessage"; import { applyFiltersToMsg } from "./applyFiltersToMsg"; +import { messageLock } from "../../../utils/lockNameHelpers"; export async function onMessageUpdate(pluginData: GuildPluginData, savedMessage: SavedMessage) { if (savedMessage.is_bot) return; - const lock = await pluginData.locks.acquire(`message-${savedMessage.id}`); + const lock = await pluginData.locks.acquire(messageLock(savedMessage)); const wasDeleted = await applyFiltersToMsg(pluginData, savedMessage); diff --git a/backend/src/plugins/Counters/functions/changeCounterValue.ts b/backend/src/plugins/Counters/functions/changeCounterValue.ts index be214eb7..d5da6825 100644 --- a/backend/src/plugins/Counters/functions/changeCounterValue.ts +++ b/backend/src/plugins/Counters/functions/changeCounterValue.ts @@ -1,4 +1,5 @@ import { GuildPluginData } from "knub"; +import { counterIdLock } from "../../../utils/lockNameHelpers"; import { CountersPluginType } from "../types"; import { checkCounterTrigger } from "./checkCounterTrigger"; import { checkReverseCounterTrigger } from "./checkReverseCounterTrigger"; @@ -28,7 +29,7 @@ export async function changeCounterValue( userId = counter.per_user ? userId : null; const counterId = pluginData.state.counterIds[counterName]; - const lock = await pluginData.locks.acquire(counterId.toString()); + const lock = await pluginData.locks.acquire(counterIdLock(counterId)); await pluginData.state.counters.changeCounterValue(counterId, channelId, userId, change); diff --git a/backend/src/plugins/Counters/functions/decayCounter.ts b/backend/src/plugins/Counters/functions/decayCounter.ts index 175cb158..7db4cef3 100644 --- a/backend/src/plugins/Counters/functions/decayCounter.ts +++ b/backend/src/plugins/Counters/functions/decayCounter.ts @@ -2,6 +2,7 @@ import { GuildPluginData } from "knub"; import { CountersPluginType } from "../types"; import { checkAllValuesForTrigger } from "./checkAllValuesForTrigger"; import { checkAllValuesForReverseTrigger } from "./checkAllValuesForReverseTrigger"; +import { counterIdLock } from "../../../utils/lockNameHelpers"; export async function decayCounter( pluginData: GuildPluginData, @@ -16,7 +17,7 @@ export async function decayCounter( } const counterId = pluginData.state.counterIds[counterName]; - const lock = await pluginData.locks.acquire(counterId.toString()); + const lock = await pluginData.locks.acquire(counterIdLock(counterId)); await pluginData.state.counters.decay(counterId, decayPeriodMS, decayAmount); diff --git a/backend/src/plugins/Counters/functions/setCounterValue.ts b/backend/src/plugins/Counters/functions/setCounterValue.ts index 2eefed8f..697c8503 100644 --- a/backend/src/plugins/Counters/functions/setCounterValue.ts +++ b/backend/src/plugins/Counters/functions/setCounterValue.ts @@ -1,4 +1,5 @@ import { GuildPluginData } from "knub"; +import { counterIdLock } from "../../../utils/lockNameHelpers"; import { CountersPluginType } from "../types"; import { checkCounterTrigger } from "./checkCounterTrigger"; import { checkReverseCounterTrigger } from "./checkReverseCounterTrigger"; @@ -25,7 +26,7 @@ export async function setCounterValue( } const counterId = pluginData.state.counterIds[counterName]; - const lock = await pluginData.locks.acquire(counterId.toString()); + const lock = await pluginData.locks.acquire(counterIdLock(counterId)); await pluginData.state.counters.setCounterValue(counterId, channelId, userId, value); diff --git a/backend/src/plugins/ModActions/commands/BanCmd.ts b/backend/src/plugins/ModActions/commands/BanCmd.ts index 4fe9561e..1f943087 100644 --- a/backend/src/plugins/ModActions/commands/BanCmd.ts +++ b/backend/src/plugins/ModActions/commands/BanCmd.ts @@ -8,9 +8,10 @@ import { formatReasonWithAttachments } from "../functions/formatReasonWithAttach import { banUserId } from "../functions/banUserId"; import { getMemberLevel, waitForReaction } from "knub/dist/helpers"; import humanizeDuration from "humanize-duration"; -import { CasesPlugin } from "src/plugins/Cases/CasesPlugin"; -import { CaseTypes } from "src/data/CaseTypes"; -import { LogType } from "src/data/LogType"; +import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; +import { CaseTypes } from "../../../data/CaseTypes"; +import { LogType } from "../../../data/LogType"; +import { banLock } from "../../../utils/lockNameHelpers"; const opts = { mod: ct.member({ option: true }), @@ -62,7 +63,7 @@ export const BanCmd = modActionsCmd({ } // acquire a lock because of the needed user-inputs below (if banned/not on server) - const lock = await pluginData.locks.acquire(`ban-${user.id}`); + const lock = await pluginData.locks.acquire(banLock(user)); let forceban = false; const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id); const banned = await isBanned(pluginData, user.id); diff --git a/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts b/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts index 890636a6..2e57e8da 100644 --- a/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts +++ b/backend/src/plugins/Mutes/events/ReapplyActiveMuteOnJoinEvt.ts @@ -1,6 +1,7 @@ import { mutesEvt } from "../types"; import { LogType } from "../../../data/LogType"; import { stripObjectToScalars } from "../../../utils"; +import { memberRolesLock } from "../../../utils/lockNameHelpers"; /** * Reapply active mutes on join @@ -11,9 +12,9 @@ export const ReapplyActiveMuteOnJoinEvt = mutesEvt("guildMemberAdd", async ({ pl const muteRole = pluginData.config.get().mute_role; if (muteRole) { - const memberRolesLock = await pluginData.locks.acquire(`member-roles-${member.id}`); + const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member)); await member.addRole(muteRole); - memberRolesLock.unlock(); + memberRoleLock.unlock(); } pluginData.state.serverLogs.log(LogType.MEMBER_MUTE_REJOIN, { diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index 393a00e1..29ba54ab 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -17,8 +17,8 @@ import { CasesPlugin } from "../../Cases/CasesPlugin"; import { CaseTypes } from "../../../data/CaseTypes"; import { LogType } from "../../../data/LogType"; import { Case } from "../../../data/entities/Case"; -import { sendErrorMessage } from "src/pluginUtils"; -import { LogsPlugin } from "src/plugins/Logs/LogsPlugin"; +import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin"; +import { muteLock } from "../../../utils/lockNameHelpers"; export async function muteUser( pluginData: GuildPluginData, @@ -29,7 +29,7 @@ export async function muteUser( removeRolesOnMuteOverride: boolean | string[] | null = null, restoreRolesOnMuteOverride: boolean | string[] | null = null, ) { - const lock = await pluginData.locks.acquire(`mute-${userId}`); + const lock = await pluginData.locks.acquire(muteLock({ id: userId })); const muteRole = pluginData.config.get().mute_role; if (!muteRole) { diff --git a/backend/src/plugins/Persist/events/LoadDataEvt.ts b/backend/src/plugins/Persist/events/LoadDataEvt.ts index e88cb333..bd356acb 100644 --- a/backend/src/plugins/Persist/events/LoadDataEvt.ts +++ b/backend/src/plugins/Persist/events/LoadDataEvt.ts @@ -7,6 +7,7 @@ import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { missingPermissionError } from "../../../utils/missingPermissionError"; import { canAssignRole } from "../../../utils/canAssignRole"; +import { memberRolesLock } from "../../../utils/lockNameHelpers"; const p = Constants.Permissions; @@ -17,11 +18,11 @@ export const LoadDataEvt = persistEvt({ const member = meta.args.member; const pluginData = meta.pluginData; - const memberRolesLock = await pluginData.locks.acquire(`member-roles-${member.id}`); + const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member)); const persistedData = await pluginData.state.persistedData.find(member.id); if (!persistedData) { - memberRolesLock.unlock(); + memberRoleLock.unlock(); return; } @@ -79,6 +80,6 @@ export const LoadDataEvt = persistEvt({ }); } - memberRolesLock.unlock(); + memberRoleLock.unlock(); }, }); diff --git a/backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts b/backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts index 2b2dfd0d..00ae6108 100644 --- a/backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts +++ b/backend/src/plugins/ReactionRoles/util/addMemberPendingRoleChange.ts @@ -2,6 +2,7 @@ import { GuildPluginData } from "knub"; import { ReactionRolesPluginType, RoleChangeMode, PendingMemberRoleChanges } from "../types"; import { resolveMember } from "../../../utils"; import { logger } from "../../../logger"; +import { memberRolesLock } from "../../../utils/lockNameHelpers"; const ROLE_CHANGE_BATCH_DEBOUNCE_TIME = 1500; @@ -18,7 +19,7 @@ export async function addMemberPendingRoleChange( applyFn: async () => { pluginData.state.pendingRoleChanges.delete(memberId); - const lock = await pluginData.locks.acquire(`member-roles-${memberId}`); + const lock = await pluginData.locks.acquire(memberRolesLock({ id: memberId })); const member = await resolveMember(pluginData.client, pluginData.guild, memberId); if (member) { diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts index 2c9a1d0f..8fe86f04 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleAddCmd.ts @@ -6,6 +6,7 @@ import { splitRoleNames } from "../util/splitRoleNames"; import { normalizeRoleNames } from "../util/normalizeRoleNames"; import { findMatchingRoles } from "../util/findMatchingRoles"; import { Role } from "eris"; +import { memberRolesLock } from "../../../utils/lockNameHelpers"; export const RoleAddCmd = selfGrantableRolesCmd({ trigger: ["role", "role add"], @@ -16,7 +17,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({ }, async run({ message: msg, args, pluginData }) { - const lock = await pluginData.locks.acquire(`grantableRoles:${msg.author.id}`); + const lock = await pluginData.locks.acquire(memberRolesLock(msg.author)); const applyingEntries = getApplyingEntries(pluginData, msg); if (applyingEntries.length === 0) { diff --git a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts index 7cabb3c4..c2011e05 100644 --- a/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts +++ b/backend/src/plugins/SelfGrantableRoles/commands/RoleRemoveCmd.ts @@ -5,6 +5,7 @@ import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { splitRoleNames } from "../util/splitRoleNames"; import { normalizeRoleNames } from "../util/normalizeRoleNames"; import { findMatchingRoles } from "../util/findMatchingRoles"; +import { memberRolesLock } from "../../../utils/lockNameHelpers"; export const RoleRemoveCmd = selfGrantableRolesCmd({ trigger: "role remove", @@ -15,7 +16,7 @@ export const RoleRemoveCmd = selfGrantableRolesCmd({ }, async run({ message: msg, args, pluginData }) { - const lock = await pluginData.locks.acquire(`grantableRoles:${msg.author.id}`); + const lock = await pluginData.locks.acquire(memberRolesLock(msg.author)); const applyingEntries = getApplyingEntries(pluginData, msg); if (applyingEntries.length === 0) { diff --git a/backend/src/plugins/Slowmode/util/onMessageCreate.ts b/backend/src/plugins/Slowmode/util/onMessageCreate.ts index 8e1b8564..4f0c3989 100644 --- a/backend/src/plugins/Slowmode/util/onMessageCreate.ts +++ b/backend/src/plugins/Slowmode/util/onMessageCreate.ts @@ -10,6 +10,7 @@ import { BOT_SLOWMODE_PERMISSIONS } from "../requiredPermissions"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogType } from "../../../data/LogType"; import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { messageLock } from "../../../utils/lockNameHelpers"; export async function onMessageCreate(pluginData: GuildPluginData, msg: SavedMessage) { if (msg.is_bot) return; @@ -18,7 +19,7 @@ export async function onMessageCreate(pluginData: GuildPluginData board.enabled) diff --git a/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts b/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts index 07f85670..90160a4a 100644 --- a/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts +++ b/backend/src/plugins/Starboard/events/StarboardReactionRemoveEvts.ts @@ -1,10 +1,11 @@ +import { allStarboardsLock } from "../../../utils/lockNameHelpers"; import { starboardEvt } from "../types"; export const StarboardReactionRemoveEvt = starboardEvt({ event: "messageReactionRemove", async listener(meta) { - const boardLock = await meta.pluginData.locks.acquire(`starboards`); + const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock()); await meta.pluginData.state.starboardReactions.deleteStarboardReaction(meta.args.message.id, meta.args.member.id); boardLock.unlock(); }, @@ -14,7 +15,7 @@ export const StarboardReactionRemoveAllEvt = starboardEvt({ event: "messageReactionRemoveAll", async listener(meta) { - const boardLock = await meta.pluginData.locks.acquire(`starboards`); + const boardLock = await meta.pluginData.locks.acquire(allStarboardsLock()); await meta.pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(meta.args.message.id); boardLock.unlock(); }, diff --git a/backend/src/utils/lockNameHelpers.ts b/backend/src/utils/lockNameHelpers.ts new file mode 100644 index 00000000..4a16bd33 --- /dev/null +++ b/backend/src/utils/lockNameHelpers.ts @@ -0,0 +1,26 @@ +import { Member, Message, User } from "eris"; +import { SavedMessage } from "../data/entities/SavedMessage"; + +export function allStarboardsLock() { + return `starboards`; +} + +export function banLock(user: Member | User | { id: string }) { + return `ban-${user.id}`; +} + +export function counterIdLock(counterId: number | string) { + return `counter-${counterId}`; +} + +export function memberRolesLock(member: Member | User | { id: string }) { + return `member-roles-${member.id}`; +} + +export function messageLock(message: Message | SavedMessage | { id: string }) { + return `message-${message.id}`; +} + +export function muteLock(user: Member | User | { id: string }) { + return `mute-${user.id}`; +} From 2af168b8e3a617b52fa6f4e7cb43d9a55bc29e26 Mon Sep 17 00:00:00 2001 From: vcokltfre Date: Fri, 2 Apr 2021 14:43:52 +0100 Subject: [PATCH 07/58] chore: update wording of permission error for -mod (#172) --- backend/src/plugins/ModActions/commands/AddCaseCmd.ts | 2 +- backend/src/plugins/ModActions/commands/BanCmd.ts | 2 +- backend/src/plugins/ModActions/commands/ForcebanCmd.ts | 2 +- backend/src/plugins/ModActions/commands/UnbanCmd.ts | 2 +- backend/src/plugins/ModActions/commands/WarnCmd.ts | 2 +- backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts | 2 +- backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts | 2 +- backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts index 7bd06bf8..3bed2911 100644 --- a/backend/src/plugins/ModActions/commands/AddCaseCmd.ts +++ b/backend/src/plugins/ModActions/commands/AddCaseCmd.ts @@ -45,7 +45,7 @@ export const AddCaseCmd = modActionsCmd({ let mod = msg.member; if (args.mod) { if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) { - sendErrorMessage(pluginData, msg.channel, "No permission for -mod"); + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/commands/BanCmd.ts b/backend/src/plugins/ModActions/commands/BanCmd.ts index 1f943087..da7156c2 100644 --- a/backend/src/plugins/ModActions/commands/BanCmd.ts +++ b/backend/src/plugins/ModActions/commands/BanCmd.ts @@ -55,7 +55,7 @@ export const BanCmd = modActionsCmd({ let mod = msg.member; if (args.mod) { if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) { - sendErrorMessage(pluginData, msg.channel, "No permission for -mod"); + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts index 907237ef..afda940e 100644 --- a/backend/src/plugins/ModActions/commands/ForcebanCmd.ts +++ b/backend/src/plugins/ModActions/commands/ForcebanCmd.ts @@ -54,7 +54,7 @@ export const ForcebanCmd = modActionsCmd({ let mod = msg.member; if (args.mod) { if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) { - sendErrorMessage(pluginData, msg.channel, "No permission for -mod"); + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/commands/UnbanCmd.ts b/backend/src/plugins/ModActions/commands/UnbanCmd.ts index 80cac4f2..0d5abc1c 100644 --- a/backend/src/plugins/ModActions/commands/UnbanCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnbanCmd.ts @@ -37,7 +37,7 @@ export const UnbanCmd = modActionsCmd({ let mod = msg.member; if (args.mod) { if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) { - sendErrorMessage(pluginData, msg.channel, "No permission for -mod"); + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/commands/WarnCmd.ts b/backend/src/plugins/ModActions/commands/WarnCmd.ts index 8ac19baa..e36dbb86 100644 --- a/backend/src/plugins/ModActions/commands/WarnCmd.ts +++ b/backend/src/plugins/ModActions/commands/WarnCmd.ts @@ -57,7 +57,7 @@ export const WarnCmd = modActionsCmd({ let mod = msg.member; if (args.mod) { if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) { - msg.channel.createMessage(errorMessage("No permission for -mod")); + msg.channel.createMessage(errorMessage("You don't have permission to use -mod")); return; } diff --git a/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts b/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts index 18ee2137..e467212c 100644 --- a/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts @@ -52,7 +52,7 @@ export async function actualKickMemberCmd( let mod = msg.member; if (args.mod) { if (!hasPermission(pluginData.config.getForMessage(msg), "can_act_as_other")) { - sendErrorMessage(pluginData, msg.channel, "No permission for -mod"); + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts b/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts index 6d640ec6..19c13477 100644 --- a/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualMuteUserCmd.ts @@ -28,7 +28,7 @@ export async function actualMuteUserCmd( if (args.mod) { if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) { - sendErrorMessage(pluginData, msg.channel, "No permission for -mod"); + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); return; } diff --git a/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts b/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts index 0faee47c..7931aac1 100644 --- a/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts +++ b/backend/src/plugins/ModActions/functions/actualUnmuteUserCmd.ts @@ -19,7 +19,7 @@ export async function actualUnmuteCmd( if (args.mod) { if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) { - sendErrorMessage(pluginData, msg.channel, "No permission for -mod"); + sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); return; } From c4a8c3014e430583dc8cbdc7ee63e80a7d95dd17 Mon Sep 17 00:00:00 2001 From: DEX Date: Fri, 2 Apr 2021 17:45:00 +0400 Subject: [PATCH 08/58] Adding av as an alias for the avatar command (#169) --- backend/src/plugins/Utility/commands/AvatarCmd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/plugins/Utility/commands/AvatarCmd.ts b/backend/src/plugins/Utility/commands/AvatarCmd.ts index 9e413a3d..7ab1fac8 100644 --- a/backend/src/plugins/Utility/commands/AvatarCmd.ts +++ b/backend/src/plugins/Utility/commands/AvatarCmd.ts @@ -5,7 +5,7 @@ import { sendErrorMessage } from "../../../pluginUtils"; import { EmbedOptions } from "eris"; export const AvatarCmd = utilityCmd({ - trigger: "avatar", + trigger: ["avatar", "av"], description: "Retrieves a user's profile picture", permission: "can_avatar", From 56ade239dcd535bcccbfc68700955476233d3dbe Mon Sep 17 00:00:00 2001 From: Nils <7890309+DarkView@users.noreply.github.com> Date: Fri, 2 Apr 2021 15:53:09 +0200 Subject: [PATCH 09/58] Allow mute and unmute to be upgraded to their force variant (#174) --- .../plugins/ModActions/commands/MuteCmd.ts | 19 ++++++++++-------- .../plugins/ModActions/commands/UnmuteCmd.ts | 20 +++++++++++-------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/backend/src/plugins/ModActions/commands/MuteCmd.ts b/backend/src/plugins/ModActions/commands/MuteCmd.ts index ada23e2f..3aa96c38 100644 --- a/backend/src/plugins/ModActions/commands/MuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/MuteCmd.ts @@ -6,7 +6,7 @@ import { formatReasonWithAttachments } from "../functions/formatReasonWithAttach import { CasesPlugin } from "../../Cases/CasesPlugin"; import { LogType } from "../../../data/LogType"; import { CaseTypes } from "../../../data/CaseTypes"; -import { errorMessage, resolveMember, resolveUser, stripObjectToScalars } from "../../../utils"; +import { errorMessage, noop, resolveMember, resolveUser, stripObjectToScalars } from "../../../utils"; import { isBanned } from "../functions/isBanned"; import { waitForReaction } from "knub/dist/helpers"; import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; @@ -59,15 +59,18 @@ export const MuteCmd = modActionsCmd({ msg.channel, `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`, ); + return; } else { - sendErrorMessage( - pluginData, - msg.channel, - `User is not on the server. Use \`${prefix}forcemute\` if you want to mute them anyway.`, - ); - } + // Ask the mod if we should upgrade to a forcemute as the user is not on the server + const notOnServerMsg = await msg.channel.createMessage("User not found on the server, forcemute instead?"); + const reply = await waitForReaction(pluginData.client, notOnServerMsg, ["✅", "❌"], msg.author.id); - return; + notOnServerMsg.delete().catch(noop); + if (!reply || reply.name === "❌") { + sendErrorMessage(pluginData, msg.channel, "User not on server, mute cancelled by moderator"); + return; + } + } } // Make sure we're allowed to mute this member diff --git a/backend/src/plugins/ModActions/commands/UnmuteCmd.ts b/backend/src/plugins/ModActions/commands/UnmuteCmd.ts index f6efdd39..c294517c 100644 --- a/backend/src/plugins/ModActions/commands/UnmuteCmd.ts +++ b/backend/src/plugins/ModActions/commands/UnmuteCmd.ts @@ -1,10 +1,11 @@ import { modActionsCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { canActOn, sendErrorMessage } from "../../../pluginUtils"; -import { resolveUser, resolveMember } from "../../../utils"; +import { resolveUser, resolveMember, noop } from "../../../utils"; import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin"; import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd"; import { isBanned } from "../functions/isBanned"; +import { waitForReaction } from "knub/dist/helpers"; const opts = { mod: ct.member({ option: true }), @@ -57,15 +58,18 @@ export const UnmuteCmd = modActionsCmd({ msg.channel, `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`, ); + return; } else { - sendErrorMessage( - pluginData, - msg.channel, - `User is not on the server. Use \`${prefix}forceunmute\` to unmute them anyway.`, - ); - } + // Ask the mod if we should upgrade to a forceunmute as the user is not on the server + const notOnServerMsg = await msg.channel.createMessage("User not found on the server, forceunmute instead?"); + const reply = await waitForReaction(pluginData.client, notOnServerMsg, ["✅", "❌"], msg.author.id); - return; + notOnServerMsg.delete().catch(noop); + if (!reply || reply.name === "❌") { + sendErrorMessage(pluginData, msg.channel, "User not on server, unmute cancelled by moderator"); + return; + } + } } // Make sure we're allowed to unmute this member From 41472981207d84985d6a8fa89167c675d25faf86 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 2 Apr 2021 17:44:21 +0300 Subject: [PATCH 10/58] Add counter documentation/examples. Tweak counter triggers/actions in automod. Rename change_counter automod action to add_to_counter, add set_counter action, rename counter trigger to counter_trigger. --- .../{changeCounter.ts => addToCounter.ts} | 15 +- .../Automod/actions/availableActions.ts | 9 +- .../src/plugins/Automod/actions/setCounter.ts | 22 ++ .../Automod/triggers/availableTriggers.ts | 6 +- .../{counter.ts => counterTrigger.ts} | 0 .../src/plugins/Counters/CountersPlugin.ts | 8 + dashboard/src/components/docs/Counters.vue | 295 ++++++++++++++++++ dashboard/src/components/docs/DocsLayout.vue | 4 + dashboard/src/routes.ts | 4 + 9 files changed, 347 insertions(+), 16 deletions(-) rename backend/src/plugins/Automod/actions/{changeCounter.ts => addToCounter.ts} (62%) create mode 100644 backend/src/plugins/Automod/actions/setCounter.ts rename backend/src/plugins/Automod/triggers/{counter.ts => counterTrigger.ts} (100%) create mode 100644 dashboard/src/components/docs/Counters.vue diff --git a/backend/src/plugins/Automod/actions/changeCounter.ts b/backend/src/plugins/Automod/actions/addToCounter.ts similarity index 62% rename from backend/src/plugins/Automod/actions/changeCounter.ts rename to backend/src/plugins/Automod/actions/addToCounter.ts index a3db8d31..fe293e4c 100644 --- a/backend/src/plugins/Automod/actions/changeCounter.ts +++ b/backend/src/plugins/Automod/actions/addToCounter.ts @@ -2,26 +2,21 @@ import * as t from "io-ts"; import { automodAction } from "../helpers"; import { CountersPlugin } from "../../Counters/CountersPlugin"; -export const ChangeCounterAction = automodAction({ +export const AddToCounterAction = automodAction({ configType: t.type({ - name: t.string, - change: t.string, + counter: t.string, + amount: t.number, }), defaultConfig: {}, async apply({ pluginData, contexts, actionConfig, matchResult }) { - const change = parseInt(actionConfig.change, 10); - if (Number.isNaN(change)) { - throw new Error("Invalid change number"); - } - const countersPlugin = pluginData.getPlugin(CountersPlugin); countersPlugin.changeCounterValue( - actionConfig.name, + actionConfig.counter, contexts[0].message?.channel_id || null, contexts[0].user?.id || null, - change, + actionConfig.amount, ); }, }); diff --git a/backend/src/plugins/Automod/actions/availableActions.ts b/backend/src/plugins/Automod/actions/availableActions.ts index 21ebe945..7857db6f 100644 --- a/backend/src/plugins/Automod/actions/availableActions.ts +++ b/backend/src/plugins/Automod/actions/availableActions.ts @@ -12,7 +12,8 @@ import { AddRolesAction } from "./addRoles"; import { RemoveRolesAction } from "./removeRoles"; import { SetAntiraidLevelAction } from "./setAntiraidLevel"; import { ReplyAction } from "./reply"; -import { ChangeCounterAction } from "./changeCounter"; +import { AddToCounterAction } from "./addToCounter"; +import { SetCounterAction } from "./setCounter"; export const availableActions: Record> = { clean: CleanAction, @@ -27,7 +28,8 @@ export const availableActions: Record> = { remove_roles: RemoveRolesAction, set_antiraid_level: SetAntiraidLevelAction, reply: ReplyAction, - change_counter: ChangeCounterAction, + add_to_counter: AddToCounterAction, + set_counter: SetCounterAction, }; export const AvailableActions = t.type({ @@ -43,5 +45,6 @@ export const AvailableActions = t.type({ remove_roles: RemoveRolesAction.configType, set_antiraid_level: SetAntiraidLevelAction.configType, reply: ReplyAction.configType, - change_counter: ChangeCounterAction.configType, + add_to_counter: AddToCounterAction.configType, + set_counter: SetCounterAction.configType, }); diff --git a/backend/src/plugins/Automod/actions/setCounter.ts b/backend/src/plugins/Automod/actions/setCounter.ts new file mode 100644 index 00000000..0dbbaa37 --- /dev/null +++ b/backend/src/plugins/Automod/actions/setCounter.ts @@ -0,0 +1,22 @@ +import * as t from "io-ts"; +import { automodAction } from "../helpers"; +import { CountersPlugin } from "../../Counters/CountersPlugin"; + +export const SetCounterAction = automodAction({ + configType: t.type({ + counter: t.string, + value: t.number, + }), + + defaultConfig: {}, + + async apply({ pluginData, contexts, actionConfig, matchResult }) { + const countersPlugin = pluginData.getPlugin(CountersPlugin); + countersPlugin.setCounterValue( + actionConfig.counter, + contexts[0].message?.channel_id || null, + contexts[0].user?.id || null, + actionConfig.value, + ); + }, +}); diff --git a/backend/src/plugins/Automod/triggers/availableTriggers.ts b/backend/src/plugins/Automod/triggers/availableTriggers.ts index 175df6d7..18c671b6 100644 --- a/backend/src/plugins/Automod/triggers/availableTriggers.ts +++ b/backend/src/plugins/Automod/triggers/availableTriggers.ts @@ -17,7 +17,7 @@ import { MemberJoinTrigger } from "./memberJoin"; import { RoleAddedTrigger } from "./roleAdded"; import { RoleRemovedTrigger } from "./roleRemoved"; import { StickerSpamTrigger } from "./stickerSpam"; -import { CounterTrigger } from "./counter"; +import { CounterTrigger } from "./counterTrigger"; import { NoteTrigger } from "./note"; import { WarnTrigger } from "./warn"; import { MuteTrigger } from "./mute"; @@ -46,7 +46,7 @@ export const availableTriggers: Record member_join_spam: MemberJoinSpamTrigger, sticker_spam: StickerSpamTrigger, - counter: CounterTrigger, + counter_trigger: CounterTrigger, note: NoteTrigger, warn: WarnTrigger, @@ -77,7 +77,7 @@ export const AvailableTriggers = t.type({ member_join_spam: MemberJoinSpamTrigger.configType, sticker_spam: StickerSpamTrigger.configType, - counter: CounterTrigger.configType, + counter_trigger: CounterTrigger.configType, note: NoteTrigger.configType, warn: WarnTrigger.configType, diff --git a/backend/src/plugins/Automod/triggers/counter.ts b/backend/src/plugins/Automod/triggers/counterTrigger.ts similarity index 100% rename from backend/src/plugins/Automod/triggers/counter.ts rename to backend/src/plugins/Automod/triggers/counterTrigger.ts diff --git a/backend/src/plugins/Counters/CountersPlugin.ts b/backend/src/plugins/Counters/CountersPlugin.ts index b6789e81..68a5bcea 100644 --- a/backend/src/plugins/Counters/CountersPlugin.ts +++ b/backend/src/plugins/Counters/CountersPlugin.ts @@ -102,6 +102,14 @@ const configPreprocessor: ConfigPreprocessorFn = options => * After being triggered, a trigger is "reset" if the counter value no longer matches the trigger (e.g. drops to 100 or below in the above example). After this, that trigger can be triggered again. */ export const CountersPlugin = zeppelinGuildPlugin()("counters", { + showInDocs: true, + info: { + prettyName: "Counters", + description: + "Keep track of per-user, per-channel, or global numbers and trigger specific actions based on this number", + configurationGuide: "See Counters setup guide", + }, + configSchema: ConfigSchema, defaultOptions, configPreprocessor, diff --git a/dashboard/src/components/docs/Counters.vue b/dashboard/src/components/docs/Counters.vue new file mode 100644 index 00000000..c8af5159 --- /dev/null +++ b/dashboard/src/components/docs/Counters.vue @@ -0,0 +1,295 @@ + + + diff --git a/dashboard/src/components/docs/DocsLayout.vue b/dashboard/src/components/docs/DocsLayout.vue index b508ef46..e8657d8a 100644 --- a/dashboard/src/components/docs/DocsLayout.vue +++ b/dashboard/src/components/docs/DocsLayout.vue @@ -115,6 +115,10 @@ to: '/docs/setup-guides/moderation', label: 'Moderation', }, + { + to: '/docs/setup-guides/counters', + label: 'Counters', + }, ], }, ]; diff --git a/dashboard/src/routes.ts b/dashboard/src/routes.ts index c38d1f7e..4ef8e996 100644 --- a/dashboard/src/routes.ts +++ b/dashboard/src/routes.ts @@ -53,6 +53,10 @@ export const router = new VueRouter({ path: "setup-guides/moderation", component: () => import("./components/docs/WorkInProgress.vue"), }, + { + path: "setup-guides/counters", + component: () => import("./components/docs/Counters.vue"), + }, { path: "plugins/:pluginName/:tab?", component: () => import("./components/docs/Plugin.vue"), From 13294ad351a53a8c734e273f1790c964075858cc Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 2 Apr 2021 17:52:50 +0300 Subject: [PATCH 11/58] automod: allow setting antiraid level to null/off with set_antiraid_level action --- backend/src/plugins/Automod/actions/setAntiraidLevel.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/plugins/Automod/actions/setAntiraidLevel.ts b/backend/src/plugins/Automod/actions/setAntiraidLevel.ts index fca9632f..8d15a35f 100644 --- a/backend/src/plugins/Automod/actions/setAntiraidLevel.ts +++ b/backend/src/plugins/Automod/actions/setAntiraidLevel.ts @@ -1,12 +1,13 @@ import * as t from "io-ts"; import { automodAction } from "../helpers"; import { setAntiraidLevel } from "../functions/setAntiraidLevel"; +import { tNullable } from "../../../utils"; export const SetAntiraidLevelAction = automodAction({ - configType: t.string, + configType: tNullable(t.string), defaultConfig: "", async apply({ pluginData, contexts, actionConfig }) { - setAntiraidLevel(pluginData, actionConfig); + setAntiraidLevel(pluginData, actionConfig ?? null); }, }); From b28186aa0a97e14669787150af4c817af60eeed3 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 2 Apr 2021 17:53:16 +0300 Subject: [PATCH 12/58] automod: add antiraid_level trigger --- .../events/runAutomodOnAntiraidLevel.ts | 16 +++++ .../Automod/functions/setAntiraidLevel.ts | 3 + .../plugins/Automod/triggers/antiraidLevel.ts | 32 ++++++++++ backend/src/plugins/Automod/types.ts | 3 + dashboard/src/components/docs/Counters.vue | 60 +++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts create mode 100644 backend/src/plugins/Automod/triggers/antiraidLevel.ts diff --git a/backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts b/backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts new file mode 100644 index 00000000..370d6f23 --- /dev/null +++ b/backend/src/plugins/Automod/events/runAutomodOnAntiraidLevel.ts @@ -0,0 +1,16 @@ +import { GuildPluginData } from "knub"; +import { AutomodContext, AutomodPluginType } from "../types"; +import { runAutomod } from "../functions/runAutomod"; + +export async function runAutomodOnAntiraidLevel(pluginData: GuildPluginData, level: string | null) { + const context: AutomodContext = { + timestamp: Date.now(), + antiraid: { + level, + }, + }; + + pluginData.state.queue.add(async () => { + await runAutomod(pluginData, context); + }); +} diff --git a/backend/src/plugins/Automod/functions/setAntiraidLevel.ts b/backend/src/plugins/Automod/functions/setAntiraidLevel.ts index 2d446bf8..cac98286 100644 --- a/backend/src/plugins/Automod/functions/setAntiraidLevel.ts +++ b/backend/src/plugins/Automod/functions/setAntiraidLevel.ts @@ -4,6 +4,7 @@ import { AutomodPluginType } from "../types"; import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogType } from "../../../data/LogType"; import { stripObjectToScalars } from "../../../utils"; +import { runAutomodOnAntiraidLevel } from "../events/runAutomodOnAntiraidLevel"; export async function setAntiraidLevel( pluginData: GuildPluginData, @@ -13,6 +14,8 @@ export async function setAntiraidLevel( pluginData.state.cachedAntiraidLevel = newLevel; await pluginData.state.antiraidLevels.set(newLevel); + runAutomodOnAntiraidLevel(pluginData, newLevel); + const logs = pluginData.getPlugin(LogsPlugin); if (user) { diff --git a/backend/src/plugins/Automod/triggers/antiraidLevel.ts b/backend/src/plugins/Automod/triggers/antiraidLevel.ts new file mode 100644 index 00000000..15a61dbd --- /dev/null +++ b/backend/src/plugins/Automod/triggers/antiraidLevel.ts @@ -0,0 +1,32 @@ +import * as t from "io-ts"; +import { automodTrigger } from "../helpers"; +import { tNullable } from "../../../utils"; + +// tslint:disable-next-line +interface AntiraidLevelTriggerResult {} + +export const AntiraidLevelTrigger = automodTrigger()({ + configType: t.type({ + level: tNullable(t.string), + }), + + defaultConfig: {}, + + async match({ triggerConfig, context, pluginData }) { + if (!context.antiraid) { + return; + } + + if (context.antiraid.level !== triggerConfig.level) { + return; + } + + return { + extra: {}, + }; + }, + + renderMatchInformation({ matchResult, pluginData, contexts, triggerConfig }) { + return `Antiraid level was set to ...`; + }, +}); diff --git a/backend/src/plugins/Automod/types.ts b/backend/src/plugins/Automod/types.ts index 37202143..f85b8429 100644 --- a/backend/src/plugins/Automod/types.ts +++ b/backend/src/plugins/Automod/types.ts @@ -123,6 +123,9 @@ export interface AutomodContext { type: ModActionType; reason?: string; }; + antiraid?: { + level: string | null; + }; } export interface RecentAction { diff --git a/dashboard/src/components/docs/Counters.vue b/dashboard/src/components/docs/Counters.vue index c8af5159..6720b15d 100644 --- a/dashboard/src/components/docs/Counters.vue +++ b/dashboard/src/components/docs/Counters.vue @@ -23,6 +23,7 @@