diff --git a/backend/src/index.ts b/backend/src/index.ts index 108a3cbe..3022f7a7 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -250,6 +250,13 @@ connect().then(async () => { async getConfig(id) { const key = id === "global" ? "global" : `guild-${id}`; + if (id !== "global") { + const allowedGuild = await allowedGuilds.find(id); + if (!allowedGuild) { + return {}; + } + } + const row = await guildConfigs.getActiveByKey(key); if (row) { try { diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts index 6770b8ef..23ba5dcc 100644 --- a/backend/src/plugins/BotControl/BotControlPlugin.ts +++ b/backend/src/plugins/BotControl/BotControlPlugin.ts @@ -19,12 +19,14 @@ import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd"; import { ServersCmd } from "./commands/ServersCmd"; import { BotControlPluginType, ConfigSchema } from "./types"; import { PerformanceCmd } from "./commands/PerformanceCmd"; +import { AddServerFromInviteCmd } from "./commands/AddServerFromInviteCmd"; const defaultOptions = { config: { can_use: false, can_eligible: false, can_performance: false, + can_add_server_from_invite: false, update_cmd: null, }, }; @@ -48,6 +50,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin()({ ListDashboardPermsCmd, EligibleCmd, PerformanceCmd, + AddServerFromInviteCmd, ], async afterLoad(pluginData) { diff --git a/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts new file mode 100644 index 00000000..c7e2f28c --- /dev/null +++ b/backend/src/plugins/BotControl/commands/AddServerFromInviteCmd.ts @@ -0,0 +1,68 @@ +import { ApiPermissions } from "@shared/apiPermissions"; +import { TextChannel } from "discord.js"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; +import { DBDateFormat, isGuildInvite, isSnowflake, resolveInvite } from "../../../utils"; +import { botControlCmd } from "../types"; +import moment from "moment-timezone"; +import { isEligible } from "../functions/isEligible"; + +export const AddServerFromInviteCmd = botControlCmd({ + trigger: ["add_server_from_invite", "allow_server_from_invite"], + permission: "can_add_server_from_invite", + + signature: { + user: ct.resolvedUser(), + inviteCode: ct.string(), + }, + + async run({ pluginData, message: msg, args }) { + const invite = await resolveInvite(pluginData.client, args.inviteCode, true); + if (!invite || !isGuildInvite(invite)) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not resolve invite"); // :D + return; + } + + const existing = await pluginData.state.allowedGuilds.find(invite.guild.id); + if (existing) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "Server is already allowed!"); + return; + } + + const { result, explanation } = await isEligible(pluginData, args.user, invite); + if (!result) { + sendErrorMessage( + pluginData, + msg.channel as TextChannel, + `Could not add server because it's not eligible: ${explanation}`, + ); + return; + } + + await pluginData.state.allowedGuilds.add(invite.guild.id, { name: invite.guild.name }); + await pluginData.state.configs.saveNewRevision(`guild-${invite.guild.id}`, "plugins: {}", msg.author.id); + + await pluginData.state.apiPermissionAssignments.addUser(invite.guild.id, args.user.id, [ + ApiPermissions.ManageAccess, + ]); + + if (args.user.id !== msg.author.id) { + // Add temporary access to user who added server + await pluginData.state.apiPermissionAssignments.addUser( + invite.guild.id, + msg.author.id, + [ApiPermissions.ManageAccess], + moment + .utc() + .add(1, "hour") + .format(DBDateFormat), + ); + } + + sendSuccessMessage( + pluginData, + msg.channel as TextChannel, + "Server was eligible and is now allowed to use Zeppelin!", + ); + }, +}); diff --git a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts index 2ff34f6d..304e7705 100644 --- a/backend/src/plugins/BotControl/commands/AllowServerCmd.ts +++ b/backend/src/plugins/BotControl/commands/AllowServerCmd.ts @@ -2,8 +2,9 @@ import { ApiPermissions } from "@shared/apiPermissions"; import { TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isOwnerPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { isSnowflake } from "../../../utils"; +import { DBDateFormat, isSnowflake } from "../../../utils"; import { botControlCmd } from "../types"; +import moment from "moment-timezone"; export const AllowServerCmd = botControlCmd({ trigger: ["allow_server", "allowserver", "add_server", "addserver"], @@ -38,7 +39,20 @@ export const AllowServerCmd = botControlCmd({ await pluginData.state.configs.saveNewRevision(`guild-${args.guildId}`, "plugins: {}", msg.author.id); if (args.userId) { - await pluginData.state.apiPermissionAssignments.addUser(args.guildId, args.userId, [ApiPermissions.EditConfig]); + await pluginData.state.apiPermissionAssignments.addUser(args.guildId, args.userId, [ApiPermissions.ManageAccess]); + } + + if (args.userId !== msg.author.id) { + // Add temporary access to user who added server + await pluginData.state.apiPermissionAssignments.addUser( + args.guildId, + msg.author.id, + [ApiPermissions.ManageAccess], + moment + .utc() + .add(1, "hour") + .format(DBDateFormat), + ); } sendSuccessMessage(pluginData, msg.channel as TextChannel, "Server is now allowed to use Zeppelin!"); diff --git a/backend/src/plugins/BotControl/commands/EligibleCmd.ts b/backend/src/plugins/BotControl/commands/EligibleCmd.ts index 522cf3a0..0be29978 100644 --- a/backend/src/plugins/BotControl/commands/EligibleCmd.ts +++ b/backend/src/plugins/BotControl/commands/EligibleCmd.ts @@ -1,10 +1,9 @@ -import { TextChannel } from "discord.js"; +import { Guild, TextChannel } from "discord.js"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; -import { resolveInvite, verboseUserMention } from "../../../utils"; +import { GuildInvite, isGuildInvite, resolveInvite, verboseUserMention } from "../../../utils"; import { botControlCmd } from "../types"; - -const REQUIRED_MEMBER_COUNT = 5000; +import { isEligible } from "../functions/isEligible"; export const EligibleCmd = botControlCmd({ trigger: ["eligible", "is_eligible", "iseligible"], @@ -16,45 +15,19 @@ export const EligibleCmd = botControlCmd({ }, async run({ pluginData, message: msg, args }) { - if ((await pluginData.state.apiPermissionAssignments.getByUserId(args.user.id)).length) { - sendSuccessMessage( - pluginData, - msg.channel as TextChannel, - `${verboseUserMention(args.user)} is an existing bot operator. They are eligible!`, - ); - return; - } - const invite = await resolveInvite(pluginData.client, args.inviteCode, true); - if (!invite || !invite.guild) { - sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not resolve server from invite"); + if (!invite || !isGuildInvite(invite)) { + sendErrorMessage(pluginData, msg.channel as TextChannel, "Could not resolve invite"); return; } - if (invite.guild.features.includes("PARTNERED")) { - sendSuccessMessage(pluginData, msg.channel as TextChannel, `Server is partnered. It is eligible!`); + const { result, explanation } = await isEligible(pluginData, args.user, invite); + + if (result) { + sendSuccessMessage(pluginData, msg.channel as TextChannel, `Server is eligible: ${explanation}`); return; } - if (invite.guild.features.includes("VERIFIED")) { - sendSuccessMessage(pluginData, msg.channel as TextChannel, `Server is verified. It is eligible!`); - return; - } - - const memberCount = invite.memberCount || 0; - if (memberCount >= REQUIRED_MEMBER_COUNT) { - sendSuccessMessage( - pluginData, - msg.channel as TextChannel, - `Server has ${memberCount} members, which is equal or higher than the required ${REQUIRED_MEMBER_COUNT}. It is eligible!`, - ); - return; - } - - sendErrorMessage( - pluginData, - msg.channel as TextChannel, - `Server **${invite.guild.name}** (\`${invite.guild.id}\`) is not eligible`, - ); + sendErrorMessage(pluginData, msg.channel as TextChannel, `Server is **NOT** eligible: ${explanation}`); }, }); diff --git a/backend/src/plugins/BotControl/functions/isEligible.ts b/backend/src/plugins/BotControl/functions/isEligible.ts new file mode 100644 index 00000000..164430f3 --- /dev/null +++ b/backend/src/plugins/BotControl/functions/isEligible.ts @@ -0,0 +1,46 @@ +import { User } from "discord.js"; +import { BotControlPluginType } from "../types"; +import { GlobalPluginData } from "knub"; +import { GuildInvite } from "../../../utils"; + +const REQUIRED_MEMBER_COUNT = 5000; + +export async function isEligible( + pluginData: GlobalPluginData, + user: User, + invite: GuildInvite, +): Promise<{ result: boolean; explanation: string }> { + if ((await pluginData.state.apiPermissionAssignments.getByUserId(user.id)).length) { + return { + result: true, + explanation: "User is an existing bot operator", + }; + } + + if (invite.guild.features.includes("PARTNERED")) { + return { + result: true, + explanation: "Server is partnered", + }; + } + + if (invite.guild.features.includes("VERIFIED")) { + return { + result: true, + explanation: "Server is verified", + }; + } + + const memberCount = invite.memberCount || 0; + if (memberCount >= REQUIRED_MEMBER_COUNT) { + return { + result: true, + explanation: `Server has ${memberCount} members, which is equal or higher than the required ${REQUIRED_MEMBER_COUNT}`, + }; + } + + return { + result: false, + explanation: "Server does not meet requirements", + }; +} diff --git a/backend/src/plugins/BotControl/types.ts b/backend/src/plugins/BotControl/types.ts index 44e57cf8..b5c00657 100644 --- a/backend/src/plugins/BotControl/types.ts +++ b/backend/src/plugins/BotControl/types.ts @@ -10,6 +10,7 @@ export const ConfigSchema = t.type({ can_use: t.boolean, can_eligible: t.boolean, can_performance: t.boolean, + can_add_server_from_invite: t.boolean, update_cmd: tNullable(t.string), }); export type TConfigSchema = t.TypeOf;