diff --git a/.eslintrc.json b/.eslintrc.json index 49192e8..31ce605 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,9 @@ "issue_number", "issue_comment", "serverId_level_roleId", - "serverId_roleId" + "serverId_roleId", + "invites_disabled_until", + "dms_disabled_until" ] } ] diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8275dce..8d57d95 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -90,3 +90,12 @@ model birthdays { @@unique([serverId, userId], map: "serverId_userId") } + +model security { + id String @id @default(auto()) @map("_id") @db.ObjectId + serverId String + lockDms Boolean @default(false) + lockInvites Boolean @default(false) + + @@unique([serverId], map: "serverId") +} diff --git a/src/commands/secure.ts b/src/commands/secure.ts new file mode 100644 index 0000000..4a99560 --- /dev/null +++ b/src/commands/secure.ts @@ -0,0 +1,101 @@ +import { PermissionFlagsBits, SlashCommandBuilder } from "discord.js"; + +import { Command } from "../interfaces/Command"; +import { errorHandler } from "../utils/errorHandler"; + +export const secure: Command = { + data: new SlashCommandBuilder() + .setName("secure") + .setDescription( + "Toggles the feature to keep dms/invites locked or unlocked" + ) + .setDMPermission(false) + .addBooleanOption((option) => + option + .setName("invites") + .setDescription("Keep invites locked down?") + .setRequired(true) + ) + .addBooleanOption((option) => + option + .setName("dms") + .setDescription("Keep DMs locked down?") + .setRequired(true) + ), + run: async (bot, interaction) => { + try { + const { guild, member } = interaction; + const lockInvites = interaction.options.getBoolean("invites", true); + const lockDms = interaction.options.getBoolean("dms", true); + + if ( + ![ + PermissionFlagsBits.Administrator, + PermissionFlagsBits.KickMembers, + PermissionFlagsBits.BanMembers, + PermissionFlagsBits.ManageGuild + ].some((perm) => member.permissions.has(perm)) + ) { + await interaction.editReply({ + content: "You do not have permission to use this command." + }); + return; + } + + const botMember = await guild.members + .fetch(bot.user?.id || "oopsie") + .catch(() => null); + + if ( + !botMember || + ![ + PermissionFlagsBits.Administrator, + PermissionFlagsBits.KickMembers, + PermissionFlagsBits.BanMembers, + PermissionFlagsBits.ManageGuild + ].some((perm) => botMember.permissions.has(perm)) + ) { + await interaction.editReply({ + content: "I do not have the correct permissions to do this." + }); + return; + } + + const date = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); + + await bot.db.security.upsert({ + where: { serverId: guild.id }, + update: { lockDms, lockInvites }, + create: { lockDms, lockInvites, serverId: guild.id } + }); + + await fetch( + `https://discord.com/api/v10/guilds/${guild.id}/incident-actions`, + { + method: "PUT", + headers: { + Authorization: `Bot ${bot.env.token}`, + "content-type": "application/json" + }, + body: JSON.stringify({ + dms_disabled_until: lockDms ? date : null, + invites_disabled_until: lockInvites ? date : null + }) + } + ).catch( + async (e) => + await errorHandler(bot, `incident-actions for ${guild.id}`, e) + ); + await interaction.editReply({ + content: `Security options have been updated.\nInvites are ${ + lockInvites ? "disabled" : "enabled" + }.\nDMs are ${lockDms ? "disabled" : "enabled"}.` + }); + } catch (err) { + const id = await errorHandler(bot, "secure command", err); + await interaction.editReply({ + content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\`` + }); + } + } +}; diff --git a/src/events/client/onReady.ts b/src/events/client/onReady.ts index 42dd95f..7c24852 100644 --- a/src/events/client/onReady.ts +++ b/src/events/client/onReady.ts @@ -1,6 +1,7 @@ import { scheduleJob } from "node-schedule"; import { ExtendedClient } from "../../interfaces/ExtendedClient"; +import { maintainSecurity } from "../../modules/maintainSecurity"; import { postBirthdays } from "../../modules/postBirthdays"; import { registerCommands } from "../../utils/registerCommands"; import { sendDebugMessage } from "../../utils/sendDebugMessage"; @@ -16,4 +17,10 @@ export const onReady = async (bot: ExtendedClient) => { // Daily at 9am PST scheduleJob("birthdays", "0 9 * * *", async () => await postBirthdays(bot)); + // Daily at midnight and noon + scheduleJob( + "birthdays", + "0 0,12 * * *", + async () => await maintainSecurity(bot) + ); }; diff --git a/src/modules/maintainSecurity.ts b/src/modules/maintainSecurity.ts new file mode 100644 index 0000000..3f69b3c --- /dev/null +++ b/src/modules/maintainSecurity.ts @@ -0,0 +1,36 @@ +import { ExtendedClient } from "../interfaces/ExtendedClient"; +import { errorHandler } from "../utils/errorHandler"; + +/** + * Loops through the list of configured servers to lock security for, and processes the + * API calls. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const maintainSecurity = async (bot: ExtendedClient) => { + try { + const records = await bot.db.security.findMany(); + const date = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); + for (const record of records) { + await fetch( + `https://discord.com/api/v10/guilds/${record.serverId}/incident-actions`, + { + method: "PUT", + headers: { + Authorization: `Bot ${bot.env.token}`, + "content-type": "application/json" + }, + body: JSON.stringify({ + dms_disabled_until: record.lockDms ? date : null, + invites_disabled_until: record.lockInvites ? date : null + }) + } + ).catch( + async (e) => + await errorHandler(bot, `incident-actions for ${record.serverId}`, e) + ); + } + } catch (err) { + await errorHandler(bot, "maintain security", err); + } +};