From 53c4847dbe6e1f9422fa65f4caf935a5f4912d8f Mon Sep 17 00:00:00 2001 From: didinele Date: Sat, 6 May 2023 19:01:55 +0300 Subject: [PATCH 01/12] chore: init package --- .github/CODEOWNERS | 1 + .github/issue-labeler.yml | 3 + .github/labeler.yml | 3 + .github/labels.yml | 2 + packages/brokers/LICENSE | 2 +- packages/proxy-container/LICENSE | 2 +- packages/proxy-container/tsconfig.json | 3 - packages/proxy/LICENSE | 2 +- packages/redis-gateway/.eslintrc.json | 3 + packages/redis-gateway/.gitignore | 28 +++ packages/redis-gateway/.lintstagedrc.cjs | 1 + packages/redis-gateway/.prettierignore | 6 + packages/redis-gateway/.prettierrc.cjs | 1 + packages/redis-gateway/LICENSE | 191 ++++++++++++++++++++ packages/redis-gateway/README.md | 67 +++++++ packages/redis-gateway/package.json | 66 +++++++ packages/redis-gateway/src/index.ts | 1 + packages/redis-gateway/tsconfig.eslint.json | 20 ++ packages/redis-gateway/tsconfig.json | 4 + packages/redis-gateway/tsup.config.ts | 6 + packages/ws/LICENSE | 2 +- yarn.lock | 30 ++- 22 files changed, 434 insertions(+), 10 deletions(-) create mode 100644 packages/redis-gateway/.eslintrc.json create mode 100644 packages/redis-gateway/.gitignore create mode 100644 packages/redis-gateway/.lintstagedrc.cjs create mode 100644 packages/redis-gateway/.prettierignore create mode 100644 packages/redis-gateway/.prettierrc.cjs create mode 100644 packages/redis-gateway/LICENSE create mode 100644 packages/redis-gateway/README.md create mode 100644 packages/redis-gateway/package.json create mode 100644 packages/redis-gateway/src/index.ts create mode 100644 packages/redis-gateway/tsconfig.eslint.json create mode 100644 packages/redis-gateway/tsconfig.json create mode 100644 packages/redis-gateway/tsup.config.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8b89a5e7f159..0e2c96615d74 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,6 +20,7 @@ /packages/next/ @discordjs/core /packages/proxy/ @discordjs/proxy /packages/proxy-container/ @discordjs/proxy +/packages/redis-gateway/ @discordjs/redis-gateway /packages/rest/ @discordjs/rest /packages/scripts/ @discordjs/scripts /packages/ui/ @discordjs/ui diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml index ff7e027a8b09..91ab03800470 100644 --- a/.github/issue-labeler.yml +++ b/.github/issue-labeler.yml @@ -34,6 +34,9 @@ packages:proxy: packages:proxy-container: - "### Which (application|package|application or package) is this (bug report|feature request) for\\?\\n\\nproxy-container\\n" +packages:redis-gateway: + - "### Which (application|package|application or package) is this (bug + report|feature request) for\\?\\n\\nredis-gateway\\n" packages:rest: - "### Which (application|package|application or package) is this (bug report|feature request) for\\?\\n\\nrest\\n" diff --git a/.github/labeler.yml b/.github/labeler.yml index 91c0c1e1fecf..b40cda6ef8cb 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -37,6 +37,9 @@ packages:proxy: packages:proxy-container: - packages/proxy-container/* - packages/proxy-container/**/* +packages:redis-gateway: + - packages/redis-gateway/* + - packages/redis-gateway/**/* packages:rest: - packages/rest/* - packages/rest/**/* diff --git a/.github/labels.yml b/.github/labels.yml index c053d9e3983d..08eecdf881d1 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -74,6 +74,8 @@ color: fbca04 - name: packages:proxy-container color: fbca04 +- name: packages:redis-gateway + color: fbca04 - name: packages:rest color: fbca04 - name: packages:ui diff --git a/packages/brokers/LICENSE b/packages/brokers/LICENSE index f9786ff8f379..39794ad7ebf3 100644 --- a/packages/brokers/LICENSE +++ b/packages/brokers/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS Copyright 2022 Noel Buechler - Copyright 2022 Charlotte Cristea + Copyright 2022 Denis Cristea Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/proxy-container/LICENSE b/packages/proxy-container/LICENSE index f9786ff8f379..39794ad7ebf3 100644 --- a/packages/proxy-container/LICENSE +++ b/packages/proxy-container/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS Copyright 2022 Noel Buechler - Copyright 2022 Charlotte Cristea + Copyright 2022 Denis Cristea Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/proxy-container/tsconfig.json b/packages/proxy-container/tsconfig.json index cad37088e6f7..fd8b5e417b9f 100644 --- a/packages/proxy-container/tsconfig.json +++ b/packages/proxy-container/tsconfig.json @@ -1,7 +1,4 @@ { "extends": "../../tsconfig.json", - "compilerOptions": { - "skipLibCheck": true - }, "include": ["src/**/*.ts"] } diff --git a/packages/proxy/LICENSE b/packages/proxy/LICENSE index f9786ff8f379..39794ad7ebf3 100644 --- a/packages/proxy/LICENSE +++ b/packages/proxy/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS Copyright 2022 Noel Buechler - Copyright 2022 Charlotte Cristea + Copyright 2022 Denis Cristea Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/packages/redis-gateway/.eslintrc.json b/packages/redis-gateway/.eslintrc.json new file mode 100644 index 000000000000..99ef7cec8051 --- /dev/null +++ b/packages/redis-gateway/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "../../.eslintrc.json" +} diff --git a/packages/redis-gateway/.gitignore b/packages/redis-gateway/.gitignore new file mode 100644 index 000000000000..90500960f32b --- /dev/null +++ b/packages/redis-gateway/.gitignore @@ -0,0 +1,28 @@ +# Packages +node_modules + +# Log files +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Env +.env + +# Dist +dist +dist-docs + +# Docs +docs/**/* +!docs/README.md + +# Miscellaneous +.turbo +.tmp +coverage diff --git a/packages/redis-gateway/.lintstagedrc.cjs b/packages/redis-gateway/.lintstagedrc.cjs new file mode 100644 index 000000000000..dc17706a55ac --- /dev/null +++ b/packages/redis-gateway/.lintstagedrc.cjs @@ -0,0 +1 @@ +module.exports = require('../../.lintstagedrc.json'); diff --git a/packages/redis-gateway/.prettierignore b/packages/redis-gateway/.prettierignore new file mode 100644 index 000000000000..fc03103c7b8c --- /dev/null +++ b/packages/redis-gateway/.prettierignore @@ -0,0 +1,6 @@ +.turbo +coverage +dist +dist-docs +docs/docs.api.json +CHANGELOG.md diff --git a/packages/redis-gateway/.prettierrc.cjs b/packages/redis-gateway/.prettierrc.cjs new file mode 100644 index 000000000000..f004026c7647 --- /dev/null +++ b/packages/redis-gateway/.prettierrc.cjs @@ -0,0 +1 @@ +module.exports = require('../../.prettierrc.json'); diff --git a/packages/redis-gateway/LICENSE b/packages/redis-gateway/LICENSE new file mode 100644 index 000000000000..106fee63c52e --- /dev/null +++ b/packages/redis-gateway/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2023 Noel Buechler + Copyright 2023 Denis Cristea + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/redis-gateway/README.md b/packages/redis-gateway/README.md new file mode 100644 index 000000000000..451701cf8a22 --- /dev/null +++ b/packages/redis-gateway/README.md @@ -0,0 +1,67 @@ +
+
+

+ discord.js +

+
+

+ Discord server + dockerhub version + dockerhub pulls + Build status +

+

+ Vercel + Cloudflare Workers +

+
+ +## About + +`@discordjs/redis-gateway` - Discord gateway running behind Redis as a message broker + +## Usage + +Quickly spin up an instance: + + + +`docker run -d --restart unless-stopped --name gateway discordjs/redis-gateway` + +Use it: + +```ts +// TODO +``` + +## Links + +- [Website][website] ([source][website-source]) +- [Documentation][documentation] +- [Guide][guide] ([source][guide-source]) + Also see the v13 to v14 [Update Guide][guide-update], which includes updated and removed items from the library. +- [discord.js Discord server][discord] +- [Discord API Discord server][discord-api] +- [GitHub][source] +- [Related libraries][related-libs] + +## Contributing + +Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the +[documentation][documentation]. +See [the contribution guide][contributing] if you'd like to submit a PR. + +## Help + +If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle nudge in the right direction, please don't hesitate to join our official [discord.js Server][discord]. + +[website]: https://discord.js.org +[website-source]: https://github.com/discordjs/discord.js/tree/main/apps/website +[guide]: https://discordjs.guide/ +[guide-source]: https://github.com/discordjs/guide +[guide-update]: https://discordjs.guide/additional-info/changes-in-v14.html +[discord]: https://discord.gg/djs +[discord-api]: https://discord.gg/discord-api +[source]: https://github.com/discordjs/discord.js/tree/main/packages/redis-gateway +[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries +[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md diff --git a/packages/redis-gateway/package.json b/packages/redis-gateway/package.json new file mode 100644 index 000000000000..8f8ec6bca66e --- /dev/null +++ b/packages/redis-gateway/package.json @@ -0,0 +1,66 @@ +{ + "name": "@discordjs/redis-gateway", + "version": "0.1.0", + "description": "Discord gateway running behind Redis as a message broker", + "scripts": { + "build": "tsup", + "lint": "prettier --check . && cross-env TIMING=1 eslint src --ext .mjs,.js,.ts --format=pretty", + "format": "prettier --write . && cross-env TIMING=1 eslint src --ext .mjs,.js,.ts --fix --format=pretty", + "fmt": "yarn format", + "prepack": "yarn build && yarn lint" + }, + "type": "module", + "main": "./dist/index.js", + "directories": { + "lib": "src" + }, + "files": [ + "dist" + ], + "contributors": [ + "Crawl ", + "SpaceEEC ", + "Vlad Frangu ", + "Aura Román ", + "DD " + ], + "license": "Apache-2.0", + "keywords": [ + "discord", + "api", + "ws", + "brokers", + "gateway", + "discordapp", + "discordjs" + ], + "repository": { + "type": "git", + "url": "https://github.com/discordjs/discord.js.git", + "directory": "packages/redis-gateway" + }, + "bugs": { + "url": "https://github.com/discordjs/discord.js/issues" + }, + "homepage": "https://discord.js.org", + "dependencies": { + "tslib": "^2.5.0" + }, + "devDependencies": { + "@types/node": "16.18.25", + "cross-env": "^7.0.3", + "eslint": "^8.39.0", + "eslint-config-neon": "^0.1.46", + "eslint-formatter-pretty": "^5.0.0", + "prettier": "^2.8.8", + "tsup": "^6.7.0", + "turbo": "^1.9.4-canary.9", + "typescript": "^5.0.4" + }, + "engines": { + "node": ">=16.9.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/redis-gateway/src/index.ts b/packages/redis-gateway/src/index.ts new file mode 100644 index 000000000000..3609ee7bc02f --- /dev/null +++ b/packages/redis-gateway/src/index.ts @@ -0,0 +1 @@ +console.log('Hello, from @discordjs/redis-gateway'); diff --git a/packages/redis-gateway/tsconfig.eslint.json b/packages/redis-gateway/tsconfig.eslint.json new file mode 100644 index 000000000000..d04d4be3aedc --- /dev/null +++ b/packages/redis-gateway/tsconfig.eslint.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowJs": true + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.mjs", + "**/*.jsx", + "**/*.test.ts", + "**/*.test.js", + "**/*.test.mjs", + "**/*.spec.ts", + "**/*.spec.js", + "**/*.spec.mjs" + ], + "exclude": [] +} diff --git a/packages/redis-gateway/tsconfig.json b/packages/redis-gateway/tsconfig.json new file mode 100644 index 000000000000..fd8b5e417b9f --- /dev/null +++ b/packages/redis-gateway/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts"] +} diff --git a/packages/redis-gateway/tsup.config.ts b/packages/redis-gateway/tsup.config.ts new file mode 100644 index 000000000000..93b776cac16b --- /dev/null +++ b/packages/redis-gateway/tsup.config.ts @@ -0,0 +1,6 @@ +import { createTsupConfig } from '../../tsup.config.js'; + +export default createTsupConfig({ + format: ['esm'], + minify: true, +}); diff --git a/packages/ws/LICENSE b/packages/ws/LICENSE index f9786ff8f379..39794ad7ebf3 100644 --- a/packages/ws/LICENSE +++ b/packages/ws/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS Copyright 2022 Noel Buechler - Copyright 2022 Charlotte Cristea + Copyright 2022 Denis Cristea Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/yarn.lock b/yarn.lock index 729d2218c1a6..7529560d0ded 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2313,6 +2313,23 @@ __metadata: languageName: unknown linkType: soft +"@discordjs/redis-gateway@workspace:packages/redis-gateway": + version: 0.0.0-use.local + resolution: "@discordjs/redis-gateway@workspace:packages/redis-gateway" + dependencies: + "@types/node": 16.18.25 + cross-env: ^7.0.3 + eslint: ^8.39.0 + eslint-config-neon: ^0.1.46 + eslint-formatter-pretty: ^5.0.0 + prettier: ^2.8.8 + tslib: ^2.5.0 + tsup: ^6.7.0 + turbo: ^1.9.4-canary.9 + typescript: ^5.0.4 + languageName: unknown + linkType: soft + "@discordjs/rest@^1.7.1, @discordjs/rest@workspace:^, @discordjs/rest@workspace:packages/rest": version: 0.0.0-use.local resolution: "@discordjs/rest@workspace:packages/rest" @@ -6650,6 +6667,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:16.18.25": + version: 16.18.25 + resolution: "@types/node@npm:16.18.25" + checksum: 181760ad6b54fcc498dfeb249e98bbf0be51d7c35e92e760e1a82004fa42b86e8c33a8f8dd7743b5ef872bda0753d9e6a5b8e3f0aed63e9eb79b4e65760c1fbe + languageName: node + linkType: hard + "@types/node@npm:16.18.26, @types/node@npm:^16.0.0": version: 16.18.26 resolution: "@types/node@npm:16.18.26" @@ -12246,7 +12270,7 @@ __metadata: languageName: node linkType: hard -"eslint-config-neon@npm:^0.1.47": +"eslint-config-neon@npm:^0.1.46, eslint-config-neon@npm:^0.1.47": version: 0.1.47 resolution: "eslint-config-neon@npm:0.1.47" dependencies: @@ -12838,7 +12862,7 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.40.0": +"eslint@npm:^8.39.0, eslint@npm:^8.40.0": version: 8.40.0 resolution: "eslint@npm:8.40.0" dependencies: @@ -24609,7 +24633,7 @@ __metadata: languageName: node linkType: hard -"turbo@npm:^1.9.4-canary.10": +"turbo@npm:^1.9.4-canary.10, turbo@npm:^1.9.4-canary.9": version: 1.9.4-canary.10 resolution: "turbo@npm:1.9.4-canary.10" dependencies: From 5ce966a82216d7e283a7ab83afb135ba18629830 Mon Sep 17 00:00:00 2001 From: didinele Date: Sat, 6 May 2023 19:15:24 +0300 Subject: [PATCH 02/12] chore: dockerfile and ci --- .github/workflows/publish-dev-docker.yml | 11 +++++++++- .github/workflows/publish-docker.yml | 13 +++++++++++- packages/redis-gateway/Dockerfile | 26 ++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 packages/redis-gateway/Dockerfile diff --git a/.github/workflows/publish-dev-docker.yml b/.github/workflows/publish-dev-docker.yml index bafe823f7a57..63ada5a2ce20 100644 --- a/.github/workflows/publish-dev-docker.yml +++ b/.github/workflows/publish-dev-docker.yml @@ -6,6 +6,14 @@ on: jobs: docker-publish: name: Docker publish + strategy: + fail-fast: false + matrix: + include: + - package: '@discordjs/proxy-container' + image: 'proxy-container' + - package: '@discordjs/redis-gateway' + image: 'redis-gateway' runs-on: ubuntu-latest if: github.repository_owner == 'discordjs' steps: @@ -16,6 +24,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: 18 + registry-url: https://registry.npmjs.org/ - name: Install dependencies uses: ./packages/actions/src/yarnCache @@ -30,4 +39,4 @@ jobs: run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin - name: Build & push docker image - run: yarn docker build --buildkit @discordjs/proxy-container -t discordjs/proxy:latest --push + run: yarn docker build --buildkit ${{ matrix.package }} -t discordjs/${{ matrix.image }}:latest --push diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 4854fc3d3ccc..a6d6f00b5642 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -4,6 +4,16 @@ on: jobs: docker-publish: name: Docker publish + strategy: + fail-fast: false + matrix: + include: + - package: '@discordjs/proxy-container' + folder: 'proxy-container' + image: 'proxy-container' + - package: '@discordjs/redis-gateway' + folder: 'redis-gateway' + image: 'redis-gateway' runs-on: ubuntu-latest steps: - name: Checkout repository @@ -13,6 +23,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: 18 + registry-url: https://registry.npmjs.org/ - name: Install dependencies uses: ./packages/actions/src/yarnCache @@ -27,4 +38,4 @@ jobs: run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin - name: Build & push docker image - run: yarn docker build --buildkit @discordjs/proxy-container -t discordjs/proxy:$(cut -d '.' -f1 <<< $(jq --raw-output '.version' packages/proxy-container/package.json)) --push + run: yarn docker build --buildkit ${{ matrix.package }} -t discordjs/${{ matrix.image }}:$(cut -d '.' -f1 <<< $(jq --raw-output '.version' packages/${{ matrix.folder }}/package.json)) --push diff --git a/packages/redis-gateway/Dockerfile b/packages/redis-gateway/Dockerfile new file mode 100644 index 000000000000..65c2a96356ff --- /dev/null +++ b/packages/redis-gateway/Dockerfile @@ -0,0 +1,26 @@ +FROM node:18-alpine AS builder + +RUN apk update +RUN apk add --no-cache libc6-compat + +WORKDIR /usr/redis-gateway + +COPY manifests . + +RUN npm install --global is-ci husky + +RUN yarn install --immutable --inline-builds +RUN rm -rf .yarn/cache + +FROM node:18-alpine AS runner + +WORKDIR /usr/redis-gateway + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 redis-gateway +USER redis-gateway + +COPY --from=builder /usr/redis-gateway . +COPY packs . + +CMD ["node", "--enable-source-maps", "dist/index.js"] From f7d8aa6e590abeed783157cfe2b04746cdbae273 Mon Sep 17 00:00:00 2001 From: didinele Date: Sun, 7 May 2023 21:44:54 +0300 Subject: [PATCH 03/12] chore(core): document gateway --- packages/core/src/Gateway.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core/src/Gateway.ts b/packages/core/src/Gateway.ts index f9c3778bbd42..0924d1405977 100644 --- a/packages/core/src/Gateway.ts +++ b/packages/core/src/Gateway.ts @@ -2,11 +2,20 @@ import type { Awaitable } from '@discordjs/util'; import type { ManagerShardEventsMap, WebSocketShardEvents } from '@discordjs/ws'; import type { GatewaySendPayload } from 'discord-api-types/v10'; +/** + * A Discord gateway-like interface that can be used to send & recieve events. + */ export interface Gateway { + /** + * Gets how many shards your bot is running. + */ getShardCount(): Awaitable; on( event: WebSocketShardEvents.Dispatch, listener: (...params: ManagerShardEventsMap[WebSocketShardEvents.Dispatch]) => Awaitable, ): this; + /** + * Sends a payload to the specified shard + */ send(shardId: number, payload: GatewaySendPayload): Awaitable; } From cf580972c06fb64a2898c1db5110f7147efe7796 Mon Sep 17 00:00:00 2001 From: didinele Date: Sun, 7 May 2023 21:45:49 +0300 Subject: [PATCH 04/12] feat: redis gateway --- packages/redis-gateway/README.md | 44 ++++++++++++-- packages/redis-gateway/package.json | 8 ++- packages/redis-gateway/src/discordEvents.ts | 15 +++++ packages/redis-gateway/src/env.ts | 35 ++++++++++++ packages/redis-gateway/src/index.ts | 63 ++++++++++++++++++++- yarn.lock | 8 ++- 6 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 packages/redis-gateway/src/discordEvents.ts create mode 100644 packages/redis-gateway/src/env.ts diff --git a/packages/redis-gateway/README.md b/packages/redis-gateway/README.md index 451701cf8a22..5fa772e5adee 100644 --- a/packages/redis-gateway/README.md +++ b/packages/redis-gateway/README.md @@ -22,18 +22,52 @@ ## Usage -Quickly spin up an instance: +Set up an `.env` file: + +``` +REDIS_URL=redis://localhost:6379 +DISCORD_TOKEN=your-token-here +DISCORD_PROXY_URL=htt://localhost:8080 # if you want to use an HTTP proxy for DAPI calls (optional) +INTENTS=0 # intents to use (optional, defaults to none) +SHARD_COUNT=1 # number of total shards your bot should be running (optional, defaults to Discord recommended count) +SHARD_IDS=0 # comma-separated list of shard IDs to run (optional, defaults to all shards) +SHARDS_PER_WORKER=2 # number of shards per worker_thread or "all" (optional, if not specified, all shards will be run in the main thread) +``` - +Quickly spin up an instance: -`docker run -d --restart unless-stopped --name gateway discordjs/redis-gateway` +`docker run -d --restart unless-stopped --env-file .env --name gateway discordjs/redis-gateway` Use it: -```ts -// TODO +```js +import Redis from 'ioredis'; +import { PubSubRedisBroker } from '@discordjs/brokers'; +import { GatewayDispatchEvents } from 'discord-api-types/v10'; + +const redis = new Redis(); +const broker = new PubSubRedisBroker({ redisClient: redis, encode, decode }); + +broker.on(GatewayDispatchEvents.InteractionCreate, async ({ data: interaction, ack }) => { + if (interaction.type !== InteractionType.ApplicationCommand) { + return; + } + + if (interaction.data.name === 'ping') { + // reply with pong using your favorite Discord API library + } + + await ack(); +}); ``` +For TypeScript usage, you can pass in a gereric type to the `PubSubRedisBroker` to map out all the events, +refer to [this container's implementation](https://github.com/discordjs/discord.js/tree/main/packages/redis-gateway/src/index.ts#L15) for reference. + +Also note that [core](https://github.com/discordjs/discord.js/tree/main/packages/core) supports an +abstract `gateway` property that can be easily implemented, making this pretty comfortable to +use in conjunction. Refer to the [Gateway documentation](https://discord.js.org/docs/packages/core/main/Gateway:Interface) + ## Links - [Website][website] ([source][website-source]) diff --git a/packages/redis-gateway/package.json b/packages/redis-gateway/package.json index 8f8ec6bca66e..30276b34006a 100644 --- a/packages/redis-gateway/package.json +++ b/packages/redis-gateway/package.json @@ -44,7 +44,13 @@ }, "homepage": "https://discord.js.org", "dependencies": { - "tslib": "^2.5.0" + "@discordjs/brokers": "workspace:^", + "@discordjs/rest": "workspace:^", + "@discordjs/ws": "workspace:^", + "discord-api-types": "^0.37.41", + "ioredis": "^5.3.2", + "tslib": "^2.5.0", + "undici": "^5.22.0" }, "devDependencies": { "@types/node": "16.18.25", diff --git a/packages/redis-gateway/src/discordEvents.ts b/packages/redis-gateway/src/discordEvents.ts new file mode 100644 index 000000000000..1b72c0818a36 --- /dev/null +++ b/packages/redis-gateway/src/discordEvents.ts @@ -0,0 +1,15 @@ +import type { GatewayDispatchEvents, GatewayDispatchPayload, GatewaySendPayload } from 'discord-api-types/v10'; + +// need this to be its own type for some reason, the compiler doesn't behave the same way if we in-line it +type _DiscordEvents = { + [K in GatewayDispatchEvents]: GatewayDispatchPayload & { + t: K; + }; +}; + +export type DiscordEvents = { + // @ts-expect-error - unclear why this ignore is needed, might be because dapi-types is missing some events from the union again + [K in keyof _DiscordEvents]: _DiscordEvents[K]['d']; +} & { + gateway_send: GatewaySendPayload; +}; diff --git a/packages/redis-gateway/src/env.ts b/packages/redis-gateway/src/env.ts new file mode 100644 index 000000000000..c327a3702228 --- /dev/null +++ b/packages/redis-gateway/src/env.ts @@ -0,0 +1,35 @@ +import process from 'node:process'; +import type { GatewayIntentBits } from 'discord-api-types/v10'; + +export class Env { + public readonly redisUrl: string = process.env.REDIS_URL!; + + public readonly discordToken: string = process.env.DISCORD_TOKEN!; + + public readonly discordProxyURL: string | null = process.env.DISCORD_PROXY_URL ?? null; + + public readonly intents: GatewayIntentBits | 0 = Number(process.env.INTENTS ?? 0); + + public readonly shardCount: number | null = process.env.SHARD_COUNT ? Number(process.env.SHARD_COUNT) : null; + + public readonly shardIds: number[] | null = process.env.SHARD_IDS + ? process.env.SHARD_IDS.split(',').map(Number) + : null; + + public readonly shardsPerWorker: number | 'all' | null = + process.env.SHARDS_PER_WORKER === 'all' + ? 'all' + : process.env.SHARDS_PER_WORKER + ? Number(process.env.SHARDS_PER_WORKER) + : null; + + private readonly REQUIRED_ENV_VARS = ['REDIS_URL', 'DISCORD_TOKEN'] as const; + + public constructor() { + for (const key of this.REQUIRED_ENV_VARS) { + if (!(key in process.env)) { + throw new Error(`Missing required environment variable: ${key}`); + } + } + } +} diff --git a/packages/redis-gateway/src/index.ts b/packages/redis-gateway/src/index.ts index 3609ee7bc02f..6a349c519fc5 100644 --- a/packages/redis-gateway/src/index.ts +++ b/packages/redis-gateway/src/index.ts @@ -1 +1,62 @@ -console.log('Hello, from @discordjs/redis-gateway'); +import { randomBytes } from 'node:crypto'; +import { PubSubRedisBroker } from '@discordjs/brokers'; +import type { RESTOptions } from '@discordjs/rest'; +import { REST } from '@discordjs/rest'; +import type { OptionalWebSocketManagerOptions, RequiredWebSocketManagerOptions } from '@discordjs/ws'; +import { WorkerShardingStrategy, CompressionMethod, WebSocketManager, WebSocketShardEvents } from '@discordjs/ws'; +import Redis from 'ioredis'; +import { ProxyAgent } from 'undici'; +import type { DiscordEvents } from './discordEvents.js'; +import { Env } from './env.js'; + +const env = new Env(); + +const redisClient = new Redis(env.redisUrl); +const broker = new PubSubRedisBroker({ + redisClient, +}); + +const restOptions: Partial = {}; +if (env.discordProxyURL) { + restOptions.api = `${env.discordProxyURL}/api`; +} + +const rest = new REST(restOptions).setToken(env.discordToken); +if (env.discordProxyURL) { + rest.setAgent(new ProxyAgent(env.discordProxyURL)); +} + +const gatewayOptions: Partial & RequiredWebSocketManagerOptions = { + token: env.discordToken, + rest, + intents: env.intents, + compression: CompressionMethod.ZlibStream, + shardCount: env.shardCount, + shardIds: env.shardIds, +}; +if (env.shardsPerWorker) { + gatewayOptions.buildStrategy = (manager) => + new WorkerShardingStrategy(manager, { shardsPerWorker: env.shardsPerWorker! }); +} + +const gateway = new WebSocketManager(gatewayOptions); + +gateway + .on(WebSocketShardEvents.Debug, ({ message, shardId }) => console.log(`[WS Shard ${shardId}] [DEBUG]`, message)) + .on(WebSocketShardEvents.Hello, ({ shardId }) => console.log(`[WS Shard ${shardId}] [HELLO]`)) + .on(WebSocketShardEvents.Ready, ({ shardId }) => console.log(`[WS Shard ${shardId}] [READY]`)) + .on(WebSocketShardEvents.Resumed, ({ shardId }) => console.log(`[WS Shard ${shardId}] [RESUMED]`)) + .on(WebSocketShardEvents.Dispatch, ({ data }) => void broker.publish(data.t, data.d)); + +broker.on('gateway_send', async ({ data, ack }) => { + for (const shardId of await gateway.getShardIds()) { + await gateway.send(shardId, data); + } + + await ack(); +}); + +// we use a random group name because we don't want work-balancing, +// we need this to be fanned out so all shards get the payload +await broker.subscribe(randomBytes(16).toString('hex'), ['gateway_send']); +await gateway.connect(); diff --git a/yarn.lock b/yarn.lock index 7529560d0ded..d23d4deb9057 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2019,7 +2019,7 @@ __metadata: languageName: unknown linkType: soft -"@discordjs/brokers@workspace:packages/brokers": +"@discordjs/brokers@workspace:^, @discordjs/brokers@workspace:packages/brokers": version: 0.0.0-use.local resolution: "@discordjs/brokers@workspace:packages/brokers" dependencies: @@ -2317,16 +2317,22 @@ __metadata: version: 0.0.0-use.local resolution: "@discordjs/redis-gateway@workspace:packages/redis-gateway" dependencies: + "@discordjs/brokers": "workspace:^" + "@discordjs/rest": "workspace:^" + "@discordjs/ws": "workspace:^" "@types/node": 16.18.25 cross-env: ^7.0.3 + discord-api-types: ^0.37.41 eslint: ^8.39.0 eslint-config-neon: ^0.1.46 eslint-formatter-pretty: ^5.0.0 + ioredis: ^5.3.2 prettier: ^2.8.8 tslib: ^2.5.0 tsup: ^6.7.0 turbo: ^1.9.4-canary.9 typescript: ^5.0.4 + undici: ^5.22.0 languageName: unknown linkType: soft From 0be62db4b95329d93c3db5b5efb2c49deceecb7c Mon Sep 17 00:00:00 2001 From: didinele Date: Sun, 7 May 2023 21:56:06 +0300 Subject: [PATCH 05/12] chore: additional readme mention --- packages/redis-gateway/README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/redis-gateway/README.md b/packages/redis-gateway/README.md index 5fa772e5adee..c7ba590fa6f7 100644 --- a/packages/redis-gateway/README.md +++ b/packages/redis-gateway/README.md @@ -43,7 +43,7 @@ Use it: ```js import Redis from 'ioredis'; import { PubSubRedisBroker } from '@discordjs/brokers'; -import { GatewayDispatchEvents } from 'discord-api-types/v10'; +import { GatewayDispatchEvents, InteractionType, GatewayOpcodes } from 'discord-api-types/v10'; const redis = new Redis(); const broker = new PubSubRedisBroker({ redisClient: redis, encode, decode }); @@ -59,6 +59,17 @@ broker.on(GatewayDispatchEvents.InteractionCreate, async ({ data: interaction, a await ack(); }); + +// you can also use the broker to send payloads to the gateway +await broker.publish('gateway_send', { + op: GatewayOpcodes.PresenceUpdate, + d: { + activities: [{ type: ActivityType.Playing, name: 'meow :3' }], + afk: false, + since: null, + status: PresenceUpdateStatus.Online, + }, +}); ``` For TypeScript usage, you can pass in a gereric type to the `PubSubRedisBroker` to map out all the events, From 87dee7072cde3061a69d493e35fe3b76f719104e Mon Sep 17 00:00:00 2001 From: didinele Date: Sun, 7 May 2023 22:03:37 +0300 Subject: [PATCH 06/12] chore: nits --- .github/CODEOWNERS | 2 +- .github/workflows/publish-dev-docker.yml | 1 - .github/workflows/publish-docker.yml | 1 - packages/proxy-container/README.md | 1 - packages/redis-gateway/README.md | 5 ++--- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0e2c96615d74..dafeb5012811 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,7 +20,7 @@ /packages/next/ @discordjs/core /packages/proxy/ @discordjs/proxy /packages/proxy-container/ @discordjs/proxy -/packages/redis-gateway/ @discordjs/redis-gateway +/packages/redis-gateway/ @discordjs/brokers @discordjs/ws /packages/rest/ @discordjs/rest /packages/scripts/ @discordjs/scripts /packages/ui/ @discordjs/ui diff --git a/.github/workflows/publish-dev-docker.yml b/.github/workflows/publish-dev-docker.yml index 63ada5a2ce20..4a38e117ff74 100644 --- a/.github/workflows/publish-dev-docker.yml +++ b/.github/workflows/publish-dev-docker.yml @@ -24,7 +24,6 @@ jobs: uses: actions/setup-node@v3 with: node-version: 18 - registry-url: https://registry.npmjs.org/ - name: Install dependencies uses: ./packages/actions/src/yarnCache diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index a6d6f00b5642..c879e6e97495 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -23,7 +23,6 @@ jobs: uses: actions/setup-node@v3 with: node-version: 18 - registry-url: https://registry.npmjs.org/ - name: Install dependencies uses: ./packages/actions/src/yarnCache diff --git a/packages/proxy-container/README.md b/packages/proxy-container/README.md index 6821bb725c49..cf1773cfaf6a 100644 --- a/packages/proxy-container/README.md +++ b/packages/proxy-container/README.md @@ -55,7 +55,6 @@ Webhooks with tokens or other requests that don't include the Authorization head ## Links - [Website][website] ([source][website-source]) -- [Documentation][documentation] - [Guide][guide] ([source][guide-source]) Also see the v13 to v14 [Update Guide][guide-update], which includes updated and removed items from the library. - [discord.js Discord server][discord] diff --git a/packages/redis-gateway/README.md b/packages/redis-gateway/README.md index c7ba590fa6f7..519f4893e8b0 100644 --- a/packages/redis-gateway/README.md +++ b/packages/redis-gateway/README.md @@ -46,7 +46,7 @@ import { PubSubRedisBroker } from '@discordjs/brokers'; import { GatewayDispatchEvents, InteractionType, GatewayOpcodes } from 'discord-api-types/v10'; const redis = new Redis(); -const broker = new PubSubRedisBroker({ redisClient: redis, encode, decode }); +const broker = new PubSubRedisBroker({ redisClient: redis }); broker.on(GatewayDispatchEvents.InteractionCreate, async ({ data: interaction, ack }) => { if (interaction.type !== InteractionType.ApplicationCommand) { @@ -77,12 +77,11 @@ refer to [this container's implementation](https://github.com/discordjs/discord. Also note that [core](https://github.com/discordjs/discord.js/tree/main/packages/core) supports an abstract `gateway` property that can be easily implemented, making this pretty comfortable to -use in conjunction. Refer to the [Gateway documentation](https://discord.js.org/docs/packages/core/main/Gateway:Interface) +use in conjunction. Refer to the [Gateway documentation](https://discord.js.org/docs/packages/core/main/Gateway:Interface). ## Links - [Website][website] ([source][website-source]) -- [Documentation][documentation] - [Guide][guide] ([source][guide-source]) Also see the v13 to v14 [Update Guide][guide-update], which includes updated and removed items from the library. - [discord.js Discord server][discord] From 66497f123c2668fcc967c48e37dce384b8f78276 Mon Sep 17 00:00:00 2001 From: didinele Date: Mon, 8 May 2023 12:15:05 +0300 Subject: [PATCH 07/12] refactor(core): allow easily supporting redis gateway --- packages/core/package.json | 1 + packages/core/src/client.ts | 162 +++----------------- packages/core/src/{ => gateway}/Gateway.ts | 3 - packages/core/src/gateway/RedisGateway.ts | 50 ++++++ packages/core/src/index.ts | 4 +- packages/redis-gateway/README.md | 46 +++++- packages/redis-gateway/package.json | 1 + packages/redis-gateway/src/discordEvents.ts | 15 -- packages/redis-gateway/src/index.ts | 14 +- yarn.lock | 2 + 10 files changed, 126 insertions(+), 172 deletions(-) rename packages/core/src/{ => gateway}/Gateway.ts (91%) create mode 100644 packages/core/src/gateway/RedisGateway.ts delete mode 100644 packages/redis-gateway/src/discordEvents.ts diff --git a/packages/core/package.json b/packages/core/package.json index 3b673263e8d3..d3845692c103 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -54,6 +54,7 @@ }, "homepage": "https://discord.js.org", "dependencies": { + "@discordjs/brokers": "workspace:^", "@discordjs/rest": "workspace:^", "@discordjs/util": "workspace:^", "@discordjs/ws": "workspace:^", diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index a7c3b9c037a4..b55206d43e76 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -4,75 +4,16 @@ import { calculateShardId } from '@discordjs/util'; import { WebSocketShardEvents } from '@discordjs/ws'; import { DiscordSnowflake } from '@sapphire/snowflake'; import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter'; -import { - GatewayDispatchEvents, - GatewayOpcodes, - type APIGuildMember, - type GatewayAutoModerationActionExecutionDispatchData, - type GatewayAutoModerationRuleCreateDispatchData, - type GatewayAutoModerationRuleDeleteDispatchData, - type GatewayAutoModerationRuleUpdateDispatchData, - type GatewayChannelCreateDispatchData, - type GatewayChannelDeleteDispatchData, - type GatewayChannelPinsUpdateDispatchData, - type GatewayChannelUpdateDispatchData, - type GatewayGuildAuditLogEntryCreateDispatchData, - type GatewayGuildBanAddDispatchData, - type GatewayGuildBanRemoveDispatchData, - type GatewayGuildCreateDispatchData, - type GatewayGuildDeleteDispatchData, - type GatewayGuildEmojisUpdateDispatchData, - type GatewayGuildIntegrationsUpdateDispatchData, - type GatewayGuildMemberAddDispatchData, - type GatewayGuildMemberRemoveDispatchData, - type GatewayGuildMemberUpdateDispatchData, - type GatewayGuildMembersChunkDispatchData, - type GatewayGuildRoleCreateDispatchData, - type GatewayGuildRoleDeleteDispatchData, - type GatewayGuildRoleUpdateDispatchData, - type GatewayGuildScheduledEventCreateDispatchData, - type GatewayGuildScheduledEventDeleteDispatchData, - type GatewayGuildScheduledEventUpdateDispatchData, - type GatewayGuildScheduledEventUserAddDispatchData, - type GatewayGuildScheduledEventUserRemoveDispatchData, - type GatewayGuildStickersUpdateDispatchData, - type GatewayGuildUpdateDispatchData, - type GatewayIntegrationCreateDispatchData, - type GatewayIntegrationDeleteDispatchData, - type GatewayIntegrationUpdateDispatchData, - type GatewayInteractionCreateDispatchData, - type GatewayInviteCreateDispatchData, - type GatewayInviteDeleteDispatchData, - type GatewayMessageCreateDispatchData, - type GatewayMessageDeleteBulkDispatchData, - type GatewayMessageDeleteDispatchData, - type GatewayMessageReactionAddDispatchData, - type GatewayMessageReactionRemoveAllDispatchData, - type GatewayMessageReactionRemoveDispatchData, - type GatewayMessageReactionRemoveEmojiDispatchData, - type GatewayMessageUpdateDispatchData, - type GatewayPresenceUpdateData, - type GatewayPresenceUpdateDispatchData, - type GatewayReadyDispatchData, - type GatewayRequestGuildMembersData, - type GatewayStageInstanceCreateDispatchData, - type GatewayStageInstanceDeleteDispatchData, - type GatewayStageInstanceUpdateDispatchData, - type GatewayThreadCreateDispatchData, - type GatewayThreadDeleteDispatchData, - type GatewayThreadListSyncDispatchData, - type GatewayThreadMemberUpdateDispatchData, - type GatewayThreadMembersUpdateDispatchData, - type GatewayThreadUpdateDispatchData, - type GatewayTypingStartDispatchData, - type GatewayUserUpdateDispatchData, - type GatewayVoiceServerUpdateDispatchData, - type GatewayVoiceStateUpdateData, - type GatewayVoiceStateUpdateDispatchData, - type GatewayWebhooksUpdateDispatchData, +import { GatewayDispatchEvents, GatewayOpcodes } from 'discord-api-types/v10'; +import type { + GatewayDispatchPayload, + APIGuildMember, + GatewayRequestGuildMembersData, + GatewayPresenceUpdateData, + GatewayVoiceStateUpdateData, } from 'discord-api-types/v10'; -import type { Gateway } from './Gateway.js'; import { API } from './api/index.js'; +import type { Gateway } from './gateway/Gateway.js'; export interface IntrinsicProps { /** @@ -89,79 +30,20 @@ export interface WithIntrinsicProps extends IntrinsicProps { data: T; } -export interface MappedEvents { - [GatewayDispatchEvents.AutoModerationActionExecution]: [ - WithIntrinsicProps, - ]; - [GatewayDispatchEvents.AutoModerationRuleCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.AutoModerationRuleDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.AutoModerationRuleUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ChannelCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ChannelDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ChannelPinsUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ChannelUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildAuditLogEntryCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildBanAdd]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildBanRemove]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildEmojisUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildIntegrationsUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildMemberAdd]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildMemberRemove]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildMemberUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildMembersChunk]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildRoleCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildRoleDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildRoleUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildScheduledEventCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildScheduledEventDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildScheduledEventUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildScheduledEventUserAdd]: [ - WithIntrinsicProps, - ]; - [GatewayDispatchEvents.GuildScheduledEventUserRemove]: [ - WithIntrinsicProps, - ]; - [GatewayDispatchEvents.GuildStickersUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.GuildUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.IntegrationCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.IntegrationDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.IntegrationUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.InteractionCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.InviteCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.InviteDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.MessageCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.MessageDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.MessageDeleteBulk]: [WithIntrinsicProps]; - [GatewayDispatchEvents.MessageReactionAdd]: [WithIntrinsicProps]; - [GatewayDispatchEvents.MessageReactionRemove]: [WithIntrinsicProps]; - [GatewayDispatchEvents.MessageReactionRemoveAll]: [WithIntrinsicProps]; - [GatewayDispatchEvents.MessageReactionRemoveEmoji]: [ - WithIntrinsicProps, - ]; - [GatewayDispatchEvents.MessageUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.PresenceUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.Ready]: [WithIntrinsicProps]; - [GatewayDispatchEvents.Resumed]: [WithIntrinsicProps]; - [GatewayDispatchEvents.StageInstanceCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.StageInstanceDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.StageInstanceUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ThreadCreate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ThreadDelete]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ThreadListSync]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ThreadMemberUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ThreadMembersUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.ThreadUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.TypingStart]: [WithIntrinsicProps]; - [GatewayDispatchEvents.UserUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.VoiceServerUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.VoiceStateUpdate]: [WithIntrinsicProps]; - [GatewayDispatchEvents.WebhooksUpdate]: [WithIntrinsicProps]; -} +// need this to be its own type for some reason, the compiler doesn't behave the same way if we in-line it +type _DiscordEvents = { + [K in GatewayDispatchEvents]: GatewayDispatchPayload & { + t: K; + }; +}; + +export type DiscordEvents = { + // @ts-expect-error - unclear why this is an error, this behaves as expected + [K in keyof _DiscordEvents]: _DiscordEvents[K]['d']; +}; -export type ManagerShardEventsMap = { - [K in keyof MappedEvents]: MappedEvents[K]; +export type MappedEvents = { + [K in keyof DiscordEvents]: [WithIntrinsicProps]; }; export interface ClientOptions { @@ -169,7 +51,7 @@ export interface ClientOptions { rest: REST; } -export class Client extends AsyncEventEmitter { +export class Client extends AsyncEventEmitter { public readonly rest: REST; public readonly gateway: Gateway; diff --git a/packages/core/src/Gateway.ts b/packages/core/src/gateway/Gateway.ts similarity index 91% rename from packages/core/src/Gateway.ts rename to packages/core/src/gateway/Gateway.ts index 0924d1405977..f63ebd05d99f 100644 --- a/packages/core/src/Gateway.ts +++ b/packages/core/src/gateway/Gateway.ts @@ -6,9 +6,6 @@ import type { GatewaySendPayload } from 'discord-api-types/v10'; * A Discord gateway-like interface that can be used to send & recieve events. */ export interface Gateway { - /** - * Gets how many shards your bot is running. - */ getShardCount(): Awaitable; on( event: WebSocketShardEvents.Dispatch, diff --git a/packages/core/src/gateway/RedisGateway.ts b/packages/core/src/gateway/RedisGateway.ts new file mode 100644 index 000000000000..30c3472d49f3 --- /dev/null +++ b/packages/core/src/gateway/RedisGateway.ts @@ -0,0 +1,50 @@ +import type { PubSubRedisBroker } from '@discordjs/brokers'; +import type { ManagerShardEventsMap, WebSocketShardEvents } from '@discordjs/ws'; +import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter'; +import type { GatewaySendPayload, GatewayDispatchEvents } from 'discord-api-types/v10'; +import type { DiscordEvents } from '../client.js'; +import type { Gateway } from './Gateway.js'; + +interface BrokerIntrinsicProps { + shardId: number; +} + +interface Events extends DiscordEvents { + gateway_send: GatewaySendPayload; +} + +export type RedisBrokerDiscordEvents = { + [K in keyof Events]: BrokerIntrinsicProps & { payload: Events[K] }; +}; + +export class RedisGateway + extends AsyncEventEmitter<{ dispatch: ManagerShardEventsMap[WebSocketShardEvents.Dispatch] }> + implements Gateway +{ + public constructor( + private readonly broker: PubSubRedisBroker, + private readonly shardCount: number, + ) { + super(); + } + + public getShardCount(): number { + return this.shardCount; + } + + public async send(shardId: number, payload: GatewaySendPayload): Promise { + await this.broker.publish('gateway_send', { payload, shardId }); + } + + public async init(group: string, events: GatewayDispatchEvents[]) { + for (const event of events) { + this.broker.on(event, ({ data: { payload, shardId }, ack }) => { + // @ts-expect-error - Union shenanigans + this.emit('dispatch', { shardId, data: payload }); + void ack(); + }); + } + + await this.broker.subscribe(group, events); + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 67a4ce0478e1..4fbf6b235511 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,8 @@ export * from './api/index.js'; -export * from './client.js'; +export * from './gateway/Gateway.js'; +export * from './gateway/RedisGateway.js'; export * from './util/index.js'; +export * from './client.js'; export * from 'discord-api-types/v10'; diff --git a/packages/redis-gateway/README.md b/packages/redis-gateway/README.md index 519f4893e8b0..5fd85169bef3 100644 --- a/packages/redis-gateway/README.md +++ b/packages/redis-gateway/README.md @@ -70,14 +70,50 @@ await broker.publish('gateway_send', { status: PresenceUpdateStatus.Online, }, }); + +// if you were to start this process multiple times (e.g. multiple apps using 'work_balancing_group'), +// they would automatically work balance those interaction create events +await broker.subscribe('work_balancing_group', [GatewayDispatchEvents.InteractionCreate]); ``` -For TypeScript usage, you can pass in a gereric type to the `PubSubRedisBroker` to map out all the events, -refer to [this container's implementation](https://github.com/discordjs/discord.js/tree/main/packages/redis-gateway/src/index.ts#L15) for reference. +For TypeScript usage, you can pass in a gereric type to the `PubSubRedisBroker` to map out all the events, a mapped +interface is available in `@discordjs/core` as `RedisBrokerDiscordEvents`. + +If you wish, you can also just use `@discordjs/core`: + +```ts +import { REST } from '@discordjs/rest'; +import Redis from 'ioredis'; +import { PubSubRedisBroker } from '@discordjs/brokers'; +import { + GatewayDispatchEvents, + GatewayIntentBits, + InteractionType, + MessageFlags, + Client, + RedisGateway, +} from '@discordjs/core'; + +const rest = new REST({ version: '10' }).setToken(token); + +const redis = new Redis(); +// you can get retrieve your shard count however you want, it's used for some calculations and should be your bot's TOTAL shard count +// across "clusters" or anything else. +const broker = new PubSubRedisBroker({ redisClient: redis }, Number(process.env.SHARD_COUNT!)); +const gateway = new RedisGateway(broker); -Also note that [core](https://github.com/discordjs/discord.js/tree/main/packages/core) supports an -abstract `gateway` property that can be easily implemented, making this pretty comfortable to -use in conjunction. Refer to the [Gateway documentation](https://discord.js.org/docs/packages/core/main/Gateway:Interface). +const client = new Client({ rest, gateway }); + +client.on(GatewayDispatchEvents.InteractionCreate, async ({ data: interaction, api }) => { + if (interaction.type !== InteractionType.ApplicationCommand || interaction.data.name !== 'ping') { + return; + } + + await api.interactions.reply(interaction.id, interaction.token, { content: 'Pong!', flags: MessageFlags.Ephemeral }); +}); + +await gateway.init('work_balancing_group', [GatewayDispatchEvents.InteractionCreate]); +``` ## Links diff --git a/packages/redis-gateway/package.json b/packages/redis-gateway/package.json index 30276b34006a..2b3ff5304b33 100644 --- a/packages/redis-gateway/package.json +++ b/packages/redis-gateway/package.json @@ -53,6 +53,7 @@ "undici": "^5.22.0" }, "devDependencies": { + "@discordjs/core": "workspace:^", "@types/node": "16.18.25", "cross-env": "^7.0.3", "eslint": "^8.39.0", diff --git a/packages/redis-gateway/src/discordEvents.ts b/packages/redis-gateway/src/discordEvents.ts deleted file mode 100644 index 1b72c0818a36..000000000000 --- a/packages/redis-gateway/src/discordEvents.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { GatewayDispatchEvents, GatewayDispatchPayload, GatewaySendPayload } from 'discord-api-types/v10'; - -// need this to be its own type for some reason, the compiler doesn't behave the same way if we in-line it -type _DiscordEvents = { - [K in GatewayDispatchEvents]: GatewayDispatchPayload & { - t: K; - }; -}; - -export type DiscordEvents = { - // @ts-expect-error - unclear why this ignore is needed, might be because dapi-types is missing some events from the union again - [K in keyof _DiscordEvents]: _DiscordEvents[K]['d']; -} & { - gateway_send: GatewaySendPayload; -}; diff --git a/packages/redis-gateway/src/index.ts b/packages/redis-gateway/src/index.ts index 6a349c519fc5..be879721e0d2 100644 --- a/packages/redis-gateway/src/index.ts +++ b/packages/redis-gateway/src/index.ts @@ -1,18 +1,18 @@ import { randomBytes } from 'node:crypto'; import { PubSubRedisBroker } from '@discordjs/brokers'; +import type { RedisBrokerDiscordEvents } from '@discordjs/core'; import type { RESTOptions } from '@discordjs/rest'; import { REST } from '@discordjs/rest'; import type { OptionalWebSocketManagerOptions, RequiredWebSocketManagerOptions } from '@discordjs/ws'; import { WorkerShardingStrategy, CompressionMethod, WebSocketManager, WebSocketShardEvents } from '@discordjs/ws'; import Redis from 'ioredis'; import { ProxyAgent } from 'undici'; -import type { DiscordEvents } from './discordEvents.js'; import { Env } from './env.js'; const env = new Env(); const redisClient = new Redis(env.redisUrl); -const broker = new PubSubRedisBroker({ +const broker = new PubSubRedisBroker({ redisClient, }); @@ -46,13 +46,11 @@ gateway .on(WebSocketShardEvents.Hello, ({ shardId }) => console.log(`[WS Shard ${shardId}] [HELLO]`)) .on(WebSocketShardEvents.Ready, ({ shardId }) => console.log(`[WS Shard ${shardId}] [READY]`)) .on(WebSocketShardEvents.Resumed, ({ shardId }) => console.log(`[WS Shard ${shardId}] [RESUMED]`)) - .on(WebSocketShardEvents.Dispatch, ({ data }) => void broker.publish(data.t, data.d)); - -broker.on('gateway_send', async ({ data, ack }) => { - for (const shardId of await gateway.getShardIds()) { - await gateway.send(shardId, data); - } + // @ts-expect-error - Union shenanigans + .on(WebSocketShardEvents.Dispatch, ({ data, shardId }) => void broker.publish(data.t, { shardId, payload: data.d })); +broker.on('gateway_send', async ({ data: { payload, shardId }, ack }) => { + await gateway.send(shardId, payload); await ack(); }); diff --git a/yarn.lock b/yarn.lock index d23d4deb9057..9ddee52c4732 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2096,6 +2096,7 @@ __metadata: version: 0.0.0-use.local resolution: "@discordjs/core@workspace:packages/core" dependencies: + "@discordjs/brokers": "workspace:^" "@discordjs/rest": "workspace:^" "@discordjs/util": "workspace:^" "@discordjs/ws": "workspace:^" @@ -2318,6 +2319,7 @@ __metadata: resolution: "@discordjs/redis-gateway@workspace:packages/redis-gateway" dependencies: "@discordjs/brokers": "workspace:^" + "@discordjs/core": "workspace:^" "@discordjs/rest": "workspace:^" "@discordjs/ws": "workspace:^" "@types/node": 16.18.25 From cc2bc7954263fe35df983c17a7e4095a259d5a26 Mon Sep 17 00:00:00 2001 From: didinele Date: Mon, 8 May 2023 13:07:26 +0300 Subject: [PATCH 08/12] chore: include core team to codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dafeb5012811..15d96b3e3e6d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,7 +20,7 @@ /packages/next/ @discordjs/core /packages/proxy/ @discordjs/proxy /packages/proxy-container/ @discordjs/proxy -/packages/redis-gateway/ @discordjs/brokers @discordjs/ws +/packages/redis-gateway/ @discordjs/brokers @discordjs/core @discordjs/ws /packages/rest/ @discordjs/rest /packages/scripts/ @discordjs/scripts /packages/ui/ @discordjs/ui From 4444ca685f882fbd690dbf40d4ce1e197ba67ca3 Mon Sep 17 00:00:00 2001 From: DD Date: Mon, 8 May 2023 15:18:22 +0300 Subject: [PATCH 09/12] chore: fix typo Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> --- packages/core/src/gateway/Gateway.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/gateway/Gateway.ts b/packages/core/src/gateway/Gateway.ts index f63ebd05d99f..23fe305368ef 100644 --- a/packages/core/src/gateway/Gateway.ts +++ b/packages/core/src/gateway/Gateway.ts @@ -3,7 +3,7 @@ import type { ManagerShardEventsMap, WebSocketShardEvents } from '@discordjs/ws' import type { GatewaySendPayload } from 'discord-api-types/v10'; /** - * A Discord gateway-like interface that can be used to send & recieve events. + * A Discord gateway-like interface that can be used to send & receive events. */ export interface Gateway { getShardCount(): Awaitable; From 24480621a226d86e221ac9ae3117f3c9c5065d13 Mon Sep 17 00:00:00 2001 From: didinele Date: Mon, 8 May 2023 15:29:34 +0300 Subject: [PATCH 10/12] chore: fix readme example --- packages/redis-gateway/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/redis-gateway/README.md b/packages/redis-gateway/README.md index 5fd85169bef3..5b1441611481 100644 --- a/packages/redis-gateway/README.md +++ b/packages/redis-gateway/README.md @@ -48,7 +48,7 @@ import { GatewayDispatchEvents, InteractionType, GatewayOpcodes } from 'discord- const redis = new Redis(); const broker = new PubSubRedisBroker({ redisClient: redis }); -broker.on(GatewayDispatchEvents.InteractionCreate, async ({ data: interaction, ack }) => { +broker.on(GatewayDispatchEvents.InteractionCreate, async ({ data: { payload: interaction }, ack }) => { if (interaction.type !== InteractionType.ApplicationCommand) { return; } From 92bc6d8a612274815b88f71de2b916742bbc20e8 Mon Sep 17 00:00:00 2001 From: DD Date: Mon, 8 May 2023 17:02:11 +0300 Subject: [PATCH 11/12] chore: doc mistakes Co-authored-by: Vlad Frangu --- packages/redis-gateway/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/redis-gateway/README.md b/packages/redis-gateway/README.md index 5b1441611481..0769895fdf99 100644 --- a/packages/redis-gateway/README.md +++ b/packages/redis-gateway/README.md @@ -27,7 +27,7 @@ Set up an `.env` file: ``` REDIS_URL=redis://localhost:6379 DISCORD_TOKEN=your-token-here -DISCORD_PROXY_URL=htt://localhost:8080 # if you want to use an HTTP proxy for DAPI calls (optional) +DISCORD_PROXY_URL=http://localhost:8080 # if you want to use an HTTP proxy for Discord API calls (optional) INTENTS=0 # intents to use (optional, defaults to none) SHARD_COUNT=1 # number of total shards your bot should be running (optional, defaults to Discord recommended count) SHARD_IDS=0 # comma-separated list of shard IDs to run (optional, defaults to all shards) From d2c7c90df17159f69b40aab973a317b10f0c04f2 Mon Sep 17 00:00:00 2001 From: didinele Date: Mon, 8 May 2023 17:06:21 +0300 Subject: [PATCH 12/12] docs: constructor param in the wrong place --- packages/redis-gateway/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/redis-gateway/README.md b/packages/redis-gateway/README.md index 0769895fdf99..797e26a4f487 100644 --- a/packages/redis-gateway/README.md +++ b/packages/redis-gateway/README.md @@ -99,8 +99,8 @@ const rest = new REST({ version: '10' }).setToken(token); const redis = new Redis(); // you can get retrieve your shard count however you want, it's used for some calculations and should be your bot's TOTAL shard count // across "clusters" or anything else. -const broker = new PubSubRedisBroker({ redisClient: redis }, Number(process.env.SHARD_COUNT!)); -const gateway = new RedisGateway(broker); +const broker = new PubSubRedisBroker({ redisClient: redis }); +const gateway = new RedisGateway(broker, Number(process.env.SHARD_COUNT!)); const client = new Client({ rest, gateway });