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

Typing fixes; show last reload time in !about

This commit is contained in:
Dragory 2019-07-22 00:09:45 +03:00
parent 2ff93f71db
commit 3e0498f96b
26 changed files with 154 additions and 75 deletions

View file

@ -21,7 +21,7 @@ export class AutoReactionsPlugin extends ZeppelinPlugin<TConfigSchema> {
private onMessageCreateFn;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_manage: false,

View file

@ -10,10 +10,11 @@ import { IPluginOptions } from "knub";
import { GuildLogs } from "../data/GuildLogs";
import { LogType } from "../data/LogType";
import * as t from "io-ts";
import { tNullable } from "../utils";
const ConfigSchema = t.type({
log_automatic_actions: t.boolean,
case_log_channel: t.string,
case_log_channel: tNullable(t.string),
});
type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
@ -50,7 +51,7 @@ export class CasesPlugin extends ZeppelinPlugin<TConfigSchema> {
protected archives: GuildArchives;
protected logs: GuildLogs;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
log_automatic_actions: true,

View file

@ -9,6 +9,7 @@ import {
getInviteCodesInString,
getUrlsInString,
stripObjectToScalars,
tNullable,
} from "../utils";
import { ZalgoRegex } from "../data/Zalgo";
import { GuildSavedMessages } from "../data/GuildSavedMessages";
@ -20,17 +21,17 @@ import * as t from "io-ts";
const ConfigSchema = t.type({
filter_zalgo: t.boolean,
filter_invites: t.boolean,
invite_guild_whitelist: t.array(t.string),
invite_guild_blacklist: t.array(t.string),
invite_code_whitelist: t.array(t.string),
invite_code_blacklist: t.array(t.string),
invite_guild_whitelist: tNullable(t.array(t.string)),
invite_guild_blacklist: tNullable(t.array(t.string)),
invite_code_whitelist: tNullable(t.array(t.string)),
invite_code_blacklist: tNullable(t.array(t.string)),
allow_group_dm_invites: t.boolean,
filter_domains: t.boolean,
domain_whitelist: t.array(t.string),
domain_blacklist: t.array(t.string),
blocked_tokens: t.array(t.string),
blocked_words: t.array(t.string),
blocked_regex: t.array(t.string),
domain_whitelist: tNullable(t.array(t.string)),
domain_blacklist: tNullable(t.array(t.string)),
blocked_tokens: tNullable(t.array(t.string)),
blocked_words: tNullable(t.array(t.string)),
blocked_regex: tNullable(t.array(t.string)),
});
type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
@ -43,7 +44,7 @@ export class CensorPlugin extends ZeppelinPlugin<TConfigSchema> {
private onMessageCreateFn;
private onMessageUpdateFn;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
filter_zalgo: false,

View file

@ -25,7 +25,7 @@ export class CompanionChannelPlugin extends ZeppelinPlugin<TConfigSchema> {
companionChannels: Map<string, TCompanionChannel> = new Map();
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
channels: {},

View file

@ -75,7 +75,7 @@ export class CustomEventsPlugin extends ZeppelinPlugin<TConfigSchema> {
private clearTriggers: () => void;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
events: {},

View file

@ -23,7 +23,7 @@ export class LocatePlugin extends ZeppelinPlugin<TConfigSchema> {
private outdatedAlertsTimeout;
private usersWithAlerts: string[] = [];
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_where: false,

View file

@ -71,7 +71,7 @@ export class LogsPlugin extends ZeppelinPlugin<TConfigSchema> {
private excludedUserProps = ["user", "member", "mod"];
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
channels: {},

View file

@ -16,7 +16,7 @@ export class MessageSaverPlugin extends ZeppelinPlugin<TConfigSchema> {
protected savedMessages: GuildSavedMessages;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_manage: false,

View file

@ -13,6 +13,7 @@ import {
NotifyUserStatus,
stripObjectToScalars,
successMessage,
tNullable,
trimLines,
ucfirst,
UnknownUser,
@ -35,12 +36,12 @@ const ConfigSchema = t.type({
message_on_warn: t.boolean,
message_on_kick: t.boolean,
message_on_ban: t.boolean,
message_channel: t.string,
warn_message: t.string,
kick_message: t.string,
ban_message: t.string,
message_channel: tNullable(t.string),
warn_message: tNullable(t.string),
kick_message: tNullable(t.string),
ban_message: tNullable(t.string),
alert_on_rejoin: t.boolean,
alert_channel: t.string,
alert_channel: tNullable(t.string),
can_note: t.boolean,
can_warn: t.boolean,
can_mute: t.boolean,
@ -84,7 +85,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<TConfigSchema> {
this.ignoredEvents = [];
}
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
dm_on_warn: true,

View file

@ -13,6 +13,7 @@ import {
NotifyUserStatus,
stripObjectToScalars,
successMessage,
tNullable,
ucfirst,
UnknownUser,
} from "../utils";
@ -28,14 +29,14 @@ import { Case } from "../data/entities/Case";
import * as t from "io-ts";
const ConfigSchema = t.type({
mute_role: t.string,
move_to_voice_channel: t.string,
mute_role: tNullable(t.string),
move_to_voice_channel: tNullable(t.string),
dm_on_mute: t.boolean,
message_on_mute: t.boolean,
message_channel: t.string,
mute_message: t.string,
timed_mute_message: t.string,
message_channel: tNullable(t.string),
mute_message: tNullable(t.string),
timed_mute_message: tNullable(t.string),
can_view_list: t.boolean,
can_cleanup: t.boolean,
@ -70,7 +71,7 @@ export class MutesPlugin extends ZeppelinPlugin<TConfigSchema> {
protected serverLogs: GuildLogs;
private muteClearIntervalId: NodeJS.Timer;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
mute_role: null,
@ -366,7 +367,7 @@ export class MutesPlugin extends ZeppelinPlugin<TConfigSchema> {
if (!member) {
if (!bannedIds) {
const bans = await this.guild.getBans();
bannedIds = bans.map(u => u.id);
bannedIds = bans.map(u => u.user.id);
}
muteWithDetails.banned = bannedIds.includes(mute.user_id);

View file

@ -18,7 +18,7 @@ export class NameHistoryPlugin extends ZeppelinPlugin<TConfigSchema> {
protected nicknameHistory: GuildNicknameHistory;
protected usernameHistory: UsernameHistory;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_view: false,

View file

@ -22,7 +22,7 @@ export class PersistPlugin extends ZeppelinPlugin<TConfigSchema> {
protected persistedData: GuildPersistedData;
protected logs: GuildLogs;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
persisted_roles: [],

View file

@ -21,7 +21,7 @@ export class PingableRolesPlugin extends ZeppelinPlugin<TConfigSchema> {
protected cache: Map<string, PingableRole[]>;
protected timeouts: Map<string, any>;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_manage: false,

View file

@ -58,7 +58,7 @@ export class PostPlugin extends ZeppelinPlugin<TConfigSchema> {
clearTimeout(this.scheduledPostLoopTimeout);
}
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_post: false,

View file

@ -55,7 +55,7 @@ export class ReactionRolesPlugin extends ZeppelinPlugin<TConfigSchema> {
private autoRefreshTimeout;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
auto_refresh_interval: MIN_AUTO_REFRESH,

View file

@ -32,7 +32,7 @@ export class RemindersPlugin extends ZeppelinPlugin<TConfigSchema> {
private postRemindersTimeout;
private unloaded = false;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_use: false,

View file

@ -18,7 +18,7 @@ export class SelfGrantableRolesPlugin extends ZeppelinPlugin<TConfigSchema> {
protected selfGrantableRoles: GuildSelfGrantableRoles;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_manage: false,

View file

@ -42,7 +42,7 @@ export class SlowmodePlugin extends ZeppelinPlugin<TConfigSchema> {
private onMessageCreateFn;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
use_native_slowmode: true,

View file

@ -8,6 +8,7 @@ import {
getUserMentions,
noop,
stripObjectToScalars,
tNullable,
trimLines,
} from "../utils";
import { LogType } from "../data/LogType";
@ -23,30 +24,26 @@ import { MuteResult, MutesPlugin } from "./Mutes";
import { CasesPlugin } from "./Cases";
import * as t from "io-ts";
const BaseSingleSpamConfig = t.intersection([
t.type({
interval: t.number,
count: t.number,
}),
t.partial({
mute: t.boolean,
mute_time: t.number,
clean: t.boolean,
}),
]);
const BaseSingleSpamConfig = t.type({
interval: t.number,
count: t.number,
mute: tNullable(t.boolean),
mute_time: tNullable(t.number),
clean: tNullable(t.boolean),
});
type TBaseSingleSpamConfig = t.TypeOf<typeof BaseSingleSpamConfig>;
const ConfigSchema = t.type({
max_censor: BaseSingleSpamConfig,
max_messages: BaseSingleSpamConfig,
max_mentions: BaseSingleSpamConfig,
max_links: BaseSingleSpamConfig,
max_attachments: BaseSingleSpamConfig,
max_emojis: BaseSingleSpamConfig,
max_newlines: BaseSingleSpamConfig,
max_duplicates: BaseSingleSpamConfig,
max_characters: BaseSingleSpamConfig,
max_voice_moves: BaseSingleSpamConfig,
max_censor: tNullable(BaseSingleSpamConfig),
max_messages: tNullable(BaseSingleSpamConfig),
max_mentions: tNullable(BaseSingleSpamConfig),
max_links: tNullable(BaseSingleSpamConfig),
max_attachments: tNullable(BaseSingleSpamConfig),
max_emojis: tNullable(BaseSingleSpamConfig),
max_newlines: tNullable(BaseSingleSpamConfig),
max_duplicates: tNullable(BaseSingleSpamConfig),
max_characters: tNullable(BaseSingleSpamConfig),
max_voice_moves: tNullable(BaseSingleSpamConfig),
});
type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
@ -99,7 +96,7 @@ export class SpamPlugin extends ZeppelinPlugin<TConfigSchema> {
private expiryInterval;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
max_censor: null,

View file

@ -32,7 +32,7 @@ export class StarboardPlugin extends ZeppelinPlugin<TConfigSchema> {
private onMessageDeleteFn;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_manage: false,

View file

@ -34,7 +34,7 @@ export class TagsPlugin extends ZeppelinPlugin<TConfigSchema> {
protected tagFunctions;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
prefix: "!!",

View file

@ -90,8 +90,9 @@ export class UtilityPlugin extends ZeppelinPlugin<TConfigSchema> {
protected archives: GuildArchives;
protected lastFullMemberRefresh = 0;
protected lastReload;
getDefaultOptions(): IPluginOptions<TConfigSchema> {
protected static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
return {
config: {
can_roles: false,
@ -142,6 +143,8 @@ export class UtilityPlugin extends ZeppelinPlugin<TConfigSchema> {
this.savedMessages = GuildSavedMessages.getGuildInstance(this.guildId);
this.archives = GuildArchives.getGuildInstance(this.guildId);
this.lastReload = Date.now();
if (activeReloads && activeReloads.has(this.guildId)) {
activeReloads.get(this.guildId).createMessage(successMessage("Reloaded!"));
activeReloads.delete(this.guildId);
@ -1003,8 +1006,14 @@ export class UtilityPlugin extends ZeppelinPlugin<TConfigSchema> {
const shard = this.bot.shards.get(this.bot.guildShardMap[this.guildId]);
const lastReload = humanizeDuration(Date.now() - this.lastReload, {
largest: 2,
round: true,
});
const basicInfoRows = [
["Uptime", prettyUptime],
["Last reload", `${lastReload} ago`],
["Last update", moment(lastCommit.committer.date, "X").format("LL [at] H:mm [(UTC)]")],
["Version", lastCommit.shortHash],
["API latency", `${shard.latency}ms`],

View file

@ -1,4 +1,4 @@
import { IBasePluginConfig, IPluginOptions, logger, Plugin } from "knub";
import { IBasePluginConfig, IPluginOptions, logger, Plugin, configUtils } from "knub";
import { PluginRuntimeError } from "../PluginRuntimeError";
import * as t from "io-ts";
import { pipe } from "fp-ts/lib/pipeable";
@ -29,19 +29,47 @@ export class ZeppelinPlugin<TConfig extends {} = IBasePluginConfig> extends Plug
return ourLevel > memberLevel;
}
protected static getStaticDefaultOptions() {
// Implemented by plugin
return {};
}
protected getDefaultOptions(): IPluginOptions<TConfig> {
return (this.constructor as typeof ZeppelinPlugin).getStaticDefaultOptions() as IPluginOptions<TConfig>;
}
public static validateOptions(options: any): string[] | null {
// Validate config values
if (this.configSchema) {
if (options.config) {
const errors = validateStrict(this.configSchema, options.config);
if (errors) return errors;
const merged = configUtils.mergeConfig(
{},
(this.getStaticDefaultOptions() as any).config || {},
options.config,
);
const errors = validateStrict(this.configSchema, merged);
if (errors) {
return errors;
}
}
if (options.overrides) {
for (const override of options.overrides) {
for (const [i, override] of options.overrides.entries()) {
if (override.config) {
const errors = validateStrict(this.configSchema, override.config);
if (errors) return errors;
// For type checking overrides, apply default config + supplied config + any overrides preceding this override + finally this override
// Exhaustive type checking would require checking against all combinations of preceding overrides but that's... costy. This will do for now.
// TODO: Override default config retrieval functions and do some sort of memoized checking there?
const merged = configUtils.mergeConfig(
{},
(this.getStaticDefaultOptions() as any).config || {},
options.config || {},
...options.overrides.slice(0, i),
override.config,
);
const errors = validateStrict(this.configSchema, merged);
if (errors) {
return errors;
}
}
}
}
@ -55,7 +83,7 @@ export class ZeppelinPlugin<TConfig extends {} = IBasePluginConfig> extends Plug
const mergedOptions = this.getMergedOptions();
const validationErrors = ((this.constructor as unknown) as typeof ZeppelinPlugin).validateOptions(mergedOptions);
if (validationErrors) {
throw new Error(`Invalid options:\n${validationErrors.join("\n")}`);
throw new Error(validationErrors.join("\n"));
}
return super.runLoad();

View file

@ -25,6 +25,7 @@ import { GuildInfoSaverPlugin } from "./GuildInfoSaver";
import { LogServerPlugin } from "./LogServer";
import { CompanionChannelPlugin } from "./CompanionChannels";
import { LocatePlugin } from "./LocateUser";
import { GuildConfigReloader } from "./GuildConfigReloader";
/**
* Plugins available to be loaded for individual guilds
@ -70,4 +71,4 @@ export const basePlugins = [
/**
* Available global plugins (can't be loaded per-guild, only globally)
*/
export const availableGlobalPlugins = [BotControlPlugin, UsernameSaver, LogServerPlugin];
export const availableGlobalPlugins = [BotControlPlugin, UsernameSaver, LogServerPlugin, GuildConfigReloader];

View file

@ -11,7 +11,8 @@ import {
} from "eris";
import url from "url";
import tlds from "tlds";
import emojiRegex from "emoji-regex/es2015/text";
import emojiRegex from "emoji-regex/text";
import * as t from "io-ts";
import fs from "fs";
const fsp = fs.promises;
@ -28,6 +29,10 @@ const delayStringMultipliers = {
s: 1000,
};
export function tNullable(type: t.Mixed) {
return t.union([type, t.undefined, t.null]);
}
/**
* Turns a "delay string" such as "1h30m" to milliseconds
*/

View file

@ -1,7 +1,42 @@
import * as t from "io-ts";
import { pipe } from "fp-ts/lib/pipeable";
import { fold } from "fp-ts/lib/Either";
import { PathReporter } from "io-ts/lib/PathReporter";
import { noop } from "./utils";
// From io-ts/lib/PathReporter
function stringify(v) {
if (typeof v === "function") {
return t.getFunctionName(v);
}
if (typeof v === "number" && !isFinite(v)) {
if (isNaN(v)) {
return "NaN";
}
return v > 0 ? "Infinity" : "-Infinity";
}
return JSON.stringify(v);
}
// From io-ts/lib/PathReporter
// tslint:disable
function getContextPath(context) {
return context
.map(function(_a) {
var key = _a.key,
type = _a.type;
return key + ": " + type.name;
})
.join("/");
}
// tslint:enable
const report = fold((errors: any) => {
return errors.map(err => {
if (err.message) return err.message;
const context = err.context.map(c => c.key).filter(k => k && !k.startsWith("{"));
return `Invalid value <${stringify(err.value)}> supplied to <${context.join("/")}>`;
});
}, noop);
/**
* Validates the given value against the given schema while also disallowing extra properties
@ -12,7 +47,7 @@ export function validateStrict(schema: t.Type<any, any, any>, value: any): strin
return pipe(
validationResult,
fold(
err => PathReporter.report(validationResult),
err => report(validationResult),
result => {
// Make sure there are no extra properties
if (JSON.stringify(value) !== JSON.stringify(result)) {