diff --git a/backend/src/data/AllowedGuilds.ts b/backend/src/data/AllowedGuilds.ts index 4c0564c2..0efe9770 100644 --- a/backend/src/data/AllowedGuilds.ts +++ b/backend/src/data/AllowedGuilds.ts @@ -39,4 +39,18 @@ export class AllowedGuilds extends BaseRepository { updateInfo(id, name, icon, ownerId) { return this.allowedGuilds.update({ id }, { name, icon, owner_id: ownerId }); } + + add(id, data: Partial> = {}) { + return this.allowedGuilds.insert({ + name: "Server", + icon: null, + owner_id: "0", + ...data, + id, + }); + } + + remove(id) { + return this.allowedGuilds.delete({ id }); + } } diff --git a/backend/src/data/ApiPermissionAssignments.ts b/backend/src/data/ApiPermissionAssignments.ts index 3699ee06..dbcc0e26 100644 --- a/backend/src/data/ApiPermissionAssignments.ts +++ b/backend/src/data/ApiPermissionAssignments.ts @@ -1,6 +1,7 @@ import { getRepository, Repository } from "typeorm"; import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment"; import { BaseRepository } from "./BaseRepository"; +import { ApiPermissions } from "@shared/apiPermissions"; export enum ApiPermissionTypes { User = "USER", @@ -41,4 +42,17 @@ export class ApiPermissionAssignments extends BaseRepository { }, }); } + + addUser(guildId, userId, permissions: ApiPermissions[]) { + return this.apiPermissions.insert({ + guild_id: guildId, + type: ApiPermissionTypes.User, + target_id: userId, + permissions, + }); + } + + removeUser(guildId, userId) { + return this.apiPermissions.delete({ guild_id: guildId, type: ApiPermissionTypes.User, target_id: userId }); + } } diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts index 904dc618..c76e2ce7 100644 --- a/backend/src/plugins/BotControl/BotControlPlugin.ts +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -8,6 +8,11 @@ import { ReloadGlobalPluginsCmd } from "./commands/ReloadGlobalPluginsCmd"; import { ServersCmd } from "./commands/ServersCmd"; import { LeaveServerCmd } from "./commands/LeaveServerCmd"; import { ReloadServerCmd } from "./commands/ReloadServerCmd"; +import { AllowedGuilds } from "../../data/AllowedGuilds"; +import { AllowServerCmd } from "./commands/AllowServerCmd"; +import { DisallowServerCmd } from "./commands/DisallowServerCmd"; +import { AddDashboardUserCmd } from "./commands/AddDashboardUserCmd"; +import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd"; const defaultOptions = { config: { @@ -20,10 +25,21 @@ export const BotControlPlugin = zeppelinGlobalPlugin()("bo configSchema: ConfigSchema, defaultOptions, - commands: [ReloadGlobalPluginsCmd, ServersCmd, LeaveServerCmd, ReloadServerCmd], + // prettier-ignore + commands: [ + ReloadGlobalPluginsCmd, + ServersCmd, + LeaveServerCmd, + ReloadServerCmd, + AllowServerCmd, + DisallowServerCmd, + AddDashboardUserCmd, + RemoveDashboardUserCmd, + ], onLoad(pluginData) { pluginData.state.archives = new GuildArchives(0); + pluginData.state.allowedGuilds = new AllowedGuilds(); if (getActiveReload()) { const [guildId, channelId] = getActiveReload(); diff --git a/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts new file mode 100644 index 00000000..741e07f0 --- /dev/null +++ b/backend/src/plugins/BotControl/commands/AddDashboardUserCmd.ts @@ -0,0 +1,46 @@ +import { botControlCmd } from "../types"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { ApiPermissions } from "@shared/apiPermissions"; + +export const AddDashboardUserCmd = botControlCmd({ + trigger: ["add_dashboard_user"], + permission: null, + config: { + preFilters: [isOwnerPreFilter], + }, + + signature: { + guildId: ct.string(), + users: ct.user({ rest: true }), + }, + + async run({ pluginData, message: msg, args }) { + const guild = await pluginData.state.allowedGuilds.find(args.guildId); + if (!guild) { + sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + return; + } + + for (const user of args.users) { + const existingAssignment = await pluginData.state.apiPermissionAssignments.getByGuildAndUserId( + args.guildId, + user.id, + ); + if (existingAssignment) { + continue; + } + + await pluginData.state.apiPermissionAssignments.addUser(args.guildId, user.id, [ApiPermissions.EditConfig]); + } + + const userNameList = args.users.map( + user => `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`, + ); + sendSuccessMessage( + pluginData, + msg.channel, + `The following users were given dashboard access for **${guild.name}**:\n\n${userNameList}`, + ); + }, +}); diff --git a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts new file mode 100644 index 00000000..996da823 --- /dev/null +++ b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts @@ -0,0 +1,26 @@ +import { botControlCmd } from "../types"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; + +export const AllowServerCmd = botControlCmd({ + trigger: ["allow_server", "allowserver", "add_server", "addserver"], + permission: null, + config: { + preFilters: [isOwnerPreFilter], + }, + + signature: { + guildId: ct.string(), + }, + + async run({ pluginData, message: msg, args }) { + const existing = await pluginData.state.allowedGuilds.find(args.guildId); + if (existing) { + sendErrorMessage(pluginData, msg.channel, "Server is already allowed!"); + return; + } + + await pluginData.state.allowedGuilds.add(args.guildId); + sendSuccessMessage(pluginData, msg.channel, "Server is now allowed to use Zeppelin!"); + }, +}); diff --git a/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts new file mode 100644 index 00000000..fa882b8e --- /dev/null +++ b/backend/src/plugins/BotControl/commands/DisallowServerCmd.ts @@ -0,0 +1,28 @@ +import { botControlCmd } from "../types"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { noop } from "../../../utils"; + +export const DisallowServerCmd = botControlCmd({ + trigger: ["disallow_server", "disallowserver", "remove_server", "removeserver"], + permission: null, + config: { + preFilters: [isOwnerPreFilter], + }, + + signature: { + guildId: ct.string(), + }, + + async run({ pluginData, message: msg, args }) { + const existing = await pluginData.state.allowedGuilds.find(args.guildId); + if (!existing) { + sendErrorMessage(pluginData, msg.channel, "That server is not allowed in the first place!"); + return; + } + + await pluginData.state.allowedGuilds.remove(args.guildId); + await pluginData.client.leaveGuild(args.guildId).catch(noop); + sendSuccessMessage(pluginData, msg.channel, "Server removed!"); + }, +}); diff --git a/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts new file mode 100644 index 00000000..078c189c --- /dev/null +++ b/backend/src/plugins/BotControl/commands/RemoveDashboardUserCmd.ts @@ -0,0 +1,46 @@ +import { botControlCmd } from "../types"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { ApiPermissions } from "@shared/apiPermissions"; + +export const RemoveDashboardUserCmd = botControlCmd({ + trigger: ["remove_dashboard_user"], + permission: null, + config: { + preFilters: [isOwnerPreFilter], + }, + + signature: { + guildId: ct.string(), + users: ct.user({ rest: true }), + }, + + async run({ pluginData, message: msg, args }) { + const guild = await pluginData.state.allowedGuilds.find(args.guildId); + if (!guild) { + sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin"); + return; + } + + for (const user of args.users) { + const existingAssignment = await pluginData.state.apiPermissionAssignments.getByGuildAndUserId( + args.guildId, + user.id, + ); + if (!existingAssignment) { + continue; + } + + await pluginData.state.apiPermissionAssignments.removeUser(args.guildId, user.id); + } + + const userNameList = args.users.map( + user => `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`, + ); + sendSuccessMessage( + pluginData, + msg.channel, + `The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`, + ); + }, +}); diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts index a59a4d17..e7d495f8 100644 --- a/backend/src/plugins/BotControl/types.ts +++ b/backend/src/plugins/BotControl/types.ts @@ -2,6 +2,8 @@ import * as t from "io-ts"; import { tNullable } from "../../utils"; import { BasePluginType, globalCommand, globalEventListener } from "knub"; import { GuildArchives } from "../../data/GuildArchives"; +import { AllowedGuilds } from "../../data/AllowedGuilds"; +import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; export const ConfigSchema = t.type({ can_use: t.boolean, @@ -13,6 +15,8 @@ export interface BotControlPluginType extends BasePluginType { config: TConfigSchema; state: { archives: GuildArchives; + allowedGuilds: AllowedGuilds; + apiPermissionAssignments: ApiPermissionAssignments; }; }