From 75b73f0e8c2ae7f7c24f6a5e9b1e53ec0a08fd3b Mon Sep 17 00:00:00 2001 From: Dominic Ruggiero Date: Fri, 17 Jan 2025 19:10:28 -0500 Subject: [PATCH] a minimal (but functional) ban system --- package.json | 1 + pnpm-lock.yaml | 16 ++++ prisma/schema.prisma | 11 +++ src/modules/bans.ts | 171 ++++++++++++++++++++++++++++++++++++++++ src/modules/devtools.ts | 6 +- src/modules/order.ts | 18 +++++ 6 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/modules/bans.ts diff --git a/package.json b/package.json index 2187b26..02e8bd7 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@prisma/client": "^6.1.0", "@sentry/node": "^7.91.0", "aws-sdk": "^2.1565.0", + "chrono-node": "^2.7.7", "discord.js": "^14.13.0", "express": "^4.20.0", "prom-client": "^15.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ddd1026..6f5d0e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: aws-sdk: specifier: ^2.1565.0 version: 2.1565.0 + chrono-node: + specifier: ^2.7.7 + version: 2.7.7 discord.js: specifier: ^14.13.0 version: 14.13.0 @@ -219,6 +222,10 @@ packages: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + chrono-node@2.7.7: + resolution: {integrity: sha512-p3S7gotuTPu5oqhRL2p1fLwQXGgdQaRTtWR3e8Di9P1Pa9mzkK5DWR5AWBieMUh2ZdOnPgrK+zCrbbtyuA+D/Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -234,6 +241,9 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -858,6 +868,10 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.1 + chrono-node@2.7.7: + dependencies: + dayjs: 1.11.13 + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -868,6 +882,8 @@ snapshots: cookie@0.6.0: {} + dayjs@1.11.13: {} + debug@2.6.9: dependencies: ms: 2.0.0 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7b9b412..524a205 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -64,3 +64,14 @@ model application { active Boolean @default(true) createdAt DateTime @default(now()) } + +model ban { + id Int @id @default(autoincrement()) + user String + reason String + message String + endAt DateTime + appealAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/src/modules/bans.ts b/src/modules/bans.ts new file mode 100644 index 0000000..fafef78 --- /dev/null +++ b/src/modules/bans.ts @@ -0,0 +1,171 @@ +import { ban } from "@prisma/client"; +import bot, { prisma } from ".."; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + ModalBuilder, + StringSelectMenuBuilder, + TextInputBuilder, + TextInputStyle, + UserSelectMenuBuilder, +} from "discord.js"; +import * as chrono from "chrono-node"; + +const bans: ban[] = []; +//load active bans +const loadActiveBans = async () => { + const beans = await prisma.ban.findMany({ + where: { + endAt: { gt: new Date() }, + }, + }); + bans.push(...beans); + console.log(bans); +}; +loadActiveBans(); + +export const userActiveBans = (userId: string) => { + return bans.filter((ban) => ban.user === userId && ban.endAt > new Date()); +}; + +const addBan = async ( + userId: string, + reason: string, + message: string, + endAt: Date, + appealAt?: Date +) => { + const ban = await prisma.ban.create({ + data: { + user: userId, + reason, + message, + endAt, + appealAt, + }, + }); + bans.push(ban); +}; + +bot.registerButton("devtools:manage-bans", async (interaction) => { + const a1 = new ActionRowBuilder().addComponents([ + new UserSelectMenuBuilder().setCustomId("devtools:manage-bans:user"), + ]); + return await interaction.update({ + components: [a1], + }); +}); + +bot.registerUserSelectMenu("devtools:manage-bans:user", async (interaction) => { + const user = interaction.users.first()!; + const bans = userActiveBans(user.id); + const embed = new EmbedBuilder() + .setTitle(`Bans for ${user.tag}`) + .setDescription( + bans.length > 0 ? "User is currently banned" : "User is not banned" + ) + .addFields( + bans.map((ban) => ({ + name: ban.id.toString(), + value: `Reason: ${ban.reason}\nMessage: ${ + ban.message + }\nEnd at: \nAppeal at: `, + })) + ); + const a1 = new ActionRowBuilder().addComponents([ + new StringSelectMenuBuilder() + .setCustomId("devtools:manage-bans:select") + .setPlaceholder("Select a ban") + .addOptions( + bans.map((ban) => ({ + label: `Ban ${ban.id}`, + value: ban.id.toString(), + })) + ) + .setDisabled(bans.length == 0), + ]); + if (bans.length == 0) + a1.components[0].addOptions({ + label: "No bans", + value: "no-bans", + }); + const a2 = new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setCustomId(`devtools:manage-bans:create:${user.id}`) + .setLabel("Create ban") + .setStyle(ButtonStyle.Danger) + .setEmoji("🔨"), + ]); + return await interaction.update({ + embeds: [embed], + components: [a1, a2], + }); +}); + +bot.registerButton(/devtools:manage-bans:create:(.+)/, async (interaction) => { + const userId = interaction.customId.split(":")[3]; + const modal = new ModalBuilder() + .setTitle("Create ban") + .setCustomId(`devtools:manage-bans:create:${userId}:modal`) + .addComponents([ + new ActionRowBuilder().addComponents([ + new TextInputBuilder() + .setCustomId("reason") + .setLabel("reason") + .setStyle(TextInputStyle.Paragraph), + ]), + new ActionRowBuilder().addComponents([ + new TextInputBuilder() + .setCustomId("message") + .setLabel("message") + .setStyle(TextInputStyle.Paragraph), + ]), + new ActionRowBuilder().addComponents([ + new TextInputBuilder() + .setCustomId("endAt") + .setLabel("end at") + .setStyle(TextInputStyle.Short), + ]), + new ActionRowBuilder().addComponents([ + new TextInputBuilder() + .setCustomId("appealAt") + .setLabel("appeal at") + .setStyle(TextInputStyle.Short) + .setRequired(false), + ]), + ]); + return await interaction.showModal(modal); +}); + +bot.registerModal( + /devtools:manage-bans:create:(.+):modal/, + async (interaction) => { + const userId = interaction.customId.split(":")[3]; + const reason = interaction.fields.getTextInputValue("reason"); + const message = interaction.fields.getTextInputValue("message"); + const endAtInput = interaction.fields.getTextInputValue("endAt"); + const appealAtInput = interaction.fields.getTextInputValue("appealAt"); + + const endAt = chrono.parseDate(endAtInput); + const appealAt = chrono.parseDate(appealAtInput); + + if (!endAt || (appealAtInput && !appealAt)) + return interaction.reply({ + content: + 'Invalid date (s).\nIf you\'re trying to specify a relative date start with "in" like "in 2 months".', + ephemeral: true, + }); + + await addBan(userId, reason, message, endAt, appealAt ?? undefined); + return await interaction.reply({ + content: "Ban added", + ephemeral: true, + }); + } +); diff --git a/src/modules/devtools.ts b/src/modules/devtools.ts index 6adbca7..61ea3bc 100644 --- a/src/modules/devtools.ts +++ b/src/modules/devtools.ts @@ -49,10 +49,14 @@ messagesClient.addGlobalCommand( .setCustomId("devtools:dm") .setLabel("Message user") .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId("devtools:manage-bans") + .setLabel("manage bans") + .setStyle(ButtonStyle.Secondary), new ButtonBuilder() .setCustomId("devtools:edit-order") .setLabel("edit order") - .setStyle(ButtonStyle.Primary), + .setStyle(ButtonStyle.Secondary), ]); const a3 = new ActionRowBuilder().addComponents([ new ButtonBuilder() diff --git a/src/modules/order.ts b/src/modules/order.ts index fb155b1..2a509ff 100644 --- a/src/modules/order.ts +++ b/src/modules/order.ts @@ -3,6 +3,7 @@ import bot from ".."; import { closed, closedReason } from "./closed"; import { createOrder, getActiveOrdersForUser } from "../orders/cache"; +import { userActiveBans } from "./bans"; bot.addGlobalCommand( new SlashCommandBuilder() @@ -83,6 +84,23 @@ bot.addGlobalCommand( ], }); + const bans = userActiveBans(interaction.user.id); + if (bans.length > 0) + return interaction.editReply({ + embeds: [ + { + title: "You are banned from ordering!", + description: `${ + bans[0].message + }\n\nYou may appeal your ban starting . Otherwise, it will expire `, + }, + ], + }); + await createOrder( orderText, interaction.guild.id,