3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-18 23:09:59 +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
backend
package-lock.jsonpackage.json
src
Queue.ts
api
commandTypes.tsconfigValidator.ts
data
index.tspluginUtils.ts
plugins
AutoDelete
AutoReactions
Automod
BotControl
Cases
Censor
ChannelArchiver
CompanionChannels
Counters
CustomEvents
GuildAccessMonitor
GuildConfigReloader
GuildInfoSaver
LocateUser
Logs
MessageSaver
ModActions
Mutes
NameHistory
Persist
PingableRoles
Post
ReactionRoles
Reminders
Roles

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

View file

@ -1,23 +1,38 @@
import { SECONDS } from "./utils";
type QueueFn = (...args: any[]) => Promise<any>;
type InternalQueueFn = () => Promise<void>;
type AnyFn = (...args: any[]) => any;
const DEFAULT_TIMEOUT = 10 * SECONDS;
export class Queue {
protected running: boolean = false;
protected queue: QueueFn[] = [];
protected timeout: number;
export class Queue<TQueueFunction extends AnyFn = AnyFn> {
protected running = false;
protected queue: InternalQueueFn[] = [];
protected _timeout: number;
constructor(timeout = DEFAULT_TIMEOUT) {
this.timeout = timeout;
this._timeout = timeout;
}
public add(fn) {
const promise = new Promise(resolve => {
get timeout(): number {
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 () => {
await fn();
resolve(undefined);
resolve();
});
if (!this.running) this.next();
@ -26,7 +41,7 @@ export class Queue {
return promise;
}
public next() {
public next(): void {
this.running = true;
if (this.queue.length === 0) {
@ -37,8 +52,8 @@ export class Queue {
const fn = this.queue.shift()!;
new Promise(resolve => {
// Either fn() completes or the timeout is reached
fn().then(resolve);
setTimeout(resolve, this.timeout);
void fn().then(resolve);
setTimeout(resolve, this._timeout);
}).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("X-Content-Type-Options", "nosniff");
res.end(body);
});
}

View file

@ -86,6 +86,7 @@ export function initAuth(app: express.Express) {
const userId = await apiLogins.getUserIdByApiKey(apiKey);
if (userId) {
void apiLogins.refreshApiKeyExpiryTime(apiKey); // Refresh expiry time in the background
return cb(null, { apiKey, userId });
}
@ -154,13 +155,19 @@ export function initAuth(app: express.Express) {
await apiLogins.expireApiKey(req.user!.apiKey);
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() {
return [
passport.authenticate("api-token", { failWithError: true }),
(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,
} from "./utils";
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 { getChannelIdFromMessageId } from "./data/getChannelIdFromMessageId";
import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget";
@ -107,7 +107,7 @@ export const commandTypes = {
};
export const commandTypeHelpers = {
...baseTypeHelpers,
...baseCommandParameterTypeHelpers,
delay: createTypeHelper<number>(commandTypes.delay),
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)!;
try {
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) {
if (err instanceof ConfigValidationError || err instanceof StrictValidationError) {
return `${pluginName}: ${err.message}`;

View file

@ -5,7 +5,9 @@ import crypto from "crypto";
import moment from "moment-timezone";
// tslint:disable-next-line:no-submodule-imports
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 {
private apiLogins: Repository<ApiLogin>;
@ -68,7 +70,7 @@ export class ApiLogins extends BaseRepository {
logged_in_at: moment.utc().format(DBDateFormat),
expires_at: moment
.utc()
.add(1, "day")
.add(LOGIN_EXPIRY_TIME, "ms")
.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 { ISavedMessageData, SavedMessage } from "./entities/SavedMessage";
import { QueuedEventEmitter } from "../QueuedEventEmitter";
import { GuildChannel, Message } from "eris";
import { GuildChannel, Message, PossiblyUncachedTextableChannel } from "eris";
import moment from "moment-timezone";
import { MINUTES, SECONDS } from "../utils";
import { isAPI } from "../globals";
@ -34,7 +34,7 @@ export class GuildSavedMessages extends BaseGuildRepository {
this.toBePermanent = new Set();
}
public msgToSavedMessageData(msg: Message): ISavedMessageData {
public msgToSavedMessageData(msg: Message<PossiblyUncachedTextableChannel>): ISavedMessageData {
const data: ISavedMessageData = {
author: {
username: msg.author.username,
@ -139,7 +139,7 @@ export class GuildSavedMessages extends BaseGuildRepository {
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);
if (existingSavedMsg) return;
@ -222,7 +222,7 @@ export class GuildSavedMessages extends BaseGuildRepository {
this.events.emit(`update:${id}`, [newMessage, oldMessage]);
}
async saveEditFromMsg(msg: Message) {
async saveEditFromMsg(msg: Message<PossiblyUncachedTextableChannel>) {
const newData = this.msgToSavedMessageData(msg);
return this.saveEdit(msg.id, newData);
}

View file

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

View file

@ -27,8 +27,12 @@ export function canActOn(pluginData: GuildPluginData<any>, member1: Member, memb
return allowSameLevel ? ourLevel >= memberLevel : ourLevel > memberLevel;
}
export function hasPermission(pluginData: AnyPluginData<any>, permission: string, matchParams: ExtendedMatchParams) {
const config = pluginData.config.getMatchingConfig(matchParams);
export async function hasPermission(
pluginData: AnyPluginData<any>,
permission: string,
matchParams: ExtendedMatchParams,
) {
const config = await pluginData.config.getMatchingConfig(matchParams);
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({
enabled: tNullable(t.boolean),
config: tNullable(t.unknown),
@ -70,7 +87,7 @@ export function getPluginConfigPreprocessor(
blueprint: ZeppelinPlugin,
customPreprocessor?: ZeppelinPlugin["configPreprocessor"],
) {
return async (options: PluginOptions<any>) => {
return async (options: PluginOptions<any>, strict?: boolean) => {
// 1. Validate the basic structure of plugin config
const basicOptionsValidation = validate(BasicPluginStructureType, options);
if (basicOptionsValidation instanceof StrictValidationError) {
@ -89,8 +106,32 @@ export function getPluginConfigPreprocessor(
if (options.overrides) {
for (const override of options.overrides) {
const partialOverrideConfigValidation = validate(partialConfigSchema, override.config || {});
if (partialOverrideConfigValidation) {
// Validate criteria and extra criteria
// 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);
}
}

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

View file

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

View file

@ -7,7 +7,7 @@ import { addMessageToDeletionQueue } from "./addMessageToDeletionQueue";
export async function onMessageCreate(pluginData: GuildPluginData<AutoDeletePluginType>, msg: SavedMessage) {
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) {
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,
info: {
prettyName: "Auto-reactions",
@ -47,10 +48,8 @@ export const AutoReactionsPlugin = zeppelinGuildPlugin<AutoReactionsPluginType>(
AddReactionsEvt,
],
onLoad(pluginData) {
const { state, guild } = pluginData;
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
state.autoReactions = GuildAutoReactions.getGuildInstance(guild.id);
beforeLoad(pluginData) {
pluginData.state.savedMessages = GuildSavedMessages.getGuildInstance(pluginData.guild.id);
pluginData.state.autoReactions = GuildAutoReactions.getGuildInstance(pluginData.guild.id);
},
});

View file

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

View file

@ -152,7 +152,8 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = options => {
return options;
};
export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod", {
export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
name: "automod",
showInDocs: true,
info: pluginInfo,
@ -168,8 +169,10 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
defaultOptions,
configPreprocessor,
customOverrideMatcher(pluginData, criteria, matchParams) {
return criteria?.antiraid_level ? criteria.antiraid_level === pluginData.state.cachedAntiraidLevel : false;
customOverrideCriteriaFunctions: {
antiraid_level: (pluginData, matchParams, value) => {
return value ? value === pluginData.state.cachedAntiraidLevel : false;
},
},
// prettier-ignore
@ -181,22 +184,16 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
commands: [AntiraidClearCmd, SetAntiraidCmd, ViewAntiraidCmd],
async onLoad(pluginData) {
async beforeLoad(pluginData) {
pluginData.state.queue = new Queue();
pluginData.state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`);
pluginData.state.recentActions = [];
pluginData.state.clearRecentActionsInterval = setInterval(() => clearOldRecentActions(pluginData), 1 * MINUTES);
pluginData.state.recentSpam = [];
pluginData.state.clearRecentSpamInterval = setInterval(() => clearOldRecentSpam(pluginData), 1 * SECONDS);
pluginData.state.recentNicknameChanges = new Map();
pluginData.state.clearRecentNicknameChangesInterval = setInterval(
() => clearOldRecentNicknameChanges(pluginData),
30 * SECONDS,
);
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.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.savedMessages.events.on("create", pluginData.state.onMessageCreateFn);
pluginData.state.onMessageUpdateFn = message => runAutomodOnMessage(pluginData, message, true);
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);
pluginData.state.onCounterTrigger = (name, triggerName, channelId, userId) => {
@ -268,7 +272,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
registerEventListenersFromMap(mutesEvents, pluginData.state.mutesListeners);
},
async onBeforeUnload(pluginData) {
async beforeUnload(pluginData) {
const countersPlugin = pluginData.getPlugin(CountersPlugin);
countersPlugin.offCounterEvent("trigger", pluginData.state.onCounterTrigger);
countersPlugin.offCounterEvent("reverseTrigger", pluginData.state.onCounterReverseTrigger);
@ -278,9 +282,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
const mutesEvents = pluginData.getPlugin(MutesPlugin).getEventEmitter();
unregisterEventListenersFromMap(mutesEvents, pluginData.state.mutesListeners);
},
async onUnload(pluginData) {
pluginData.state.queue.clear();
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 { setAntiraidLevel } from "../functions/setAntiraidLevel";
import { sendSuccessMessage } from "../../../pluginUtils";
export const AntiraidClearCmd = guildCommand<AutomodPluginType>()({
export const AntiraidClearCmd = typedGuildCommand<AutomodPluginType>()({
trigger: ["antiraid clear", "antiraid reset", "antiraid none", "antiraid off"],
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 { setAntiraidLevel } from "../functions/setAntiraidLevel";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { commandTypeHelpers as ct } from "../../../commandTypes";
export const SetAntiraidCmd = guildCommand<AutomodPluginType>()({
export const SetAntiraidCmd = typedGuildCommand<AutomodPluginType>()({
trigger: "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 { setAntiraidLevel } from "../functions/setAntiraidLevel";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { commandTypeHelpers as ct } from "../../../commandTypes";
export const ViewAntiraidCmd = guildCommand<AutomodPluginType>()({
export const ViewAntiraidCmd = typedGuildCommand<AutomodPluginType>()({
trigger: "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 { runAutomod } from "../functions/runAutomod";
import { RecentActionType } from "../constants";
export const RunAutomodOnJoinEvt = guildEventListener<AutomodPluginType>()(
"guildMemberAdd",
({ pluginData, args: { member } }) => {
export const RunAutomodOnJoinEvt = typedGuildEventListener<AutomodPluginType>()({
event: "guildMemberAdd",
listener({ pluginData, args: { member } }) {
const context: AutomodContext = {
timestamp: Date.now(),
user: member.user,
@ -24,4 +24,4 @@ export const RunAutomodOnJoinEvt = guildEventListener<AutomodPluginType>()(
runAutomod(pluginData, context);
});
},
);
});

View file

@ -1,13 +1,13 @@
import { guildEventListener } from "knub";
import { typedGuildEventListener } from "knub";
import { AutomodContext, AutomodPluginType } from "../types";
import { RecentActionType } from "../constants";
import { runAutomod } from "../functions/runAutomod";
import isEqual from "lodash.isequal";
import diff from "lodash.difference";
export const RunAutomodOnMemberUpdate = guildEventListener<AutomodPluginType>()(
"guildMemberUpdate",
({ pluginData, args: { member, oldMember } }) => {
export const RunAutomodOnMemberUpdate = typedGuildEventListener<AutomodPluginType>()({
event: "guildMemberUpdate",
listener({ pluginData, args: { member, oldMember } }) {
if (!oldMember) 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 categoryId = channel?.parentID;
const config = pluginData.config.getMatchingConfig({
const config = await pluginData.config.getMatchingConfig({
channelId,
categoryId,
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,
defaultOptions,
@ -46,7 +47,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()("bo
EligibleCmd,
],
onLoad(pluginData) {
afterLoad(pluginData) {
pluginData.state.archives = new GuildArchives(0);
pluginData.state.allowedGuilds = new AllowedGuilds();
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 (args.user) {
const userInfo = `**${args.user.username}#${args.user.discriminator}** (\`${args.user.id}\`)`;
for (const assignment of existingUserAssignment!) {
if (guild != null && assignment.guild_id !== args.guildId) continue;
finalMessage += `The user has the following permissions on server \`${
assignment.guild_id
}\`:\n${assignment.permissions.join("\n")}\n\n`;
const assignmentGuild = await pluginData.state.allowedGuilds.find(assignment.guild_id);
const guildName = assignmentGuild?.name ?? "Unknown";
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 === "") {
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;
}
// Else display all users that have permissions on the specified guild
} else if (guild) {
const guildInfo = `**${guild.name}** (\`${guild.id}\`)`;
const existingGuildAssignment = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id);
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;
}
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) {
const user = await resolveUser(pluginData.client, assignment.target_id);
finalMessage += `\n**${user.username}#${user.discriminator}**, \`${

View file

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

View file

@ -16,6 +16,6 @@ export const ReloadGlobalPluginsCmd = botControlCmd({
setActiveReload((message.channel as TextChannel).guild?.id, message.channel.id);
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 { tNullable } from "../../utils";
import { BasePluginType, globalCommand, globalEventListener } from "knub";
import { BasePluginType, typedGlobalCommand, typedGlobalEventListener } from "knub";
import { GuildArchives } from "../../data/GuildArchives";
import { AllowedGuilds } from "../../data/AllowedGuilds";
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
@ -23,5 +23,5 @@ export interface BotControlPluginType extends BasePluginType {
};
}
export const botControlCmd = globalCommand<BotControlPluginType>();
export const botControlEvt = globalEventListener<BotControlPluginType>();
export const botControlCmd = typedGlobalCommand<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,
info: {
prettyName: "Cases",
@ -73,7 +74,7 @@ export const CasesPlugin = zeppelinGuildPlugin<CasesPluginType>()("cases", {
getCaseSummary: mapToPublicFn(getCaseSummary),
},
onLoad(pluginData) {
afterLoad(pluginData) {
pluginData.state.logs = new GuildLogs(pluginData.guild.id);
pluginData.state.archives = GuildArchives.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 (
args.postInCaseLogOverride === true ||
((!args.automatic || modConfig.log_automatic_actions) && args.postInCaseLogOverride !== false)

View file

@ -13,6 +13,7 @@ export async function getCaseEmbed(
pluginData: GuildPluginData<CasesPluginType>,
caseOrCaseId: Case | number,
requestMemberId?: string,
noOriginalCaseLink?: boolean,
): Promise<AdvancedMessageContent> {
const theCase = await pluginData.state.cases.with("notes").find(resolveCaseId(caseOrCaseId));
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 link = messageLink(pluginData.guild.id, channelId, messageId);
embed.fields.push({

View file

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

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { tDelayString, tPartialDictionary, tNullable } from "../../utils";
import { CaseNameToType, CaseTypes } from "../../data/CaseTypes";
import { BasePluginType, guildCommand, guildEventListener } from "knub";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildCases } from "../../data/GuildCases";
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,
info: {
prettyName: "Censor",
@ -57,13 +58,17 @@ export const CensorPlugin = zeppelinGuildPlugin<CensorPluginType>()("censor", {
configSchema: ConfigSchema,
defaultOptions,
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.serverLogs = new GuildLogs(guild.id);
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`);
},
afterLoad(pluginData) {
const { state, guild } = pluginData;
state.onMessageCreateFn = msg => onMessageCreate(pluginData, msg);
state.savedMessages.events.on("create", state.onMessageCreateFn);
@ -72,7 +77,7 @@ export const CensorPlugin = zeppelinGuildPlugin<CensorPluginType>()("censor", {
state.savedMessages.events.on("update", state.onMessageUpdateFn);
},
onUnload(pluginData) {
beforeUnload(pluginData) {
discardRegExpRunner(`guild-${pluginData.guild.id}`);
pluginData.state.savedMessages.events.off("create", pluginData.state.onMessageCreateFn);

View file

@ -15,7 +15,7 @@ export async function applyFiltersToMsg(
savedMessage: SavedMessage,
): Promise<boolean> {
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 || "";
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 { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginType>()("channel_archiver", {
export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginType>()({
name: "channel_archiver",
showInDocs: false,
dependencies: [TimeAndDatePlugin],
@ -13,9 +14,5 @@ export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginTy
// prettier-ignore
commands: [
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 {
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,
info: {
prettyName: "Companion channels",
@ -30,7 +31,7 @@ export const CompanionChannelsPlugin = zeppelinGuildPlugin<CompanionChannelsPlug
events: [VoiceChannelJoinEvt, VoiceChannelSwitchEvt, VoiceChannelLeaveEvt],
onLoad(pluginData) {
beforeLoad(pluginData) {
pluginData.state.errorCooldownManager = new CooldownManager();
},
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import * as t from "io-ts";
import { tNullable } from "../../utils";
import { BasePluginType, CooldownManager, guildEventListener } from "knub";
import { BasePluginType, CooldownManager, typedGuildEventListener } from "knub";
import { GuildLogs } from "../../data/GuildLogs";
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).
* 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,
info: {
prettyName: "Counters",
@ -145,7 +146,7 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()("counter
ResetAllCounterValuesCmd,
],
async onLoad(pluginData) {
async beforeLoad(pluginData) {
pluginData.state.counters = new GuildCounters(pluginData.guild.id);
pluginData.state.events = new EventEmitter();
pluginData.state.counterTriggersByCounterId = new Map();
@ -189,6 +190,10 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()("counter
// Mark old/unused triggers to be deleted later
await pluginData.state.counters.markUnusedTriggersToBeDeleted(activeTriggerIds);
},
async afterLoad(pluginData) {
const config = pluginData.config.get();
// Start decay timers
pluginData.state.decayTimers = [];
@ -207,7 +212,7 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()("counter
}
},
onUnload(pluginData) {
beforeUnload(pluginData) {
for (const interval of pluginData.state.decayTimers) {
clearInterval(interval);
}

View file

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

View file

@ -1,17 +1,17 @@
import { guildCommand } from "knub";
import { typedGuildCommand } from "knub";
import { CountersPluginType } from "../types";
import { sendErrorMessage } from "../../../pluginUtils";
import { trimMultilineString, ucfirst } from "../../../utils";
import { getGuildPrefix } from "../../../utils/getGuildPrefix";
export const CountersListCmd = guildCommand<CountersPluginType>()({
export const CountersListCmd = typedGuildCommand<CountersPluginType>()({
trigger: ["counters list", "counter list", "counters"],
permission: "can_view",
signature: {},
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);
if (!countersToShow.length) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint";
import { BasePluginType, globalEventListener, GlobalPluginData } from "knub";
import { BasePluginType, typedGlobalEventListener, GlobalPluginData } from "knub";
import * as t from "io-ts";
import { AllowedGuilds } from "../../data/AllowedGuilds";
import { Guild } from "eris";
@ -14,25 +14,32 @@ interface GuildAccessMonitorPluginType extends BasePluginType {
async function checkGuild(pluginData: GlobalPluginData<GuildAccessMonitorPluginType>, guild: Guild) {
if (!(await pluginData.state.allowedGuilds.isAllowed(guild.id))) {
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
*/
export const GuildAccessMonitorPlugin = zeppelinGlobalPlugin<GuildAccessMonitorPluginType>()("guild_access_monitor", {
export const GuildAccessMonitorPlugin = zeppelinGlobalPlugin<GuildAccessMonitorPluginType>()({
name: "guild_access_monitor",
configSchema: t.type({}),
events: [
globalEventListener<GuildAccessMonitorPluginType>()("guildAvailable", ({ pluginData, args: { guild } }) => {
checkGuild(pluginData, guild);
typedGlobalEventListener<GuildAccessMonitorPluginType>()({
event: "guildAvailable",
listener({ pluginData, args: { guild } }) {
checkGuild(pluginData, guild);
},
}),
],
onLoad(pluginData) {
beforeLoad(pluginData) {
pluginData.state.allowedGuilds = new AllowedGuilds();
},
afterLoad(pluginData) {
for (const guild of pluginData.client.guilds.values()) {
checkGuild(pluginData, guild);
}

View file

@ -4,23 +4,23 @@ import { Configs } from "../../data/Configs";
import { reloadChangedGuilds } from "./functions/reloadChangedGuilds";
import * as t from "io-ts";
export const GuildConfigReloaderPlugin = zeppelinGlobalPlugin<GuildConfigReloaderPluginType>()(
"guild_config_reloader",
{
showInDocs: false,
export const GuildConfigReloaderPlugin = zeppelinGlobalPlugin<GuildConfigReloaderPluginType>()({
name: "guild_config_reloader",
showInDocs: false,
configSchema: t.type({}),
configSchema: t.type({}),
async onLoad(pluginData) {
pluginData.state.guildConfigs = new Configs();
pluginData.state.highestConfigId = await pluginData.state.guildConfigs.getHighestId();
reloadChangedGuilds(pluginData);
},
onUnload(pluginData) {
clearTimeout(pluginData.state.nextCheckTimeout);
pluginData.state.unloaded = true;
},
async beforeLoad(pluginData) {
pluginData.state.guildConfigs = new Configs();
pluginData.state.highestConfigId = await pluginData.state.guildConfigs.getHighestId();
},
);
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 * as t from "io-ts";
export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType>()("guild_info_saver", {
export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType>()({
name: "guild_info_saver",
showInDocs: false,
configSchema: t.type({}),
onLoad(pluginData) {
const { state, guild } = pluginData;
state.allowedGuilds = new AllowedGuilds();
beforeLoad(pluginData) {
pluginData.state.allowedGuilds = new AllowedGuilds();
},
afterLoad(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,
info: {
prettyName: "Locate user",
@ -58,19 +59,21 @@ export const LocateUserPlugin = zeppelinGuildPlugin<LocateUserPluginType>()("loc
GuildBanRemoveAlertsEvt
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.alerts = GuildVCAlerts.getGuildInstance(guild.id);
state.outdatedAlertsTimeout = null;
state.usersWithAlerts = [];
state.unloaded = false;
},
afterLoad(pluginData) {
outdatedAlertsLoop(pluginData);
fillActiveAlertsList(pluginData);
},
onUnload(pluginData) {
beforeUnload(pluginData) {
clearTimeout(pluginData.state.outdatedAlertsTimeout as Timeout);
pluginData.state.unloaded = true;
},

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildVCAlerts } from "../../data/GuildVCAlerts";
import Timeout = NodeJS.Timeout;
@ -19,5 +19,5 @@ export interface LocateUserPluginType extends BasePluginType {
};
}
export const locateUserCmd = guildCommand<LocateUserPluginType>();
export const locateUserEvt = guildEventListener<LocateUserPluginType>();
export const locateUserCmd = typedGuildCommand<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,
info: {
prettyName: "Logs",
@ -84,7 +85,7 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()("logs", {
},
},
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.guildLogs = new GuildLogs(guild.id);
@ -92,11 +93,17 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()("logs", {
state.archives = GuildArchives.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.guildLogs.on("log", state.logListener);
state.batches = new Map();
state.onMessageDeleteFn = msg => onMessageDelete(pluginData, msg);
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.savedMessages.events.on("update", state.onMessageUpdateFn);
state.regexRunner = getRegExpRunner(`guild-${pluginData.guild.id}`);
state.regexRunnerRepeatedTimeoutListener = (regexSource, timeoutMs, failedTimes) => {
logger.warn(`Disabled heavy regex temporarily: ${regexSource}`);
log(pluginData, LogType.BOT_ALERT, {
@ -122,7 +128,7 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()("logs", {
state.regexRunner.on("repeatedTimeout", state.regexRunnerRepeatedTimeoutListener);
},
onUnload(pluginData) {
beforeUnload(pluginData) {
pluginData.state.guildLogs.removeListener("log", pluginData.state.logListener);
pluginData.state.savedMessages.events.off("delete", pluginData.state.onMessageDeleteFn);

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { BasePluginType, guildEventListener } from "knub";
import { BasePluginType, typedGuildEventListener } from "knub";
import { TRegex } from "../../validatorUtils";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
@ -21,6 +21,7 @@ const LogChannel = t.partial({
excluded_channels: t.array(t.string),
excluded_categories: t.array(t.string),
exclude_bots: t.boolean,
excluded_roles: t.array(t.string),
format: tNullable(tLogFormats),
timestamp_format: t.string,
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);
}
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))
if (config.allow_user_mentions) {

View file

@ -2,11 +2,16 @@ import { GuildPluginData } from "knub";
import { LogsPluginType, TLogChannelMap } from "../types";
import { LogType } from "../../../data/LogType";
import { TextChannel } from "eris";
import { createChunkedMessage, noop } from "../../../utils";
import { createChunkedMessage, get, noop } from "../../../utils";
import { getLogMessage } from "./getLogMessage";
import { allowTimeout } from "../../../RegExpRunner";
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) {
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 (opts.excluded_channels) {
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,
configSchema: ConfigSchema,
@ -40,7 +41,7 @@ export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()(
MessageDeleteBulkEvt,
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
},

View file

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

View file

@ -34,7 +34,7 @@ import { Member, Message } from "eris";
import { kickMember } from "./functions/kickMember";
import { banUserId } from "./functions/banUserId";
import { MassmuteCmd } from "./commands/MassmuteCmd";
import { trimPluginDescription } from "../../utils";
import { MINUTES, trimPluginDescription } from "../../utils";
import { DeleteCaseCmd } from "./commands/DeleteCaseCmd";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
import { GuildTempbans } from "../../data/GuildTempbans";
@ -44,6 +44,7 @@ import { mapToPublicFn } from "../../pluginUtils";
import { onModActionsEvent } from "./functions/onModActionsEvent";
import { offModActionsEvent } from "./functions/offModActionsEvent";
import { updateCase } from "./functions/updateCase";
import { Queue } from "../../Queue";
const defaultOptions = {
config: {
@ -109,7 +110,8 @@ const defaultOptions = {
],
};
export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()("mod_actions", {
export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()({
name: "mod_actions",
showInDocs: true,
info: {
prettyName: "Mod actions",
@ -186,7 +188,7 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()("mod
},
},
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.mutes = GuildMutes.getGuildInstance(guild.id);
@ -197,13 +199,18 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()("mod
state.unloaded = false;
state.outdatedTempbansTimeout = null;
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();
},
afterLoad(pluginData) {
outdatedTempbansLoop(pluginData);
},
onUnload(pluginData) {
beforeUnload(pluginData) {
pluginData.state.unloaded = true;
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
let mod = msg.member;
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");
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
let mod = msg.member;
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");
return;
}
@ -160,7 +160,8 @@ export const BanCmd = modActionsCmd({
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(
pluginData,
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
let mod = msg.member;
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");
return;
}

View file

@ -1,7 +1,7 @@
import { modActionsCmd, IgnoredEventType } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes";
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 { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
@ -12,6 +12,9 @@ import { waitForReply } from "knub/dist/helpers";
import { ignoreEvent } from "../functions/ignoreEvent";
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin";
import { LogType } from "../../../data/LogType";
import { performance } from "perf_hooks";
import { humanizeDurationShort } from "../../../humanizeDurationShort";
import { load } from "js-yaml";
export const MassbanCmd = modActionsCmd({
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
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 failedBans: string[] = [];
const casesPlugin = pluginData.getPlugin(CasesPlugin);
for (const userId of args.userIds) {
try {
await pluginData.guild.banMember(userId, 1, banReason != null ? encodeURIComponent(banReason) : undefined);
const waitTimeStart = performance.now();
const waitingInterval = setInterval(() => {
const waitTime = humanizeDurationShort(performance.now() - waitTimeStart, { round: true });
loadingMsg
.edit(`Massban queued. Still waiting for previous massban to finish (waited ${waitTime}).`)
.catch(() => clearInterval(waitingInterval));
}, 1 * MINUTES);
await casesPlugin.createCase({
userId,
modId: msg.author.id,
type: CaseTypes.Ban,
reason: `Mass ban: ${banReason}`,
postInCaseLogOverride: false,
pluginData.state.massbanQueue.add(async () => {
clearInterval(waitingInterval);
if (pluginData.state.unloaded) {
void loadingMsg.delete().catch(noop);
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);
} catch {
failedBans.push(userId);
if (failedBans.length) {
sendSuccessMessage(
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
let mod = msg.member;
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");
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
let mod = msg.member;
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"));
return;
}

View file

@ -13,9 +13,9 @@ import { Case } from "../../../data/entities/Case";
* Create a BAN case automatically when a user is banned manually.
* Attempts to find the ban's details in the audit log.
*/
export const CreateBanCaseOnManualBanEvt = modActionsEvt(
"guildBanAdd",
async ({ pluginData, args: { guild, user } }) => {
export const CreateBanCaseOnManualBanEvt = modActionsEvt({
event: "guildBanAdd",
async listener({ pluginData, args: { guild, user } }) {
if (isEventIgnored(pluginData, IgnoredEventType.Ban, user.id)) {
clearIgnoredEvents(pluginData, IgnoredEventType.Ban, user.id);
return;
@ -39,7 +39,7 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt(
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) {
reason = relevantAuditLogEntry.reason || "";
@ -72,4 +72,4 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt(
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.
* Attempts to find the kick's details in the audit log.
*/
export const CreateKickCaseOnManualKickEvt = modActionsEvt(
"guildMemberRemove",
async ({ pluginData, args: { member } }) => {
export const CreateKickCaseOnManualKickEvt = modActionsEvt({
event: "guildMemberRemove",
async listener({ pluginData, args: { member } }) {
if (isEventIgnored(pluginData, IgnoredEventType.Kick, member.id)) {
clearIgnoredEvents(pluginData, IgnoredEventType.Kick, member.id);
return;
@ -42,7 +42,7 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt(
} else {
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) {
const casesPlugin = pluginData.getPlugin(CasesPlugin);
@ -67,4 +67,4 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt(
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.
* Attempts to find the unban's details in the audit log.
*/
export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt(
"guildBanRemove",
async ({ pluginData, args: { guild, user } }) => {
export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt({
event: "guildBanRemove",
async listener({ pluginData, args: { guild, user } }) {
if (isEventIgnored(pluginData, IgnoredEventType.Unban, user.id)) {
clearIgnoredEvents(pluginData, IgnoredEventType.Unban, user.id);
return;
@ -38,7 +38,7 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt(
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) {
createdCase = await casesPlugin.createCase({
@ -69,4 +69,4 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt(
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
*/
export const PostAlertOnMemberJoinEvt = modActionsEvt(
"guildMemberAdd",
async ({ pluginData, args: { guild, member } }) => {
export const PostAlertOnMemberJoinEvt = modActionsEvt({
event: "guildMemberAdd",
async listener({ pluginData, args: { guild, member } }) {
const config = pluginData.config.get();
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
let mod = msg.member;
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");
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 { hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { GuildPluginData } from "knub";
@ -10,7 +10,6 @@ import { MutesPlugin } from "../../Mutes/MutesPlugin";
import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { logger } from "../../../logger";
import { GuildMessage } from "knub/dist/helpers";
/**
* The actual function run by both !mute and !forcemute.
@ -19,7 +18,7 @@ import { GuildMessage } from "knub/dist/helpers";
export async function actualMuteUserCmd(
pluginData: GuildPluginData<ModActionsPluginType>,
user: User | UnknownUser,
msg: GuildMessage,
msg: Message<GuildTextableChannel>,
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
@ -27,7 +26,7 @@ export async function actualMuteUserCmd(
let pp: User | null = null;
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");
return;
}

View file

@ -18,7 +18,7 @@ export async function actualUnmuteCmd(
let pp: User | null = null;
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");
return;
}

View file

@ -1,6 +1,6 @@
import * as t from "io-ts";
import { tNullable, UserNotificationMethod, UserNotificationResult } from "../../utils";
import { BasePluginType, guildCommand, guildEventListener } from "knub";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildMutes } from "../../data/GuildMutes";
import { GuildCases } from "../../data/GuildCases";
import { GuildLogs } from "../../data/GuildLogs";
@ -10,6 +10,7 @@ import { TextChannel } from "eris";
import { GuildTempbans } from "../../data/GuildTempbans";
import Timeout = NodeJS.Timeout;
import { EventEmitter } from "events";
import { Queue } from "../../Queue";
export const ConfigSchema = t.type({
dm_on_warn: t.boolean,
@ -72,6 +73,7 @@ export interface ModActionsPluginType extends BasePluginType {
unloaded: boolean;
outdatedTempbansTimeout: Timeout | null;
ignoredEvents: IIgnoredEvent[];
massbanQueue: Queue;
events: ModActionsEventEmitter;
};
@ -146,5 +148,5 @@ export interface BanOptions {
export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" | "unban";
export const modActionsCmd = guildCommand<ModActionsPluginType>();
export const modActionsEvt = guildEventListener<ModActionsPluginType>();
export const modActionsCmd = typedGuildCommand<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();
const FIRST_CHECK_INCREMENT = 5 * 1000;
export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()("mutes", {
export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()({
name: "mutes",
showInDocs: true,
info: {
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.cases = GuildCases.getGuildInstance(pluginData.guild.id);
pluginData.state.serverLogs = new GuildLogs(pluginData.guild.id);
pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id);
pluginData.state.events = new EventEmitter();
},
afterLoad(pluginData) {
// Check for expired mutes every 5s
const firstCheckTime = Math.max(Date.now(), FIRST_CHECK_TIME) + FIRST_CHECK_INCREMENT;
FIRST_CHECK_TIME = firstCheckTime;
@ -126,7 +129,7 @@ export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()("mutes", {
}, firstCheckTime - Date.now());
},
onUnload(pluginData) {
beforeUnload(pluginData) {
clearInterval(pluginData.state.muteClearIntervalId);
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
*/
export const ClearActiveMuteOnMemberBanEvt = mutesEvt("guildBanAdd", async ({ pluginData, args: { user } }) => {
const mute = await pluginData.state.mutes.findExistingMuteForUserId(user.id);
if (mute) {
pluginData.state.mutes.clear(user.id);
}
export const ClearActiveMuteOnMemberBanEvt = mutesEvt({
event: "guildBanAdd",
async listener({ pluginData, args: { user } }) {
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
*/
export const ClearActiveMuteOnRoleRemovalEvt = mutesEvt(
"guildMemberUpdate",
async ({ pluginData, args: { member } }) => {
export const ClearActiveMuteOnRoleRemovalEvt = mutesEvt({
event: "guildMemberUpdate",
async listener({ pluginData, args: { member } }) {
const muteRole = pluginData.config.get().mute_role;
if (!muteRole) return;
@ -17,4 +17,4 @@ export const ClearActiveMuteOnRoleRemovalEvt = mutesEvt(
await pluginData.state.mutes.clear(muteRole);
}
},
);
});

View file

@ -6,19 +6,22 @@ import { memberRolesLock } from "../../../utils/lockNameHelpers";
/**
* Reapply active mutes on join
*/
export const ReapplyActiveMuteOnJoinEvt = mutesEvt("guildMemberAdd", async ({ pluginData, args: { member } }) => {
const mute = await pluginData.state.mutes.findExistingMuteForUserId(member.id);
if (mute) {
const muteRole = pluginData.config.get().mute_role;
export const ReapplyActiveMuteOnJoinEvt = mutesEvt({
event: "guildMemberAdd",
async listener({ pluginData, args: { member } }) {
const mute = await pluginData.state.mutes.findExistingMuteForUserId(member.id);
if (mute) {
const muteRole = pluginData.config.get().mute_role;
if (muteRole) {
const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member));
await member.addRole(muteRole);
memberRoleLock.unlock();
if (muteRole) {
const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member));
await member.addRole(muteRole);
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 config = pluginData.config.getMatchingConfig({ member, userId });
const config = await pluginData.config.getMatchingConfig({ member, userId });
muteTime = muteTime !== undefined
? muteTime

View file

@ -3,7 +3,7 @@ import { tDelayString, tNullable, UserNotificationMethod, UserNotificationResult
import { Mute } from "../../data/entities/Mute";
import { Member } from "eris";
import { Case } from "../../data/entities/Case";
import { BasePluginType, guildCommand, guildEventListener } from "knub";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildCases } from "../../data/GuildCases";
import { GuildArchives } from "../../data/GuildArchives";
@ -80,5 +80,5 @@ export interface MuteOptions {
isAutomodAction?: boolean;
}
export const mutesCmd = guildCommand<MutesPluginType>();
export const mutesEvt = guildEventListener<MutesPluginType>();
export const mutesCmd = typedGuildCommand<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,
configSchema: ConfigSchema,
@ -38,7 +39,7 @@ export const NameHistoryPlugin = zeppelinGuildPlugin<NameHistoryPluginType>()("n
MessageCreateEvt,
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.nicknameHistory = GuildNicknameHistory.getGuildInstance(guild.id);

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildNicknameHistory } from "../../data/GuildNicknameHistory";
import { UsernameHistory } from "../../data/UsernameHistory";
import { Queue } from "../../Queue";
@ -18,5 +18,5 @@ export interface NameHistoryPluginType extends BasePluginType {
};
}
export const nameHistoryCmd = guildCommand<NameHistoryPluginType>();
export const nameHistoryEvt = guildEventListener<NameHistoryPluginType>();
export const nameHistoryCmd = typedGuildCommand<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,
info: {
prettyName: "Persist",
@ -36,7 +37,7 @@ export const PersistPlugin = zeppelinGuildPlugin<PersistPluginType>()("persist",
LoadDataEvt,
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.persistedData = GuildPersistedData.getGuildInstance(guild.id);

View file

@ -27,12 +27,12 @@ export const LoadDataEvt = persistEvt({
}
const toRestore: MemberOptions = {};
const config = pluginData.config.getForMember(member);
const config = await pluginData.config.getForMember(member);
const restoredData: string[] = [];
// Check permissions
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.persisted_roles) requiredPermissions |= p.manageRoles;
const missingPermissions = getMissingPermissions(me.permission, requiredPermissions);

View file

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

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { BasePluginType, guildEventListener } from "knub";
import { BasePluginType, typedGuildEventListener } from "knub";
import { GuildPersistedData } from "../../data/GuildPersistedData";
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,
info: {
prettyName: "Pingable roles",
@ -41,7 +42,7 @@ export const PingableRolesPlugin = zeppelinGuildPlugin<PingableRolesPluginType>(
MessageCreateDisablePingableEvt,
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.pingableRoles = GuildPingableRoles.getGuildInstance(guild.id);

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { BasePluginType, guildCommand, guildEventListener } from "knub";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildPingableRoles } from "../../data/GuildPingableRoles";
import { PingableRole } from "../../data/entities/PingableRole";
@ -18,5 +18,5 @@ export interface PingableRolesPluginType extends BasePluginType {
};
}
export const pingableRolesCmd = guildCommand<PingableRolesPluginType>();
export const pingableRolesEvt = guildEventListener<PingableRolesPluginType>();
export const pingableRolesCmd = typedGuildCommand<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,
info: {
prettyName: "Post",
@ -49,17 +50,19 @@ export const PostPlugin = zeppelinGuildPlugin<PostPluginType>()("post", {
ScheduledPostsDeleteCmd,
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
state.scheduledPosts = GuildScheduledPosts.getGuildInstance(guild.id);
state.logs = new GuildLogs(guild.id);
},
afterLoad(pluginData) {
scheduledPostLoop(pluginData);
},
onUnload(pluginData) {
beforeUnload(pluginData) {
clearTimeout(pluginData.state.scheduledPostLoopTimeout);
},
});

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { BasePluginType, guildCommand } from "knub";
import { BasePluginType, typedGuildCommand } from "knub";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildScheduledPosts } from "../../data/GuildScheduledPosts";
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,
info: {
prettyName: "Reaction roles",
@ -53,7 +54,7 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>(
AddReactionRoleEvt,
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.reactionRoles = GuildReactionRoles.getGuildInstance(guild.id);
@ -62,7 +63,9 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>(
state.roleChangeQueue = new Queue();
state.pendingRoleChanges = new Map();
state.pendingRefreshes = new Set();
},
afterLoad(pluginData) {
let autoRefreshInterval = pluginData.config.get().auto_refresh_interval;
if (autoRefreshInterval != null) {
autoRefreshInterval = Math.max(MIN_AUTO_REFRESH, autoRefreshInterval);
@ -70,7 +73,7 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>(
}
},
onUnload(pluginData) {
beforeUnload(pluginData) {
if (pluginData.state.autoRefreshTimeout) {
clearTimeout(pluginData.state.autoRefreshTimeout);
}

View file

@ -55,7 +55,7 @@ export const AddReactionRoleEvt = reactionRolesEvt({
}
// 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) {
setTimeout(() => {
pluginData.state.reactionRemoveQueue.add(async () => {

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { BasePluginType, guildEventListener, guildCommand } from "knub";
import { BasePluginType, typedGuildEventListener, typedGuildCommand } from "knub";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildReactionRoles } from "../../data/GuildReactionRoles";
import { Queue } from "../../Queue";
@ -41,5 +41,5 @@ export interface ReactionRolesPluginType extends BasePluginType {
};
}
export const reactionRolesCmd = guildCommand<ReactionRolesPluginType>();
export const reactionRolesEvt = guildEventListener<ReactionRolesPluginType>();
export const reactionRolesCmd = typedGuildCommand<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,
info: {
prettyName: "Reminders",
@ -39,7 +40,7 @@ export const RemindersPlugin = zeppelinGuildPlugin<RemindersPluginType>()("remin
RemindersDeleteCmd,
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.reminders = GuildReminders.getGuildInstance(guild.id);
@ -47,10 +48,13 @@ export const RemindersPlugin = zeppelinGuildPlugin<RemindersPluginType>()("remin
state.unloaded = false;
state.postRemindersTimeout = null;
},
afterLoad(pluginData) {
postDueRemindersLoop(pluginData);
},
onUnload(pluginData) {
beforeUnload(pluginData) {
clearTimeout(pluginData.state.postRemindersTimeout);
pluginData.state.unloaded = true;
},

View file

@ -1,5 +1,5 @@
import * as t from "io-ts";
import { BasePluginType, guildCommand } from "knub";
import { BasePluginType, typedGuildCommand } from "knub";
import { GuildReminders } from "../../data/GuildReminders";
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,
info: {
prettyName: "Roles",
@ -50,7 +51,7 @@ export const RolesPlugin = zeppelinGuildPlugin<RolesPluginType>()("roles", {
MassRemoveRoleCmd,
],
onLoad(pluginData) {
beforeLoad(pluginData) {
const { state, guild } = pluginData;
state.logs = new GuildLogs(guild.id);

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