From eaf05aebc37512036677ff8af70b7e50ce17e458 Mon Sep 17 00:00:00 2001 From: Nix Krystik Date: Wed, 26 Aug 2026 12:03:25 +0800 Subject: [PATCH] (events): postgres sucks. --- .env.sample | 2 - src/commands/index.ts | 4 ++ src/commands/optin.ts | 23 +++++++++ src/commands/optout.ts | 23 +++++++++ src/events/message/messageReactionAdd.ts | 8 ++- src/events/message/messageReactionRemove.ts | 8 ++- src/index.ts | 2 + src/types/database.d.ts | 11 ++++ src/types/index.d.ts | 1 + src/utils/database.ts | 57 ++++++++++++++++++++- 10 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 src/commands/index.ts create mode 100644 src/commands/optin.ts create mode 100644 src/commands/optout.ts create mode 100644 src/types/database.d.ts create mode 100644 src/types/index.d.ts diff --git a/.env.sample b/.env.sample index f769d89..c481edc 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,3 @@ -NODE_ENV=production - DATABASE_URL= DEV_GUILD= DISCORD_TOKEN= diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..ddc65e6 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,4 @@ +import optin from "./optin"; +import optout from "./optout"; + +export default [optin, optout]; diff --git a/src/commands/optin.ts b/src/commands/optin.ts new file mode 100644 index 0000000..7e8d7d2 --- /dev/null +++ b/src/commands/optin.ts @@ -0,0 +1,23 @@ +import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime"; +import { setUserPrivacy } from "../utils"; + +@SlashCommand( + new CommandBuilder('optin', 'Opt into the Pet the Pond event.') + .setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall) + .setContexts(Constants.InteractionContextType.Guild) + .setCommandType(Constants.ApplicationCommandType.ChatInput) +) +class OptInCommand extends Command { + cooldown = 5; + + async handleCommand(context: CommandClient, interaction: CommandInteraction) { + await setUserPrivacy(interaction.member.id, { optin: true }); + + await interaction.createMessage({ + content: "Successfully opted in. Interactions will be acknowledged.", + flags: Constants.MessageFlags.Ephemeral + }); + } +} + +export default new OptInCommand('optin'); diff --git a/src/commands/optout.ts b/src/commands/optout.ts new file mode 100644 index 0000000..a7f9660 --- /dev/null +++ b/src/commands/optout.ts @@ -0,0 +1,23 @@ +import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime"; +import { setUserPrivacy } from "../utils"; + +@SlashCommand( + new CommandBuilder('optout', 'Opt out of the Pet the Pond event.') + .setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall) + .setContexts(Constants.InteractionContextType.Guild) + .setCommandType(Constants.ApplicationCommandType.ChatInput) +) +class OptOutCommand extends Command { + cooldown = 5; + + async handleCommand(context: CommandClient, interaction: CommandInteraction) { + await setUserPrivacy(interaction.member.id, { optin: false }); + + await interaction.createMessage({ + content: "Successfully opted out. Interactions will not be acknowledged.", + flags: Constants.MessageFlags.Ephemeral + }); + } +} + +export default new OptOutCommand('optout'); diff --git a/src/events/message/messageReactionAdd.ts b/src/events/message/messageReactionAdd.ts index ee32f6b..de6aa1f 100644 --- a/src/events/message/messageReactionAdd.ts +++ b/src/events/message/messageReactionAdd.ts @@ -1,7 +1,8 @@ import { CommandClient, Constants, Event, Member, Message } from "athena-prime"; import config from '../../config'; +import { getUser, setUserXp } from "../../utils"; -// ---------- +/// ---------- class MessageReactionAddEvent extends Event { event: string = 'messageReactionAdd' as const; @@ -10,7 +11,10 @@ class MessageReactionAddEvent extends Event { const msg = await context.getMessage(message.channel.id, message.id); if (msg.author.id !== config.identifiers.targetId || emoji.id !== config.identifiers.emojiId) return; - console.log(`[MESSAGE_REACTION_ADD] - ID: ${member.id}`); + const _user = await getUser(member.id); + if (!_user.privacy.optin) return; + + await setUserXp(member.id, { current: _user.xp.current + 1 }); } } diff --git a/src/events/message/messageReactionRemove.ts b/src/events/message/messageReactionRemove.ts index a7d4fa6..66ce8b1 100644 --- a/src/events/message/messageReactionRemove.ts +++ b/src/events/message/messageReactionRemove.ts @@ -1,16 +1,20 @@ import { CommandClient, Constants, Event, Member, Message } from "athena-prime"; import config from '../../config'; +import { getUser, setUserXp } from "../../utils"; // ---------- class MessageReactionRemoveEvent extends Event { event: string = 'messageReactionRemove' as const; - async handle(context: CommandClient, message: { id: string, channel: { id: string }}, emoji: Constants.APIEmoji, user: string) { + async handle(context: CommandClient, message: { id: string, channel: { id: string }}, emoji: Constants.APIEmoji, userId: string) { const msg = await context.getMessage(message.channel.id, message.id); if (msg.author.id !== config.identifiers.targetId || emoji.id !== config.identifiers.emojiId) return; - console.log(`[MESSAGE_REACTION_REMOVE] - ID: ${user}`); + const _user = await getUser(userId); + if (!_user.privacy.optin) return; + + await setUserXp(userId, { current: _user.xp.current - 1 }); } } diff --git a/src/index.ts b/src/index.ts index 1653b32..8b9c580 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import 'dotenv/config'; import { CommandClient, Constants, Guild, Member, Message, NullCollection, User } from 'athena-prime'; +import commands from './commands'; import events from './events'; // ---------- @@ -27,6 +28,7 @@ const client: CommandClient = new CommandClient({ }, }); +commands.forEach(command => client.registerCommand(command)); events.forEach(event => client.registerEvent(event)); // ---------- diff --git a/src/types/database.d.ts b/src/types/database.d.ts new file mode 100644 index 0000000..137e954 --- /dev/null +++ b/src/types/database.d.ts @@ -0,0 +1,11 @@ +export type User = { + user_id: string; + + privacy: { + optin: boolean; + }, + + xp: { + current: number; + } +}; diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 0000000..a51157c --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1 @@ +export * from './database.d'; diff --git a/src/utils/database.ts b/src/utils/database.ts index 55b6e3f..7994219 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,7 +1,62 @@ import postgres from "postgres"; +import { User } from "../types"; // ---------- const client = postgres(process.env.DATABASE_URL); -export default {}; +const getDefaultUser = (userId: string): User => ({ + user_id: userId, + + privacy: { + optin: true, + }, + + xp: { + current: 0 + }, +}); + +export async function getUser(userId: string): Promise { + const [user] = await client<{ user_id: string; data: User }[]>` + INSERT INTO users (user_id, data) + VALUES (${userId}, ${JSON.stringify(getDefaultUser(userId))}::jsonb) + ON CONFLICT (user_id) + DO UPDATE SET user_id = EXCLUDED.user_id + RETURNING user_id, data + `; + + return user.data; +} + +export async function setUser(user: User): Promise { + await client` + INSERT INTO users (user_id, data) + VALUES (${user.user_id}, ${JSON.stringify(user)}::jsonb) + ON CONFLICT (user_id) + DO UPDATE SET data = EXCLUDED.data + `; +} + +export async function deleteUser(userId: string): Promise { + await client` + DELETE FROM users + WHERE user_id = '${userId}' + `; +} + +export async function setUserPrivacy(userId: string, privacy: User["privacy"]): Promise { + await client` + UPDATE users + SET data = jsonb_set(data, '{privacy}', ${JSON.stringify(privacy)}::jsonb) + WHERE user_id = '${userId}' + `; +} + +export async function setUserXp(userId: string, xp: User["xp"]): Promise { + await client` + UPDATE users + SET data = jsonb_set(data, '{xp}', ${JSON.stringify(xp)}::jsonb) + WHERE user_id = '${userId}' + `; +}