From 643204c7c44b28a3f53e62f58228bfce65629ace Mon Sep 17 00:00:00 2001 From: David Malchin Date: Tue, 10 Sep 2024 01:20:43 +0300 Subject: [PATCH] Move to websockets --- package-lock.json | 824 ++++++++++++++++-- package.json | 4 +- patches/@discordjs+ws+1.0.0.patch | 14 + server/components/core/index.ts | 7 + server/components/core/interaction-handler.ts | 95 ++ server/components/core/ready.ts | 11 + server/components/loader.ts | 10 +- server/components/ping/command.ts | 30 +- server/components/ping/heartbeat.ts | 15 + server/components/ping/index.ts | 2 + server/components/types.ts | 37 +- server/http/handler.ts | 138 --- server/http/types.ts | 18 - server/http/verify.ts | 16 - server/index.ts | 11 +- server/utils/env.ts | 12 +- server/utils/interactions.ts | 2 +- 17 files changed, 950 insertions(+), 296 deletions(-) create mode 100644 patches/@discordjs+ws+1.0.0.patch create mode 100644 server/components/core/index.ts create mode 100644 server/components/core/interaction-handler.ts create mode 100644 server/components/core/ready.ts create mode 100644 server/components/ping/heartbeat.ts delete mode 100644 server/http/handler.ts delete mode 100644 server/http/types.ts delete mode 100644 server/http/verify.ts diff --git a/package-lock.json b/package-lock.json index a3ac613..30429fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,16 +4,18 @@ "requires": true, "packages": { "": { + "hasInstallScript": true, "dependencies": { "@discordjs/core": "1.0.0", "@discordjs/rest": "2.0.0", "@discordjs/util": "1.0.0", - "tweetnacl": "^1.0.3" + "@discordjs/ws": "1.0.0" }, "devDependencies": { "@citizenfx/server": "^2.0.9780-1", "@types/node": "16.9.1", "esbuild": "^0.23.1", + "patch-package": "^8.0.0", "prettier": "^3.3.3", "rimraf": "^6.0.1", "typescript": "^5.5.4" @@ -82,94 +84,23 @@ } }, "node_modules/@discordjs/ws": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz", - "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/collection": "^2.1.0", - "@discordjs/rest": "^2.3.0", - "@discordjs/util": "^1.1.0", - "@sapphire/async-queue": "^1.5.2", - "@types/ws": "^8.5.10", - "@vladfrangu/async_event_emitter": "^2.2.4", - "discord-api-types": "0.37.83", - "tslib": "^2.6.2", - "ws": "^8.16.0" - }, - "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/ws/node_modules/@discordjs/rest": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.0.tgz", - "integrity": "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.0.tgz", + "integrity": "sha512-POiImjuQJzwCxjJs4JCtDcTjzvjVsVQbnsaoW/F03yTVdrj/xSpmgv4383AnpNEYXI+CA6ggkz37phZDsZQ1NQ==", "license": "Apache-2.0", "dependencies": { - "@discordjs/collection": "^2.1.1", - "@discordjs/util": "^1.1.1", - "@sapphire/async-queue": "^1.5.3", - "@sapphire/snowflake": "^3.5.3", - "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "0.37.97", - "magic-bytes.js": "^1.10.0", - "tslib": "^2.6.3", - "undici": "6.19.8" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/ws/node_modules/@discordjs/rest/node_modules/discord-api-types": { - "version": "0.37.97", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.97.tgz", - "integrity": "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==", - "license": "MIT" - }, - "node_modules/@discordjs/ws/node_modules/@discordjs/util": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", - "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" + "@discordjs/collection": "^1.5.2", + "@discordjs/rest": "^2.0.0", + "@discordjs/util": "^1.0.0", + "@sapphire/async-queue": "^1.5.0", + "@types/ws": "^8.5.5", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "^0.37.50", + "tslib": "^2.6.1", + "ws": "^8.13.0" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/ws/node_modules/discord-api-types": { - "version": "0.37.83", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", - "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==", - "license": "MIT" - }, - "node_modules/@discordjs/ws/node_modules/undici": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", - "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", - "license": "MIT", "engines": { - "node": ">=18.17" + "node": ">=16.9.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -663,6 +594,13 @@ "npm": ">=7.0.0" } }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -689,6 +627,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -706,6 +654,88 @@ "balanced-match": "^1.0.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -726,6 +756,13 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -741,6 +778,24 @@ "node": ">= 8" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/discord-api-types": { "version": "0.37.100", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.100.tgz", @@ -761,6 +816,29 @@ "dev": true, "license": "MIT" }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", @@ -801,6 +879,29 @@ "@esbuild/win32-x64": "0.23.1" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -818,6 +919,59 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", @@ -842,6 +996,123 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -852,6 +1123,36 @@ "node": ">=8" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -878,6 +1179,58 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/json-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/lru-cache": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", @@ -894,6 +1247,20 @@ "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", "license": "MIT" }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/minimatch": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", @@ -910,6 +1277,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -920,6 +1297,53 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", @@ -927,6 +1351,107 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/patch-package/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/patch-package/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -954,6 +1479,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/prettier": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", @@ -990,6 +1528,37 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1026,6 +1595,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -1130,18 +1709,51 @@ "node": ">=8" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "license": "0BSD" }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "license": "Unlicense" - }, "node_modules/typescript": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", @@ -1168,6 +1780,16 @@ "node": ">=14.0" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1282,6 +1904,13 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -1302,6 +1931,19 @@ "optional": true } } + }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } } } } diff --git a/package.json b/package.json index d3a0198..e28ddbf 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "private": true, "type": "module", "scripts": { + "postinstall": "patch-package", "lint": "prettier --ignore-path .gitignore -c .", "format": "prettier --ignore-path .gitignore -w .", "typecheck": "tsc -p server", @@ -12,12 +13,13 @@ "@discordjs/core": "1.0.0", "@discordjs/rest": "2.0.0", "@discordjs/util": "1.0.0", - "tweetnacl": "^1.0.3" + "@discordjs/ws": "1.0.0" }, "devDependencies": { "@citizenfx/server": "^2.0.9780-1", "@types/node": "16.9.1", "esbuild": "^0.23.1", + "patch-package": "^8.0.0", "prettier": "^3.3.3", "rimraf": "^6.0.1", "typescript": "^5.5.4" diff --git a/patches/@discordjs+ws+1.0.0.patch b/patches/@discordjs+ws+1.0.0.patch new file mode 100644 index 0000000..85d30c4 --- /dev/null +++ b/patches/@discordjs+ws+1.0.0.patch @@ -0,0 +1,14 @@ +diff --git a/node_modules/@discordjs/ws/dist/index.mjs b/node_modules/@discordjs/ws/dist/index.mjs +index be08932..4b8fe39 100644 +--- a/node_modules/@discordjs/ws/dist/index.mjs ++++ b/node_modules/@discordjs/ws/dist/index.mjs +@@ -220,9 +220,6 @@ var WorkerShardingStrategy = class { + } + resolveWorkerPath() { + const path2 = this.options.workerPath; +- if (!path2) { +- return join(__dirname, "defaultWorker.js"); +- } + if (isAbsolute(path2)) { + return path2; + } diff --git a/server/components/core/index.ts b/server/components/core/index.ts new file mode 100644 index 0000000..0165313 --- /dev/null +++ b/server/components/core/index.ts @@ -0,0 +1,7 @@ +import { Component } from '../types.js'; +import { interactionHandler } from './interaction-handler.js'; +import { ready } from './ready.js'; + +export default { + gatewayEvents: [ready, interactionHandler], +} satisfies Component; diff --git a/server/components/core/interaction-handler.ts b/server/components/core/interaction-handler.ts new file mode 100644 index 0000000..ce325ff --- /dev/null +++ b/server/components/core/interaction-handler.ts @@ -0,0 +1,95 @@ +import { + ApplicationCommandType, + GatewayDispatchEvents, + InteractionType, +} from '@discordjs/core'; +import { interactions, statefuls } from '../loader.js'; +import { GatewayEvent } from '../types.js'; + +function findStateful(id: string, list: string[]): string | undefined { + return list + .filter((s) => id.startsWith(s)) + .sort((a, b) => b.length - a.length)[0]; +} + +export const interactionHandler = { + name: GatewayDispatchEvents.InteractionCreate, + type: 'on', + async execute(props) { + const { data: interaction } = props; + + switch (interaction.type) { + case InteractionType.ApplicationCommand: + case InteractionType.ApplicationCommandAutocomplete: + const command = interactions.commands.get( + interaction.data.name, + ); + + if (!command) + throw new Error( + `Command not defined for ${interaction.data.name}.`, + ); + + if ( + interaction.type === InteractionType.ApplicationCommand && + (command.data.type ?? ApplicationCommandType.ChatInput) === + interaction.data.type + ) + //@ts-ignore + await command.execute(props); + else if (command.autocomplete) + //@ts-ignore + await command.autocomplete(props); + break; + + case InteractionType.MessageComponent: + const componentId = interaction.data.custom_id; + + let component = interactions.messageComponents.get(componentId); + + if (!component) { + const staticId = findStateful( + componentId, + statefuls.messageComponents, + ); + + if (staticId) + component = + interactions.messageComponents.get(staticId); + } + + if (!component) + throw new Error( + `Message component not defined for ${interaction.data.custom_id}.`, + ); + + if (component.data.type === interaction.data.component_type) + //@ts-ignore + await component.execute(props); + break; + + case InteractionType.ModalSubmit: + const modalId = interaction.data.custom_id; + + let modal = interactions.modals.get(modalId); + + if (!modal) { + const staticId = findStateful(modalId, statefuls.modals); + + if (staticId) modal = interactions.modals.get(staticId); + } + + if (!modal) + throw new Error( + `Modal not defined for ${interaction.data.custom_id}.`, + ); + + //@ts-ignore + await modal.execute(props); + break; + + default: + break; + } + }, +} satisfies GatewayEvent; diff --git a/server/components/core/ready.ts b/server/components/core/ready.ts new file mode 100644 index 0000000..011e81c --- /dev/null +++ b/server/components/core/ready.ts @@ -0,0 +1,11 @@ +import { GatewayDispatchEvents } from '@discordjs/core'; +import { GatewayEvent } from '../types.js'; + +export const ready = { + name: GatewayDispatchEvents.Ready, + type: 'once', + async execute({ data }) { + const { username, discriminator } = data.user; + console.log(`Ready as ${username}#${discriminator}!`); + }, +} satisfies GatewayEvent; diff --git a/server/components/loader.ts b/server/components/loader.ts index 41d43f4..f5455c9 100644 --- a/server/components/loader.ts +++ b/server/components/loader.ts @@ -1,9 +1,10 @@ import { Collection } from '@discordjs/collection'; -import { RESTPutAPIApplicationCommandsJSONBody } from '@discordjs/core/http-only'; +import { RESTPutAPIApplicationCommandsJSONBody } from '@discordjs/core'; import EventEmitter from 'node:events'; import { inspect } from 'node:util'; -import { rest } from '../utils/env.js'; +import { client, gateway, rest } from '../utils/env.js'; import { isStatefulInteraction } from '../utils/stateful.js'; +import core from './core/index.js'; import ping from './ping/index.js'; import { ApplicationCommand, @@ -45,11 +46,15 @@ function registerEvents(emitter: EventEmitter, events: EventsMap[EventName][]) { function loadComponent({ restEvents, + wsEvents, + gatewayEvents, commands: componentCommands, messageComponents, modals, }: Component) { restEvents && registerEvents(rest, restEvents); + wsEvents && registerEvents(gateway, wsEvents); + gatewayEvents && registerEvents(client, gatewayEvents); componentCommands?.map((command) => { interactions.commands.set(command.data.name, command); @@ -71,5 +76,6 @@ function loadComponent({ } export function loadComponents() { + loadComponent(core); loadComponent(ping); } diff --git a/server/components/ping/command.ts b/server/components/ping/command.ts index e8f72db..7c1c56c 100644 --- a/server/components/ping/command.ts +++ b/server/components/ping/command.ts @@ -1,4 +1,7 @@ +import { WebSocketShardEvents } from '@discordjs/ws'; +import { gateway } from '../../utils/env.js'; import { ChatInputCommand } from '../types.js'; +import { getPing } from './heartbeat.js'; function pingMessage(p: string) { return `🏓 Pong! \`${p}\``; @@ -9,20 +12,27 @@ export const command = { name: 'ping', description: 'Ping command', }, - async execute({ api, data: interaction, cb }) { - cb(); + async execute({ api, data: interaction }) { + let replied = false; - const first = Date.now(); - await api.interactions.reply(interaction.id, interaction.token, { - content: pingMessage('fetching...'), - }); + if (getPing() < 0) { + const heartbeatPromise = new Promise((resolve) => { + gateway.once(WebSocketShardEvents.HeartbeatComplete, resolve); + }); - const ping = Math.ceil((Date.now() - first) / 2); - await api.interactions.editReply( - interaction.application_id, + await api.interactions.reply(interaction.id, interaction.token, { + content: pingMessage('fetching...'), + }); + + await heartbeatPromise; + replied = true; + } + + await api.interactions[replied ? 'editReply' : 'reply']( + replied ? interaction.application_id : interaction.id, interaction.token, { - content: pingMessage(`${ping}ms`), + content: pingMessage(`${getPing()}ms`), }, ); }, diff --git a/server/components/ping/heartbeat.ts b/server/components/ping/heartbeat.ts new file mode 100644 index 0000000..e990da2 --- /dev/null +++ b/server/components/ping/heartbeat.ts @@ -0,0 +1,15 @@ +import { WebSocketShardEvents } from '@discordjs/ws'; +import { WebSocketEvent } from '../types.js'; + +let ping = -1; +export function getPing() { + return ping; +} + +export const heartbeat = { + name: WebSocketShardEvents.HeartbeatComplete, + type: 'on', + async execute({ latency }) { + ping = latency; + }, +} satisfies WebSocketEvent; diff --git a/server/components/ping/index.ts b/server/components/ping/index.ts index f6209df..66fd16e 100644 --- a/server/components/ping/index.ts +++ b/server/components/ping/index.ts @@ -1,6 +1,8 @@ import { Component } from '../types.js'; import { command } from './command.js'; +import { heartbeat } from './heartbeat.js'; export default { + wsEvents: [heartbeat], commands: [command], } satisfies Component; diff --git a/server/components/types.ts b/server/components/types.ts index 4bcaa61..49ede1d 100644 --- a/server/components/types.ts +++ b/server/components/types.ts @@ -1,5 +1,4 @@ import { - API, APIApplicationCommandAutocompleteInteraction, APIApplicationCommandInteraction, APIButtonComponentWithCustomId, @@ -7,7 +6,6 @@ import { APIChatInputApplicationCommandInteraction, APIContextMenuInteraction, APIInteraction, - APIInteractionResponse, APIMentionableSelectComponent, APIMessageApplicationCommandInteraction, APIMessageComponentButtonInteraction, @@ -21,17 +19,27 @@ import { APIUserSelectComponent, ApplicationCommandType, ComponentType, + MappedEvents, RESTPostAPIChatInputApplicationCommandsJSONBody, RESTPostAPIContextMenuApplicationCommandsJSONBody, -} from '@discordjs/core/http-only'; + WithIntrinsicProps, +} from '@discordjs/core'; import { RestEvents } from '@discordjs/rest'; import { Awaitable } from '@discordjs/util'; +import { ManagerShardEventsMap } from '@discordjs/ws'; -export type EventName = keyof RestEvents; +export type EventName = + | keyof RestEvents + | keyof ManagerShardEventsMap + | keyof MappedEvents; export type EventExecuteArgs = T extends keyof RestEvents ? RestEvents[T] - : never; + : T extends keyof ManagerShardEventsMap + ? ManagerShardEventsMap[T] + : T extends keyof MappedEvents + ? MappedEvents[T] + : never; export interface IEvent { readonly type: 'on' | 'once'; @@ -72,11 +80,8 @@ export type InteractionData = ? APIModalInteractionResponseCallbackData : never; -export type InteractionExecuteArgs = { - api: API; - data: T; - cb: (response?: APIInteractionResponse) => void; -}; +export type InteractionExecuteArgs = + WithIntrinsicProps; export interface IInteraction { readonly data: InteractionData; @@ -92,11 +97,19 @@ export type SelectMenuInteractionWithType = APIMessageComponentSelectMenuInteraction & { data: { component_type: T } }; export type RestEvent = IEvent; +export type WebSocketEvent = IEvent; +export type GatewayEvent = IEvent; export type RestEventsMap = { [T in keyof RestEvents]: RestEvent; }; -export type EventsMap = RestEventsMap; +export type WebSocketEventsMap = { + [T in keyof ManagerShardEventsMap]: WebSocketEvent; +}; +export type GatewayEventsMap = { + [T in keyof MappedEvents]: GatewayEvent; +}; +export type EventsMap = RestEventsMap & WebSocketEventsMap & GatewayEventsMap; export type ChatInputCommand = IInteraction; @@ -134,6 +147,8 @@ export type Modal = IInteraction; export interface Component { readonly restEvents?: RestEventsMap[keyof RestEvents][]; + readonly wsEvents?: WebSocketEventsMap[keyof ManagerShardEventsMap][]; + readonly gatewayEvents?: GatewayEventsMap[keyof MappedEvents][]; readonly commands?: ApplicationCommand[]; readonly messageComponents?: MessageComponent[]; readonly modals?: Modal[]; diff --git a/server/http/handler.ts b/server/http/handler.ts deleted file mode 100644 index baa3a3b..0000000 --- a/server/http/handler.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - APIInteraction, - APIInteractionResponse, - ApplicationCommandType, - InteractionResponseType, - InteractionType, -} from '@discordjs/core/http-only'; -import { inspect } from 'util'; -import { interactions, statefuls } from '../components/loader.js'; -import { api } from '../utils/env.js'; -import { HttpHandlerRequest, HttpHandlerResponse } from './types.js'; -import { verifyDiscordRequest } from './verify.js'; - -function findStateful(id: string, list: string[]): string | undefined { - return list - .filter((s) => id.startsWith(s)) - .sort((a, b) => b.length - a.length)[0]; -} - -function handleInteraction( - interaction: APIInteraction, - cb: (response?: APIInteractionResponse) => void, -) { - const props = { data: interaction, cb, api }; - - switch (interaction.type) { - case InteractionType.Ping: - cb({ type: InteractionResponseType.Pong }); - break; - - case InteractionType.ApplicationCommand: - case InteractionType.ApplicationCommandAutocomplete: - const command = interactions.commands.get(interaction.data.name); - - if (!command) - throw new Error( - `Command not defined for ${interaction.data.name}`, - ); - - if ( - interaction.type === InteractionType.ApplicationCommand && - (command.data.type ?? ApplicationCommandType.ChatInput) === - interaction.data.type - ) - //@ts-ignore - command.execute(props); - else if (command.autocomplete) - //@ts-ignore - command.autocomplete(props); - break; - - case InteractionType.MessageComponent: - const componentId = interaction.data.custom_id; - - let component = interactions.messageComponents.get(componentId); - - if (!component) { - const staticId = findStateful( - componentId, - statefuls.messageComponents, - ); - - if (staticId) - component = interactions.messageComponents.get(staticId); - } - - if (!component) - throw new Error( - `Message component not defined for ${interaction.data.custom_id}.`, - ); - - if (component.data.type === interaction.data.component_type) - //@ts-ignore - component.execute(props); - break; - - case InteractionType.ModalSubmit: - const modalId = interaction.data.custom_id; - - let modal = interactions.modals.get(modalId); - - if (!modal) { - const staticId = findStateful(modalId, statefuls.modals); - - if (staticId) modal = interactions.modals.get(staticId); - } - - if (!modal) - throw new Error( - `Modal not defined for ${interaction.data.custom_id}.`, - ); - - //@ts-ignore - modal.execute(props); - break; - - default: - break; - } -} - -export function setupHttpHandler(verifyKey: string) { - const verify = verifyDiscordRequest(verifyKey); - - async function handler( - request: HttpHandlerRequest, - response: HttpHandlerResponse, - ) { - request.setDataHandler(async (body) => { - if (!verify(request, body)) { - response.writeHead(401); - return response.send('invalid request signature'); - } - - const interaction = JSON.parse(body) as APIInteraction; - try { - handleInteraction(interaction, (interactionResponse) => { - if (!interactionResponse) { - response.writeHead(202); - return response.send(); - } - - response.writeHead(200, { - 'Content-Type': 'application/json', - }); - response.write(JSON.stringify(interactionResponse)); - response.send(); - }); - } catch (e) { - console.error(e instanceof Error ? e.message : inspect(e)); - response.writeHead(500); - return response.send('internal server error'); - } - }); - } - - SetHttpHandler(handler); -} diff --git a/server/http/types.ts b/server/http/types.ts deleted file mode 100644 index 335b1c1..0000000 --- a/server/http/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface HttpHandlerRequest { - address: string; - headers: Record; - method: string; - path: string; - setDataHandler(handler: (data: string) => void): void; - setDataHandler( - handler: (data: ArrayBuffer) => void, - binary: 'binary', - ): void; - setCancelHandler(handler: () => void): void; -} - -export interface HttpHandlerResponse { - writeHead(code: number, headers?: Record): void; - write(data: string): void; - send(data?: string): void; -} diff --git a/server/http/verify.ts b/server/http/verify.ts deleted file mode 100644 index 679ada4..0000000 --- a/server/http/verify.ts +++ /dev/null @@ -1,16 +0,0 @@ -import nacl from 'tweetnacl'; -import { HttpHandlerRequest } from './types.js'; - -export function verifyDiscordRequest(verifyKey: string) { - return (request: HttpHandlerRequest, body: string): boolean => { - const signature = request.headers['X-Signature-Ed25519']; - const timestamp = request.headers['X-Signature-Timestamp']; - if (!signature || !timestamp) return false; - - return nacl.sign.detached.verify( - Buffer.from(timestamp + body), - Buffer.from(signature, 'hex'), - Buffer.from(verifyKey, 'hex'), - ); - }; -} diff --git a/server/index.ts b/server/index.ts index 6e67140..e46c722 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,8 +1,7 @@ -import { setTimeout } from 'timers/promises'; -import { inspect } from 'util'; +import { setTimeout } from 'node:timers/promises'; +import { inspect } from 'node:util'; import { commands, loadComponents } from './components/loader.js'; -import { setupHttpHandler } from './http/handler.js'; -import { api, botToken } from './utils/env.js'; +import { api, botToken, gateway } from './utils/env.js'; (async () => { await setTimeout(0); @@ -17,8 +16,6 @@ import { api, botToken } from './utils/env.js'; loadComponents(); const app = await api.oauth2.getCurrentBotApplicationInformation(); - setupHttpHandler(app.verify_key); - RegisterCommand( 'discordbot:deploy', async () => { @@ -31,4 +28,6 @@ import { api, botToken } from './utils/env.js'; }, true, ); + + await gateway.connect(); })().catch((e) => console.log(inspect(e))); diff --git a/server/utils/env.ts b/server/utils/env.ts index c1303e4..acca110 100644 --- a/server/utils/env.ts +++ b/server/utils/env.ts @@ -1,7 +1,15 @@ -import { API } from '@discordjs/core/http-only'; +import { Client } from '@discordjs/core'; import { REST } from '@discordjs/rest'; +import { WebSocketManager } from '@discordjs/ws'; export const botToken = GetConvar('discordbot:botToken', ''); export const rest = new REST({ version: '10' }).setToken(botToken); -export const api = new API(rest); +export const gateway = new WebSocketManager({ + token: botToken, + intents: 0, + rest: rest, +}); + +export const client = new Client({ rest, gateway }); +export const api = client.api; diff --git a/server/utils/interactions.ts b/server/utils/interactions.ts index c24788e..9cc2d60 100644 --- a/server/utils/interactions.ts +++ b/server/utils/interactions.ts @@ -6,7 +6,7 @@ import { APIChatInputApplicationCommandInteractionData, APIModalSubmission, ApplicationCommandOptionType, -} from '@discordjs/core/http-only'; +} from '@discordjs/core'; type MappedChatInputOptionValues = Record;