From 03529bc548017402114d92f32649768a0de087c8 Mon Sep 17 00:00:00 2001 From: Robin-Sch Date: Thu, 4 Apr 2024 21:49:42 +0200 Subject: [PATCH] levels --- .../workflows/build-and-push-beta-image.yml | 2 +- .../workflows/build-and-push-docker-image.yml | 4 +- package.json | 14 ++--- src/commands/level.ts | 21 +++++++ src/commands/open.ts | 3 +- src/events/messageCreate.ts | 60 ++++++++++++------- src/utils/levels.ts | 3 + src/utils/postgres.ts | 29 +++++---- 8 files changed, 92 insertions(+), 44 deletions(-) create mode 100644 src/commands/level.ts create mode 100644 src/utils/levels.ts diff --git a/.github/workflows/build-and-push-beta-image.yml b/.github/workflows/build-and-push-beta-image.yml index 4101fc1..1b782dc 100644 --- a/.github/workflows/build-and-push-beta-image.yml +++ b/.github/workflows/build-and-push-beta-image.yml @@ -33,4 +33,4 @@ jobs: context: . push: true tags: | - ghcr.io/Robin-Sch/discord-template:beta \ No newline at end of file + ghcr.io/robin-sch/discord-template:beta \ No newline at end of file diff --git a/.github/workflows/build-and-push-docker-image.yml b/.github/workflows/build-and-push-docker-image.yml index 8a33d60..c1ab64b 100644 --- a/.github/workflows/build-and-push-docker-image.yml +++ b/.github/workflows/build-and-push-docker-image.yml @@ -41,5 +41,5 @@ jobs: context: . push: true tags: | - ghcr.io/Robin-Sch/discord-template:latest - ghcr.io/Robin-Sch/discord-template:${{ steps.set-version.outputs.result }} \ No newline at end of file + ghcr.io/robin-sch/discord-template:latest + ghcr.io/robin-sch/discord-template:${{ steps.set-version.outputs.result }} \ No newline at end of file diff --git a/package.json b/package.json index 2e3a404..89979f3 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,16 @@ "description": "A template for your Discord bot ", "main": "src/index.ts", "scripts": { - "start": "npm run build && node dist/index.js", - "build": "tsc", - "format": "npx prettier --write \"src/**/*.ts\" \"./**/*.json\"" + "build": "npx tsc", + "start": "npm run build && node dist/index.js", + "eslint": "npx eslint src" }, "keywords": [], "author": "Robin", "license": "MIT", "dependencies": { "@types/node": "^20.11.20", - "discord.js": "^14.14.1", + "discord.js": "^14.12.1", "pg": "^8.11.3", "typescript": "^5.3.3", "uuid": "^9.0.1" @@ -21,9 +21,9 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.0.2", "@typescript-eslint/parser": "^7.0.2", - "eslint": "^8.56.0", - "eslint-config-prettier": "^8.10.0", - "eslint-plugin-prettier": "^5.1.3", + "eslint": "^8.56.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-prettier": "^5.1.3", "prettier": "^3.2.5" } } diff --git a/src/commands/level.ts b/src/commands/level.ts new file mode 100644 index 0000000..c4c8823 --- /dev/null +++ b/src/commands/level.ts @@ -0,0 +1,21 @@ +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; + +import { getXpNeeded } from '../utils/levels'; +import { query } from '../utils/postgres'; + +export default { + data: new SlashCommandBuilder().setName('level').setDescription('Check your level'), + + category: 'General', + + async execute(interaction: ChatInputCommandInteraction) { + if (!interaction.inCachedGuild()) return interaction.client.error.ONLY_IN_GUILD(interaction); + const { rows: levelRows } = await query('SELECT xp, level FROM levels WHERE member = $1 AND guild = $2;', [interaction.member.id, interaction.guildId]); + + const xpNeeded = getXpNeeded(levelRows[0].level); + + return interaction.editReply({ + content: `You are currently level ${levelRows[0].level} and have ${levelRows[0].xp} xp! You need ${xpNeeded - levelRows[0].xp} more xp for the next level!`, + }); + }, +}; diff --git a/src/commands/open.ts b/src/commands/open.ts index faebc9e..fc1523e 100644 --- a/src/commands/open.ts +++ b/src/commands/open.ts @@ -25,7 +25,8 @@ export default { if (possibleChannel?.type === ChannelType.GuildText) channel = possibleChannel; } catch (e) { // Channel is manually deleted - await closeTicket(currentSupportRows[0].channel, interaction.guildId, channel); + const result = await closeTicket(currentSupportRows[0].channel, interaction.guildId, channel); + if (!result.success) console.log('ERROR: something went wrong while deleting a stale support ticket!'); } } diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index eddbec3..e3dbcc2 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -3,6 +3,7 @@ import { ChannelType, Events, Message, OverwriteType, PermissionFlagsBits, TextC import { createEmbed } from '../utils/embed'; import { query } from '../utils/postgres'; import { closeTicket } from '../utils/tickets'; +import { getXpNeeded } from '../utils/levels'; export default { name: Events.MessageCreate, @@ -10,8 +11,36 @@ export default { async execute(message: Message) { const client = message.client; - if (message.channel.type === ChannelType.DM && !message.author.bot && client.mailguild) { - const { rows: currentMailRows } = await query('SELECT channel FROM mails WHERE member = $1;', [message.author.id]); + const user = message.author; + const userID = user.id; + + if (message.channel.type === ChannelType.GuildText && !message.author.bot) { + if (client.mailguild) { + const { rows: currentMailRows } = await query('SELECT member FROM mails WHERE channel = $1;', [message.channelId]); + if (currentMailRows.length > 0) { + try { + const member = await client.users.fetch(currentMailRows[0].member); + await member.send(message.content); + await message.react('✅'); + } catch (e) { + await message.react('❌'); + } + } + } + await query('INSERT INTO guilds (id) VALUES ($1) ON CONFLICT DO NOTHING;', [message.guildId]); + await query('INSERT INTO users (id) VALUES ($1) ON CONFLICT DO NOTHING;', [userID]); + + const { rows: levelRows } = await query('UPDATE levels SET xp = xp + 1 WHERE member = $1 AND guild = $2 RETURNING xp, level;', [userID, message.guildId]); + if (levelRows.length === 0) await query('INSERT INTO levels (guild, member, xp, level) VALUES ($1,$2,1,1);', [message.guildId, userID]); + else { + const xpNeeded = getXpNeeded(levelRows[0].level); + if (levelRows[0].xp >= xpNeeded) { + await query('UPDATE levels SET xp = xp - $1, level = level + 1 WHERE member = $2 AND guild = $3;', [xpNeeded, userID, message.guildId]); + message.channel.send(`${user} just leveled up to level ${levelRows[0].level + 1}!`).catch(); + } + } + } else if (message.channel.type === ChannelType.DM && !message.author.bot && client.mailguild) { + const { rows: currentMailRows } = await query('SELECT channel FROM mails WHERE member = $1;', [userID]); let channel: TextChannel | undefined; if (currentMailRows.length !== 0) { @@ -20,13 +49,15 @@ export default { if (possibleChannel?.type === ChannelType.GuildText) channel = possibleChannel; } catch (e) { // Channel is manually deleted - await closeTicket(currentMailRows[0].channel, client.mailguild.id, channel); + const result = await closeTicket(currentMailRows[0].channel, client.mailguild.id, channel); + if (!result.success) console.log('ERROR: something went wrong while deleting a stale mail ticket!'); + else if (result.embed && result.memberID) await message.author.send({ embeds: [result.embed] }).catch(); } } if (!channel) { channel = await client.mailguild.channels.create({ - name: message.author.username + '-mail', + name: user.username + '-mail', type: ChannelType.GuildText, parent: client.mailcategory, permissionOverwrites: [ @@ -41,11 +72,11 @@ export default { const embed = await createEmbed(); embed.setDescription('Your mail ticket has been created. To reply, just send a message in my DM.'); embed.setFooter({ text: 'Messages with a ✅ have been successfully sent!' }); - await message.author.send({ embeds: [embed] }).catch(); + await user.send({ embeds: [embed] }).catch(); - await query('INSERT INTO users (id) VALUES ($1) ON CONFLICT DO NOTHING;', [message.author.id]); + await query('INSERT INTO users (id) VALUES ($1) ON CONFLICT DO NOTHING;', [userID]); - await query('INSERT INTO mails (member, channel) VALUES ($1, $2);', [message.author.id, channel.id]); + await query('INSERT INTO mails (member, channel) VALUES ($1, $2);', [userID, channel.id]); } let webhook = (await channel.fetchWebhooks()).find((webhook) => webhook.name === 'mail'); @@ -53,21 +84,10 @@ export default { await webhook.send({ content: message.content, - username: message.author.username, - avatarURL: message.author.displayAvatarURL(), + username: user.username, + avatarURL: user.displayAvatarURL(), }); await message.react('✅'); - } else if (message.channel.type === ChannelType.GuildText && !message.author.bot && client.mailguild) { - const { rows: currentMailRows } = await query('SELECT member FROM mails WHERE channel = $1;', [message.channelId]); - if (currentMailRows.length > 0) { - try { - const member = await client.users.fetch(currentMailRows[0].member); - await member.send(message.content); - await message.react('✅'); - } catch (e) { - await message.react('❌'); - } - } } }, }; diff --git a/src/utils/levels.ts b/src/utils/levels.ts new file mode 100644 index 0000000..6c90578 --- /dev/null +++ b/src/utils/levels.ts @@ -0,0 +1,3 @@ +export function getXpNeeded(level: number) { + return 5 * level ** 2 + 50 * level; +} diff --git a/src/utils/postgres.ts b/src/utils/postgres.ts index 33dac7c..4512e26 100644 --- a/src/utils/postgres.ts +++ b/src/utils/postgres.ts @@ -149,12 +149,12 @@ export async function start() { ); await query( `CREATE TABLE if not exists supports ( + guild varchar(64), member varchar(64), channel varchar(64), - guild varchar(64), - PRIMARY KEY(member, guild), - CONSTRAINT support_member FOREIGN KEY(member) REFERENCES users(id), - CONSTRAINT support_guild FOREIGN KEY(guild) REFERENCES guilds(id) + PRIMARY KEY(guild, member), + CONSTRAINT support_guild FOREIGN KEY(guild) REFERENCES guilds(id), + CONSTRAINT support_member FOREIGN KEY(member) REFERENCES users(id) );`, [] ); @@ -179,13 +179,16 @@ export async function start() { );`, [] ); - // await query( - // `CREATE TABLE if not exists levels ( - // guild varchar(64), - // member varchar(64), - // xp int, - // level int - // );`, - // [] - // ); + await query( + `CREATE TABLE if not exists levels ( + guild varchar(64), + member varchar(64), + xp smallint, + level smallint, + PRIMARY KEY(guild, member), + CONSTRAINT level_guild FOREIGN KEY(guild) REFERENCES guilds(id), + CONSTRAINT level_member FOREIGN KEY(member) REFERENCES users(id) + );`, + [] + ); }