mirror of
https://github.com/nx-bat/pet-the-pond.git
synced 2026-09-22 10:29:11 -04:00
im tired.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Pet the Pond
|
||||
A points-based reaction bot for Discord. If anyone wants to take this project off my hands, please take it. I don't want it anymore.
|
||||
@@ -15,6 +15,8 @@ services:
|
||||
POSTGRES_DB: postgres
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
||||
Generated
+377
-1
@@ -11,7 +11,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "26.2.0",
|
||||
"esbuild": "0.28.2"
|
||||
"esbuild": "0.28.2",
|
||||
"typescript": "7.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
@@ -466,6 +467,346 @@
|
||||
"undici-types": "~8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-aix-ppc64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
|
||||
"integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-darwin-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-darwin-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-freebsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-freebsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-arm": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
|
||||
"integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-loong64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
|
||||
"integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-mips64el": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
|
||||
"integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-ppc64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
|
||||
"integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-riscv64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
|
||||
"integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-s390x": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
|
||||
"integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-netbsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-netbsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-openbsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-openbsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-sunos-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-win32-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-win32-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/athena-prime": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://npm.hep.gg/athena-prime/-/athena-prime-4.4.0.tgz",
|
||||
@@ -564,6 +905,41 @@
|
||||
"url": "https://github.com/sponsors/porsager"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://npm.hep.gg/typescript/-/typescript-7.0.2.tgz",
|
||||
"integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@typescript/typescript-aix-ppc64": "7.0.2",
|
||||
"@typescript/typescript-darwin-arm64": "7.0.2",
|
||||
"@typescript/typescript-darwin-x64": "7.0.2",
|
||||
"@typescript/typescript-freebsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-freebsd-x64": "7.0.2",
|
||||
"@typescript/typescript-linux-arm": "7.0.2",
|
||||
"@typescript/typescript-linux-arm64": "7.0.2",
|
||||
"@typescript/typescript-linux-loong64": "7.0.2",
|
||||
"@typescript/typescript-linux-mips64el": "7.0.2",
|
||||
"@typescript/typescript-linux-ppc64": "7.0.2",
|
||||
"@typescript/typescript-linux-riscv64": "7.0.2",
|
||||
"@typescript/typescript-linux-s390x": "7.0.2",
|
||||
"@typescript/typescript-linux-x64": "7.0.2",
|
||||
"@typescript/typescript-netbsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-netbsd-x64": "7.0.2",
|
||||
"@typescript/typescript-openbsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-openbsd-x64": "7.0.2",
|
||||
"@typescript/typescript-sunos-x64": "7.0.2",
|
||||
"@typescript/typescript-win32-arm64": "7.0.2",
|
||||
"@typescript/typescript-win32-x64": "7.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://npm.hep.gg/undici-types/-/undici-types-8.3.0.tgz",
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@
|
||||
|
||||
"devDependencies": {
|
||||
"@types/node": "26.2.0",
|
||||
"esbuild": "0.28.2"
|
||||
"esbuild": "0.28.2",
|
||||
"typescript": "7.0.2"
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from 'athena-prime';
|
||||
import config from '../../config';
|
||||
import { database } from '../../utils';
|
||||
|
||||
// ----------
|
||||
|
||||
@SlashCommand(
|
||||
new CommandBuilder('init', 'Runs pre-start operations, then starts the bot for operation.')
|
||||
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||
.setContexts(Constants.InteractionContextType.Guild)
|
||||
.setCommandType(Constants.ApplicationCommandType.ChatInput)
|
||||
.setMemberPermission(Constants.PermissionFlagsBits.Administrator),
|
||||
)
|
||||
class InitCommand extends Command<CommandClient> {
|
||||
users = [
|
||||
'1363132678022631428', // nxbat
|
||||
'454653142500507649', // bitdash
|
||||
];
|
||||
|
||||
async #createInformationMessage(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||
await database.init();
|
||||
|
||||
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',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||
for (const handler of [this.#createInformationMessage]) await handler(context, interaction);
|
||||
await interaction.createMessage({ content: 'Done.', flags: Constants.MessageFlags.Ephemeral });
|
||||
}
|
||||
}
|
||||
|
||||
export default new InitCommand('init');
|
||||
@@ -1,9 +1,7 @@
|
||||
import init from './admin/init';
|
||||
import leaderboard from './leaderboard';
|
||||
import optin from './privacy/optin';
|
||||
import optout from './privacy/optout';
|
||||
import profile from './profile';
|
||||
import rank from './rank';
|
||||
import settings from './settings';
|
||||
|
||||
// ----------
|
||||
|
||||
export default [init, leaderboard, optin, optout, profile];
|
||||
export default [leaderboard, rank, settings];
|
||||
|
||||
+11
-16
@@ -1,5 +1,5 @@
|
||||
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from 'athena-prime';
|
||||
import { database } from '../utils';
|
||||
import database from '../database';
|
||||
|
||||
// ----------
|
||||
|
||||
@@ -10,35 +10,30 @@ import { database } from '../utils';
|
||||
.setCommandType(Constants.ApplicationCommandType.ChatInput),
|
||||
)
|
||||
class LeaderboardCommand extends Command<CommandClient> {
|
||||
cooldown = 15;
|
||||
|
||||
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||
if (!interaction.inGuild()) return;
|
||||
|
||||
await interaction.defer();
|
||||
|
||||
const _data = await database.getHighestPets();
|
||||
const description = _data.length
|
||||
? _data
|
||||
.map((entry, index) => `**${index + 1}.** <@${entry.userId}> — **${entry.pets.toLocaleString()}** pets!`)
|
||||
const data = await database.leaderboard.getLeaderboard(interaction.guild.id);
|
||||
const description = data.length
|
||||
? data
|
||||
.map((entry, index) => `**${index + 1}.** <@${entry.userId}> — **${entry.points}** points!`)
|
||||
.join('\n')
|
||||
: 'Nobody has any pets yet!';
|
||||
: 'Nobody has any points yet!';
|
||||
|
||||
await interaction.createMessage({
|
||||
embeds: [
|
||||
{
|
||||
author: {
|
||||
name: interaction.user.username,
|
||||
icon_url: interaction.user.avatarURL,
|
||||
name: interaction.member.username,
|
||||
icon_url: interaction.member.avatarURL,
|
||||
},
|
||||
|
||||
color: 0xe77ed1,
|
||||
|
||||
title: "Pond's Petting Leaderboard",
|
||||
title: `Leaderboard | ${interaction.guild.name}`,
|
||||
description: description,
|
||||
|
||||
footer: {
|
||||
text: `Made by nxbat @ Archwing`,
|
||||
icon_url: 'https://femboytrain.ing/nxbat-archwing',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from 'athena-prime';
|
||||
import { database } from '../../utils';
|
||||
|
||||
// ----------
|
||||
|
||||
@SlashCommand(
|
||||
new CommandBuilder('optin', 'Stop counting your pet interactions.')
|
||||
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||
.setContexts(Constants.InteractionContextType.Guild)
|
||||
.setCommandType(Constants.ApplicationCommandType.ChatInput),
|
||||
)
|
||||
class OptInCommand extends Command<CommandClient> {
|
||||
cooldown = 5;
|
||||
|
||||
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||
await interaction.defer(true);
|
||||
|
||||
await database.updateUserSettings(interaction.user.id, { participating: true });
|
||||
|
||||
await interaction.createMessage({
|
||||
content: "Successfully opted in. You'll receive points for your pets!",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new OptInCommand('optin');
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from 'athena-prime';
|
||||
import { database } from '../../utils';
|
||||
|
||||
// ----------
|
||||
|
||||
@SlashCommand(
|
||||
new CommandBuilder('optout', 'Stop counting your pet interactions.')
|
||||
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||
.setContexts(Constants.InteractionContextType.Guild)
|
||||
.setCommandType(Constants.ApplicationCommandType.ChatInput),
|
||||
)
|
||||
class OptOutCommand extends Command<CommandClient> {
|
||||
cooldown = 5;
|
||||
|
||||
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||
await interaction.defer(true);
|
||||
|
||||
await database.updateUserSettings(interaction.user.id, { participating: false });
|
||||
|
||||
await interaction.createMessage({
|
||||
content: "Successfully opted out. You won't receive any points for petting :<",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new OptOutCommand('optout');
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from 'athena-prime';
|
||||
import { database } from '../utils';
|
||||
|
||||
// ----------
|
||||
|
||||
@SlashCommand(
|
||||
new CommandBuilder('profile', 'View your profile.')
|
||||
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||
.setContexts(Constants.InteractionContextType.Guild)
|
||||
.setCommandType(Constants.ApplicationCommandType.ChatInput),
|
||||
)
|
||||
class ProfileCommand extends Command<CommandClient> {
|
||||
cooldown = 15;
|
||||
|
||||
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||
await interaction.defer();
|
||||
|
||||
const _user = await database.getUser(interaction.user.id);
|
||||
const position = await database.getPetPosition(interaction.user.id);
|
||||
|
||||
await interaction.createMessage({
|
||||
embeds: [
|
||||
{
|
||||
author: {
|
||||
name: interaction.user.username,
|
||||
icon_url: interaction.user.avatarURL,
|
||||
},
|
||||
|
||||
color: 0xe77ed1,
|
||||
|
||||
title: `Your Profile | Pet the Pond`,
|
||||
fields: [
|
||||
{
|
||||
name: 'Pets',
|
||||
value: `${_user.statistics.pets}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Rank',
|
||||
value: position ? `#${position}` : 'N/A',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Participating',
|
||||
value: _user.settings.participating ? 'Yes' : 'No',
|
||||
inline: false,
|
||||
},
|
||||
],
|
||||
|
||||
footer: {
|
||||
text: `Made by nxbat @ Archwing`,
|
||||
icon_url: 'https://femboytrain.ing/nxbat-archwing',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProfileCommand('profile');
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime";
|
||||
import database from "../database";
|
||||
|
||||
// ----------
|
||||
|
||||
@SlashCommand(
|
||||
new CommandBuilder('rank', 'See your rank information.')
|
||||
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||
.setContexts(Constants.InteractionContextType.Guild)
|
||||
.setCommandType(Constants.ApplicationCommandType.ChatInput)
|
||||
)
|
||||
class RankCommand extends Command<CommandClient> {
|
||||
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||
if (!interaction.inGuild()) return;
|
||||
|
||||
await interaction.defer();
|
||||
|
||||
const _points = await database.points.getOrCreatePoints(interaction.guild.id, interaction.member.id);
|
||||
const _rank = await database.leaderboard.getLeaderboardPosition(interaction.guild.id, interaction.member.id);
|
||||
|
||||
await interaction.createMessage({
|
||||
embeds: [{
|
||||
author: {
|
||||
name: interaction.member.username,
|
||||
icon_url: interaction.member.avatarURL
|
||||
},
|
||||
|
||||
color: 0xe77ed1,
|
||||
title: `Rank | ${interaction.member.username}`,
|
||||
|
||||
fields: [
|
||||
{ name: 'Points', value: `${_points}`, inline: true },
|
||||
{ name: 'Rank', value: `${_rank ?? 'N/A'}`, inline: true }
|
||||
],
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new RankCommand('rank');
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime";
|
||||
import database from "../database";
|
||||
|
||||
// ----------
|
||||
|
||||
@SlashCommand(
|
||||
new CommandBuilder('settings', 'Configure guild-level settings.')
|
||||
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
|
||||
.setContexts(Constants.InteractionContextType.Guild)
|
||||
.setCommandType(Constants.ApplicationCommandType.ChatInput)
|
||||
.setMemberPermission(Constants.PermissionFlagsBits.ManageGuild)
|
||||
.addSubcommand('emoji', 'Set the emoji used for petting.', (builder) => {
|
||||
builder.addStringOption({
|
||||
name: 'value',
|
||||
description: 'The custom emoji ID to use.',
|
||||
required: true,
|
||||
min_length: 17,
|
||||
max_length: 20,
|
||||
});
|
||||
})
|
||||
)
|
||||
class SettingsCommand extends Command<CommandClient> {
|
||||
userPermissions = [Constants.PermissionFlagsBits.ManageGuild];
|
||||
|
||||
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||
if (!interaction.inGuild() || !interaction.subcommand) return;
|
||||
await interaction.defer(true);
|
||||
|
||||
switch (interaction.subcommand) {
|
||||
case 'emoji':
|
||||
const config = await database.config.getOrCreateConfig(interaction.guild.id);
|
||||
const emojiId = interaction.getRequiredString('value');
|
||||
|
||||
if (!/^\d{17,20}$/.test(emojiId)) {
|
||||
return await interaction.createMessage({ content: 'Please provide a valid custom emoji ID.' });
|
||||
}
|
||||
|
||||
await database.config.updateConfig(interaction.guild.id, {
|
||||
...config,
|
||||
emoji_id: emojiId,
|
||||
});
|
||||
|
||||
return await interaction.createMessage({ content: `The reaction emoji ID has been set to ${emojiId}.` });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new SettingsCommand('settings');
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
identifiers: {
|
||||
emojiId: '448912932340891670',
|
||||
targetId: '1363132678022631428',
|
||||
},
|
||||
};
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
import postgres, { Sql } from 'postgres';
|
||||
|
||||
// ----------
|
||||
|
||||
const client: Sql<any> = postgres(process.env.DATABASE_URL);
|
||||
|
||||
export async function init(): Promise<void> {
|
||||
await client`
|
||||
CREATE TABLE IF NOT EXISTS guilds (
|
||||
guild_id TEXT PRIMARY KEY,
|
||||
config JSONB NOT NULL default '{}'::jsonb
|
||||
);`
|
||||
|
||||
await client`
|
||||
CREATE TABLE IF NOT EXISTS points (
|
||||
guild_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
points BIGINT NOT NULL DEFAULT 0,
|
||||
|
||||
PRIMARY KEY (guild_id, user_id),
|
||||
|
||||
CONSTRAINT fk_points_guild
|
||||
FOREIGN KEY (guild_id)
|
||||
REFERENCES guilds (guild_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
}
|
||||
|
||||
//#region Configuration
|
||||
|
||||
export async function getOrCreateConfig(guildId: string): Promise<Application.Configuration> {
|
||||
const [inserted] = await client<{ config: Application.Configuration }[]>`
|
||||
INSERT INTO guilds (guild_id)
|
||||
VALUES (${guildId})
|
||||
ON CONFLICT (guild_id) DO NOTHING
|
||||
RETURNING config
|
||||
`;
|
||||
|
||||
if (inserted) return inserted.config;
|
||||
|
||||
const [row] = await client<{ config: Application.Configuration }[]>`
|
||||
SELECT config FROM guilds
|
||||
WHERE guild_id = ${guildId}
|
||||
`;
|
||||
|
||||
return row.config;
|
||||
}
|
||||
|
||||
export async function updateConfig(guildId: string, config: Application.Configuration): Promise<void> {
|
||||
await client`
|
||||
UPDATE guilds SET config = ${client.json(config)}
|
||||
WHERE guild_id = ${guildId}
|
||||
`;
|
||||
}
|
||||
|
||||
export async function deleteConfig(guildId: string): Promise<void> {
|
||||
await client`
|
||||
DELETE FROM guilds
|
||||
WHERE guild_id = ${guildId}
|
||||
`;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Points
|
||||
|
||||
export async function getOrCreatePoints(guildId: string, userId: string): Promise<bigint> {
|
||||
const [inserted] = await client<{ points: bigint }[]>`
|
||||
INSERT INTO points (guild_id, user_id)
|
||||
VALUES (${guildId}, ${userId})
|
||||
ON CONFLICT (guild_id, user_id) DO NOTHING
|
||||
RETURNING points
|
||||
`;
|
||||
|
||||
if (inserted) return inserted.points;
|
||||
|
||||
const [row] = await client<{ points: bigint }[]>`
|
||||
SELECT points FROM points
|
||||
WHERE guild_id = ${guildId} AND user_id = ${userId}
|
||||
`;
|
||||
|
||||
return row.points;
|
||||
}
|
||||
|
||||
export async function updatePoints(guildId: string, userId: string, points: bigint): Promise<void> {
|
||||
await client`
|
||||
UPDATE points SET points = ${points}
|
||||
WHERE guild_id = ${guildId} AND user_id = ${userId}
|
||||
`;
|
||||
}
|
||||
|
||||
export async function deletePoints(guildId: string, userId: string): Promise<void> {
|
||||
await client`
|
||||
DELETE FROM points
|
||||
WHERE guild_id = ${guildId} AND user_id = ${userId}
|
||||
`;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Leaderboard
|
||||
|
||||
export async function getLeaderboard(guildId: string, limit: number = 10): Promise<{ userId: string; points: bigint }[]> {
|
||||
return client<{ userId: string; points: bigint }[]>`
|
||||
SELECT user_id AS "userId", points FROM points
|
||||
WHERE guild_id = ${guildId} AND points > 0
|
||||
ORDER BY points DESC, user_id ASC
|
||||
LIMIT ${limit}
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getLeaderboardPosition(guildId: string, userId: string): Promise<number | null> {
|
||||
const [row] = await client<{ rank: number | null }[]>`
|
||||
WITH target AS (
|
||||
SELECT points FROM points
|
||||
WHERE guild_id = ${guildId} AND user_id = ${userId}
|
||||
)
|
||||
SELECT CASE WHEN COUNT(target.*) = 0 THEN NULL ELSE COUNT(candidate.*)::int + 1 END AS rank
|
||||
FROM target
|
||||
LEFT JOIN points AS candidate
|
||||
ON candidate.guild_id = ${guildId}
|
||||
AND (
|
||||
candidate.points > target.points
|
||||
OR (candidate.points = target.points AND candidate.user_id < ${userId})
|
||||
)
|
||||
`;
|
||||
|
||||
return row.rank;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export default {
|
||||
init,
|
||||
|
||||
config: {
|
||||
getOrCreateConfig,
|
||||
updateConfig,
|
||||
deleteConfig
|
||||
},
|
||||
|
||||
points: {
|
||||
getOrCreatePoints,
|
||||
updatePoints,
|
||||
deletePoints
|
||||
},
|
||||
|
||||
leaderboard: {
|
||||
getLeaderboard,
|
||||
getLeaderboardPosition,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { CommandClient, Event, Guild } from 'athena-prime';
|
||||
import database from '../../database';
|
||||
|
||||
// ----------
|
||||
|
||||
class GuildCreateEvent extends Event<CommandClient> {
|
||||
event: string = 'guildCreate' as const;
|
||||
|
||||
async handle(context: CommandClient<any, any>, guild: Guild) {
|
||||
await database.config.getOrCreateConfig(guild.id);
|
||||
}
|
||||
}
|
||||
|
||||
export default new GuildCreateEvent('guildCreate');
|
||||
@@ -0,0 +1,15 @@
|
||||
import { CommandClient, Event, Guild } from 'athena-prime';
|
||||
import database from '../../database';
|
||||
|
||||
// ----------
|
||||
|
||||
class GuildCreateEvent extends Event<CommandClient> {
|
||||
event: string = 'guildDelete' as const;
|
||||
|
||||
// TODO: Implement weekly config cleanup task instead.
|
||||
async handle(context: CommandClient<any, any>, guild: Guild | { id: string }) {
|
||||
await database.config.deleteConfig(guild.id);
|
||||
}
|
||||
}
|
||||
|
||||
export default new GuildCreateEvent('guildDelete');
|
||||
+4
-1
@@ -1,3 +1,5 @@
|
||||
import guildCreate from './guild/guildCreate';
|
||||
import guildDelete from './guild/guildDelete';
|
||||
import messageReactionAdd from './message/messageReactionAdd';
|
||||
import messageReactionRemove from './message/messageReactionRemove';
|
||||
import ready from './client/ready';
|
||||
@@ -5,7 +7,8 @@ import ready from './client/ready';
|
||||
// ----------
|
||||
|
||||
export default [
|
||||
// Client.
|
||||
guildCreate,
|
||||
guildDelete,
|
||||
messageReactionAdd,
|
||||
messageReactionRemove,
|
||||
ready,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CommandClient, Constants, Event, Member, Message } from 'athena-prime';
|
||||
import config from '../../config';
|
||||
import { database } from '../../utils';
|
||||
import database from '../../database';
|
||||
|
||||
// ----------
|
||||
|
||||
@@ -8,13 +7,11 @@ class MessageReactionAddEvent extends Event<CommandClient> {
|
||||
event: string = 'messageReactionAdd' as const;
|
||||
|
||||
async handle(context: CommandClient<any, any>, message: Message, emoji: Constants.APIEmoji, member: Member) {
|
||||
const msg = await context.getMessage(message.channel.id, message.id);
|
||||
if (msg.author.id !== config.identifiers.targetId || emoji.id !== config.identifiers.emojiId) return;
|
||||
const _config = await database.config.getOrCreateConfig(member.guild.id);
|
||||
if (emoji.id !== _config.emoji_id) return;
|
||||
|
||||
const _user = await database.getUser(member.id);
|
||||
if (!_user.settings.participating) return;
|
||||
|
||||
await database.updateUserStatistics(member.id, { pets: _user.statistics.pets + 1 });
|
||||
const _entry = await database.points.getOrCreatePoints(member.guild.id, member.id);
|
||||
await database.points.updatePoints(member.guild.id, member.id, _entry + 1n);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import { CommandClient, Constants, Event } from 'athena-prime';
|
||||
import config from '../../config';
|
||||
import { database } from '../../utils';
|
||||
import database from '../../database';
|
||||
|
||||
// ----------
|
||||
|
||||
class MessageReactionRemoveEvent extends Event<CommandClient> {
|
||||
event: string = 'messageReactionRemove' as const;
|
||||
|
||||
async handle(
|
||||
context: CommandClient<any, any>,
|
||||
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;
|
||||
async handle(context: CommandClient<any, any>, message: { id: string; channel: { id: string } }, emoji: Constants.APIEmoji, userId: string) {
|
||||
const _msg = await context.getMessage(message.channel.id, message.id);
|
||||
if (!_msg.guildID) return;
|
||||
|
||||
const _user = await database.getUser(userId);
|
||||
if (!_user.settings.participating) return;
|
||||
const _config = await database.config.getOrCreateConfig(_msg.guildID);
|
||||
if (emoji.id !== _config.emoji_id) return;
|
||||
|
||||
if (_user.statistics.pets >= 0) return;
|
||||
await database.updateUserStatistics(userId, { pets: _user.statistics.pets - 1 });
|
||||
const _entry = await database.points.getOrCreatePoints(_msg.guildID, userId);
|
||||
await database.points.updatePoints(_msg.guildID, userId, _entry - 1n);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-8
@@ -2,6 +2,7 @@ import 'dotenv/config';
|
||||
|
||||
import { CommandClient, Constants, Guild, Member, Message, NullCollection, User } from 'athena-prime';
|
||||
import commands from './commands';
|
||||
import database from './database';
|
||||
import events from './events';
|
||||
|
||||
// ----------
|
||||
@@ -16,14 +17,7 @@ const client: CommandClient = new CommandClient({
|
||||
Constants.GatewayIntentBits.GuildMessageReactions,
|
||||
],
|
||||
|
||||
largeBotOptimizations: true,
|
||||
|
||||
cache: {
|
||||
users: () => new NullCollection(User),
|
||||
members: () => new NullCollection(Member),
|
||||
messages: () => new NullCollection(Message),
|
||||
guilds: () => new NullCollection(Guild),
|
||||
},
|
||||
largeBotOptimizations: true
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,4 +26,5 @@ events.forEach((event) => client.registerEvent(event, true));
|
||||
|
||||
// ----------
|
||||
|
||||
database.init();
|
||||
client.connect();
|
||||
|
||||
Vendored
+6
-2
@@ -1,6 +1,10 @@
|
||||
// Used for typing purposes only.
|
||||
// There's an expectation that the person setting up the bot isn't a complete idiot.
|
||||
declare global {
|
||||
namespace Application {
|
||||
type Configuration = {
|
||||
emoji_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
DATABASE_URL: string;
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
export type UserSettings = {
|
||||
participating: boolean;
|
||||
};
|
||||
|
||||
export type UserStatistics = {
|
||||
pets: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Database User.
|
||||
*/
|
||||
export type User = {
|
||||
/**
|
||||
* User settings specific to each user.
|
||||
*/
|
||||
settings: UserSettings;
|
||||
|
||||
/**
|
||||
* User statistics.
|
||||
*/
|
||||
statistics: UserStatistics;
|
||||
};
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export * from './database.d';
|
||||
@@ -1,126 +0,0 @@
|
||||
import postgres from 'postgres';
|
||||
import { User } from '../types';
|
||||
|
||||
const client = postgres(process.env.DATABASE_URL);
|
||||
|
||||
const getDefaultUser = (): User => ({
|
||||
settings: {
|
||||
participating: true,
|
||||
},
|
||||
|
||||
statistics: {
|
||||
pets: 0,
|
||||
},
|
||||
});
|
||||
|
||||
export async function init(): Promise<void> {
|
||||
await client`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
data JSONB NOT NULL
|
||||
)
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getUser(userId: string): Promise<User> {
|
||||
let [user] = await client<{ user_id: string; data: User }[]>`
|
||||
SELECT user_id, data FROM users
|
||||
WHERE user_id = ${userId}
|
||||
`;
|
||||
|
||||
if (user) return user.data;
|
||||
|
||||
[user] = await client<{ user_id: string; data: User }[]>`
|
||||
INSERT INTO users (user_id, data)
|
||||
VALUES (${userId}, ${client.json(getDefaultUser())})
|
||||
ON CONFLICT (user_id)
|
||||
DO NOTHING
|
||||
RETURNING user_id, data
|
||||
`;
|
||||
|
||||
return user?.data ?? getDefaultUser();
|
||||
}
|
||||
|
||||
export async function setUser(userId: string, data: User): Promise<void> {
|
||||
await client`
|
||||
INSERT INTO users (user_id, data)
|
||||
VALUES (${userId}, ${client.json(data)})
|
||||
ON CONFLICT (user_id)
|
||||
DO UPDATE SET data = EXCLUDED.data
|
||||
`;
|
||||
}
|
||||
|
||||
export async function deleteUser(userId: string): Promise<void> {
|
||||
await client`
|
||||
DELETE FROM users
|
||||
WHERE user_id = ${userId}
|
||||
`;
|
||||
}
|
||||
|
||||
export async function updateUserSettings(userId: string, settings: Partial<User['settings']>): Promise<void> {
|
||||
await client`
|
||||
UPDATE users
|
||||
SET data = jsonb_set(
|
||||
data,
|
||||
'{settings}',
|
||||
data->'settings' || ${client.json(settings)}
|
||||
)
|
||||
WHERE user_id = ${userId}
|
||||
`;
|
||||
}
|
||||
|
||||
export async function updateUserStatistics(userId: string, statistics: Partial<User['statistics']>): Promise<void> {
|
||||
await client`
|
||||
UPDATE users
|
||||
SET data = jsonb_set(
|
||||
data,
|
||||
'{statistics}',
|
||||
data->'statistics' || ${client.json(statistics)}
|
||||
)
|
||||
WHERE user_id = ${userId}
|
||||
`;
|
||||
}
|
||||
|
||||
//#region Leaderboard
|
||||
|
||||
export async function getHighestPets(limit = 10): Promise<{ userId: string; pets: number }[]> {
|
||||
const users = await client<{ user_id: string; pets: number }[]>`
|
||||
SELECT user_id, (data->'statistics'->>'pets')::integer AS pets
|
||||
FROM users
|
||||
WHERE
|
||||
COALESCE((data->'flags'->>'blacklisted')::boolean, false) = false
|
||||
AND COALESCE((data->'settings'->>'participating')::boolean, true) = true
|
||||
ORDER BY pets DESC, user_id ASC
|
||||
LIMIT ${limit}
|
||||
`;
|
||||
|
||||
return users.map((user) => ({
|
||||
userId: user.user_id,
|
||||
pets: user.pets,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getPetPosition(userId: string): Promise<number | null> {
|
||||
const result = await client<{ position: number }[]>`
|
||||
SELECT position
|
||||
FROM (
|
||||
SELECT
|
||||
user_id,
|
||||
RANK() OVER (ORDER BY pets DESC, user_id ASC) AS position
|
||||
FROM (
|
||||
SELECT
|
||||
user_id,
|
||||
(data->'statistics'->>'pets')::integer AS pets
|
||||
FROM users
|
||||
WHERE
|
||||
COALESCE((data->'flags'->>'blacklisted')::boolean, false) = false
|
||||
AND COALESCE((data->'settings'->>'participating')::boolean, true) = true
|
||||
) ranked
|
||||
) ranked_users
|
||||
WHERE user_id = ${userId}
|
||||
`;
|
||||
|
||||
return result[0]?.position ?? null;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -1 +0,0 @@
|
||||
export * as database from './database';
|
||||
+1
-1
@@ -8,5 +8,5 @@
|
||||
"experimentalDecorators": true,
|
||||
},
|
||||
|
||||
"include": ["src/**/*.ts"]
|
||||
"include": ["src/**/*.ts"],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user