From 9633c836a9fe635c23aa1e6fe52a146a1244e7ef Mon Sep 17 00:00:00 2001 From: Kath Date: Tue, 30 Jul 2024 21:26:44 +0800 Subject: [PATCH] automod permit --- .gitignore | 3 +- config.example.json | 4 +- index.ts | 4 +- package.json | 4 + pnpm-lock.yaml | 36 +++++++ src/commands/automod.ts | 96 +++++++++++++++++++ src/events/ready.ts | 7 +- src/functions/CheckPermits.ts | 25 +++++ .../{deployCommands.ts => DeployCommands.ts} | 0 .../{deployEvents.ts => DeployEvents.ts} | 0 10 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 src/commands/automod.ts create mode 100644 src/functions/CheckPermits.ts rename src/functions/{deployCommands.ts => DeployCommands.ts} (100%) rename src/functions/{deployEvents.ts => DeployEvents.ts} (100%) diff --git a/.gitignore b/.gitignore index e54f688..f1f792c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /config.json node_modules/ logs/ -build/ \ No newline at end of file +build/ +data/ \ No newline at end of file diff --git a/config.example.json b/config.example.json index 093ba23..0c6f6df 100644 --- a/config.example.json +++ b/config.example.json @@ -4,5 +4,7 @@ "teamRole": "TEAM_ROLE_ID", "devRole": "DEV_ROLE_ID", "contributorsRole": "CONTRIBUTORS_ROLE_ID", - "supportCategory": "SUPPORT_CATEGORY_ID" + "supportCategory": "SUPPORT_CATEGORY_ID", + "autoModBypassRole": "AUTOMOD_BYPASS_ROLE_ID", + "serverId": "SERVER_ID" } diff --git a/index.ts b/index.ts index d7e3d7d..f88e837 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,5 @@ import { Client, Events, GatewayIntentBits } from 'discord.js'; -import deployCommands from './src/functions/deployCommands'; +import DeployCommands from './src/functions/DeployCommands'; import { execute } from './src/events/ready'; import { token } from './config.json'; @@ -12,7 +12,7 @@ const client: Client = new Client({ ] }); -deployCommands(client); +DeployCommands(client); client.on(Events.ClientReady, () => { execute(client); diff --git a/package.json b/package.json index 874c998..3a646e5 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,16 @@ "discord.js": "^14.15.3", "discord.js-docs": "^0.3.0", "mongoose": "^8.5.1", + "ms": "^2.1.3", + "node-cron": "^3.0.3", "winston": "^3.13.1" }, "devDependencies": { "@eslint/js": "^9.8.0", "@types/eslint": "^9.6.0", + "@types/ms": "^0.7.34", "@types/node": "^22.0.0", + "@types/node-cron": "^3.0.11", "eslint": "^9.8.0", "eslint-config-prettier": "^9.1.0", "globals": "^15.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 842f523..2514f76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,12 @@ importers: mongoose: specifier: ^8.5.1 version: 8.5.1 + ms: + specifier: ^2.1.3 + version: 2.1.3 + node-cron: + specifier: ^3.0.3 + version: 3.0.3 winston: specifier: ^3.13.1 version: 3.13.1 @@ -27,9 +33,15 @@ importers: '@types/eslint': specifier: ^9.6.0 version: 9.6.0 + '@types/ms': + specifier: ^0.7.34 + version: 0.7.34 '@types/node': specifier: ^22.0.0 version: 22.0.0 + '@types/node-cron': + specifier: ^3.0.11 + version: 3.0.11 eslint: specifier: ^9.8.0 version: 9.8.0 @@ -297,6 +309,12 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + + '@types/node-cron@3.0.11': + resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==} + '@types/node@22.0.0': resolution: {integrity: sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==} @@ -781,6 +799,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-cron@3.0.3: + resolution: {integrity: sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==} + engines: {node: '>=6.0.0'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -980,6 +1002,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -1224,6 +1250,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/ms@0.7.34': {} + + '@types/node-cron@3.0.11': {} + '@types/node@22.0.0': dependencies: undici-types: 6.11.1 @@ -1755,6 +1785,10 @@ snapshots: natural-compare@1.4.0: {} + node-cron@3.0.3: + dependencies: + uuid: 8.3.2 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -1914,6 +1948,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@8.3.2: {} + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} diff --git a/src/commands/automod.ts b/src/commands/automod.ts new file mode 100644 index 0000000..f5d1bd7 --- /dev/null +++ b/src/commands/automod.ts @@ -0,0 +1,96 @@ +import { ChatInputCommandInteraction, GuildMemberRoleManager, SlashCommandBuilder, EmbedBuilder } from 'discord.js'; +import { contributorsRole, teamRole, devRole, autoModBypassRole } from '../../config.json'; +import { readFileSync, writeFileSync } from 'fs'; +import ms from 'ms'; + +export const data = new SlashCommandBuilder() + .setName('automod') + .setDescription('Manage AutoMod') + .addSubcommand((subcommand) => + subcommand + .setName('permit') + .setDescription('Allow someone to bypass automod') + .addUserOption((option) => option.setName('user').setDescription('The user to permit').setRequired(true)) + .addStringOption((option) => + option.setName('time').setDescription('The Message link to reply with the tag').setRequired(false) + ) + ); + +export interface Permit { + id: string; + removeTime: number; +} + +export async function execute(interaction: ChatInputCommandInteraction): Promise { + try { + if (!interaction.member || !interaction.guild) return; + const subCommand = interaction.options.getSubcommand(); + const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((role) => role.id); + switch (subCommand) { + case 'permit': { + if (!memberRoles.some((role) => [contributorsRole, teamRole, devRole].includes(role))) { + await interaction.reply({ + content: 'You do not have permission to use this command', + ephemeral: true + }); + } + const permitData = readFileSync('data/permit.json'); + if (!permitData) { + await interaction.reply({ + content: 'The linked data file does not exist. Please contact an administrator.' + }); + } + + const permit = JSON.parse(permitData.toString()); + if (!permit) { + await interaction.reply({ + content: 'The linked data file is malformed. Please contact an administrator.' + }); + } + const user = interaction.options.getUser('user'); + const time = interaction.options.getString('time') || '10m'; + if (!user) { + await interaction.reply({ + content: 'Please provide a valid user', + ephemeral: true + }); + return; + } + const guildUser = await interaction.guild.members.fetch(user.id); + const msTime = ms(time); + const removeTime = Math.floor((new Date().getTime() + msTime) / 1000); + permit.push({ + id: user.id, + removeTime + }); + writeFileSync('data/permit.json', JSON.stringify(permit)); + guildUser.roles.add(autoModBypassRole); + const embed = new EmbedBuilder() + .setTitle('User permitted') + .setDescription(`User ${user} has been permitted to ()`); + await interaction.reply({ embeds: [embed] }); + break; + } + default: { + const embed = new EmbedBuilder() + .setTitle('Invalid subcommand') + .setDescription('Please provide a valid subcommand'); + await interaction.reply({ embeds: [embed] }); + } + } + } catch (error) { + // eslint-disable-next-line no-console + console.log(error); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + content: 'Something went wrong. Please try again later.', + ephemeral: true + }); + return; + } + await interaction.reply({ + content: 'Something went wrong. Please try again later.', + ephemeral: true + }); + } +} diff --git a/src/events/ready.ts b/src/events/ready.ts index 7158d0d..9247949 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,13 +1,16 @@ -import deployEvents from '../functions/deployEvents'; +import CheckPermits from '../functions/CheckPermits'; +import DeployEvents from '../functions/DeployEvents'; import { eventMessage } from '../functions/logger'; import { connectDB } from '../functions/mongo'; import { Client } from 'discord.js'; +import cron from 'node-cron'; export function execute(client: Client): void { try { eventMessage(`Logged in as ${client.user?.username} (${client.user?.id})!`); - deployEvents(client); + DeployEvents(client); connectDB(); + cron.schedule(`* * * * *`, () => CheckPermits(client)); } catch (error) { // eslint-disable-next-line no-console console.log(error); diff --git a/src/functions/CheckPermits.ts b/src/functions/CheckPermits.ts new file mode 100644 index 0000000..22d0fe5 --- /dev/null +++ b/src/functions/CheckPermits.ts @@ -0,0 +1,25 @@ +import { serverId, autoModBypassRole } from '../../config.json'; +import { readFileSync, writeFileSync } from 'fs'; +import { Permit } from '../commands/automod'; +import { Client } from 'discord.js'; + +export default async function CheckPermits(client: Client) { + const guild = await client.guilds.fetch(serverId); + const permitData = readFileSync('data/permit.json'); + if (!permitData) return; + const permit = JSON.parse(permitData.toString()); + if (!permit) return; + + const currentTime = Math.floor(new Date().getTime() / 1000); + permit.forEach((user: Permit) => { + if (user.removeTime < currentTime) { + const guildMember = guild.members.cache.get(user.id); + if (guildMember) { + guildMember.roles.remove(autoModBypassRole); + } + permit.splice(permit.indexOf(user), 1); + } + }); + + writeFileSync('data/permit.json', JSON.stringify(permit)); +} diff --git a/src/functions/deployCommands.ts b/src/functions/DeployCommands.ts similarity index 100% rename from src/functions/deployCommands.ts rename to src/functions/DeployCommands.ts diff --git a/src/functions/deployEvents.ts b/src/functions/DeployEvents.ts similarity index 100% rename from src/functions/deployEvents.ts rename to src/functions/DeployEvents.ts