3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-16 14:11:50 +00:00

Merge branch 'Dragory:master' into usoka-dev

This commit is contained in:
Usoka 2021-05-24 17:37:11 +12:00
commit 613c98e378
150 changed files with 6918 additions and 495 deletions

6126
backend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -31,7 +31,7 @@
"deep-diff": "^1.0.2", "deep-diff": "^1.0.2",
"dotenv": "^4.0.0", "dotenv": "^4.0.0",
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"eris": "github:abalabahaha/eris#dev", "eris": "^0.15.1",
"erlpack": "github:abalabahaha/erlpack", "erlpack": "github:abalabahaha/erlpack",
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
"express": "^4.17.0", "express": "^4.17.0",
@ -39,7 +39,7 @@
"humanize-duration": "^3.15.0", "humanize-duration": "^3.15.0",
"io-ts": "^2.0.0", "io-ts": "^2.0.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"knub": "^30.0.0-beta.35", "knub": "^30.0.0-beta.37",
"knub-command-manager": "^8.1.2", "knub-command-manager": "^8.1.2",
"last-commit-log": "^2.1.0", "last-commit-log": "^2.1.0",
"lodash.chunk": "^4.2.0", "lodash.chunk": "^4.2.0",

View file

@ -1,23 +1,38 @@
import { SECONDS } from "./utils"; import { SECONDS } from "./utils";
type QueueFn = (...args: any[]) => Promise<any>; type InternalQueueFn = () => Promise<void>;
type AnyFn = (...args: any[]) => any;
const DEFAULT_TIMEOUT = 10 * SECONDS; const DEFAULT_TIMEOUT = 10 * SECONDS;
export class Queue { export class Queue<TQueueFunction extends AnyFn = AnyFn> {
protected running: boolean = false; protected running = false;
protected queue: QueueFn[] = []; protected queue: InternalQueueFn[] = [];
protected timeout: number; protected _timeout: number;
constructor(timeout = DEFAULT_TIMEOUT) { constructor(timeout = DEFAULT_TIMEOUT) {
this.timeout = timeout; this._timeout = timeout;
} }
public add(fn) { get timeout(): number {
const promise = new Promise(resolve => { return this._timeout;
}
/**
* The number of operations that are currently queued up or running.
* I.e. backlog (queue) + current running process, if any.
*
* If this is 0, queueing a function will run it as soon as possible.
*/
get length(): number {
return this.queue.length + (this.running ? 1 : 0);
}
public add(fn: TQueueFunction): Promise<void> {
const promise = new Promise<void>(resolve => {
this.queue.push(async () => { this.queue.push(async () => {
await fn(); await fn();
resolve(undefined); resolve();
}); });
if (!this.running) this.next(); if (!this.running) this.next();
@ -26,7 +41,7 @@ export class Queue {
return promise; return promise;
} }
public next() { public next(): void {
this.running = true; this.running = true;
if (this.queue.length === 0) { if (this.queue.length === 0) {
@ -37,8 +52,8 @@ export class Queue {
const fn = this.queue.shift()!; const fn = this.queue.shift()!;
new Promise(resolve => { new Promise(resolve => {
// Either fn() completes or the timeout is reached // Either fn() completes or the timeout is reached
fn().then(resolve); void fn().then(resolve);
setTimeout(resolve, this.timeout); setTimeout(resolve, this._timeout);
}).then(() => this.next()); }).then(() => this.next());
} }

View file

@ -30,6 +30,7 @@ export function initArchives(app: express.Express) {
} }
res.setHeader("Content-Type", "text/plain; charset=UTF-8"); res.setHeader("Content-Type", "text/plain; charset=UTF-8");
res.setHeader("X-Content-Type-Options", "nosniff");
res.end(body); res.end(body);
}); });
} }

View file

@ -86,6 +86,7 @@ export function initAuth(app: express.Express) {
const userId = await apiLogins.getUserIdByApiKey(apiKey); const userId = await apiLogins.getUserIdByApiKey(apiKey);
if (userId) { if (userId) {
void apiLogins.refreshApiKeyExpiryTime(apiKey); // Refresh expiry time in the background
return cb(null, { apiKey, userId }); return cb(null, { apiKey, userId });
} }
@ -154,13 +155,19 @@ export function initAuth(app: express.Express) {
await apiLogins.expireApiKey(req.user!.apiKey); await apiLogins.expireApiKey(req.user!.apiKey);
return ok(res); return ok(res);
}); });
// API route to refresh the given API token's expiry time
// The actual refreshing happens in the api-token passport strategy above, so we just return 200 OK here
app.post("/auth/refresh", ...apiTokenAuthHandlers(), (req, res) => {
return ok(res);
});
} }
export function apiTokenAuthHandlers() { export function apiTokenAuthHandlers() {
return [ return [
passport.authenticate("api-token", { failWithError: true }), passport.authenticate("api-token", { failWithError: true }),
(err, req: Request, res: Response, next) => { (err, req: Request, res: Response, next) => {
return res.json({ error: err.message }); return res.status(401).json({ error: err.message });
}, },
]; ];
} }

View file

@ -12,7 +12,7 @@ import {
UnknownUser, UnknownUser,
} from "./utils"; } from "./utils";
import { GuildChannel, Member, TextChannel, User } from "eris"; import { GuildChannel, Member, TextChannel, User } from "eris";
import { baseTypeConverters, baseTypeHelpers, CommandContext, TypeConversionError } from "knub"; import { baseTypeConverters, baseCommandParameterTypeHelpers, CommandContext, TypeConversionError } from "knub";
import { createTypeHelper } from "knub-command-manager"; import { createTypeHelper } from "knub-command-manager";
import { getChannelIdFromMessageId } from "./data/getChannelIdFromMessageId"; import { getChannelIdFromMessageId } from "./data/getChannelIdFromMessageId";
import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget"; import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget";
@ -107,7 +107,7 @@ export const commandTypes = {
}; };
export const commandTypeHelpers = { export const commandTypeHelpers = {
...baseTypeHelpers, ...baseCommandParameterTypeHelpers,
delay: createTypeHelper<number>(commandTypes.delay), delay: createTypeHelper<number>(commandTypes.delay),
resolvedUser: createTypeHelper<Promise<User>>(commandTypes.resolvedUser), resolvedUser: createTypeHelper<Promise<User>>(commandTypes.resolvedUser),

View file

@ -37,7 +37,7 @@ export async function validateGuildConfig(config: any): Promise<string | null> {
const plugin = pluginNameToPlugin.get(pluginName)!; const plugin = pluginNameToPlugin.get(pluginName)!;
try { try {
const mergedOptions = configUtils.mergeConfig(plugin.defaultOptions || {}, pluginOptions); const mergedOptions = configUtils.mergeConfig(plugin.defaultOptions || {}, pluginOptions);
await plugin.configPreprocessor?.(mergedOptions as PluginOptions<any>); await plugin.configPreprocessor?.((mergedOptions as unknown) as PluginOptions<any>, true);
} catch (err) { } catch (err) {
if (err instanceof ConfigValidationError || err instanceof StrictValidationError) { if (err instanceof ConfigValidationError || err instanceof StrictValidationError) {
return `${pluginName}: ${err.message}`; return `${pluginName}: ${err.message}`;

View file

@ -5,7 +5,9 @@ import crypto from "crypto";
import moment from "moment-timezone"; import moment from "moment-timezone";
// tslint:disable-next-line:no-submodule-imports // tslint:disable-next-line:no-submodule-imports
import uuidv4 from "uuid/v4"; import uuidv4 from "uuid/v4";
import { DBDateFormat } from "../utils"; import { DAYS, DBDateFormat } from "../utils";
const LOGIN_EXPIRY_TIME = 1 * DAYS;
export class ApiLogins extends BaseRepository { export class ApiLogins extends BaseRepository {
private apiLogins: Repository<ApiLogin>; private apiLogins: Repository<ApiLogin>;
@ -68,7 +70,7 @@ export class ApiLogins extends BaseRepository {
logged_in_at: moment.utc().format(DBDateFormat), logged_in_at: moment.utc().format(DBDateFormat),
expires_at: moment expires_at: moment
.utc() .utc()
.add(1, "day") .add(LOGIN_EXPIRY_TIME, "ms")
.format(DBDateFormat), .format(DBDateFormat),
}); });
@ -86,4 +88,19 @@ export class ApiLogins extends BaseRepository {
}, },
); );
} }
async refreshApiKeyExpiryTime(apiKey) {
const [loginId, token] = apiKey.split(".");
if (!loginId || !token) return;
await this.apiLogins.update(
{ id: loginId },
{
expires_at: moment()
.utc()
.add(LOGIN_EXPIRY_TIME, "ms")
.format(DBDateFormat),
},
);
}
} }

View file

@ -2,7 +2,7 @@ import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository"; import { BaseGuildRepository } from "./BaseGuildRepository";
import { ISavedMessageData, SavedMessage } from "./entities/SavedMessage"; import { ISavedMessageData, SavedMessage } from "./entities/SavedMessage";
import { QueuedEventEmitter } from "../QueuedEventEmitter"; import { QueuedEventEmitter } from "../QueuedEventEmitter";
import { GuildChannel, Message } from "eris"; import { GuildChannel, Message, PossiblyUncachedTextableChannel } from "eris";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { MINUTES, SECONDS } from "../utils"; import { MINUTES, SECONDS } from "../utils";
import { isAPI } from "../globals"; import { isAPI } from "../globals";
@ -34,7 +34,7 @@ export class GuildSavedMessages extends BaseGuildRepository {
this.toBePermanent = new Set(); this.toBePermanent = new Set();
} }
public msgToSavedMessageData(msg: Message): ISavedMessageData { public msgToSavedMessageData(msg: Message<PossiblyUncachedTextableChannel>): ISavedMessageData {
const data: ISavedMessageData = { const data: ISavedMessageData = {
author: { author: {
username: msg.author.username, username: msg.author.username,
@ -139,7 +139,7 @@ export class GuildSavedMessages extends BaseGuildRepository {
this.events.emit(`create:${data.id}`, [inserted]); this.events.emit(`create:${data.id}`, [inserted]);
} }
async createFromMsg(msg: Message, overrides = {}) { async createFromMsg(msg: Message<PossiblyUncachedTextableChannel>, overrides = {}) {
const existingSavedMsg = await this.find(msg.id); const existingSavedMsg = await this.find(msg.id);
if (existingSavedMsg) return; if (existingSavedMsg) return;
@ -222,7 +222,7 @@ export class GuildSavedMessages extends BaseGuildRepository {
this.events.emit(`update:${id}`, [newMessage, oldMessage]); this.events.emit(`update:${id}`, [newMessage, oldMessage]);
} }
async saveEditFromMsg(msg: Message) { async saveEditFromMsg(msg: Message<PossiblyUncachedTextableChannel>) {
const newData = this.msgToSavedMessageData(msg); const newData = this.msgToSavedMessageData(msg);
return this.saveEdit(msg.id, newData); return this.saveEdit(msg.id, newData);
} }

View file

@ -156,6 +156,9 @@ connect().then(async () => {
restMode: true, restMode: true,
compress: false, compress: false,
guildCreateTimeout: 0, guildCreateTimeout: 0,
rest: {
ratelimiterOffset: 150,
},
// Disable mentions by default // Disable mentions by default
allowedMentions: { allowedMentions: {
everyone: false, everyone: false,

View file

@ -27,8 +27,12 @@ export function canActOn(pluginData: GuildPluginData<any>, member1: Member, memb
return allowSameLevel ? ourLevel >= memberLevel : ourLevel > memberLevel; return allowSameLevel ? ourLevel >= memberLevel : ourLevel > memberLevel;
} }
export function hasPermission(pluginData: AnyPluginData<any>, permission: string, matchParams: ExtendedMatchParams) { export async function hasPermission(
const config = pluginData.config.getMatchingConfig(matchParams); pluginData: AnyPluginData<any>,
permission: string,
matchParams: ExtendedMatchParams,
) {
const config = await pluginData.config.getMatchingConfig(matchParams);
return helpers.hasPermission(config, permission); return helpers.hasPermission(config, permission);
} }
@ -50,6 +54,19 @@ const PluginOverrideCriteriaType: t.Type<PluginOverrideCriteria<unknown>> = t.re
}), }),
); );
const validTopLevelOverrideKeys = [
"channel",
"category",
"level",
"user",
"role",
"all",
"any",
"not",
"extra",
"config",
];
const BasicPluginStructureType = t.type({ const BasicPluginStructureType = t.type({
enabled: tNullable(t.boolean), enabled: tNullable(t.boolean),
config: tNullable(t.unknown), config: tNullable(t.unknown),
@ -70,7 +87,7 @@ export function getPluginConfigPreprocessor(
blueprint: ZeppelinPlugin, blueprint: ZeppelinPlugin,
customPreprocessor?: ZeppelinPlugin["configPreprocessor"], customPreprocessor?: ZeppelinPlugin["configPreprocessor"],
) { ) {
return async (options: PluginOptions<any>) => { return async (options: PluginOptions<any>, strict?: boolean) => {
// 1. Validate the basic structure of plugin config // 1. Validate the basic structure of plugin config
const basicOptionsValidation = validate(BasicPluginStructureType, options); const basicOptionsValidation = validate(BasicPluginStructureType, options);
if (basicOptionsValidation instanceof StrictValidationError) { if (basicOptionsValidation instanceof StrictValidationError) {
@ -89,8 +106,32 @@ export function getPluginConfigPreprocessor(
if (options.overrides) { if (options.overrides) {
for (const override of options.overrides) { for (const override of options.overrides) {
const partialOverrideConfigValidation = validate(partialConfigSchema, override.config || {}); // Validate criteria and extra criteria
if (partialOverrideConfigValidation) { // FIXME: This is ugly
for (const key of Object.keys(override)) {
if (!validTopLevelOverrideKeys.includes(key)) {
if (strict) {
throw new ConfigValidationError(`Unknown override criterion '${key}'`);
}
delete override[key];
}
}
if (override.extra != null) {
for (const extraCriterion of Object.keys(override.extra)) {
if (!blueprint.customOverrideCriteriaFunctions?.[extraCriterion]) {
if (strict) {
throw new ConfigValidationError(`Unknown override extra criterion '${extraCriterion}'`);
}
delete override.extra[extraCriterion];
}
}
}
// Validate override config
const partialOverrideConfigValidation = decodeAndValidateStrict(partialConfigSchema, override.config || {});
if (partialOverrideConfigValidation instanceof StrictValidationError) {
throw strictValidationErrorToConfigValidationError(partialOverrideConfigValidation); throw strictValidationErrorToConfigValidationError(partialOverrideConfigValidation);
} }
} }

View file

@ -16,7 +16,8 @@ const defaultOptions: PluginOptions<AutoDeletePluginType> = {
}, },
}; };
export const AutoDeletePlugin = zeppelinGuildPlugin<AutoDeletePluginType>()("auto_delete", { export const AutoDeletePlugin = zeppelinGuildPlugin<AutoDeletePluginType>()({
name: "auto_delete",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Auto-delete", prettyName: "Auto-delete",
@ -28,7 +29,7 @@ export const AutoDeletePlugin = zeppelinGuildPlugin<AutoDeletePluginType>()("aut
configSchema: ConfigSchema, configSchema: ConfigSchema,
defaultOptions, defaultOptions,
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.guildSavedMessages = GuildSavedMessages.getGuildInstance(guild.id); state.guildSavedMessages = GuildSavedMessages.getGuildInstance(guild.id);
@ -39,6 +40,10 @@ export const AutoDeletePlugin = zeppelinGuildPlugin<AutoDeletePluginType>()("aut
state.nextDeletionTimeout = null; state.nextDeletionTimeout = null;
state.maxDelayWarningSent = false; state.maxDelayWarningSent = false;
},
afterLoad(pluginData) {
const { state, guild } = pluginData;
state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg);
state.guildSavedMessages.events.on("create", state.onMessageCreateFn); state.guildSavedMessages.events.on("create", state.onMessageCreateFn);
@ -50,7 +55,7 @@ export const AutoDeletePlugin = zeppelinGuildPlugin<AutoDeletePluginType>()("aut
state.guildSavedMessages.events.on("deleteBulk", state.onMessageDeleteBulkFn); state.guildSavedMessages.events.on("deleteBulk", state.onMessageDeleteBulkFn);
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
pluginData.state.guildSavedMessages.events.off("create", pluginData.state.onMessageCreateFn); pluginData.state.guildSavedMessages.events.off("create", pluginData.state.onMessageCreateFn);
pluginData.state.guildSavedMessages.events.off("delete", pluginData.state.onMessageDeleteFn); pluginData.state.guildSavedMessages.events.off("delete", pluginData.state.onMessageDeleteFn);
pluginData.state.guildSavedMessages.events.off("deleteBulk", pluginData.state.onMessageDeleteBulkFn); pluginData.state.guildSavedMessages.events.off("deleteBulk", pluginData.state.onMessageDeleteBulkFn);

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { tDelayString, MINUTES } from "../../utils"; import { tDelayString, MINUTES } from "../../utils";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";

View file

@ -7,7 +7,7 @@ import { addMessageToDeletionQueue } from "./addMessageToDeletionQueue";
export async function onMessageCreate(pluginData: GuildPluginData<AutoDeletePluginType>, msg: SavedMessage) { export async function onMessageCreate(pluginData: GuildPluginData<AutoDeletePluginType>, msg: SavedMessage) {
const member = await resolveMember(pluginData.client, pluginData.guild, msg.user_id); const member = await resolveMember(pluginData.client, pluginData.guild, msg.user_id);
const config = pluginData.config.getMatchingConfig({ member, channelId: msg.channel_id }); const config = await pluginData.config.getMatchingConfig({ member, channelId: msg.channel_id });
if (config.enabled) { if (config.enabled) {
let delay = convertDelayStringToMS(config.delay)!; let delay = convertDelayStringToMS(config.delay)!;

View file

@ -23,7 +23,8 @@ const defaultOptions: PluginOptions<AutoReactionsPluginType> = {
], ],
}; };
export const AutoReactionsPlugin = zeppelinGuildPlugin<AutoReactionsPluginType>()("auto_reactions", { export const AutoReactionsPlugin = zeppelinGuildPlugin<AutoReactionsPluginType>()({
name: "auto_reactions",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Auto-reactions", prettyName: "Auto-reactions",
@ -47,10 +48,8 @@ export const AutoReactionsPlugin = zeppelinGuildPlugin<AutoReactionsPluginType>(
AddReactionsEvt, AddReactionsEvt,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; pluginData.state.savedMessages = GuildSavedMessages.getGuildInstance(pluginData.guild.id);
pluginData.state.autoReactions = GuildAutoReactions.getGuildInstance(pluginData.guild.id);
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
state.autoReactions = GuildAutoReactions.getGuildInstance(guild.id);
}, },
}); });

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildAutoReactions } from "../../data/GuildAutoReactions"; import { GuildAutoReactions } from "../../data/GuildAutoReactions";
@ -18,5 +18,5 @@ export interface AutoReactionsPluginType extends BasePluginType {
}; };
} }
export const autoReactionsCmd = guildCommand<AutoReactionsPluginType>(); export const autoReactionsCmd = typedGuildCommand<AutoReactionsPluginType>();
export const autoReactionsEvt = guildEventListener<AutoReactionsPluginType>(); export const autoReactionsEvt = typedGuildEventListener<AutoReactionsPluginType>();

View file

@ -152,7 +152,8 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = options => {
return options; return options;
}; };
export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod", { export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
name: "automod",
showInDocs: true, showInDocs: true,
info: pluginInfo, info: pluginInfo,
@ -168,8 +169,10 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
defaultOptions, defaultOptions,
configPreprocessor, configPreprocessor,
customOverrideMatcher(pluginData, criteria, matchParams) { customOverrideCriteriaFunctions: {
return criteria?.antiraid_level ? criteria.antiraid_level === pluginData.state.cachedAntiraidLevel : false; antiraid_level: (pluginData, matchParams, value) => {
return value ? value === pluginData.state.cachedAntiraidLevel : false;
},
}, },
// prettier-ignore // prettier-ignore
@ -181,22 +184,16 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
commands: [AntiraidClearCmd, SetAntiraidCmd, ViewAntiraidCmd], commands: [AntiraidClearCmd, SetAntiraidCmd, ViewAntiraidCmd],
async onLoad(pluginData) { async beforeLoad(pluginData) {
pluginData.state.queue = new Queue(); pluginData.state.queue = new Queue();
pluginData.state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`); pluginData.state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`);
pluginData.state.recentActions = []; pluginData.state.recentActions = [];
pluginData.state.clearRecentActionsInterval = setInterval(() => clearOldRecentActions(pluginData), 1 * MINUTES);
pluginData.state.recentSpam = []; pluginData.state.recentSpam = [];
pluginData.state.clearRecentSpamInterval = setInterval(() => clearOldRecentSpam(pluginData), 1 * SECONDS);
pluginData.state.recentNicknameChanges = new Map(); pluginData.state.recentNicknameChanges = new Map();
pluginData.state.clearRecentNicknameChangesInterval = setInterval(
() => clearOldRecentNicknameChanges(pluginData),
30 * SECONDS,
);
pluginData.state.ignoredRoleChanges = new Set(); pluginData.state.ignoredRoleChanges = new Set();
@ -207,16 +204,23 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
pluginData.state.antiraidLevels = GuildAntiraidLevels.getGuildInstance(pluginData.guild.id); pluginData.state.antiraidLevels = GuildAntiraidLevels.getGuildInstance(pluginData.guild.id);
pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id); pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id);
pluginData.state.cachedAntiraidLevel = await pluginData.state.antiraidLevels.get();
},
async afterLoad(pluginData) {
pluginData.state.clearRecentActionsInterval = setInterval(() => clearOldRecentActions(pluginData), 1 * MINUTES);
pluginData.state.clearRecentSpamInterval = setInterval(() => clearOldRecentSpam(pluginData), 1 * SECONDS);
pluginData.state.clearRecentNicknameChangesInterval = setInterval(
() => clearOldRecentNicknameChanges(pluginData),
30 * SECONDS,
);
pluginData.state.onMessageCreateFn = message => runAutomodOnMessage(pluginData, message, false); pluginData.state.onMessageCreateFn = message => runAutomodOnMessage(pluginData, message, false);
pluginData.state.savedMessages.events.on("create", pluginData.state.onMessageCreateFn); pluginData.state.savedMessages.events.on("create", pluginData.state.onMessageCreateFn);
pluginData.state.onMessageUpdateFn = message => runAutomodOnMessage(pluginData, message, true); pluginData.state.onMessageUpdateFn = message => runAutomodOnMessage(pluginData, message, true);
pluginData.state.savedMessages.events.on("update", pluginData.state.onMessageUpdateFn); pluginData.state.savedMessages.events.on("update", pluginData.state.onMessageUpdateFn);
pluginData.state.cachedAntiraidLevel = await pluginData.state.antiraidLevels.get();
},
async onAfterLoad(pluginData) {
const countersPlugin = pluginData.getPlugin(CountersPlugin); const countersPlugin = pluginData.getPlugin(CountersPlugin);
pluginData.state.onCounterTrigger = (name, triggerName, channelId, userId) => { pluginData.state.onCounterTrigger = (name, triggerName, channelId, userId) => {
@ -268,7 +272,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
registerEventListenersFromMap(mutesEvents, pluginData.state.mutesListeners); registerEventListenersFromMap(mutesEvents, pluginData.state.mutesListeners);
}, },
async onBeforeUnload(pluginData) { async beforeUnload(pluginData) {
const countersPlugin = pluginData.getPlugin(CountersPlugin); const countersPlugin = pluginData.getPlugin(CountersPlugin);
countersPlugin.offCounterEvent("trigger", pluginData.state.onCounterTrigger); countersPlugin.offCounterEvent("trigger", pluginData.state.onCounterTrigger);
countersPlugin.offCounterEvent("reverseTrigger", pluginData.state.onCounterReverseTrigger); countersPlugin.offCounterEvent("reverseTrigger", pluginData.state.onCounterReverseTrigger);
@ -278,9 +282,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
const mutesEvents = pluginData.getPlugin(MutesPlugin).getEventEmitter(); const mutesEvents = pluginData.getPlugin(MutesPlugin).getEventEmitter();
unregisterEventListenersFromMap(mutesEvents, pluginData.state.mutesListeners); unregisterEventListenersFromMap(mutesEvents, pluginData.state.mutesListeners);
},
async onUnload(pluginData) {
pluginData.state.queue.clear(); pluginData.state.queue.clear();
discardRegExpRunner(`guild-${pluginData.guild.id}`); discardRegExpRunner(`guild-${pluginData.guild.id}`);

View file

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

View file

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

View file

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

View file

@ -1,11 +1,11 @@
import { guildEventListener } from "knub"; import { typedGuildEventListener } from "knub";
import { AutomodContext, AutomodPluginType } from "../types"; import { AutomodContext, AutomodPluginType } from "../types";
import { runAutomod } from "../functions/runAutomod"; import { runAutomod } from "../functions/runAutomod";
import { RecentActionType } from "../constants"; import { RecentActionType } from "../constants";
export const RunAutomodOnJoinEvt = guildEventListener<AutomodPluginType>()( export const RunAutomodOnJoinEvt = typedGuildEventListener<AutomodPluginType>()({
"guildMemberAdd", event: "guildMemberAdd",
({ pluginData, args: { member } }) => { listener({ pluginData, args: { member } }) {
const context: AutomodContext = { const context: AutomodContext = {
timestamp: Date.now(), timestamp: Date.now(),
user: member.user, user: member.user,
@ -24,4 +24,4 @@ export const RunAutomodOnJoinEvt = guildEventListener<AutomodPluginType>()(
runAutomod(pluginData, context); runAutomod(pluginData, context);
}); });
}, },
); });

View file

@ -1,13 +1,13 @@
import { guildEventListener } from "knub"; import { typedGuildEventListener } from "knub";
import { AutomodContext, AutomodPluginType } from "../types"; import { AutomodContext, AutomodPluginType } from "../types";
import { RecentActionType } from "../constants"; import { RecentActionType } from "../constants";
import { runAutomod } from "../functions/runAutomod"; import { runAutomod } from "../functions/runAutomod";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import diff from "lodash.difference"; import diff from "lodash.difference";
export const RunAutomodOnMemberUpdate = guildEventListener<AutomodPluginType>()( export const RunAutomodOnMemberUpdate = typedGuildEventListener<AutomodPluginType>()({
"guildMemberUpdate", event: "guildMemberUpdate",
({ pluginData, args: { member, oldMember } }) => { listener({ pluginData, args: { member, oldMember } }) {
if (!oldMember) return; if (!oldMember) return;
if (isEqual(oldMember.roles, member.roles)) return; if (isEqual(oldMember.roles, member.roles)) return;
@ -31,4 +31,4 @@ export const RunAutomodOnMemberUpdate = guildEventListener<AutomodPluginType>()(
}); });
} }
}, },
); });

View file

@ -15,7 +15,7 @@ export async function runAutomod(pluginData: GuildPluginData<AutomodPluginType>,
const channel = channelId ? (pluginData.guild.channels.get(channelId) as TextChannel) : null; const channel = channelId ? (pluginData.guild.channels.get(channelId) as TextChannel) : null;
const categoryId = channel?.parentID; const categoryId = channel?.parentID;
const config = pluginData.config.getMatchingConfig({ const config = await pluginData.config.getMatchingConfig({
channelId, channelId,
categoryId, categoryId,
userId, userId,

View file

@ -27,7 +27,8 @@ const defaultOptions = {
}, },
}; };
export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()("bot_control", { export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()({
name: "bot_control",
configSchema: ConfigSchema, configSchema: ConfigSchema,
defaultOptions, defaultOptions,
@ -46,7 +47,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()("bo
EligibleCmd, EligibleCmd,
], ],
onLoad(pluginData) { afterLoad(pluginData) {
pluginData.state.archives = new GuildArchives(0); pluginData.state.archives = new GuildArchives(0);
pluginData.state.allowedGuilds = new AllowedGuilds(); pluginData.state.allowedGuilds = new AllowedGuilds();
pluginData.state.configs = new Configs(); pluginData.state.configs = new Configs();

View file

@ -45,26 +45,36 @@ export const ListDashboardPermsCmd = botControlCmd({
// If we have user, always display which guilds they have permissions in (or only specified guild permissions) // If we have user, always display which guilds they have permissions in (or only specified guild permissions)
if (args.user) { if (args.user) {
const userInfo = `**${args.user.username}#${args.user.discriminator}** (\`${args.user.id}\`)`;
for (const assignment of existingUserAssignment!) { for (const assignment of existingUserAssignment!) {
if (guild != null && assignment.guild_id !== args.guildId) continue; if (guild != null && assignment.guild_id !== args.guildId) continue;
finalMessage += `The user has the following permissions on server \`${ const assignmentGuild = await pluginData.state.allowedGuilds.find(assignment.guild_id);
assignment.guild_id const guildName = assignmentGuild?.name ?? "Unknown";
}\`:\n${assignment.permissions.join("\n")}\n\n`; const guildInfo = `**${guildName}** (\`${assignment.guild_id}\`)`;
finalMessage += `The user ${userInfo} has the following permissions on server ${guildInfo}:`;
finalMessage += `\n${assignment.permissions.join("\n")}\n\n`;
} }
if (finalMessage === "") { if (finalMessage === "") {
sendErrorMessage(pluginData, msg.channel, "The user has no assigned permissions on the specified server."); sendErrorMessage(
pluginData,
msg.channel,
`The user ${userInfo} has no assigned permissions on the specified server.`,
);
return; return;
} }
// Else display all users that have permissions on the specified guild // Else display all users that have permissions on the specified guild
} else if (guild) { } else if (guild) {
const guildInfo = `**${guild.name}** (\`${guild.id}\`)`;
const existingGuildAssignment = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id); const existingGuildAssignment = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id);
if (existingGuildAssignment.length === 0) { if (existingGuildAssignment.length === 0) {
sendErrorMessage(pluginData, msg.channel, "The server has no assigned permissions."); sendErrorMessage(pluginData, msg.channel, `The server ${guildInfo} has no assigned permissions.`);
return; return;
} }
finalMessage += `The server \`${guild.id}\` has the following assigned permissions:\n`; // Double \n for consistency with AddDashboardUserCmd finalMessage += `The server ${guildInfo} has the following assigned permissions:\n`; // Double \n for consistency with AddDashboardUserCmd
for (const assignment of existingGuildAssignment) { for (const assignment of existingGuildAssignment) {
const user = await resolveUser(pluginData.client, assignment.target_id); const user = await resolveUser(pluginData.client, assignment.target_id);
finalMessage += `\n**${user.username}#${user.discriminator}**, \`${ finalMessage += `\n**${user.username}#${user.discriminator}**, \`${

View file

@ -2,7 +2,8 @@ import { botControlCmd } from "../types";
import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { ApiPermissions } from "@shared/apiPermissions"; import { ApiPermissions } from "@shared/apiPermissions";
import { resolveUser } from "../../../utils"; import { resolveUser, UnknownUser } from "../../../utils";
import { User } from "eris";
export const ListDashboardUsersCmd = botControlCmd({ export const ListDashboardUsersCmd = botControlCmd({
trigger: ["list_dashboard_users"], trigger: ["list_dashboard_users"],

View file

@ -16,6 +16,6 @@ export const ReloadGlobalPluginsCmd = botControlCmd({
setActiveReload((message.channel as TextChannel).guild?.id, message.channel.id); setActiveReload((message.channel as TextChannel).guild?.id, message.channel.id);
await message.channel.createMessage("Reloading global plugins..."); await message.channel.createMessage("Reloading global plugins...");
pluginData.getKnubInstance().reloadAllGlobalPlugins(); pluginData.getKnubInstance().reloadGlobalContext();
}, },
}); });

View file

@ -1,6 +1,6 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { tNullable } from "../../utils"; import { tNullable } from "../../utils";
import { BasePluginType, globalCommand, globalEventListener } from "knub"; import { BasePluginType, typedGlobalCommand, typedGlobalEventListener } from "knub";
import { GuildArchives } from "../../data/GuildArchives"; import { GuildArchives } from "../../data/GuildArchives";
import { AllowedGuilds } from "../../data/AllowedGuilds"; import { AllowedGuilds } from "../../data/AllowedGuilds";
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
@ -23,5 +23,5 @@ export interface BotControlPluginType extends BasePluginType {
}; };
} }
export const botControlCmd = globalCommand<BotControlPluginType>(); export const botControlCmd = typedGlobalCommand<BotControlPluginType>();
export const botControlEvt = globalEventListener<BotControlPluginType>(); export const botControlEvt = typedGlobalEventListener<BotControlPluginType>();

View file

@ -28,7 +28,8 @@ const defaultOptions = {
}, },
}; };
export const CasesPlugin = zeppelinGuildPlugin<CasesPluginType>()("cases", { export const CasesPlugin = zeppelinGuildPlugin<CasesPluginType>()({
name: "cases",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Cases", prettyName: "Cases",
@ -73,7 +74,7 @@ export const CasesPlugin = zeppelinGuildPlugin<CasesPluginType>()("cases", {
getCaseSummary: mapToPublicFn(getCaseSummary), getCaseSummary: mapToPublicFn(getCaseSummary),
}, },
onLoad(pluginData) { afterLoad(pluginData) {
pluginData.state.logs = new GuildLogs(pluginData.guild.id); pluginData.state.logs = new GuildLogs(pluginData.guild.id);
pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id); pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id);
pluginData.state.cases = GuildCases.getGuildInstance(pluginData.guild.id); pluginData.state.cases = GuildCases.getGuildInstance(pluginData.guild.id);

View file

@ -46,7 +46,7 @@ export async function createCaseNote(pluginData: GuildPluginData<CasesPluginType
} }
} }
const modConfig = pluginData.config.getForUser(mod); const modConfig = await pluginData.config.getForUser(mod);
if ( if (
args.postInCaseLogOverride === true || args.postInCaseLogOverride === true ||
((!args.automatic || modConfig.log_automatic_actions) && args.postInCaseLogOverride !== false) ((!args.automatic || modConfig.log_automatic_actions) && args.postInCaseLogOverride !== false)

View file

@ -13,6 +13,7 @@ export async function getCaseEmbed(
pluginData: GuildPluginData<CasesPluginType>, pluginData: GuildPluginData<CasesPluginType>,
caseOrCaseId: Case | number, caseOrCaseId: Case | number,
requestMemberId?: string, requestMemberId?: string,
noOriginalCaseLink?: boolean,
): Promise<AdvancedMessageContent> { ): Promise<AdvancedMessageContent> {
const theCase = await pluginData.state.cases.with("notes").find(resolveCaseId(caseOrCaseId)); const theCase = await pluginData.state.cases.with("notes").find(resolveCaseId(caseOrCaseId));
if (!theCase) { if (!theCase) {
@ -98,7 +99,7 @@ export async function getCaseEmbed(
}); });
} }
if (theCase.log_message_id) { if (theCase.log_message_id && noOriginalCaseLink !== false) {
const [channelId, messageId] = theCase.log_message_id.split("-"); const [channelId, messageId] = theCase.log_message_id.split("-");
const link = messageLink(pluginData.guild.id, channelId, messageId); const link = messageLink(pluginData.guild.id, channelId, messageId);
embed.fields.push({ embed.fields.push({

View file

@ -43,7 +43,7 @@ export async function postCaseToCaseLogChannel(
const theCase = await pluginData.state.cases.find(resolveCaseId(caseOrCaseId)); const theCase = await pluginData.state.cases.find(resolveCaseId(caseOrCaseId));
if (!theCase) return null; if (!theCase) return null;
const caseEmbed = await getCaseEmbed(pluginData, caseOrCaseId); const caseEmbed = await getCaseEmbed(pluginData, caseOrCaseId, undefined, true);
if (!caseEmbed) return null; if (!caseEmbed) return null;
if (theCase.log_message_id) { if (theCase.log_message_id) {

View file

@ -1,7 +1,7 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { tDelayString, tPartialDictionary, tNullable } from "../../utils"; import { tDelayString, tPartialDictionary, tNullable } from "../../utils";
import { CaseNameToType, CaseTypes } from "../../data/CaseTypes"; import { CaseNameToType, CaseTypes } from "../../data/CaseTypes";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildCases } from "../../data/GuildCases"; import { GuildCases } from "../../data/GuildCases";
import { GuildArchives } from "../../data/GuildArchives"; import { GuildArchives } from "../../data/GuildArchives";

View file

@ -43,7 +43,8 @@ const defaultOptions: PluginOptions<CensorPluginType> = {
], ],
}; };
export const CensorPlugin = zeppelinGuildPlugin<CensorPluginType>()("censor", { export const CensorPlugin = zeppelinGuildPlugin<CensorPluginType>()({
name: "censor",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Censor", prettyName: "Censor",
@ -57,13 +58,17 @@ export const CensorPlugin = zeppelinGuildPlugin<CensorPluginType>()("censor", {
configSchema: ConfigSchema, configSchema: ConfigSchema,
defaultOptions, defaultOptions,
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.serverLogs = new GuildLogs(guild.id); state.serverLogs = new GuildLogs(guild.id);
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`); state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`);
},
afterLoad(pluginData) {
const { state, guild } = pluginData;
state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg); state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg);
state.savedMessages.events.on("create", state.onMessageCreateFn); state.savedMessages.events.on("create", state.onMessageCreateFn);
@ -72,7 +77,7 @@ export const CensorPlugin = zeppelinGuildPlugin<CensorPluginType>()("censor", {
state.savedMessages.events.on("update", state.onMessageUpdateFn); state.savedMessages.events.on("update", state.onMessageUpdateFn);
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
discardRegExpRunner(`guild-${pluginData.guild.id}`); discardRegExpRunner(`guild-${pluginData.guild.id}`);
pluginData.state.savedMessages.events.off("create", pluginData.state.onMessageCreateFn); pluginData.state.savedMessages.events.off("create", pluginData.state.onMessageCreateFn);

View file

@ -15,7 +15,7 @@ export async function applyFiltersToMsg(
savedMessage: SavedMessage, savedMessage: SavedMessage,
): Promise<boolean> { ): Promise<boolean> {
const member = await resolveMember(pluginData.client, pluginData.guild, savedMessage.user_id); const member = await resolveMember(pluginData.client, pluginData.guild, savedMessage.user_id);
const config = pluginData.config.getMatchingConfig({ member, channelId: savedMessage.channel_id }); const config = await pluginData.config.getMatchingConfig({ member, channelId: savedMessage.channel_id });
let messageContent = savedMessage.data.content || ""; let messageContent = savedMessage.data.content || "";
if (savedMessage.data.attachments) messageContent += " " + JSON.stringify(savedMessage.data.attachments); if (savedMessage.data.attachments) messageContent += " " + JSON.stringify(savedMessage.data.attachments);

View file

@ -4,7 +4,8 @@ import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd";
import * as t from "io-ts"; import * as t from "io-ts";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginType>()("channel_archiver", { export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginType>()({
name: "channel_archiver",
showInDocs: false, showInDocs: false,
dependencies: [TimeAndDatePlugin], dependencies: [TimeAndDatePlugin],
@ -13,9 +14,5 @@ export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginTy
// prettier-ignore // prettier-ignore
commands: [ commands: [
ArchiveChannelCmd, ArchiveChannelCmd,
], ]
onLoad(pluginData) {
const { state, guild } = pluginData;
},
}); });

View file

@ -1,7 +1,7 @@
import { BasePluginType, guildCommand } from "knub"; import { BasePluginType, typedGuildCommand } from "knub";
export interface ChannelArchiverPluginType extends BasePluginType { export interface ChannelArchiverPluginType extends BasePluginType {
state: {}; state: {};
} }
export const channelArchiverCmd = guildCommand<ChannelArchiverPluginType>(); export const channelArchiverCmd = typedGuildCommand<ChannelArchiverPluginType>();

View file

@ -13,7 +13,8 @@ const defaultOptions = {
}, },
}; };
export const CompanionChannelsPlugin = zeppelinGuildPlugin<CompanionChannelsPluginType>()("companion_channels", { export const CompanionChannelsPlugin = zeppelinGuildPlugin<CompanionChannelsPluginType>()({
name: "companion_channels",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Companion channels", prettyName: "Companion channels",
@ -30,7 +31,7 @@ export const CompanionChannelsPlugin = zeppelinGuildPlugin<CompanionChannelsPlug
events: [VoiceChannelJoinEvt, VoiceChannelSwitchEvt, VoiceChannelLeaveEvt], events: [VoiceChannelJoinEvt, VoiceChannelSwitchEvt, VoiceChannelLeaveEvt],
onLoad(pluginData) { beforeLoad(pluginData) {
pluginData.state.errorCooldownManager = new CooldownManager(); pluginData.state.errorCooldownManager = new CooldownManager();
}, },
}); });

View file

@ -2,9 +2,9 @@ import { companionChannelsEvt } from "../types";
import { handleCompanionPermissions } from "../functions/handleCompanionPermissions"; import { handleCompanionPermissions } from "../functions/handleCompanionPermissions";
import { stripObjectToScalars } from "../../../utils"; import { stripObjectToScalars } from "../../../utils";
export const VoiceChannelJoinEvt = companionChannelsEvt( export const VoiceChannelJoinEvt = companionChannelsEvt({
"voiceChannelJoin", event: "voiceChannelJoin",
({ pluginData, args: { member, newChannel } }) => { listener({ pluginData, args: { member, newChannel } }) {
handleCompanionPermissions(pluginData, member.id, newChannel); handleCompanionPermissions(pluginData, member.id, newChannel);
}, },
); });

View file

@ -1,9 +1,9 @@
import { companionChannelsEvt } from "../types"; import { companionChannelsEvt } from "../types";
import { handleCompanionPermissions } from "../functions/handleCompanionPermissions"; import { handleCompanionPermissions } from "../functions/handleCompanionPermissions";
export const VoiceChannelLeaveEvt = companionChannelsEvt( export const VoiceChannelLeaveEvt = companionChannelsEvt({
"voiceChannelLeave", event: "voiceChannelLeave",
({ pluginData, args: { member, oldChannel } }) => { listener({ pluginData, args: { member, oldChannel } }) {
handleCompanionPermissions(pluginData, member.id, null, oldChannel); handleCompanionPermissions(pluginData, member.id, null, oldChannel);
}, },
); });

View file

@ -1,9 +1,9 @@
import { companionChannelsEvt } from "../types"; import { companionChannelsEvt } from "../types";
import { handleCompanionPermissions } from "../functions/handleCompanionPermissions"; import { handleCompanionPermissions } from "../functions/handleCompanionPermissions";
export const VoiceChannelSwitchEvt = companionChannelsEvt( export const VoiceChannelSwitchEvt = companionChannelsEvt({
"voiceChannelSwitch", event: "voiceChannelSwitch",
({ pluginData, args: { member, oldChannel, newChannel } }) => { listener({ pluginData, args: { member, oldChannel, newChannel } }) {
handleCompanionPermissions(pluginData, member.id, newChannel, oldChannel); handleCompanionPermissions(pluginData, member.id, newChannel, oldChannel);
}, },
); });

View file

@ -6,12 +6,12 @@ const defaultCompanionChannelOpts: Partial<TCompanionChannelOpts> = {
enabled: true, enabled: true,
}; };
export function getCompanionChannelOptsForVoiceChannelId( export async function getCompanionChannelOptsForVoiceChannelId(
pluginData: GuildPluginData<CompanionChannelsPluginType>, pluginData: GuildPluginData<CompanionChannelsPluginType>,
userId: string, userId: string,
voiceChannel: VoiceChannel, voiceChannel: VoiceChannel,
): TCompanionChannelOpts[] { ): Promise<TCompanionChannelOpts[]> {
const config = pluginData.config.getMatchingConfig({ userId, channelId: voiceChannel.id }); const config = await pluginData.config.getMatchingConfig({ userId, channelId: voiceChannel.id });
return Object.values(config.entries) return Object.values(config.entries)
.filter( .filter(
opts => opts =>

View file

@ -36,10 +36,10 @@ export async function handleCompanionPermissions(
const permsToSet: Map<string, number> = new Map(); // channelId => permissions const permsToSet: Map<string, number> = new Map(); // channelId => permissions
const oldChannelOptsArr: TCompanionChannelOpts[] = oldChannel const oldChannelOptsArr: TCompanionChannelOpts[] = oldChannel
? getCompanionChannelOptsForVoiceChannelId(pluginData, userId, oldChannel) ? await getCompanionChannelOptsForVoiceChannelId(pluginData, userId, oldChannel)
: []; : [];
const newChannelOptsArr: TCompanionChannelOpts[] = voiceChannel const newChannelOptsArr: TCompanionChannelOpts[] = voiceChannel
? getCompanionChannelOptsForVoiceChannelId(pluginData, userId, voiceChannel) ? await getCompanionChannelOptsForVoiceChannelId(pluginData, userId, voiceChannel)
: []; : [];
for (const oldChannelOpts of oldChannelOptsArr) { for (const oldChannelOpts of oldChannelOptsArr) {

View file

@ -1,6 +1,6 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { tNullable } from "../../utils"; import { tNullable } from "../../utils";
import { BasePluginType, CooldownManager, guildEventListener } from "knub"; import { BasePluginType, CooldownManager, typedGuildEventListener } from "knub";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
@ -29,4 +29,4 @@ export interface CompanionChannelsPluginType extends BasePluginType {
}; };
} }
export const companionChannelsEvt = guildEventListener<CompanionChannelsPluginType>(); export const companionChannelsEvt = typedGuildEventListener<CompanionChannelsPluginType>();

View file

@ -106,7 +106,8 @@ const configPreprocessor: ConfigPreprocessorFn<CountersPluginType> = options =>
* A single trigger can only trigger once per user/channel/in general, depending on how specific the counter is (e.g. a per-user trigger can only trigger once per user). * A single trigger can only trigger once per user/channel/in general, depending on how specific the counter is (e.g. a per-user trigger can only trigger once per user).
* 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. * 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<CountersPluginType>()("counters", { export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()({
name: "counters",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Counters", prettyName: "Counters",
@ -145,7 +146,7 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()("counter
ResetAllCounterValuesCmd, ResetAllCounterValuesCmd,
], ],
async onLoad(pluginData) { async beforeLoad(pluginData) {
pluginData.state.counters = new GuildCounters(pluginData.guild.id); pluginData.state.counters = new GuildCounters(pluginData.guild.id);
pluginData.state.events = new EventEmitter(); pluginData.state.events = new EventEmitter();
pluginData.state.counterTriggersByCounterId = new Map(); pluginData.state.counterTriggersByCounterId = new Map();
@ -189,6 +190,10 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()("counter
// Mark old/unused triggers to be deleted later // Mark old/unused triggers to be deleted later
await pluginData.state.counters.markUnusedTriggersToBeDeleted(activeTriggerIds); await pluginData.state.counters.markUnusedTriggersToBeDeleted(activeTriggerIds);
},
async afterLoad(pluginData) {
const config = pluginData.config.get();
// Start decay timers // Start decay timers
pluginData.state.decayTimers = []; pluginData.state.decayTimers = [];
@ -207,7 +212,7 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()("counter
} }
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
for (const interval of pluginData.state.decayTimers) { for (const interval of pluginData.state.decayTimers) {
clearInterval(interval); clearInterval(interval);
} }

View file

@ -1,4 +1,4 @@
import { guildCommand } from "knub"; import { typedGuildCommand } from "knub";
import { CountersPluginType } from "../types"; import { CountersPluginType } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage } from "../../../pluginUtils"; import { sendErrorMessage } from "../../../pluginUtils";
@ -7,7 +7,7 @@ import { TextChannel, User } from "eris";
import { resolveUser, UnknownUser } from "../../../utils"; import { resolveUser, UnknownUser } from "../../../utils";
import { changeCounterValue } from "../functions/changeCounterValue"; import { changeCounterValue } from "../functions/changeCounterValue";
export const AddCounterCmd = guildCommand<CountersPluginType>()({ export const AddCounterCmd = typedGuildCommand<CountersPluginType>()({
trigger: ["counters add", "counter add", "addcounter"], trigger: ["counters add", "counter add", "addcounter"],
permission: "can_edit", permission: "can_edit",
@ -41,7 +41,7 @@ export const AddCounterCmd = guildCommand<CountersPluginType>()({
], ],
async run({ pluginData, message, args }) { async run({ pluginData, message, args }) {
const config = pluginData.config.getForMessage(message); const config = await pluginData.config.getForMessage(message);
const counter = config.counters[args.counterName]; const counter = config.counters[args.counterName];
const counterId = pluginData.state.counterIds[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName];
if (!counter || !counterId) { if (!counter || !counterId) {

View file

@ -1,17 +1,17 @@
import { guildCommand } from "knub"; import { typedGuildCommand } from "knub";
import { CountersPluginType } from "../types"; import { CountersPluginType } from "../types";
import { sendErrorMessage } from "../../../pluginUtils"; import { sendErrorMessage } from "../../../pluginUtils";
import { trimMultilineString, ucfirst } from "../../../utils"; import { trimMultilineString, ucfirst } from "../../../utils";
import { getGuildPrefix } from "../../../utils/getGuildPrefix"; import { getGuildPrefix } from "../../../utils/getGuildPrefix";
export const CountersListCmd = guildCommand<CountersPluginType>()({ export const CountersListCmd = typedGuildCommand<CountersPluginType>()({
trigger: ["counters list", "counter list", "counters"], trigger: ["counters list", "counter list", "counters"],
permission: "can_view", permission: "can_view",
signature: {}, signature: {},
async run({ pluginData, message, args }) { async run({ pluginData, message, args }) {
const config = pluginData.config.getForMessage(message); const config = await pluginData.config.getForMessage(message);
const countersToShow = Array.from(Object.values(config.counters)).filter(c => c.can_view !== false); const countersToShow = Array.from(Object.values(config.counters)).filter(c => c.can_view !== false);
if (!countersToShow.length) { if (!countersToShow.length) {

View file

@ -1,4 +1,4 @@
import { guildCommand } from "knub"; import { typedGuildCommand } from "knub";
import { CountersPluginType } from "../types"; import { CountersPluginType } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
@ -10,7 +10,7 @@ import { setCounterValue } from "../functions/setCounterValue";
import { resetAllCounterValues } from "../functions/resetAllCounterValues"; import { resetAllCounterValues } from "../functions/resetAllCounterValues";
import { counterIdLock } from "../../../utils/lockNameHelpers"; import { counterIdLock } from "../../../utils/lockNameHelpers";
export const ResetAllCounterValuesCmd = guildCommand<CountersPluginType>()({ export const ResetAllCounterValuesCmd = typedGuildCommand<CountersPluginType>()({
trigger: ["counters reset_all"], trigger: ["counters reset_all"],
permission: "can_reset_all", permission: "can_reset_all",
@ -19,7 +19,7 @@ export const ResetAllCounterValuesCmd = guildCommand<CountersPluginType>()({
}, },
async run({ pluginData, message, args }) { async run({ pluginData, message, args }) {
const config = pluginData.config.getForMessage(message); const config = await pluginData.config.getForMessage(message);
const counter = config.counters[args.counterName]; const counter = config.counters[args.counterName];
const counterId = pluginData.state.counterIds[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName];
if (!counter || !counterId) { if (!counter || !counterId) {

View file

@ -1,4 +1,4 @@
import { guildCommand } from "knub"; import { typedGuildCommand } from "knub";
import { CountersPluginType } from "../types"; import { CountersPluginType } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage } from "../../../pluginUtils"; import { sendErrorMessage } from "../../../pluginUtils";
@ -7,7 +7,7 @@ import { TextChannel } from "eris";
import { resolveUser, UnknownUser } from "../../../utils"; import { resolveUser, UnknownUser } from "../../../utils";
import { setCounterValue } from "../functions/setCounterValue"; import { setCounterValue } from "../functions/setCounterValue";
export const ResetCounterCmd = guildCommand<CountersPluginType>()({ export const ResetCounterCmd = typedGuildCommand<CountersPluginType>()({
trigger: ["counters reset", "counter reset", "resetcounter"], trigger: ["counters reset", "counter reset", "resetcounter"],
permission: "can_edit", permission: "can_edit",
@ -36,7 +36,7 @@ export const ResetCounterCmd = guildCommand<CountersPluginType>()({
], ],
async run({ pluginData, message, args }) { async run({ pluginData, message, args }) {
const config = pluginData.config.getForMessage(message); const config = await pluginData.config.getForMessage(message);
const counter = config.counters[args.counterName]; const counter = config.counters[args.counterName];
const counterId = pluginData.state.counterIds[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName];
if (!counter || !counterId) { if (!counter || !counterId) {

View file

@ -1,4 +1,4 @@
import { guildCommand } from "knub"; import { typedGuildCommand } from "knub";
import { CountersPluginType } from "../types"; import { CountersPluginType } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage } from "../../../pluginUtils"; import { sendErrorMessage } from "../../../pluginUtils";
@ -8,7 +8,7 @@ import { resolveUser, UnknownUser } from "../../../utils";
import { changeCounterValue } from "../functions/changeCounterValue"; import { changeCounterValue } from "../functions/changeCounterValue";
import { setCounterValue } from "../functions/setCounterValue"; import { setCounterValue } from "../functions/setCounterValue";
export const SetCounterCmd = guildCommand<CountersPluginType>()({ export const SetCounterCmd = typedGuildCommand<CountersPluginType>()({
trigger: ["counters set", "counter set", "setcounter"], trigger: ["counters set", "counter set", "setcounter"],
permission: "can_edit", permission: "can_edit",
@ -42,7 +42,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
], ],
async run({ pluginData, message, args }) { async run({ pluginData, message, args }) {
const config = pluginData.config.getForMessage(message); const config = await pluginData.config.getForMessage(message);
const counter = config.counters[args.counterName]; const counter = config.counters[args.counterName];
const counterId = pluginData.state.counterIds[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName];
if (!counter || !counterId) { if (!counter || !counterId) {

View file

@ -1,4 +1,4 @@
import { guildCommand } from "knub"; import { typedGuildCommand } from "knub";
import { CountersPluginType } from "../types"; import { CountersPluginType } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage } from "../../../pluginUtils"; import { sendErrorMessage } from "../../../pluginUtils";
@ -6,7 +6,7 @@ import { resolveChannel, waitForReply } from "knub/dist/helpers";
import { TextChannel, User } from "eris"; import { TextChannel, User } from "eris";
import { resolveUser, UnknownUser } from "../../../utils"; import { resolveUser, UnknownUser } from "../../../utils";
export const ViewCounterCmd = guildCommand<CountersPluginType>()({ export const ViewCounterCmd = typedGuildCommand<CountersPluginType>()({
trigger: ["counters view", "counter view", "viewcounter", "counter"], trigger: ["counters view", "counter view", "viewcounter", "counter"],
permission: "can_view", permission: "can_view",
@ -35,7 +35,7 @@ export const ViewCounterCmd = guildCommand<CountersPluginType>()({
], ],
async run({ pluginData, message, args }) { async run({ pluginData, message, args }) {
const config = pluginData.config.getForMessage(message); const config = await pluginData.config.getForMessage(message);
const counter = config.counters[args.counterName]; const counter = config.counters[args.counterName];
const counterId = pluginData.state.counterIds[args.counterName]; const counterId = pluginData.state.counterIds[args.counterName];
if (!counter || !counterId) { if (!counter || !counterId) {

View file

@ -1,6 +1,6 @@
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { ConfigSchema, CustomEventsPluginType } from "./types"; import { ConfigSchema, CustomEventsPluginType } from "./types";
import { guildCommand, parseSignature } from "knub"; import { typedGuildCommand, parseSignature } from "knub";
import { commandTypes } from "../../commandTypes"; import { commandTypes } from "../../commandTypes";
import { stripObjectToScalars } from "../../utils"; import { stripObjectToScalars } from "../../utils";
import { runEvent } from "./functions/runEvent"; import { runEvent } from "./functions/runEvent";
@ -11,18 +11,19 @@ const defaultOptions = {
}, },
}; };
export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()("custom_events", { export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()({
name: "custom_events",
showInDocs: false, showInDocs: false,
configSchema: ConfigSchema, configSchema: ConfigSchema,
defaultOptions, defaultOptions,
onLoad(pluginData) { afterLoad(pluginData) {
const config = pluginData.config.get(); const config = pluginData.config.get();
for (const [key, event] of Object.entries(config.events)) { for (const [key, event] of Object.entries(config.events)) {
if (event.trigger.type === "command") { if (event.trigger.type === "command") {
const signature = event.trigger.params ? parseSignature(event.trigger.params, commandTypes) : {}; const signature = event.trigger.params ? parseSignature(event.trigger.params, commandTypes) : {};
const eventCommand = guildCommand({ const eventCommand = typedGuildCommand({
trigger: event.trigger.name, trigger: event.trigger.name,
permission: `events.${key}.trigger.can_use`, permission: `events.${key}.trigger.can_use`,
signature, signature,
@ -36,7 +37,7 @@ export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()(
} }
}, },
onUnload() { beforeUnload() {
// TODO: Run clearTriggers() once we actually have something there // TODO: Run clearTriggers() once we actually have something there
}, },
}); });

View file

@ -1,5 +1,5 @@
import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint";
import { BasePluginType, globalEventListener, GlobalPluginData } from "knub"; import { BasePluginType, typedGlobalEventListener, GlobalPluginData } from "knub";
import * as t from "io-ts"; import * as t from "io-ts";
import { AllowedGuilds } from "../../data/AllowedGuilds"; import { AllowedGuilds } from "../../data/AllowedGuilds";
import { Guild } from "eris"; import { Guild } from "eris";
@ -14,25 +14,32 @@ interface GuildAccessMonitorPluginType extends BasePluginType {
async function checkGuild(pluginData: GlobalPluginData<GuildAccessMonitorPluginType>, guild: Guild) { async function checkGuild(pluginData: GlobalPluginData<GuildAccessMonitorPluginType>, guild: Guild) {
if (!(await pluginData.state.allowedGuilds.isAllowed(guild.id))) { if (!(await pluginData.state.allowedGuilds.isAllowed(guild.id))) {
console.log(`Non-allowed server ${guild.name} (${guild.id}), leaving`); console.log(`Non-allowed server ${guild.name} (${guild.id}), leaving`);
guild.leave(); console.log("[Temporarily not leaving automatically]");
// guild.leave();
} }
} }
/** /**
* Global plugin to monitor if Zeppelin is invited to a non-whitelisted server, and leave it * Global plugin to monitor if Zeppelin is invited to a non-whitelisted server, and leave it
*/ */
export const GuildAccessMonitorPlugin = zeppelinGlobalPlugin<GuildAccessMonitorPluginType>()("guild_access_monitor", { export const GuildAccessMonitorPlugin = zeppelinGlobalPlugin<GuildAccessMonitorPluginType>()({
name: "guild_access_monitor",
configSchema: t.type({}), configSchema: t.type({}),
events: [ events: [
globalEventListener<GuildAccessMonitorPluginType>()("guildAvailable", ({ pluginData, args: { guild } }) => { typedGlobalEventListener<GuildAccessMonitorPluginType>()({
checkGuild(pluginData, guild); event: "guildAvailable",
listener({ pluginData, args: { guild } }) {
checkGuild(pluginData, guild);
},
}), }),
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
pluginData.state.allowedGuilds = new AllowedGuilds(); pluginData.state.allowedGuilds = new AllowedGuilds();
},
afterLoad(pluginData) {
for (const guild of pluginData.client.guilds.values()) { for (const guild of pluginData.client.guilds.values()) {
checkGuild(pluginData, guild); checkGuild(pluginData, guild);
} }

View file

@ -4,23 +4,23 @@ import { Configs } from "../../data/Configs";
import { reloadChangedGuilds } from "./functions/reloadChangedGuilds"; import { reloadChangedGuilds } from "./functions/reloadChangedGuilds";
import * as t from "io-ts"; import * as t from "io-ts";
export const GuildConfigReloaderPlugin = zeppelinGlobalPlugin<GuildConfigReloaderPluginType>()( export const GuildConfigReloaderPlugin = zeppelinGlobalPlugin<GuildConfigReloaderPluginType>()({
"guild_config_reloader", name: "guild_config_reloader",
{ showInDocs: false,
showInDocs: false,
configSchema: t.type({}), configSchema: t.type({}),
async onLoad(pluginData) { async beforeLoad(pluginData) {
pluginData.state.guildConfigs = new Configs(); pluginData.state.guildConfigs = new Configs();
pluginData.state.highestConfigId = await pluginData.state.guildConfigs.getHighestId(); pluginData.state.highestConfigId = await pluginData.state.guildConfigs.getHighestId();
reloadChangedGuilds(pluginData);
},
onUnload(pluginData) {
clearTimeout(pluginData.state.nextCheckTimeout);
pluginData.state.unloaded = true;
},
}, },
);
afterLoad(pluginData) {
reloadChangedGuilds(pluginData);
},
beforeUnload(pluginData) {
clearTimeout(pluginData.state.nextCheckTimeout);
pluginData.state.unloaded = true;
},
});

View file

@ -5,18 +5,23 @@ import { GuildInfoSaverPluginType } from "./types";
import { MINUTES } from "../../utils"; import { MINUTES } from "../../utils";
import * as t from "io-ts"; import * as t from "io-ts";
export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType>()("guild_info_saver", { export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType>()({
name: "guild_info_saver",
showInDocs: false, showInDocs: false,
configSchema: t.type({}), configSchema: t.type({}),
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; pluginData.state.allowedGuilds = new AllowedGuilds();
},
state.allowedGuilds = new AllowedGuilds();
afterLoad(pluginData) {
updateGuildInfo(pluginData); updateGuildInfo(pluginData);
state.updateInterval = setInterval(() => updateGuildInfo(pluginData), 60 * MINUTES); pluginData.state.updateInterval = setInterval(() => updateGuildInfo(pluginData), 60 * MINUTES);
},
beforeUnload(pluginData) {
clearInterval(pluginData.state.updateInterval);
}, },
}); });

View file

@ -28,7 +28,8 @@ const defaultOptions: PluginOptions<LocateUserPluginType> = {
], ],
}; };
export const LocateUserPlugin = zeppelinGuildPlugin<LocateUserPluginType>()("locate_user", { export const LocateUserPlugin = zeppelinGuildPlugin<LocateUserPluginType>()({
name: "locate_user",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Locate user", prettyName: "Locate user",
@ -58,19 +59,21 @@ export const LocateUserPlugin = zeppelinGuildPlugin<LocateUserPluginType>()("loc
GuildBanRemoveAlertsEvt GuildBanRemoveAlertsEvt
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.alerts = GuildVCAlerts.getGuildInstance(guild.id); state.alerts = GuildVCAlerts.getGuildInstance(guild.id);
state.outdatedAlertsTimeout = null; state.outdatedAlertsTimeout = null;
state.usersWithAlerts = []; state.usersWithAlerts = [];
state.unloaded = false; state.unloaded = false;
},
afterLoad(pluginData) {
outdatedAlertsLoop(pluginData); outdatedAlertsLoop(pluginData);
fillActiveAlertsList(pluginData); fillActiveAlertsList(pluginData);
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
clearTimeout(pluginData.state.outdatedAlertsTimeout as Timeout); clearTimeout(pluginData.state.outdatedAlertsTimeout as Timeout);
pluginData.state.unloaded = true; pluginData.state.unloaded = true;
}, },

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildVCAlerts } from "../../data/GuildVCAlerts"; import { GuildVCAlerts } from "../../data/GuildVCAlerts";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
@ -19,5 +19,5 @@ export interface LocateUserPluginType extends BasePluginType {
}; };
} }
export const locateUserCmd = guildCommand<LocateUserPluginType>(); export const locateUserCmd = typedGuildCommand<LocateUserPluginType>();
export const locateUserEvt = guildEventListener<LocateUserPluginType>(); export const locateUserEvt = typedGuildEventListener<LocateUserPluginType>();

View file

@ -47,7 +47,8 @@ const defaultOptions: PluginOptions<LogsPluginType> = {
], ],
}; };
export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()("logs", { export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()({
name: "logs",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Logs", prettyName: "Logs",
@ -84,7 +85,7 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()("logs", {
}, },
}, },
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.guildLogs = new GuildLogs(guild.id); state.guildLogs = new GuildLogs(guild.id);
@ -92,11 +93,17 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()("logs", {
state.archives = GuildArchives.getGuildInstance(guild.id); state.archives = GuildArchives.getGuildInstance(guild.id);
state.cases = GuildCases.getGuildInstance(guild.id); state.cases = GuildCases.getGuildInstance(guild.id);
state.batches = new Map();
state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`);
},
afterLoad(pluginData) {
const { state, guild } = pluginData;
state.logListener = ({ type, data }) => log(pluginData, type, data); state.logListener = ({ type, data }) => log(pluginData, type, data);
state.guildLogs.on("log", state.logListener); state.guildLogs.on("log", state.logListener);
state.batches = new Map();
state.onMessageDeleteFn = msg => onMessageDelete(pluginData, msg); state.onMessageDeleteFn = msg => onMessageDelete(pluginData, msg);
state.savedMessages.events.on("delete", state.onMessageDeleteFn); state.savedMessages.events.on("delete", state.onMessageDeleteFn);
@ -106,7 +113,6 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()("logs", {
state.onMessageUpdateFn = (newMsg, oldMsg) => onMessageUpdate(pluginData, newMsg, oldMsg); state.onMessageUpdateFn = (newMsg, oldMsg) => onMessageUpdate(pluginData, newMsg, oldMsg);
state.savedMessages.events.on("update", state.onMessageUpdateFn); state.savedMessages.events.on("update", state.onMessageUpdateFn);
state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`);
state.regexRunnerRepeatedTimeoutListener = (regexSource, timeoutMs, failedTimes) => { state.regexRunnerRepeatedTimeoutListener = (regexSource, timeoutMs, failedTimes) => {
logger.warn(`Disabled heavy regex temporarily: ${regexSource}`); logger.warn(`Disabled heavy regex temporarily: ${regexSource}`);
log(pluginData, LogType.BOT_ALERT, { log(pluginData, LogType.BOT_ALERT, {
@ -122,7 +128,7 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()("logs", {
state.regexRunner.on("repeatedTimeout", state.regexRunnerRepeatedTimeoutListener); state.regexRunner.on("repeatedTimeout", state.regexRunnerRepeatedTimeoutListener);
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
pluginData.state.guildLogs.removeListener("log", pluginData.state.logListener); pluginData.state.guildLogs.removeListener("log", pluginData.state.logListener);
pluginData.state.savedMessages.events.off("delete", pluginData.state.onMessageDeleteFn); pluginData.state.savedMessages.events.off("delete", pluginData.state.onMessageDeleteFn);

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildEventListener } from "knub"; import { BasePluginType, typedGuildEventListener } from "knub";
import { TRegex } from "../../validatorUtils"; import { TRegex } from "../../validatorUtils";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
@ -21,6 +21,7 @@ const LogChannel = t.partial({
excluded_channels: t.array(t.string), excluded_channels: t.array(t.string),
excluded_categories: t.array(t.string), excluded_categories: t.array(t.string),
exclude_bots: t.boolean, exclude_bots: t.boolean,
excluded_roles: t.array(t.string),
format: tNullable(tLogFormats), format: tNullable(tLogFormats),
timestamp_format: t.string, timestamp_format: t.string,
include_embed_timestamp: t.boolean, include_embed_timestamp: t.boolean,
@ -71,4 +72,4 @@ export interface LogsPluginType extends BasePluginType {
}; };
} }
export const logsEvt = guildEventListener<LogsPluginType>(); export const logsEvt = typedGuildEventListener<LogsPluginType>();

View file

@ -59,7 +59,7 @@ export async function getLogMessage(
member = await resolveMember(pluginData.client, pluginData.guild, user.id); member = await resolveMember(pluginData.client, pluginData.guild, user.id);
} }
const memberConfig = pluginData.config.getMatchingConfig({ member, userId: user.id }) || ({} as any); const memberConfig = (await pluginData.config.getMatchingConfig({ member, userId: user.id })) || ({} as any);
// Revert to old behavior (verbose name w/o ping if allow_user_mentions is enabled (for whatever reason)) // Revert to old behavior (verbose name w/o ping if allow_user_mentions is enabled (for whatever reason))
if (config.allow_user_mentions) { if (config.allow_user_mentions) {

View file

@ -2,11 +2,16 @@ import { GuildPluginData } from "knub";
import { LogsPluginType, TLogChannelMap } from "../types"; import { LogsPluginType, TLogChannelMap } from "../types";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { TextChannel } from "eris"; import { TextChannel } from "eris";
import { createChunkedMessage, noop } from "../../../utils"; import { createChunkedMessage, get, noop } from "../../../utils";
import { getLogMessage } from "./getLogMessage"; import { getLogMessage } from "./getLogMessage";
import { allowTimeout } from "../../../RegExpRunner"; import { allowTimeout } from "../../../RegExpRunner";
const excludedUserProps = ["user", "member", "mod"]; const excludedUserProps = ["user", "member", "mod"];
const excludedRoleProps = ["message.member.roles", "member.roles"];
function isRoleArray(value: any): value is string[] {
return Array.isArray(value);
}
export async function log(pluginData: GuildPluginData<LogsPluginType>, type: LogType, data: any) { export async function log(pluginData: GuildPluginData<LogsPluginType>, type: LogType, data: any) {
const logChannels: TLogChannelMap = pluginData.config.get().channels; const logChannels: TLogChannelMap = pluginData.config.get().channels;
@ -36,6 +41,21 @@ export async function log(pluginData: GuildPluginData<LogsPluginType>, type: Log
} }
} }
if (opts.excluded_roles) {
for (const prop of excludedRoleProps) {
const roles = get(data, prop);
if (!isRoleArray(roles)) {
continue;
}
for (const role of roles) {
if (opts.excluded_roles.includes(role)) {
continue logChannelLoop;
}
}
}
}
// If this entry is from an excluded channel, skip it // If this entry is from an excluded channel, skip it
if (opts.excluded_channels) { if (opts.excluded_channels) {
if ( if (

View file

@ -20,7 +20,8 @@ const defaultOptions: PluginOptions<MessageSaverPluginType> = {
], ],
}; };
export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()("message_saver", { export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()({
name: "message_saver",
showInDocs: false, showInDocs: false,
configSchema: ConfigSchema, configSchema: ConfigSchema,
@ -40,7 +41,7 @@ export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()(
MessageDeleteBulkEvt, MessageDeleteBulkEvt,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
}, },

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
export const ConfigSchema = t.type({ export const ConfigSchema = t.type({
@ -14,5 +14,5 @@ export interface MessageSaverPluginType extends BasePluginType {
}; };
} }
export const messageSaverCmd = guildCommand<MessageSaverPluginType>(); export const messageSaverCmd = typedGuildCommand<MessageSaverPluginType>();
export const messageSaverEvt = guildEventListener<MessageSaverPluginType>(); export const messageSaverEvt = typedGuildEventListener<MessageSaverPluginType>();

View file

@ -34,7 +34,7 @@ import { Member, Message } from "eris";
import { kickMember } from "./functions/kickMember"; import { kickMember } from "./functions/kickMember";
import { banUserId } from "./functions/banUserId"; import { banUserId } from "./functions/banUserId";
import { MassmuteCmd } from "./commands/MassmuteCmd"; import { MassmuteCmd } from "./commands/MassmuteCmd";
import { trimPluginDescription } from "../../utils"; import { MINUTES, trimPluginDescription } from "../../utils";
import { DeleteCaseCmd } from "./commands/DeleteCaseCmd"; import { DeleteCaseCmd } from "./commands/DeleteCaseCmd";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
import { GuildTempbans } from "../../data/GuildTempbans"; import { GuildTempbans } from "../../data/GuildTempbans";
@ -44,6 +44,7 @@ import { mapToPublicFn } from "../../pluginUtils";
import { onModActionsEvent } from "./functions/onModActionsEvent"; import { onModActionsEvent } from "./functions/onModActionsEvent";
import { offModActionsEvent } from "./functions/offModActionsEvent"; import { offModActionsEvent } from "./functions/offModActionsEvent";
import { updateCase } from "./functions/updateCase"; import { updateCase } from "./functions/updateCase";
import { Queue } from "../../Queue";
const defaultOptions = { const defaultOptions = {
config: { config: {
@ -109,7 +110,8 @@ const defaultOptions = {
], ],
}; };
export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()("mod_actions", { export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()({
name: "mod_actions",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Mod actions", prettyName: "Mod actions",
@ -186,7 +188,7 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()("mod
}, },
}, },
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.mutes = GuildMutes.getGuildInstance(guild.id); state.mutes = GuildMutes.getGuildInstance(guild.id);
@ -197,13 +199,18 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()("mod
state.unloaded = false; state.unloaded = false;
state.outdatedTempbansTimeout = null; state.outdatedTempbansTimeout = null;
state.ignoredEvents = []; state.ignoredEvents = [];
// Massbans can take a while depending on rate limits,
// so we're giving each massban 15 minutes to complete before launching the next massban
state.massbanQueue = new Queue(15 * MINUTES);
state.events = new EventEmitter(); state.events = new EventEmitter();
},
afterLoad(pluginData) {
outdatedTempbansLoop(pluginData); outdatedTempbansLoop(pluginData);
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
pluginData.state.unloaded = true; pluginData.state.unloaded = true;
pluginData.state.events.removeAllListeners(); pluginData.state.events.removeAllListeners();
}, },

View file

@ -44,7 +44,7 @@ export const AddCaseCmd = modActionsCmd({
// The moderator who did the action is the message author or, if used, the specified -mod // The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.member; let mod = msg.member;
if (args.mod) { if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) {
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
return; return;
} }

View file

@ -54,7 +54,7 @@ export const BanCmd = modActionsCmd({
// The moderator who did the action is the message author or, if used, the specified -mod // The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.member; let mod = msg.member;
if (args.mod) { if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) {
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
return; return;
} }
@ -160,7 +160,8 @@ export const BanCmd = modActionsCmd({
return; return;
} }
const deleteMessageDays = args["delete-days"] ?? pluginData.config.getForMessage(msg).ban_delete_message_days; const deleteMessageDays =
args["delete-days"] ?? (await pluginData.config.getForMessage(msg)).ban_delete_message_days;
const banResult = await banUserId( const banResult = await banUserId(
pluginData, pluginData,
user.id, user.id,

View file

@ -53,7 +53,7 @@ export const ForcebanCmd = modActionsCmd({
// The moderator who did the action is the message author or, if used, the specified -mod // The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.member; let mod = msg.member;
if (args.mod) { if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) {
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
return; return;
} }

View file

@ -1,7 +1,7 @@
import { modActionsCmd, IgnoredEventType } from "../types"; import { modActionsCmd, IgnoredEventType } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils"; import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils";
import { resolveUser, resolveMember, stripObjectToScalars } from "../../../utils"; import { resolveUser, resolveMember, stripObjectToScalars, noop, MINUTES } from "../../../utils";
import { isBanned } from "../functions/isBanned"; import { isBanned } from "../functions/isBanned";
import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs"; import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments"; import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
@ -12,6 +12,9 @@ import { waitForReply } from "knub/dist/helpers";
import { ignoreEvent } from "../functions/ignoreEvent"; import { ignoreEvent } from "../functions/ignoreEvent";
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin"; import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { performance } from "perf_hooks";
import { humanizeDurationShort } from "../../../humanizeDurationShort";
import { load } from "js-yaml";
export const MassbanCmd = modActionsCmd({ export const MassbanCmd = modActionsCmd({
trigger: "massban", trigger: "massban",
@ -50,62 +53,103 @@ export const MassbanCmd = modActionsCmd({
} }
} }
// Ignore automatic ban cases and logs for these users
// We'll create our own cases below and post a single "mass banned" log instead
args.userIds.forEach(userId => {
// Use longer timeouts since this can take a while
ignoreEvent(pluginData, IgnoredEventType.Ban, userId, 120 * 1000);
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, userId, 120 * 1000);
});
// Show a loading indicator since this can take a while // Show a loading indicator since this can take a while
const loadingMsg = await msg.channel.createMessage("Banning..."); const maxWaitTime = pluginData.state.massbanQueue.timeout * pluginData.state.massbanQueue.length;
const maxWaitTimeFormatted = humanizeDurationShort(maxWaitTime, { round: true });
const initialLoadingText =
pluginData.state.massbanQueue.length === 0
? "Banning..."
: `Massban queued. Waiting for previous massban to finish (max wait ${maxWaitTimeFormatted}).`;
const loadingMsg = await msg.channel.createMessage(initialLoadingText);
// Ban each user and count failed bans (if any) const waitTimeStart = performance.now();
const failedBans: string[] = []; const waitingInterval = setInterval(() => {
const casesPlugin = pluginData.getPlugin(CasesPlugin); const waitTime = humanizeDurationShort(performance.now() - waitTimeStart, { round: true });
for (const userId of args.userIds) { loadingMsg
try { .edit(`Massban queued. Still waiting for previous massban to finish (waited ${waitTime}).`)
await pluginData.guild.banMember(userId, 1, banReason != null ? encodeURIComponent(banReason) : undefined); .catch(() => clearInterval(waitingInterval));
}, 1 * MINUTES);
await casesPlugin.createCase({ pluginData.state.massbanQueue.add(async () => {
userId, clearInterval(waitingInterval);
modId: msg.author.id,
type: CaseTypes.Ban, if (pluginData.state.unloaded) {
reason: `Mass ban: ${banReason}`, void loadingMsg.delete().catch(noop);
postInCaseLogOverride: false, return;
}
void loadingMsg.edit("Banning...").catch(noop);
// Ban each user and count failed bans (if any)
const startTime = performance.now();
const failedBans: string[] = [];
const casesPlugin = pluginData.getPlugin(CasesPlugin);
for (const [i, userId] of args.userIds.entries()) {
if (pluginData.state.unloaded) {
break;
}
try {
// Ignore automatic ban cases and logs
// We create our own cases below and post a single "mass banned" log instead
ignoreEvent(pluginData, IgnoredEventType.Ban, userId, 120 * 1000);
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, userId, 120 * 1000);
await pluginData.guild.banMember(userId, 1, banReason != null ? encodeURIComponent(banReason) : undefined);
await casesPlugin.createCase({
userId,
modId: msg.author.id,
type: CaseTypes.Ban,
reason: `Mass ban: ${banReason}`,
postInCaseLogOverride: false,
});
pluginData.state.events.emit("ban", userId, banReason);
} catch {
failedBans.push(userId);
}
// Send a status update every 10 bans
if ((i + 1) % 10 === 0) {
loadingMsg.edit(`Banning... ${i + 1}/${args.userIds.length}`).catch(noop);
}
}
const totalTime = performance.now() - startTime;
const formattedTimeTaken = humanizeDurationShort(totalTime, { round: true });
// Clear loading indicator
loadingMsg.delete().catch(noop);
const successfulBanCount = args.userIds.length - failedBans.length;
if (successfulBanCount === 0) {
// All bans failed - don't create a log entry and notify the user
sendErrorMessage(pluginData, msg.channel, "All bans failed. Make sure the IDs are valid.");
} else {
// Some or all bans were successful. Create a log entry for the mass ban and notify the user.
pluginData.state.serverLogs.log(LogType.MASSBAN, {
mod: stripObjectToScalars(msg.author),
count: successfulBanCount,
reason: banReason,
}); });
pluginData.state.events.emit("ban", userId, banReason); if (failedBans.length) {
} catch { sendSuccessMessage(
failedBans.push(userId); pluginData,
msg.channel,
`Banned ${successfulBanCount} users in ${formattedTimeTaken}, ${
failedBans.length
} failed: ${failedBans.join(" ")}`,
);
} else {
sendSuccessMessage(
pluginData,
msg.channel,
`Banned ${successfulBanCount} users successfully in ${formattedTimeTaken}`,
);
}
} }
} });
// Clear loading indicator
loadingMsg.delete();
const successfulBanCount = args.userIds.length - failedBans.length;
if (successfulBanCount === 0) {
// All bans failed - don't create a log entry and notify the user
sendErrorMessage(pluginData, msg.channel, "All bans failed. Make sure the IDs are valid.");
} else {
// Some or all bans were successful. Create a log entry for the mass ban and notify the user.
pluginData.state.serverLogs.log(LogType.MASSBAN, {
mod: stripObjectToScalars(msg.author),
count: successfulBanCount,
reason: banReason,
});
if (failedBans.length) {
sendSuccessMessage(
pluginData,
msg.channel,
`Banned ${successfulBanCount} users, ${failedBans.length} failed: ${failedBans.join(" ")}`,
);
} else {
sendSuccessMessage(pluginData, msg.channel, `Banned ${successfulBanCount} users successfully`);
}
}
}, },
}); });

View file

@ -36,7 +36,7 @@ export const UnbanCmd = modActionsCmd({
// The moderator who did the action is the message author or, if used, the specified -mod // The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.member; let mod = msg.member;
if (args.mod) { if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) {
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
return; return;
} }

View file

@ -56,7 +56,7 @@ export const WarnCmd = modActionsCmd({
// The moderator who did the action is the message author or, if used, the specified -mod // The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.member; let mod = msg.member;
if (args.mod) { if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) {
msg.channel.createMessage(errorMessage("You don't have permission to use -mod")); msg.channel.createMessage(errorMessage("You don't have permission to use -mod"));
return; return;
} }

View file

@ -13,9 +13,9 @@ import { Case } from "../../../data/entities/Case";
* Create a BAN case automatically when a user is banned manually. * Create a BAN case automatically when a user is banned manually.
* Attempts to find the ban's details in the audit log. * Attempts to find the ban's details in the audit log.
*/ */
export const CreateBanCaseOnManualBanEvt = modActionsEvt( export const CreateBanCaseOnManualBanEvt = modActionsEvt({
"guildBanAdd", event: "guildBanAdd",
async ({ pluginData, args: { guild, user } }) => { async listener({ pluginData, args: { guild, user } }) {
if (isEventIgnored(pluginData, IgnoredEventType.Ban, user.id)) { if (isEventIgnored(pluginData, IgnoredEventType.Ban, user.id)) {
clearIgnoredEvents(pluginData, IgnoredEventType.Ban, user.id); clearIgnoredEvents(pluginData, IgnoredEventType.Ban, user.id);
return; return;
@ -39,7 +39,7 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt(
mod = await resolveUser(pluginData.client, modId); mod = await resolveUser(pluginData.client, modId);
const config = mod instanceof UnknownUser ? pluginData.config.get() : pluginData.config.getForUser(mod); const config = mod instanceof UnknownUser ? pluginData.config.get() : await pluginData.config.getForUser(mod);
if (config.create_cases_for_manual_actions) { if (config.create_cases_for_manual_actions) {
reason = relevantAuditLogEntry.reason || ""; reason = relevantAuditLogEntry.reason || "";
@ -72,4 +72,4 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt(
pluginData.state.events.emit("ban", user.id, reason); pluginData.state.events.emit("ban", user.id, reason);
}, },
); });

View file

@ -14,9 +14,9 @@ import { Case } from "../../../data/entities/Case";
* Create a KICK case automatically when a user is kicked manually. * Create a KICK case automatically when a user is kicked manually.
* Attempts to find the kick's details in the audit log. * Attempts to find the kick's details in the audit log.
*/ */
export const CreateKickCaseOnManualKickEvt = modActionsEvt( export const CreateKickCaseOnManualKickEvt = modActionsEvt({
"guildMemberRemove", event: "guildMemberRemove",
async ({ pluginData, args: { member } }) => { async listener({ pluginData, args: { member } }) {
if (isEventIgnored(pluginData, IgnoredEventType.Kick, member.id)) { if (isEventIgnored(pluginData, IgnoredEventType.Kick, member.id)) {
clearIgnoredEvents(pluginData, IgnoredEventType.Kick, member.id); clearIgnoredEvents(pluginData, IgnoredEventType.Kick, member.id);
return; return;
@ -42,7 +42,7 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt(
} else { } else {
mod = await resolveUser(pluginData.client, kickAuditLogEntry.user.id); mod = await resolveUser(pluginData.client, kickAuditLogEntry.user.id);
const config = mod instanceof UnknownUser ? pluginData.config.get() : pluginData.config.getForUser(mod); const config = mod instanceof UnknownUser ? pluginData.config.get() : await pluginData.config.getForUser(mod);
if (config.create_cases_for_manual_actions) { if (config.create_cases_for_manual_actions) {
const casesPlugin = pluginData.getPlugin(CasesPlugin); const casesPlugin = pluginData.getPlugin(CasesPlugin);
@ -67,4 +67,4 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt(
pluginData.state.events.emit("kick", member.id, kickAuditLogEntry.reason || undefined); pluginData.state.events.emit("kick", member.id, kickAuditLogEntry.reason || undefined);
} }
}, },
); });

View file

@ -13,9 +13,9 @@ import { Case } from "../../../data/entities/Case";
* Create an UNBAN case automatically when a user is unbanned manually. * Create an UNBAN case automatically when a user is unbanned manually.
* Attempts to find the unban's details in the audit log. * Attempts to find the unban's details in the audit log.
*/ */
export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt( export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt({
"guildBanRemove", event: "guildBanRemove",
async ({ pluginData, args: { guild, user } }) => { async listener({ pluginData, args: { guild, user } }) {
if (isEventIgnored(pluginData, IgnoredEventType.Unban, user.id)) { if (isEventIgnored(pluginData, IgnoredEventType.Unban, user.id)) {
clearIgnoredEvents(pluginData, IgnoredEventType.Unban, user.id); clearIgnoredEvents(pluginData, IgnoredEventType.Unban, user.id);
return; return;
@ -38,7 +38,7 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt(
mod = await resolveUser(pluginData.client, modId); mod = await resolveUser(pluginData.client, modId);
const config = mod instanceof UnknownUser ? pluginData.config.get() : pluginData.config.getForUser(mod); const config = mod instanceof UnknownUser ? pluginData.config.get() : await pluginData.config.getForUser(mod);
if (config.create_cases_for_manual_actions) { if (config.create_cases_for_manual_actions) {
createdCase = await casesPlugin.createCase({ createdCase = await casesPlugin.createCase({
@ -69,4 +69,4 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt(
pluginData.state.events.emit("unban", user.id); pluginData.state.events.emit("unban", user.id);
}, },
); });

View file

@ -8,9 +8,9 @@ import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
/** /**
* Show an alert if a member with prior notes joins the server * Show an alert if a member with prior notes joins the server
*/ */
export const PostAlertOnMemberJoinEvt = modActionsEvt( export const PostAlertOnMemberJoinEvt = modActionsEvt({
"guildMemberAdd", event: "guildMemberAdd",
async ({ pluginData, args: { guild, member } }) => { async listener({ pluginData, args: { guild, member } }) {
const config = pluginData.config.get(); const config = pluginData.config.get();
if (!config.alert_on_rejoin) return; if (!config.alert_on_rejoin) return;
@ -51,4 +51,4 @@ export const PostAlertOnMemberJoinEvt = modActionsEvt(
); );
} }
}, },
); });

View file

@ -51,7 +51,7 @@ export async function actualKickMemberCmd(
// The moderator who did the action is the message author or, if used, the specified -mod // The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.member; let mod = msg.member;
if (args.mod) { if (args.mod) {
if (!hasPermission(pluginData.config.getForMessage(msg), "can_act_as_other")) { if (!(await hasPermission(await pluginData.config.getForMessage(msg), "can_act_as_other"))) {
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
return; return;
} }

View file

@ -1,4 +1,4 @@
import { Member, Message, TextChannel, User } from "eris"; import { GuildTextableChannel, Member, Message, TextChannel, User } from "eris";
import { asSingleLine, isDiscordRESTError, UnknownUser } from "../../../utils"; import { asSingleLine, isDiscordRESTError, UnknownUser } from "../../../utils";
import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
@ -10,7 +10,6 @@ import { MutesPlugin } from "../../Mutes/MutesPlugin";
import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs"; import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { logger } from "../../../logger"; import { logger } from "../../../logger";
import { GuildMessage } from "knub/dist/helpers";
/** /**
* The actual function run by both !mute and !forcemute. * The actual function run by both !mute and !forcemute.
@ -19,7 +18,7 @@ import { GuildMessage } from "knub/dist/helpers";
export async function actualMuteUserCmd( export async function actualMuteUserCmd(
pluginData: GuildPluginData<ModActionsPluginType>, pluginData: GuildPluginData<ModActionsPluginType>,
user: User | UnknownUser, user: User | UnknownUser,
msg: GuildMessage, msg: Message<GuildTextableChannel>,
args: { time?: number; reason?: string; mod: Member; notify?: string; "notify-channel"?: TextChannel }, args: { time?: number; reason?: string; mod: Member; notify?: string; "notify-channel"?: TextChannel },
) { ) {
// The moderator who did the action is the message author or, if used, the specified -mod // The moderator who did the action is the message author or, if used, the specified -mod
@ -27,7 +26,7 @@ export async function actualMuteUserCmd(
let pp: User | null = null; let pp: User | null = null;
if (args.mod) { if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) {
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
return; return;
} }

View file

@ -18,7 +18,7 @@ export async function actualUnmuteCmd(
let pp: User | null = null; let pp: User | null = null;
if (args.mod) { if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) { if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) {
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod"); sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
return; return;
} }

View file

@ -1,6 +1,6 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { tNullable, UserNotificationMethod, UserNotificationResult } from "../../utils"; import { tNullable, UserNotificationMethod, UserNotificationResult } from "../../utils";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildMutes } from "../../data/GuildMutes"; import { GuildMutes } from "../../data/GuildMutes";
import { GuildCases } from "../../data/GuildCases"; import { GuildCases } from "../../data/GuildCases";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
@ -10,6 +10,7 @@ import { TextChannel } from "eris";
import { GuildTempbans } from "../../data/GuildTempbans"; import { GuildTempbans } from "../../data/GuildTempbans";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { Queue } from "../../Queue";
export const ConfigSchema = t.type({ export const ConfigSchema = t.type({
dm_on_warn: t.boolean, dm_on_warn: t.boolean,
@ -72,6 +73,7 @@ export interface ModActionsPluginType extends BasePluginType {
unloaded: boolean; unloaded: boolean;
outdatedTempbansTimeout: Timeout | null; outdatedTempbansTimeout: Timeout | null;
ignoredEvents: IIgnoredEvent[]; ignoredEvents: IIgnoredEvent[];
massbanQueue: Queue;
events: ModActionsEventEmitter; events: ModActionsEventEmitter;
}; };
@ -146,5 +148,5 @@ export interface BanOptions {
export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" | "unban"; export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" | "unban";
export const modActionsCmd = guildCommand<ModActionsPluginType>(); export const modActionsCmd = typedGuildCommand<ModActionsPluginType>();
export const modActionsEvt = guildEventListener<ModActionsPluginType>(); export const modActionsEvt = typedGuildEventListener<ModActionsPluginType>();

View file

@ -62,7 +62,8 @@ const EXPIRED_MUTE_CHECK_INTERVAL = 60 * 1000;
let FIRST_CHECK_TIME = Date.now(); let FIRST_CHECK_TIME = Date.now();
const FIRST_CHECK_INCREMENT = 5 * 1000; const FIRST_CHECK_INCREMENT = 5 * 1000;
export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()("mutes", { export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()({
name: "mutes",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Mutes", prettyName: "Mutes",
@ -105,14 +106,16 @@ export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()("mutes", {
}, },
}, },
onLoad(pluginData) { beforeLoad(pluginData) {
pluginData.state.mutes = GuildMutes.getGuildInstance(pluginData.guild.id); pluginData.state.mutes = GuildMutes.getGuildInstance(pluginData.guild.id);
pluginData.state.cases = GuildCases.getGuildInstance(pluginData.guild.id); pluginData.state.cases = GuildCases.getGuildInstance(pluginData.guild.id);
pluginData.state.serverLogs = new GuildLogs(pluginData.guild.id); pluginData.state.serverLogs = new GuildLogs(pluginData.guild.id);
pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id); pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id);
pluginData.state.events = new EventEmitter(); pluginData.state.events = new EventEmitter();
},
afterLoad(pluginData) {
// Check for expired mutes every 5s // Check for expired mutes every 5s
const firstCheckTime = Math.max(Date.now(), FIRST_CHECK_TIME) + FIRST_CHECK_INCREMENT; const firstCheckTime = Math.max(Date.now(), FIRST_CHECK_TIME) + FIRST_CHECK_INCREMENT;
FIRST_CHECK_TIME = firstCheckTime; FIRST_CHECK_TIME = firstCheckTime;
@ -126,7 +129,7 @@ export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()("mutes", {
}, firstCheckTime - Date.now()); }, firstCheckTime - Date.now());
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
clearInterval(pluginData.state.muteClearIntervalId); clearInterval(pluginData.state.muteClearIntervalId);
pluginData.state.events.removeAllListeners(); pluginData.state.events.removeAllListeners();
}, },

View file

@ -3,9 +3,12 @@ import { mutesEvt } from "../types";
/** /**
* Clear active mute from the member if the member is banned * Clear active mute from the member if the member is banned
*/ */
export const ClearActiveMuteOnMemberBanEvt = mutesEvt("guildBanAdd", async ({ pluginData, args: { user } }) => { export const ClearActiveMuteOnMemberBanEvt = mutesEvt({
const mute = await pluginData.state.mutes.findExistingMuteForUserId(user.id); event: "guildBanAdd",
if (mute) { async listener({ pluginData, args: { user } }) {
pluginData.state.mutes.clear(user.id); const mute = await pluginData.state.mutes.findExistingMuteForUserId(user.id);
} if (mute) {
pluginData.state.mutes.clear(user.id);
}
},
}); });

View file

@ -4,9 +4,9 @@ import { memberHasMutedRole } from "../functions/memberHasMutedRole";
/** /**
* Clear active mute if the mute role is removed manually * Clear active mute if the mute role is removed manually
*/ */
export const ClearActiveMuteOnRoleRemovalEvt = mutesEvt( export const ClearActiveMuteOnRoleRemovalEvt = mutesEvt({
"guildMemberUpdate", event: "guildMemberUpdate",
async ({ pluginData, args: { member } }) => { async listener({ pluginData, args: { member } }) {
const muteRole = pluginData.config.get().mute_role; const muteRole = pluginData.config.get().mute_role;
if (!muteRole) return; if (!muteRole) return;
@ -17,4 +17,4 @@ export const ClearActiveMuteOnRoleRemovalEvt = mutesEvt(
await pluginData.state.mutes.clear(muteRole); await pluginData.state.mutes.clear(muteRole);
} }
}, },
); });

View file

@ -6,19 +6,22 @@ import { memberRolesLock } from "../../../utils/lockNameHelpers";
/** /**
* Reapply active mutes on join * Reapply active mutes on join
*/ */
export const ReapplyActiveMuteOnJoinEvt = mutesEvt("guildMemberAdd", async ({ pluginData, args: { member } }) => { export const ReapplyActiveMuteOnJoinEvt = mutesEvt({
const mute = await pluginData.state.mutes.findExistingMuteForUserId(member.id); event: "guildMemberAdd",
if (mute) { async listener({ pluginData, args: { member } }) {
const muteRole = pluginData.config.get().mute_role; const mute = await pluginData.state.mutes.findExistingMuteForUserId(member.id);
if (mute) {
const muteRole = pluginData.config.get().mute_role;
if (muteRole) { if (muteRole) {
const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member)); const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member));
await member.addRole(muteRole); await member.addRole(muteRole);
memberRoleLock.unlock(); memberRoleLock.unlock();
}
pluginData.state.serverLogs.log(LogType.MEMBER_MUTE_REJOIN, {
member: stripObjectToScalars(member, ["user", "roles"]),
});
} }
},
pluginData.state.serverLogs.log(LogType.MEMBER_MUTE_REJOIN, {
member: stripObjectToScalars(member, ["user", "roles"]),
});
}
}); });

View file

@ -51,7 +51,7 @@ export async function muteUser(
} }
const member = await resolveMember(pluginData.client, pluginData.guild, user.id, true); // Grab the fresh member so we don't have stale role info const member = await resolveMember(pluginData.client, pluginData.guild, user.id, true); // Grab the fresh member so we don't have stale role info
const config = pluginData.config.getMatchingConfig({ member, userId }); const config = await pluginData.config.getMatchingConfig({ member, userId });
muteTime = muteTime !== undefined muteTime = muteTime !== undefined
? muteTime ? muteTime

View file

@ -3,7 +3,7 @@ import { tDelayString, tNullable, UserNotificationMethod, UserNotificationResult
import { Mute } from "../../data/entities/Mute"; import { Mute } from "../../data/entities/Mute";
import { Member } from "eris"; import { Member } from "eris";
import { Case } from "../../data/entities/Case"; import { Case } from "../../data/entities/Case";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildCases } from "../../data/GuildCases"; import { GuildCases } from "../../data/GuildCases";
import { GuildArchives } from "../../data/GuildArchives"; import { GuildArchives } from "../../data/GuildArchives";
@ -80,5 +80,5 @@ export interface MuteOptions {
isAutomodAction?: boolean; isAutomodAction?: boolean;
} }
export const mutesCmd = guildCommand<MutesPluginType>(); export const mutesCmd = typedGuildCommand<MutesPluginType>();
export const mutesEvt = guildEventListener<MutesPluginType>(); export const mutesEvt = typedGuildEventListener<MutesPluginType>();

View file

@ -21,7 +21,8 @@ const defaultOptions: PluginOptions<NameHistoryPluginType> = {
], ],
}; };
export const NameHistoryPlugin = zeppelinGuildPlugin<NameHistoryPluginType>()("name_history", { export const NameHistoryPlugin = zeppelinGuildPlugin<NameHistoryPluginType>()({
name: "name_history",
showInDocs: false, showInDocs: false,
configSchema: ConfigSchema, configSchema: ConfigSchema,
@ -38,7 +39,7 @@ export const NameHistoryPlugin = zeppelinGuildPlugin<NameHistoryPluginType>()("n
MessageCreateEvt, MessageCreateEvt,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.nicknameHistory = GuildNicknameHistory.getGuildInstance(guild.id); state.nicknameHistory = GuildNicknameHistory.getGuildInstance(guild.id);

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildNicknameHistory } from "../../data/GuildNicknameHistory"; import { GuildNicknameHistory } from "../../data/GuildNicknameHistory";
import { UsernameHistory } from "../../data/UsernameHistory"; import { UsernameHistory } from "../../data/UsernameHistory";
import { Queue } from "../../Queue"; import { Queue } from "../../Queue";
@ -18,5 +18,5 @@ export interface NameHistoryPluginType extends BasePluginType {
}; };
} }
export const nameHistoryCmd = guildCommand<NameHistoryPluginType>(); export const nameHistoryCmd = typedGuildCommand<NameHistoryPluginType>();
export const nameHistoryEvt = guildEventListener<NameHistoryPluginType>(); export const nameHistoryEvt = typedGuildEventListener<NameHistoryPluginType>();

View file

@ -16,7 +16,8 @@ const defaultOptions: PluginOptions<PersistPluginType> = {
}, },
}; };
export const PersistPlugin = zeppelinGuildPlugin<PersistPluginType>()("persist", { export const PersistPlugin = zeppelinGuildPlugin<PersistPluginType>()({
name: "persist",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Persist", prettyName: "Persist",
@ -36,7 +37,7 @@ export const PersistPlugin = zeppelinGuildPlugin<PersistPluginType>()("persist",
LoadDataEvt, LoadDataEvt,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.persistedData = GuildPersistedData.getGuildInstance(guild.id); state.persistedData = GuildPersistedData.getGuildInstance(guild.id);

View file

@ -27,12 +27,12 @@ export const LoadDataEvt = persistEvt({
} }
const toRestore: MemberOptions = {}; const toRestore: MemberOptions = {};
const config = pluginData.config.getForMember(member); const config = await pluginData.config.getForMember(member);
const restoredData: string[] = []; const restoredData: string[] = [];
// Check permissions // Check permissions
const me = pluginData.guild.members.get(pluginData.client.user.id)!; const me = pluginData.guild.members.get(pluginData.client.user.id)!;
let requiredPermissions = 0; let requiredPermissions = 0n;
if (config.persist_nicknames) requiredPermissions |= p.manageNicknames; if (config.persist_nicknames) requiredPermissions |= p.manageNicknames;
if (config.persisted_roles) requiredPermissions |= p.manageRoles; if (config.persisted_roles) requiredPermissions |= p.manageRoles;
const missingPermissions = getMissingPermissions(me.permission, requiredPermissions); const missingPermissions = getMissingPermissions(me.permission, requiredPermissions);

View file

@ -12,7 +12,7 @@ export const StoreDataEvt = persistEvt({
let persist = false; let persist = false;
const persistData: IPartialPersistData = {}; const persistData: IPartialPersistData = {};
const config = pluginData.config.getForUser(member.user); const config = await pluginData.config.getForUser(member.user);
const persistedRoles = config.persisted_roles; const persistedRoles = config.persisted_roles;
if (persistedRoles.length && member.roles) { if (persistedRoles.length && member.roles) {

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildEventListener } from "knub"; import { BasePluginType, typedGuildEventListener } from "knub";
import { GuildPersistedData } from "../../data/GuildPersistedData"; import { GuildPersistedData } from "../../data/GuildPersistedData";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
@ -19,4 +19,4 @@ export interface PersistPluginType extends BasePluginType {
}; };
} }
export const persistEvt = guildEventListener<PersistPluginType>(); export const persistEvt = typedGuildEventListener<PersistPluginType>();

View file

@ -20,7 +20,8 @@ const defaultOptions: PluginOptions<PingableRolesPluginType> = {
], ],
}; };
export const PingableRolesPlugin = zeppelinGuildPlugin<PingableRolesPluginType>()("pingable_roles", { export const PingableRolesPlugin = zeppelinGuildPlugin<PingableRolesPluginType>()({
name: "pingable_roles",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Pingable roles", prettyName: "Pingable roles",
@ -41,7 +42,7 @@ export const PingableRolesPlugin = zeppelinGuildPlugin<PingableRolesPluginType>(
MessageCreateDisablePingableEvt, MessageCreateDisablePingableEvt,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.pingableRoles = GuildPingableRoles.getGuildInstance(guild.id); state.pingableRoles = GuildPingableRoles.getGuildInstance(guild.id);

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildPingableRoles } from "../../data/GuildPingableRoles"; import { GuildPingableRoles } from "../../data/GuildPingableRoles";
import { PingableRole } from "../../data/entities/PingableRole"; import { PingableRole } from "../../data/entities/PingableRole";
@ -18,5 +18,5 @@ export interface PingableRolesPluginType extends BasePluginType {
}; };
} }
export const pingableRolesCmd = guildCommand<PingableRolesPluginType>(); export const pingableRolesCmd = typedGuildCommand<PingableRolesPluginType>();
export const pingableRolesEvt = guildEventListener<PingableRolesPluginType>(); export const pingableRolesEvt = typedGuildEventListener<PingableRolesPluginType>();

View file

@ -28,7 +28,8 @@ const defaultOptions: PluginOptions<PostPluginType> = {
], ],
}; };
export const PostPlugin = zeppelinGuildPlugin<PostPluginType>()("post", { export const PostPlugin = zeppelinGuildPlugin<PostPluginType>()({
name: "post",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Post", prettyName: "Post",
@ -49,17 +50,19 @@ export const PostPlugin = zeppelinGuildPlugin<PostPluginType>()("post", {
ScheduledPostsDeleteCmd, ScheduledPostsDeleteCmd,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id); state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
state.scheduledPosts = GuildScheduledPosts.getGuildInstance(guild.id); state.scheduledPosts = GuildScheduledPosts.getGuildInstance(guild.id);
state.logs = new GuildLogs(guild.id); state.logs = new GuildLogs(guild.id);
},
afterLoad(pluginData) {
scheduledPostLoop(pluginData); scheduledPostLoop(pluginData);
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
clearTimeout(pluginData.state.scheduledPostLoopTimeout); clearTimeout(pluginData.state.scheduledPostLoopTimeout);
}, },
}); });

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildCommand } from "knub"; import { BasePluginType, typedGuildCommand } from "knub";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildScheduledPosts } from "../../data/GuildScheduledPosts"; import { GuildScheduledPosts } from "../../data/GuildScheduledPosts";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
@ -20,4 +20,4 @@ export interface PostPluginType extends BasePluginType {
}; };
} }
export const postCmd = guildCommand<PostPluginType>(); export const postCmd = typedGuildCommand<PostPluginType>();

View file

@ -31,7 +31,8 @@ const defaultOptions: PluginOptions<ReactionRolesPluginType> = {
], ],
}; };
export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>()("reaction_roles", { export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>()({
name: "reaction_roles",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Reaction roles", prettyName: "Reaction roles",
@ -53,7 +54,7 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>(
AddReactionRoleEvt, AddReactionRoleEvt,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.reactionRoles = GuildReactionRoles.getGuildInstance(guild.id); state.reactionRoles = GuildReactionRoles.getGuildInstance(guild.id);
@ -62,7 +63,9 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>(
state.roleChangeQueue = new Queue(); state.roleChangeQueue = new Queue();
state.pendingRoleChanges = new Map(); state.pendingRoleChanges = new Map();
state.pendingRefreshes = new Set(); state.pendingRefreshes = new Set();
},
afterLoad(pluginData) {
let autoRefreshInterval = pluginData.config.get().auto_refresh_interval; let autoRefreshInterval = pluginData.config.get().auto_refresh_interval;
if (autoRefreshInterval != null) { if (autoRefreshInterval != null) {
autoRefreshInterval = Math.max(MIN_AUTO_REFRESH, autoRefreshInterval); autoRefreshInterval = Math.max(MIN_AUTO_REFRESH, autoRefreshInterval);
@ -70,7 +73,7 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>(
} }
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
if (pluginData.state.autoRefreshTimeout) { if (pluginData.state.autoRefreshTimeout) {
clearTimeout(pluginData.state.autoRefreshTimeout); clearTimeout(pluginData.state.autoRefreshTimeout);
} }

View file

@ -55,7 +55,7 @@ export const AddReactionRoleEvt = reactionRolesEvt({
} }
// Remove the reaction after a small delay // Remove the reaction after a small delay
const config = pluginData.config.getForMember(member); const config = await pluginData.config.getForMember(member);
if (config.remove_user_reactions) { if (config.remove_user_reactions) {
setTimeout(() => { setTimeout(() => {
pluginData.state.reactionRemoveQueue.add(async () => { pluginData.state.reactionRemoveQueue.add(async () => {

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildEventListener, guildCommand } from "knub"; import { BasePluginType, typedGuildEventListener, typedGuildCommand } from "knub";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildReactionRoles } from "../../data/GuildReactionRoles"; import { GuildReactionRoles } from "../../data/GuildReactionRoles";
import { Queue } from "../../Queue"; import { Queue } from "../../Queue";
@ -41,5 +41,5 @@ export interface ReactionRolesPluginType extends BasePluginType {
}; };
} }
export const reactionRolesCmd = guildCommand<ReactionRolesPluginType>(); export const reactionRolesCmd = typedGuildCommand<ReactionRolesPluginType>();
export const reactionRolesEvt = guildEventListener<ReactionRolesPluginType>(); export const reactionRolesEvt = typedGuildEventListener<ReactionRolesPluginType>();

View file

@ -22,7 +22,8 @@ const defaultOptions: PluginOptions<RemindersPluginType> = {
], ],
}; };
export const RemindersPlugin = zeppelinGuildPlugin<RemindersPluginType>()("reminders", { export const RemindersPlugin = zeppelinGuildPlugin<RemindersPluginType>()({
name: "reminders",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Reminders", prettyName: "Reminders",
@ -39,7 +40,7 @@ export const RemindersPlugin = zeppelinGuildPlugin<RemindersPluginType>()("remin
RemindersDeleteCmd, RemindersDeleteCmd,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.reminders = GuildReminders.getGuildInstance(guild.id); state.reminders = GuildReminders.getGuildInstance(guild.id);
@ -47,10 +48,13 @@ export const RemindersPlugin = zeppelinGuildPlugin<RemindersPluginType>()("remin
state.unloaded = false; state.unloaded = false;
state.postRemindersTimeout = null; state.postRemindersTimeout = null;
},
afterLoad(pluginData) {
postDueRemindersLoop(pluginData); postDueRemindersLoop(pluginData);
}, },
onUnload(pluginData) { beforeUnload(pluginData) {
clearTimeout(pluginData.state.postRemindersTimeout); clearTimeout(pluginData.state.postRemindersTimeout);
pluginData.state.unloaded = true; pluginData.state.unloaded = true;
}, },

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, guildCommand } from "knub"; import { BasePluginType, typedGuildCommand } from "knub";
import { GuildReminders } from "../../data/GuildReminders"; import { GuildReminders } from "../../data/GuildReminders";
export const ConfigSchema = t.type({ export const ConfigSchema = t.type({
@ -19,4 +19,4 @@ export interface RemindersPluginType extends BasePluginType {
}; };
} }
export const remindersCmd = guildCommand<RemindersPluginType>(); export const remindersCmd = typedGuildCommand<RemindersPluginType>();

View file

@ -30,7 +30,8 @@ const defaultOptions: PluginOptions<RolesPluginType> = {
], ],
}; };
export const RolesPlugin = zeppelinGuildPlugin<RolesPluginType>()("roles", { export const RolesPlugin = zeppelinGuildPlugin<RolesPluginType>()({
name: "roles",
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Roles", prettyName: "Roles",
@ -50,7 +51,7 @@ export const RolesPlugin = zeppelinGuildPlugin<RolesPluginType>()("roles", {
MassRemoveRoleCmd, MassRemoveRoleCmd,
], ],
onLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;
state.logs = new GuildLogs(guild.id); state.logs = new GuildLogs(guild.id);

Some files were not shown because too many files have changed in this diff Show more