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
|
POSTGRES_DB: postgres
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|||||||
Generated
+377
-1
@@ -11,7 +11,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "26.2.0",
|
"@types/node": "26.2.0",
|
||||||
"esbuild": "0.28.2"
|
"esbuild": "0.28.2",
|
||||||
|
"typescript": "7.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
@@ -466,6 +467,346 @@
|
|||||||
"undici-types": "~8.3.0"
|
"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": {
|
"node_modules/athena-prime": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://npm.hep.gg/athena-prime/-/athena-prime-4.4.0.tgz",
|
"resolved": "https://npm.hep.gg/athena-prime/-/athena-prime-4.4.0.tgz",
|
||||||
@@ -564,6 +905,41 @@
|
|||||||
"url": "https://github.com/sponsors/porsager"
|
"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": {
|
"node_modules/undici-types": {
|
||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://npm.hep.gg/undici-types/-/undici-types-8.3.0.tgz",
|
"resolved": "https://npm.hep.gg/undici-types/-/undici-types-8.3.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "26.2.0",
|
"@types/node": "26.2.0",
|
||||||
"esbuild": "0.28.2"
|
"esbuild": "0.28.2",
|
||||||
|
"typescript": "7.0.2"
|
||||||
},
|
},
|
||||||
|
|
||||||
"scripts": {
|
"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 leaderboard from './leaderboard';
|
||||||
import optin from './privacy/optin';
|
import rank from './rank';
|
||||||
import optout from './privacy/optout';
|
import settings from './settings';
|
||||||
import profile from './profile';
|
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
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 { 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),
|
.setCommandType(Constants.ApplicationCommandType.ChatInput),
|
||||||
)
|
)
|
||||||
class LeaderboardCommand extends Command<CommandClient> {
|
class LeaderboardCommand extends Command<CommandClient> {
|
||||||
cooldown = 15;
|
|
||||||
|
|
||||||
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
|
||||||
|
if (!interaction.inGuild()) return;
|
||||||
|
|
||||||
await interaction.defer();
|
await interaction.defer();
|
||||||
|
|
||||||
const _data = await database.getHighestPets();
|
const data = await database.leaderboard.getLeaderboard(interaction.guild.id);
|
||||||
const description = _data.length
|
const description = data.length
|
||||||
? _data
|
? data
|
||||||
.map((entry, index) => `**${index + 1}.** <@${entry.userId}> — **${entry.pets.toLocaleString()}** pets!`)
|
.map((entry, index) => `**${index + 1}.** <@${entry.userId}> — **${entry.points}** points!`)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
: 'Nobody has any pets yet!';
|
: 'Nobody has any points yet!';
|
||||||
|
|
||||||
await interaction.createMessage({
|
await interaction.createMessage({
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
author: {
|
author: {
|
||||||
name: interaction.user.username,
|
name: interaction.member.username,
|
||||||
icon_url: interaction.user.avatarURL,
|
icon_url: interaction.member.avatarURL,
|
||||||
},
|
},
|
||||||
|
|
||||||
color: 0xe77ed1,
|
color: 0xe77ed1,
|
||||||
|
|
||||||
title: "Pond's Petting Leaderboard",
|
title: `Leaderboard | ${interaction.guild.name}`,
|
||||||
description: description,
|
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 messageReactionAdd from './message/messageReactionAdd';
|
||||||
import messageReactionRemove from './message/messageReactionRemove';
|
import messageReactionRemove from './message/messageReactionRemove';
|
||||||
import ready from './client/ready';
|
import ready from './client/ready';
|
||||||
@@ -5,7 +7,8 @@ import ready from './client/ready';
|
|||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
// Client.
|
guildCreate,
|
||||||
|
guildDelete,
|
||||||
messageReactionAdd,
|
messageReactionAdd,
|
||||||
messageReactionRemove,
|
messageReactionRemove,
|
||||||
ready,
|
ready,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { CommandClient, Constants, Event, Member, Message } from 'athena-prime';
|
import { CommandClient, Constants, Event, Member, Message } from 'athena-prime';
|
||||||
import config from '../../config';
|
import database from '../../database';
|
||||||
import { database } from '../../utils';
|
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
@@ -8,13 +7,11 @@ class MessageReactionAddEvent extends Event<CommandClient> {
|
|||||||
event: string = 'messageReactionAdd' as const;
|
event: string = 'messageReactionAdd' as const;
|
||||||
|
|
||||||
async handle(context: CommandClient<any, any>, message: Message, emoji: Constants.APIEmoji, member: Member) {
|
async handle(context: CommandClient<any, any>, message: Message, emoji: Constants.APIEmoji, member: Member) {
|
||||||
const msg = await context.getMessage(message.channel.id, message.id);
|
const _config = await database.config.getOrCreateConfig(member.guild.id);
|
||||||
if (msg.author.id !== config.identifiers.targetId || emoji.id !== config.identifiers.emojiId) return;
|
if (emoji.id !== _config.emoji_id) return;
|
||||||
|
|
||||||
const _user = await database.getUser(member.id);
|
const _entry = await database.points.getOrCreatePoints(member.guild.id, member.id);
|
||||||
if (!_user.settings.participating) return;
|
await database.points.updatePoints(member.guild.id, member.id, _entry + 1n);
|
||||||
|
|
||||||
await database.updateUserStatistics(member.id, { pets: _user.statistics.pets + 1 });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
import { CommandClient, Constants, Event } from 'athena-prime';
|
import { CommandClient, Constants, Event } from 'athena-prime';
|
||||||
import config from '../../config';
|
import database from '../../database';
|
||||||
import { database } from '../../utils';
|
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
class MessageReactionRemoveEvent extends Event<CommandClient> {
|
class MessageReactionRemoveEvent extends Event<CommandClient> {
|
||||||
event: string = 'messageReactionRemove' as const;
|
event: string = 'messageReactionRemove' as const;
|
||||||
|
|
||||||
async handle(
|
async handle(context: CommandClient<any, any>, message: { id: string; channel: { id: string } }, emoji: Constants.APIEmoji, userId: string) {
|
||||||
context: CommandClient<any, any>,
|
const _msg = await context.getMessage(message.channel.id, message.id);
|
||||||
message: { id: string; channel: { id: string } },
|
if (!_msg.guildID) return;
|
||||||
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;
|
|
||||||
|
|
||||||
const _user = await database.getUser(userId);
|
const _config = await database.config.getOrCreateConfig(_msg.guildID);
|
||||||
if (!_user.settings.participating) return;
|
if (emoji.id !== _config.emoji_id) return;
|
||||||
|
|
||||||
if (_user.statistics.pets >= 0) return;
|
const _entry = await database.points.getOrCreatePoints(_msg.guildID, userId);
|
||||||
await database.updateUserStatistics(userId, { pets: _user.statistics.pets - 1 });
|
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 { CommandClient, Constants, Guild, Member, Message, NullCollection, User } from 'athena-prime';
|
||||||
import commands from './commands';
|
import commands from './commands';
|
||||||
|
import database from './database';
|
||||||
import events from './events';
|
import events from './events';
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
@@ -16,14 +17,7 @@ const client: CommandClient = new CommandClient({
|
|||||||
Constants.GatewayIntentBits.GuildMessageReactions,
|
Constants.GatewayIntentBits.GuildMessageReactions,
|
||||||
],
|
],
|
||||||
|
|
||||||
largeBotOptimizations: true,
|
largeBotOptimizations: true
|
||||||
|
|
||||||
cache: {
|
|
||||||
users: () => new NullCollection(User),
|
|
||||||
members: () => new NullCollection(Member),
|
|
||||||
messages: () => new NullCollection(Message),
|
|
||||||
guilds: () => new NullCollection(Guild),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,4 +26,5 @@ events.forEach((event) => client.registerEvent(event, true));
|
|||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
|
database.init();
|
||||||
client.connect();
|
client.connect();
|
||||||
|
|||||||
Vendored
+7
-3
@@ -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 {
|
declare global {
|
||||||
|
namespace Application {
|
||||||
|
type Configuration = {
|
||||||
|
emoji_id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
DATABASE_URL: string;
|
DATABASE_URL: string;
|
||||||
@@ -9,4 +13,4 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export { };
|
||||||
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,
|
"experimentalDecorators": true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"include": ["src/**/*.ts"]
|
"include": ["src/**/*.ts"],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user