diff --git a/src/commands/index.ts b/src/commands/index.ts index ddc65e6..51791d8 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,4 +1,5 @@ +import init from "./init"; import optin from "./optin"; import optout from "./optout"; -export default [optin, optout]; +export default [init, optin, optout]; diff --git a/src/commands/init.ts b/src/commands/init.ts new file mode 100644 index 0000000..8aa17dc --- /dev/null +++ b/src/commands/init.ts @@ -0,0 +1,43 @@ +import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime"; +import config from "../config"; + +@SlashCommand( + new CommandBuilder('init', 'Event setup command.') + .setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall) + .setContexts(Constants.InteractionContextType.Guild) + .setCommandType(Constants.ApplicationCommandType.ChatInput) +) +class InitCommand extends Command { + users = ['1363132678022631428']; + + async handleCommand(context: CommandClient, interaction: CommandInteraction) { + await context.createMessage(interaction.channel.id, { + embed: { + color: 0xe77ed1, + + title: 'Pet the Pond!', + description: ` + There's a Pond that needs petting, and a leaderboard to show off your level of appreciation. + + **How does it work?** + On any message sent by <@${config.identifiers.targetId}>, react with the <:pet:448912932340891670> emoji to receive a point. + + **What do you get?** + You get appreciation and proof you appreciate Pond the most. + + **What if I don't want to be involved?** + You can opt in & out using the \`/optin\` & \`/optout\` commands. + `, + + footer: { + text: `Made by nxbat @ Archwing`, + icon_url: 'https://femboytrain.ing/nxbat-archwing' + } + } + }); + + await interaction.createMessage({ content: 'Done.', flags: Constants.MessageFlags.Ephemeral }); + } +} + +export default new InitCommand('init'); diff --git a/src/commands/optin.ts b/src/commands/optin.ts index 7e8d7d2..0be4f01 100644 --- a/src/commands/optin.ts +++ b/src/commands/optin.ts @@ -1,5 +1,5 @@ import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime"; -import { setUserPrivacy } from "../utils"; +import { getUser, setUserPrivacy } from "../utils"; @SlashCommand( new CommandBuilder('optin', 'Opt into the Pet the Pond event.') @@ -11,11 +11,13 @@ class OptInCommand extends Command { cooldown = 5; async handleCommand(context: CommandClient, interaction: CommandInteraction) { - await setUserPrivacy(interaction.member.id, { optin: true }); + await interaction.defer(true); + + const _user = await getUser(interaction.user.id); + await setUserPrivacy(_user.user_id, { optin: true }); await interaction.createMessage({ - content: "Successfully opted in. Interactions will be acknowledged.", - flags: Constants.MessageFlags.Ephemeral + content: "Successfully opted in. You'll receive points for your pets!", }); } } diff --git a/src/commands/optout.ts b/src/commands/optout.ts index a7f9660..46e0ca7 100644 --- a/src/commands/optout.ts +++ b/src/commands/optout.ts @@ -1,5 +1,5 @@ import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime"; -import { setUserPrivacy } from "../utils"; +import { getUser, setUserPrivacy } from "../utils"; @SlashCommand( new CommandBuilder('optout', 'Opt out of the Pet the Pond event.') @@ -11,11 +11,13 @@ class OptOutCommand extends Command { cooldown = 5; async handleCommand(context: CommandClient, interaction: CommandInteraction) { - await setUserPrivacy(interaction.member.id, { optin: false }); + await interaction.defer(true); + + const _user = await getUser(interaction.user.id); + await setUserPrivacy(_user.user_id, { optin: false }); await interaction.createMessage({ - content: "Successfully opted out. Interactions will not be acknowledged.", - flags: Constants.MessageFlags.Ephemeral + content: "Successfully opted out. You won't receive any points for petting :<" }); } } diff --git a/src/events/message/messageReactionAdd.ts b/src/events/message/messageReactionAdd.ts index de6aa1f..096b6ad 100644 --- a/src/events/message/messageReactionAdd.ts +++ b/src/events/message/messageReactionAdd.ts @@ -14,7 +14,7 @@ class MessageReactionAddEvent extends Event { const _user = await getUser(member.id); if (!_user.privacy.optin) return; - await setUserXp(member.id, { current: _user.xp.current + 1 }); + await setUserXp(_user.user_id, { current: _user.xp.current + 1 }); } } diff --git a/src/events/message/messageReactionRemove.ts b/src/events/message/messageReactionRemove.ts index 66ce8b1..6c7e33f 100644 --- a/src/events/message/messageReactionRemove.ts +++ b/src/events/message/messageReactionRemove.ts @@ -12,9 +12,9 @@ class MessageReactionRemoveEvent extends Event { if (msg.author.id !== config.identifiers.targetId || emoji.id !== config.identifiers.emojiId) return; const _user = await getUser(userId); - if (!_user.privacy.optin) return; + if (!_user.privacy.optin || _user.xp.current <= 0) return; - await setUserXp(userId, { current: _user.xp.current - 1 }); + await setUserXp(_user.user_id, { current: _user.xp.current - 1 }); } } diff --git a/src/index.ts b/src/index.ts index 8b9c580..33769e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,8 +28,8 @@ const client: CommandClient = new CommandClient({ }, }); -commands.forEach(command => client.registerCommand(command)); -events.forEach(event => client.registerEvent(event)); +commands.forEach(command => client.registerCommand(command, true)); +events.forEach(event => client.registerEvent(event, true)); // ---------- diff --git a/src/utils/database.ts b/src/utils/database.ts index 7994219..df0f529 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,38 +1,50 @@ import postgres from "postgres"; import { User } from "../types"; -// ---------- - const client = postgres(process.env.DATABASE_URL); 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 + let [user] = await client<{ user_id: string; data: User }[]>` + SELECT user_id, data + FROM users + WHERE user_id = ${userId} `; + if (!user) { + const defaultUser = getDefaultUser(userId); + + [user] = await client<{ user_id: string; data: User }[]>` + INSERT INTO users (user_id, data) + VALUES (${userId}, ${client.json(defaultUser)}) + ON CONFLICT (user_id) + DO NOTHING + RETURNING user_id, data + `; + + if (!user) { + [user] = await client<{ user_id: string; data: User }[]>` + SELECT user_id, data FROM users WHERE user_id = ${userId} + `; + } + } + 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) + VALUES (${user.user_id}, ${client.json(user)}) ON CONFLICT (user_id) DO UPDATE SET data = EXCLUDED.data `; @@ -41,22 +53,22 @@ export async function setUser(user: User): Promise { export async function deleteUser(userId: string): Promise { await client` DELETE FROM users - WHERE user_id = '${userId}' + 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}' + SET data = jsonb_set(data, '{privacy}', ${client.json(privacy)}) + 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}' + SET data = jsonb_set(data, '{xp}', ${client.json(xp)}) + WHERE user_id = ${userId} `; }