mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 20:35:02 +00:00
Counters v0.9
Includes automod trigger/action. No user-facing commands yet.
This commit is contained in:
parent
ec37cf27a2
commit
c3407e2d5d
29 changed files with 1387 additions and 3 deletions
48
backend/src/plugins/Counters/functions/changeCounterValue.ts
Normal file
48
backend/src/plugins/Counters/functions/changeCounterValue.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType } from "../types";
|
||||
import { checkCounterTrigger } from "./checkCounterTrigger";
|
||||
import { checkReverseCounterTrigger } from "./checkReverseCounterTrigger";
|
||||
|
||||
export async function changeCounterValue(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
counterName: string,
|
||||
channelId: string | null,
|
||||
userId: string | null,
|
||||
change: number,
|
||||
) {
|
||||
const config = pluginData.config.get();
|
||||
const counter = config.counters[counterName];
|
||||
if (!counter) {
|
||||
throw new Error(`Unknown counter: ${counterName}`);
|
||||
}
|
||||
|
||||
if (counter.per_channel && !channelId) {
|
||||
throw new Error(`Counter is per channel but no channel ID was supplied`);
|
||||
}
|
||||
|
||||
if (counter.per_user && !userId) {
|
||||
throw new Error(`Counter is per user but no user ID was supplied`);
|
||||
}
|
||||
|
||||
channelId = counter.per_channel ? channelId : null;
|
||||
userId = counter.per_user ? userId : null;
|
||||
|
||||
const counterId = pluginData.state.counterIds[counterName];
|
||||
const lock = await pluginData.locks.acquire(counterId.toString());
|
||||
|
||||
await pluginData.state.counters.changeCounterValue(counterId, channelId, userId, change);
|
||||
|
||||
// Check for trigger matches, if any, when the counter value changes
|
||||
const triggers = pluginData.state.counterTriggersByCounterId.get(counterId);
|
||||
if (triggers) {
|
||||
const triggersArr = Array.from(triggers.values());
|
||||
await Promise.all(
|
||||
triggersArr.map(trigger => checkCounterTrigger(pluginData, counterName, trigger, channelId, userId)),
|
||||
);
|
||||
await Promise.all(
|
||||
triggersArr.map(trigger => checkReverseCounterTrigger(pluginData, counterName, trigger, channelId, userId)),
|
||||
);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType } from "../types";
|
||||
import { buildConditionString } from "../../../data/GuildCounters";
|
||||
import { CounterTrigger } from "../../../data/entities/CounterTrigger";
|
||||
import { emitCounterEvent } from "./emitCounterEvent";
|
||||
|
||||
export async function checkAllValuesForReverseTrigger(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
counterName: string,
|
||||
counterTrigger: CounterTrigger,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType } from "../types";
|
||||
import { buildConditionString } from "../../../data/GuildCounters";
|
||||
import { CounterTrigger } from "../../../data/entities/CounterTrigger";
|
||||
import { emitCounterEvent } from "./emitCounterEvent";
|
||||
|
||||
export async function checkAllValuesForTrigger(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
counterName: string,
|
||||
counterTrigger: CounterTrigger,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType } from "../types";
|
||||
import { buildConditionString } from "../../../data/GuildCounters";
|
||||
import { CounterTrigger } from "../../../data/entities/CounterTrigger";
|
||||
import { emitCounterEvent } from "./emitCounterEvent";
|
||||
|
||||
export async function checkCounterTrigger(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
counterName: string,
|
||||
counterTrigger: CounterTrigger,
|
||||
channelId: string | null,
|
||||
userId: string | null,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType } from "../types";
|
||||
import { buildConditionString } from "../../../data/GuildCounters";
|
||||
import { CounterTrigger } from "../../../data/entities/CounterTrigger";
|
||||
import { emitCounterEvent } from "./emitCounterEvent";
|
||||
|
||||
export async function checkReverseCounterTrigger(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
counterName: string,
|
||||
counterTrigger: CounterTrigger,
|
||||
channelId: string | null,
|
||||
userId: string | null,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
32
backend/src/plugins/Counters/functions/decayCounter.ts
Normal file
32
backend/src/plugins/Counters/functions/decayCounter.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType } from "../types";
|
||||
import { checkAllValuesForTrigger } from "./checkAllValuesForTrigger";
|
||||
import { checkAllValuesForReverseTrigger } from "./checkAllValuesForReverseTrigger";
|
||||
|
||||
export async function decayCounter(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
counterName: string,
|
||||
decayPeriodMS: number,
|
||||
decayAmount: number,
|
||||
) {
|
||||
const config = pluginData.config.get();
|
||||
const counter = config.counters[counterName];
|
||||
if (!counter) {
|
||||
throw new Error(`Unknown counter: ${counterName}`);
|
||||
}
|
||||
|
||||
const counterId = pluginData.state.counterIds[counterName];
|
||||
const lock = await pluginData.locks.acquire(counterId.toString());
|
||||
|
||||
await pluginData.state.counters.decay(counterId, decayPeriodMS, decayAmount);
|
||||
|
||||
// Check for trigger matches, if any, when the counter value changes
|
||||
const triggers = pluginData.state.counterTriggersByCounterId.get(counterId);
|
||||
if (triggers) {
|
||||
const triggersArr = Array.from(triggers.values());
|
||||
await Promise.all(triggersArr.map(trigger => checkAllValuesForTrigger(pluginData, counterName, trigger)));
|
||||
await Promise.all(triggersArr.map(trigger => checkAllValuesForReverseTrigger(pluginData, counterName, trigger)));
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
}
|
10
backend/src/plugins/Counters/functions/emitCounterEvent.ts
Normal file
10
backend/src/plugins/Counters/functions/emitCounterEvent.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { CounterEvents, CountersPluginType } from "../types";
|
||||
import { GuildPluginData } from "knub";
|
||||
|
||||
export function emitCounterEvent<TEvent extends keyof CounterEvents>(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
event: TEvent,
|
||||
...rest: Parameters<CounterEvents[TEvent]>
|
||||
) {
|
||||
return pluginData.state.events.emit(event, ...rest);
|
||||
}
|
31
backend/src/plugins/Counters/functions/initCounterTrigger.ts
Normal file
31
backend/src/plugins/Counters/functions/initCounterTrigger.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
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<CountersPluginType>,
|
||||
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);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { CounterEventEmitter, CountersPluginType } from "../types";
|
||||
import { GuildPluginData } from "knub";
|
||||
|
||||
export function offCounterEvent(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
...rest: Parameters<CounterEventEmitter["off"]>
|
||||
) {
|
||||
return pluginData.state.events.off(...rest);
|
||||
}
|
10
backend/src/plugins/Counters/functions/onCounterEvent.ts
Normal file
10
backend/src/plugins/Counters/functions/onCounterEvent.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { CounterEvents, CountersPluginType } from "../types";
|
||||
import { GuildPluginData } from "knub";
|
||||
|
||||
export function onCounterEvent<TEvent extends keyof CounterEvents>(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
event: TEvent,
|
||||
listener: CounterEvents[TEvent],
|
||||
) {
|
||||
return pluginData.state.events.on(event, listener);
|
||||
}
|
45
backend/src/plugins/Counters/functions/setCounterValue.ts
Normal file
45
backend/src/plugins/Counters/functions/setCounterValue.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType } from "../types";
|
||||
import { checkCounterTrigger } from "./checkCounterTrigger";
|
||||
import { checkReverseCounterTrigger } from "./checkReverseCounterTrigger";
|
||||
|
||||
export async function setCounterValue(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
counterName: string,
|
||||
channelId: string | null,
|
||||
userId: string | null,
|
||||
value: number,
|
||||
) {
|
||||
const config = pluginData.config.get();
|
||||
const counter = config.counters[counterName];
|
||||
if (!counter) {
|
||||
throw new Error(`Unknown counter: ${counterName}`);
|
||||
}
|
||||
|
||||
if (counter.per_channel && !channelId) {
|
||||
throw new Error(`Counter is per channel but no channel ID was supplied`);
|
||||
}
|
||||
|
||||
if (counter.per_user && !userId) {
|
||||
throw new Error(`Counter is per user but no user ID was supplied`);
|
||||
}
|
||||
|
||||
const counterId = pluginData.state.counterIds[counterName];
|
||||
const lock = await pluginData.locks.acquire(counterId.toString());
|
||||
|
||||
await pluginData.state.counters.setCounterValue(counterId, channelId, userId, value);
|
||||
|
||||
// Check for trigger matches, if any, when the counter value changes
|
||||
const triggers = pluginData.state.counterTriggersByCounterId.get(counterId);
|
||||
if (triggers) {
|
||||
const triggersArr = Array.from(triggers.values());
|
||||
await Promise.all(
|
||||
triggersArr.map(trigger => checkCounterTrigger(pluginData, counterName, trigger, channelId, userId)),
|
||||
);
|
||||
await Promise.all(
|
||||
triggersArr.map(trigger => checkReverseCounterTrigger(pluginData, counterName, trigger, channelId, userId)),
|
||||
);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType } from "../types";
|
||||
import { parseCondition } from "../../../data/GuildCounters";
|
||||
|
||||
export function validateCondition(pluginData: GuildPluginData<CountersPluginType>, condition: string) {
|
||||
const parsed = parseCondition(condition);
|
||||
return parsed != null;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue