From f50003472965011e255b5489cb6e3121f9a543ea Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 30 Jul 2020 03:21:07 +0300 Subject: [PATCH] Port BotControl --- backend/src/pluginUtils.ts | 6 +- .../plugins/BotControl/BotControlPlugin.ts | 41 +++++++++++ .../src/plugins/BotControl/activeReload.ts | 13 ++++ .../BotControl/commands/LeaveServerCmd.ts | 35 ++++++++++ .../commands/ReloadGlobalPluginsCmd.ts | 22 ++++++ .../BotControl/commands/ReloadServerCmd.ts | 33 +++++++++ .../plugins/BotControl/commands/ServersCmd.ts | 69 +++++++++++++++++++ backend/src/plugins/BotControl/types.ts | 17 +++++ backend/src/plugins/availablePlugins.ts | 2 + backend/src/utils.ts | 1 - 10 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 backend/src/plugins/BotControl/BotControlPlugin.ts create mode 100644 backend/src/plugins/BotControl/activeReload.ts create mode 100644 backend/src/plugins/BotControl/commands/LeaveServerCmd.ts create mode 100644 backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts create mode 100644 backend/src/plugins/BotControl/commands/ReloadServerCmd.ts create mode 100644 backend/src/plugins/BotControl/commands/ServersCmd.ts create mode 100644 backend/src/plugins/BotControl/types.ts diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index b5239557..02cf4f42 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -3,7 +3,7 @@ */ import { Member } from "eris"; -import { configUtils, helpers, PluginBlueprint, PluginData, PluginOptions } from "knub"; +import { CommandContext, configUtils, helpers, PluginBlueprint, PluginData, PluginOptions } from "knub"; import { decodeAndValidateStrict, StrictValidationError, validate } from "./validatorUtils"; import { deepKeyIntersect, errorMessage, successMessage, tNullable } from "./utils"; import { ZeppelinPluginBlueprint } from "./plugins/ZeppelinPluginBlueprint"; @@ -125,3 +125,7 @@ export function isOwner(pluginData: PluginData, userId: string) { return owners.includes(userId); } + +export const isOwnerPreFilter = (_, context: CommandContext) => { + return isOwner(context.pluginData, context.message.author.id); +}; diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts new file mode 100644 index 00000000..f4c6edd4 --- /dev/null +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -0,0 +1,41 @@ +import { zeppelinPlugin } from "../ZeppelinPluginBlueprint"; +import { BotControlPluginType, ConfigSchema } from "./types"; +import { GuildArchives } from "../../data/GuildArchives"; +import { TextChannel } from "eris"; +import { sendSuccessMessage } from "../../pluginUtils"; +import { getActiveReload, resetActiveReload } from "./activeReload"; +import { ReloadGlobalPluginsCmd } from "./commands/ReloadGlobalPluginsCmd"; +import { ServersCmd } from "./commands/ServersCmd"; +import { LeaveServerCmd } from "./commands/LeaveServerCmd"; +import { ReloadServerCmd } from "./commands/ReloadServerCmd"; + +const defaultOptions = { + config: { + can_use: false, + update_cmd: null, + }, +}; + +export const BotControlPlugin = zeppelinPlugin()("bot_control", { + configSchema: ConfigSchema, + defaultOptions, + + commands: [ReloadGlobalPluginsCmd, ServersCmd, LeaveServerCmd, ReloadServerCmd], + + onLoad(pluginData) { + pluginData.state.archives = new GuildArchives(0); + + if (getActiveReload()) { + const [guildId, channelId] = getActiveReload(); + resetActiveReload(); + + const guild = pluginData.client.guilds.get(guildId); + if (guild) { + const channel = guild.channels.get(channelId); + if (channel instanceof TextChannel) { + sendSuccessMessage(pluginData, channel, "Global plugins reloaded!"); + } + } + } + }, +}); diff --git a/backend/src/plugins/BotControl/activeReload.ts b/backend/src/plugins/BotControl/activeReload.ts new file mode 100644 index 00000000..1303fd0a --- /dev/null +++ b/backend/src/plugins/BotControl/activeReload.ts @@ -0,0 +1,13 @@ +let activeReload: [string, string] = null; + +export function getActiveReload() { + return activeReload; +} + +export function setActiveReload(guildId: string, channelId: string) { + activeReload = [guildId, channelId]; +} + +export function resetActiveReload() { + activeReload = null; +} diff --git a/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts new file mode 100644 index 00000000..0e74b22a --- /dev/null +++ b/backend/src/plugins/BotControl/commands/LeaveServerCmd.ts @@ -0,0 +1,35 @@ +import { command } from "knub"; +import { BotControlPluginType } from "../types"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; + +export const LeaveServerCmd = command()({ + trigger: ["leave_server", "leave_guild"], + permission: null, + config: { + preFilters: [isOwnerPreFilter], + }, + + signature: { + guildId: ct.string(), + }, + + async run({ pluginData, message: msg, args }) { + if (!pluginData.client.guilds.has(args.guildId)) { + sendErrorMessage(pluginData, msg.channel, "I am not in that guild"); + return; + } + + const guildToLeave = pluginData.client.guilds.get(args.guildId); + const guildName = guildToLeave.name; + + try { + await pluginData.client.leaveGuild(args.guildId); + } catch (e) { + sendErrorMessage(pluginData, msg.channel, `Failed to leave guild: ${e.message}`); + return; + } + + sendSuccessMessage(pluginData, msg.channel, `Left guild **${guildName}**`); + }, +}); diff --git a/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts new file mode 100644 index 00000000..ca4a801a --- /dev/null +++ b/backend/src/plugins/BotControl/commands/ReloadGlobalPluginsCmd.ts @@ -0,0 +1,22 @@ +import { command } from "knub"; +import { BotControlPluginType } from "../types"; +import { isOwnerPreFilter } from "../../../pluginUtils"; +import { getActiveReload, setActiveReload } from "../activeReload"; +import { TextChannel } from "eris"; + +export const ReloadGlobalPluginsCmd = command()({ + trigger: "bot_reload_global_plugins", + permission: null, + config: { + preFilters: [isOwnerPreFilter], + }, + + async run({ pluginData, message }) { + if (getActiveReload()) return; + + setActiveReload((message.channel as TextChannel).guild?.id, message.channel.id); + await message.channel.createMessage("Reloading global plugins..."); + + pluginData.getKnubInstance().reloadAllGlobalPlugins(); + }, +}); diff --git a/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts new file mode 100644 index 00000000..e455c369 --- /dev/null +++ b/backend/src/plugins/BotControl/commands/ReloadServerCmd.ts @@ -0,0 +1,33 @@ +import { command } from "knub"; +import { BotControlPluginType } from "../types"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; + +export const ReloadServerCmd = command()({ + trigger: ["reload_server", "reload_guild"], + permission: null, + config: { + preFilters: [isOwnerPreFilter], + }, + + signature: { + guildId: ct.string(), + }, + + async run({ pluginData, message: msg, args }) { + if (!pluginData.client.guilds.has(args.guildId)) { + sendErrorMessage(pluginData, msg.channel, "I am not in that guild"); + return; + } + + try { + await pluginData.getKnubInstance().reloadGuild(args.guildId); + } catch (e) { + sendErrorMessage(pluginData, msg.channel, `Failed to reload guild: ${e.message}`); + return; + } + + const guild = pluginData.client.guilds.get(args.guildId); + sendSuccessMessage(pluginData, msg.channel, `Reloaded guild **${guild?.name || "???"}**`); + }, +}); diff --git a/backend/src/plugins/BotControl/commands/ServersCmd.ts b/backend/src/plugins/BotControl/commands/ServersCmd.ts new file mode 100644 index 00000000..fe7ec887 --- /dev/null +++ b/backend/src/plugins/BotControl/commands/ServersCmd.ts @@ -0,0 +1,69 @@ +import { command } from "knub"; +import { BotControlPluginType } from "../types"; +import { isOwnerPreFilter } from "../../../pluginUtils"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import escapeStringRegexp from "escape-string-regexp"; +import { createChunkedMessage, sorter } from "../../../utils"; + +export const ServersCmd = command()({ + trigger: ["servers", "guilds"], + permission: null, + config: { + preFilters: [isOwnerPreFilter], + }, + + signature: { + search: ct.string({ catchAll: true, required: false }), + + all: ct.switchOption({ shortcut: "a" }), + initialized: ct.switchOption({ shortcut: "i" }), + uninitialized: ct.switchOption({ shortcut: "u" }), + }, + + async run({ pluginData, message: msg, args }) { + const showList = Boolean(args.all || args.initialized || args.uninitialized || args.search); + const search = args.search && new RegExp([...args.search].map(s => escapeStringRegexp(s)).join(".*"), "i"); + + const joinedGuilds = Array.from(pluginData.client.guilds.values()); + const loadedGuilds = pluginData.getKnubInstance().getLoadedGuilds(); + const loadedGuildsMap = loadedGuilds.reduce((map, guildData) => map.set(guildData.guildId, guildData), new Map()); + + if (showList) { + let filteredGuilds = Array.from(joinedGuilds); + + if (args.initialized) { + filteredGuilds = filteredGuilds.filter(g => loadedGuildsMap.has(g.id)); + } + + if (args.uninitialized) { + filteredGuilds = filteredGuilds.filter(g => !loadedGuildsMap.has(g.id)); + } + + if (args.search) { + filteredGuilds = filteredGuilds.filter(g => search.test(`${g.id} ${g.name}`)); + } + + if (filteredGuilds.length) { + filteredGuilds.sort(sorter(g => g.name.toLowerCase())); + const longestId = filteredGuilds.reduce((longest, guild) => Math.max(longest, guild.id.length), 0); + const lines = filteredGuilds.map(g => { + const paddedId = g.id.padEnd(longestId, " "); + return `\`${paddedId}\` **${g.name}** (${loadedGuildsMap.has(g.id) ? "initialized" : "not initialized"}) (${ + g.memberCount + } members)`; + }); + createChunkedMessage(msg.channel, lines.join("\n")); + } else { + msg.channel.createMessage("No servers matched the filters"); + } + } else { + const total = joinedGuilds.length; + const initialized = joinedGuilds.filter(g => loadedGuildsMap.has(g.id)).length; + const unInitialized = total - initialized; + + msg.channel.createMessage( + `I am on **${total} total servers**, of which **${initialized} are initialized** and **${unInitialized} are not initialized**`, + ); + } + }, +}); diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts new file mode 100644 index 00000000..50e1847a --- /dev/null +++ b/backend/src/plugins/BotControl/types.ts @@ -0,0 +1,17 @@ +import * as t from "io-ts"; +import { tNullable } from "../../utils"; +import { BasePluginType } from "knub"; +import { GuildArchives } from "../../data/GuildArchives"; + +export const ConfigSchema = t.type({ + can_use: t.boolean, + update_cmd: tNullable(t.string), +}); +export type TConfigSchema = t.TypeOf; + +export interface BotControlPluginType extends BasePluginType { + config: TConfigSchema; + state: { + archives: GuildArchives; + }; +} diff --git a/backend/src/plugins/availablePlugins.ts b/backend/src/plugins/availablePlugins.ts index 9b6963e4..fb74aa5d 100644 --- a/backend/src/plugins/availablePlugins.ts +++ b/backend/src/plugins/availablePlugins.ts @@ -29,6 +29,7 @@ import { ReactionRolesPlugin } from "./ReactionRoles/ReactionRolesPlugin"; import { AutomodPlugin } from "./Automod/AutomodPlugin"; import { CompanionChannelsPlugin } from "./CompanionChannels/CompanionChannelsPlugin"; import { CustomEventsPlugin } from "./CustomEvents/CustomEventsPlugin"; +import { BotControlPlugin } from "./BotControl/BotControlPlugin"; // prettier-ignore export const guildPlugins: Array> = [ @@ -66,4 +67,5 @@ export const guildPlugins: Array> = [ // prettier-ignore export const globalPlugins = [ GuildConfigReloaderPlugin, + BotControlPlugin, ]; diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 3106a3a6..ee225c1e 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -34,7 +34,6 @@ import { either } from "fp-ts/lib/Either"; import moment from "moment-timezone"; import { SimpleCache } from "./SimpleCache"; import { logger } from "./logger"; -import { Awaitable } from "knub/dist/utils"; const fsp = fs.promises;