summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-10-23 23:36:36 -0700
committerFuwn <[email protected]>2025-10-23 23:36:39 -0700
commit46c40a53a569f5f27de016bc0215bf30dcdba1a5 (patch)
treebec0ed98689296b6358e1f1c38c20ba6df01705b /packages
parentfeat(gateway:listeners): Add auto message deletion (diff)
downloadumabotdiscord-46c40a53a569f5f27de016bc0215bf30dcdba1a5.tar.xz
umabotdiscord-46c40a53a569f5f27de016bc0215bf30dcdba1a5.zip
feat(gateway:listeners): Add role persistence
Diffstat (limited to 'packages')
-rw-r--r--packages/gateway/bun.lock71
-rw-r--r--packages/gateway/package.json3
-rw-r--r--packages/gateway/prisma/migrations/20251024062804_init/migration.sql17
-rw-r--r--packages/gateway/prisma/migrations/migration_lock.toml3
-rw-r--r--packages/gateway/prisma/schema.prisma20
-rw-r--r--packages/gateway/src/database/prisma.ts9
-rw-r--r--packages/gateway/src/database/rolePersistence.ts118
-rw-r--r--packages/gateway/src/listeners/index.ts2
-rw-r--r--packages/gateway/src/listeners/memberJoin.ts8
-rw-r--r--packages/gateway/src/listeners/memberLeave.ts18
10 files changed, 268 insertions, 1 deletions
diff --git a/packages/gateway/bun.lock b/packages/gateway/bun.lock
index e23ad2d..1a60726 100644
--- a/packages/gateway/bun.lock
+++ b/packages/gateway/bun.lock
@@ -5,6 +5,8 @@
"name": "umabot-gateway-client",
"dependencies": {
"@discordjs/voice": "^0.19.0",
+ "@prisma/client": "^6.18.0",
+ "@prisma/extension-accelerate": "^2.0.2",
"@snazzah/davey": "^0.1.7",
"@types/ws": "^8.18.1",
"discord.js": "^14.14.1",
@@ -12,6 +14,7 @@
"iqdb-client": "^3.0.0",
"libsodium-wrappers": "^0.7.15",
"openai": "^6.1.0",
+ "prisma": "^6.18.0",
"sodium": "^3.0.2",
"ws": "^8.18.3",
},
@@ -97,6 +100,22 @@
"@napi-rs/wasm-runtime": ["@napi-rs/[email protected]", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg=="],
+ "@prisma/client": ["@prisma/[email protected]", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA=="],
+
+ "@prisma/config": ["@prisma/[email protected]", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ=="],
+
+ "@prisma/debug": ["@prisma/[email protected]", "", {}, "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg=="],
+
+ "@prisma/engines": ["@prisma/[email protected]", "", { "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "@prisma/fetch-engine": "6.18.0", "@prisma/get-platform": "6.18.0" } }, "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA=="],
+
+ "@prisma/engines-version": ["@prisma/engines-version@6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "", {}, "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ=="],
+
+ "@prisma/extension-accelerate": ["@prisma/[email protected]", "", { "peerDependencies": { "@prisma/client": ">=4.16.1" } }, "sha512-yZK6/k7uOEFpEsKoZezQS1CKDboPtBCQ0NyI70e1Un8tDiRgg80iWGyjsJmRpps2ZIut3MroHP+dyR3wVKh8lA=="],
+
+ "@prisma/fetch-engine": ["@prisma/[email protected]", "", { "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "@prisma/get-platform": "6.18.0" } }, "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A=="],
+
+ "@prisma/get-platform": ["@prisma/[email protected]", "", { "dependencies": { "@prisma/debug": "6.18.0" } }, "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg=="],
+
"@sapphire/async-queue": ["@sapphire/[email protected]", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
"@sapphire/shapeshift": ["@sapphire/[email protected]", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
@@ -133,6 +152,8 @@
"@snazzah/davey-win32-x64-msvc": ["@snazzah/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-Z2NhImUYeApi/lNn7MBcn14dPa2dtgnp5taz43JDaPpl+2cinDm9kYjpFzJE9SZMlfsa//p2dhE9B8TEVi9bHQ=="],
+ "@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
+
"@tybys/wasm-util": ["@tybys/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],
@@ -143,14 +164,30 @@
"boolbase": ["[email protected]", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
+ "c12": ["[email protected]", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="],
+
"cheerio": ["[email protected]", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg=="],
"cheerio-select": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
+ "chokidar": ["[email protected]", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
+
+ "citty": ["[email protected]", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
+
+ "confbox": ["[email protected]", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
+
+ "consola": ["[email protected]", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
+
"css-select": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
"css-what": ["[email protected]", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
+ "deepmerge-ts": ["[email protected]", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="],
+
+ "defu": ["[email protected]", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
+
+ "destr": ["[email protected]", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
+
"discord-api-types": ["[email protected]", "", {}, "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw=="],
"discord.js": ["[email protected]", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.16", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-3k+Kisd/v570Jr68A1kNs7qVhNehDwDJAPe4DZ2Syt+/zobf9zEcuYFvsfIaAOgCa0BiHMfOOKQY4eYINl0z7w=="],
@@ -165,24 +202,36 @@
"dotenv": ["[email protected]", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+ "effect": ["[email protected]", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="],
+
+ "empathic": ["[email protected]", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
+
"encoding-sniffer": ["[email protected]", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
"entities": ["[email protected]", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
+ "exsolve": ["[email protected]", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="],
+
+ "fast-check": ["[email protected]", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
+
"fast-deep-equal": ["[email protected]", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-tsconfig": ["[email protected]", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
+ "giget": ["[email protected]", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
+
"htmlparser2": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="],
"iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"iqdb-client": ["[email protected]", "", { "dependencies": { "cheerio": "^1.0.0-rc.12" } }, "sha512-Rr5lNUBeJ663ufMtRhnBVuU0H4HuoyqjdCPz7cbX7oSpIi3KlKqzxYF1xPZWJ/TFtDh4DfM47T57WgFB6wh5DQ=="],
+ "jiti": ["[email protected]", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
"libsodium": ["[email protected]", "", {}, "sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw=="],
"libsodium-wrappers": ["[email protected]", "", { "dependencies": { "libsodium": "^0.7.15" } }, "sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ=="],
@@ -195,8 +244,14 @@
"node-addon-api": ["[email protected]", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
+ "node-fetch-native": ["[email protected]", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
+
"nth-check": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
+ "nypm": ["[email protected]", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="],
+
+ "ohash": ["[email protected]", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
+
"openai": ["[email protected]", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-5sqb1wK67HoVgGlsPwcH2bUbkg66nnoIYKoyV9zi5pZPqh7EWlmSrSDjAh4O5jaIg/0rIlcDKBtWvZBuacmGZg=="],
"parse5": ["[email protected]", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
@@ -205,14 +260,30 @@
"parse5-parser-stream": ["[email protected]", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
+ "pathe": ["[email protected]", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+ "perfect-debounce": ["[email protected]", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
+
+ "pkg-types": ["[email protected]", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
+
"prism-media": ["[email protected]", "", { "peerDependencies": { "@discordjs/opus": ">=0.8.0 <1.0.0", "ffmpeg-static": "^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0", "node-opus": "^0.3.3", "opusscript": "^0.0.8" }, "optionalPeers": ["@discordjs/opus", "ffmpeg-static", "node-opus", "opusscript"] }, "sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA=="],
+ "prisma": ["[email protected]", "", { "dependencies": { "@prisma/config": "6.18.0", "@prisma/engines": "6.18.0" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g=="],
+
+ "pure-rand": ["[email protected]", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
+
+ "rc9": ["[email protected]", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
+
+ "readdirp": ["[email protected]", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
+
"resolve-pkg-maps": ["[email protected]", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"safer-buffer": ["[email protected]", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sodium": ["[email protected]", "", { "dependencies": { "node-addon-api": "*" } }, "sha512-IsTwTJeoNBU97km3XkrbCGC/n/9aUQejgD3QPr2YY2gtbSPru3TI6nhCqgoez9Mv88frF9oVZS/jrXFbd6WXyA=="],
+ "tinyexec": ["[email protected]", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
+
"ts-mixer": ["[email protected]", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
"tslib": ["[email protected]", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
diff --git a/packages/gateway/package.json b/packages/gateway/package.json
index db22d09..bab9e7e 100644
--- a/packages/gateway/package.json
+++ b/packages/gateway/package.json
@@ -12,6 +12,8 @@
},
"dependencies": {
"@discordjs/voice": "^0.19.0",
+ "@prisma/client": "^6.18.0",
+ "@prisma/extension-accelerate": "^2.0.2",
"@snazzah/davey": "^0.1.7",
"@types/ws": "^8.18.1",
"discord.js": "^14.14.1",
@@ -19,6 +21,7 @@
"iqdb-client": "^3.0.0",
"libsodium-wrappers": "^0.7.15",
"openai": "^6.1.0",
+ "prisma": "^6.18.0",
"sodium": "^3.0.2",
"ws": "^8.18.3"
},
diff --git a/packages/gateway/prisma/migrations/20251024062804_init/migration.sql b/packages/gateway/prisma/migrations/20251024062804_init/migration.sql
new file mode 100644
index 0000000..ffb366a
--- /dev/null
+++ b/packages/gateway/prisma/migrations/20251024062804_init/migration.sql
@@ -0,0 +1,17 @@
+-- CreateTable
+CREATE TABLE "UserRole" (
+ "id" TEXT NOT NULL,
+ "userId" TEXT NOT NULL,
+ "guildId" TEXT NOT NULL,
+ "roleId" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "UserRole_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE INDEX "UserRole_userId_guildId_idx" ON "UserRole"("userId", "guildId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "UserRole_userId_guildId_roleId_key" ON "UserRole"("userId", "guildId", "roleId");
diff --git a/packages/gateway/prisma/migrations/migration_lock.toml b/packages/gateway/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..044d57c
--- /dev/null
+++ b/packages/gateway/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (e.g., Git)
+provider = "postgresql"
diff --git a/packages/gateway/prisma/schema.prisma b/packages/gateway/prisma/schema.prisma
new file mode 100644
index 0000000..922ca6f
--- /dev/null
+++ b/packages/gateway/prisma/schema.prisma
@@ -0,0 +1,20 @@
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model UserRole {
+ id String @id @default(cuid())
+ userId String
+ guildId String
+ roleId String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@unique([userId, guildId, roleId])
+ @@index([userId, guildId])
+}
diff --git a/packages/gateway/src/database/prisma.ts b/packages/gateway/src/database/prisma.ts
new file mode 100644
index 0000000..d9ffaf3
--- /dev/null
+++ b/packages/gateway/src/database/prisma.ts
@@ -0,0 +1,9 @@
+import { PrismaClient } from "@prisma/client";
+import { withAccelerate } from "@prisma/extension-accelerate";
+import dotenv from "dotenv";
+
+dotenv.config({ path: "../../.dev.vars" });
+
+const prisma = new PrismaClient().$extends(withAccelerate());
+
+export default prisma;
diff --git a/packages/gateway/src/database/rolePersistence.ts b/packages/gateway/src/database/rolePersistence.ts
new file mode 100644
index 0000000..cd62e6e
--- /dev/null
+++ b/packages/gateway/src/database/rolePersistence.ts
@@ -0,0 +1,118 @@
+/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
+
+import { GuildMember, Role } from "discord.js";
+import prisma from "../database/prisma";
+import { log, LogLevel, logUnexpectedDiscordAPIError } from "../utilities";
+
+export class RolePersistenceService {
+ static async saveUserRoles(member: GuildMember): Promise<void> {
+ try {
+ const userId = member.user.id;
+ const guildId = member.guild.id;
+ const roles = member.roles.cache.filter(
+ (role) => role.id !== member.guild.id,
+ );
+
+ if (roles.size === 0) return;
+
+ await prisma.userRole.deleteMany({
+ where: {
+ userId,
+ guildId,
+ },
+ });
+
+ const roleData = Array.from(roles.keys()).map((roleId) => ({
+ userId,
+ guildId,
+ roleId,
+ }));
+
+ await prisma.userRole.createMany({
+ data: roleData,
+ });
+ } catch (error) {
+ logUnexpectedDiscordAPIError(error);
+ }
+ }
+
+ static async restoreUserRoles(member: GuildMember): Promise<void> {
+ try {
+ const userId = member.user.id;
+ const guildId = member.guild.id;
+ const savedRoles = await prisma.userRole.findMany({
+ where: {
+ userId,
+ guildId,
+ },
+ });
+
+ if (savedRoles.length === 0) return;
+
+ const validRoles: Role[] = [];
+
+ for (const savedRole of savedRoles) {
+ const role = member.guild.roles.cache.get(savedRole.roleId);
+
+ if (role && role.id !== member.guild.id) validRoles.push(role);
+ }
+
+ if (validRoles.length === 0) {
+ await prisma.userRole.deleteMany({
+ where: {
+ userId,
+ guildId,
+ },
+ });
+
+ return;
+ }
+
+ await member.roles.add(validRoles);
+ await prisma.userRole.deleteMany({
+ where: {
+ userId,
+ guildId,
+ },
+ });
+ } catch (error) {
+ logUnexpectedDiscordAPIError(error);
+ }
+ }
+
+ static async clearUserRoles(userId: string, guildId: string): Promise<void> {
+ try {
+ await prisma.userRole.deleteMany({
+ where: {
+ userId,
+ guildId,
+ },
+ });
+ } catch (error) {
+ logUnexpectedDiscordAPIError(error);
+ }
+ }
+
+ static async getSavedRoles(
+ userId: string,
+ guildId: string,
+ ): Promise<string[]> {
+ try {
+ const savedRoles = await prisma.userRole.findMany({
+ where: {
+ userId,
+ guildId,
+ },
+ select: {
+ roleId: true,
+ },
+ });
+
+ return savedRoles.map((role: { roleId: string }) => role.roleId);
+ } catch (error) {
+ logUnexpectedDiscordAPIError(error);
+
+ return [];
+ }
+ }
+}
diff --git a/packages/gateway/src/listeners/index.ts b/packages/gateway/src/listeners/index.ts
index 7547d4f..21da227 100644
--- a/packages/gateway/src/listeners/index.ts
+++ b/packages/gateway/src/listeners/index.ts
@@ -6,6 +6,7 @@ import { handleMessageDeletion } from "./messageDeletion";
import { handleMessageEdit } from "./messageEdit";
import { handleClientReady } from "./clientReady";
import { handleMemberJoin } from "./memberJoin";
+import { handleMemberLeave } from "./memberLeave";
import { handleTimeoutMirroring } from "./timeoutMirroring";
import { handleAutoDeletion } from "./autoDeletion";
// import { handleMediaModeration } from "./mediaModeration";
@@ -18,6 +19,7 @@ export const handleListeners = (client: Client) => {
handleMessageDeletion(client);
handleMessageEdit(client);
handleMemberJoin(client);
+ handleMemberLeave(client);
handleTimeoutMirroring(client);
handleAutoDeletion(client);
// handleMediaModeration(client);
diff --git a/packages/gateway/src/listeners/memberJoin.ts b/packages/gateway/src/listeners/memberJoin.ts
index cceae35..97df730 100644
--- a/packages/gateway/src/listeners/memberJoin.ts
+++ b/packages/gateway/src/listeners/memberJoin.ts
@@ -7,6 +7,7 @@ import {
import { WELCOME_GREETINGS, WELCOME_ENDINGS } from "./messageCreate/constants";
import { logUnexpectedDiscordAPIError } from "../utilities";
import { logUnexpectedDiscordAPIResult } from "../../../shared/log";
+import { RolePersistenceService } from "../database/rolePersistence";
const CENTRAL_WELCOME_CHANNEL_ID = "1406422619087044670";
const ROLEPLAY_WELCOME_CHANNEL_ID = "1423919140606971927";
@@ -29,7 +30,6 @@ class WelcomeMessageSystem {
private async initializePersistentWebhook() {
if (!this.client) return;
- // Initialize central server webhook
try {
const centralChannel = this.client.channels.cache.get(
CENTRAL_WELCOME_CHANNEL_ID,
@@ -99,6 +99,12 @@ class WelcomeMessageSystem {
public async handleMemberJoin(member: GuildMember): Promise<void> {
if (!this.client) await this.setClient(member.client);
+ try {
+ await RolePersistenceService.restoreUserRoles(member);
+ } catch (error) {
+ logUnexpectedDiscordAPIError(error);
+ }
+
if (
member.guild.id !== CENTRAL_GUILD_ID &&
member.guild.id !== ROLEPLAY_GUILD_ID
diff --git a/packages/gateway/src/listeners/memberLeave.ts b/packages/gateway/src/listeners/memberLeave.ts
new file mode 100644
index 0000000..e348917
--- /dev/null
+++ b/packages/gateway/src/listeners/memberLeave.ts
@@ -0,0 +1,18 @@
+/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
+
+import { Client, Events, GuildMember } from "discord.js";
+import { RolePersistenceService } from "../database/rolePersistence";
+import { log, LogLevel, logUnexpectedDiscordAPIError } from "../utilities";
+
+export const handleMemberLeave = (client: Client) => {
+ client.on(Events.GuildMemberRemove, async (member) => {
+ try {
+ if (!member.user) return;
+
+ if ("roles" in member)
+ await RolePersistenceService.saveUserRoles(member as GuildMember);
+ } catch (error) {
+ logUnexpectedDiscordAPIError(error);
+ }
+ });
+};