diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 77b63f18d..f454d4eed 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,30 +1,43 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.162.0/containers/deno { "name": "Deno", "dockerFile": "Dockerfile", - // Set *default* container specific settings.json values on container create. "settings": { - "terminal.integrated.shell.linux": "/bin/bash", + "terminal.integrated.defaultProfile.linux": "/bin/bash", "deno.enable": true, - "deno.lint": true, "editor.defaultFormatter": "denoland.vscode-deno", - "editor.formatOnSave": true, + "editor.minimap.enabled": false, + "editor.wordWrap": "on", "editor.codeActionsOnSave": { "source.organizeImports": true, "source.fixAll": true - } + }, + "editor.fontSize": 16, + "workbench.colorTheme": "Material Theme Darker", + "workbench.iconTheme": "eq-material-theme-icons-darker", + "breadcrumbs.enabled": true, + "editor.renderWhitespace": "all", + "editor.suggestSelection": "first", + "editor.formatOnSave": true, + "files.autoSave": "afterDelay", + "editor.fontFamily": "Fira Code, Menlo, Monaco, 'Courier New', monospace", + "typescript.updateImportsOnFileMove.enabled": "always", + "javascript.updateImportsOnFileMove.enabled": "always" }, - // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "denoland.vscode-deno" - ] + "denoland.vscode-deno", + "pkief.material-icon-theme", + "coenraads.bracket-pair-colorizer", + "equinusocio.vsc-material-theme", + "tabnine.tabnine-vscode" + ], // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], - // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker. // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], - - // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode" + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" } diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 42ad9a86c..6cdd3cb57 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing - Read the [style guide](#style-guide). -- Ask for help on the [official Discord server](https:) +- Ask for help on the [official Discord server](https://discord.gg/5vBgXk3UcZ) - If you are going to work on an issue, mention so in the issue comments before you start working on the issue. - If you are going to work on a new feature, create an issue and discuss with @@ -38,29 +38,41 @@ ## Types Guide -- Must use snake case (according to Discord API). +- Must use camel case (same property name as in the docs just in camel case). - Each field or property must be accompanied with a reasonable JSDoc comment right above its type definition. -- The name of the type must be prefixed with `Discord`. - Must be placed inside of the types module (in `src/types` directory). Example: ```ts +/** https://discord.com/developers/docs/resources/user#user-object */ export interface User { + /** The user's id */ id: string; + /** The user's username, not unique across the platform */ username: string; + /** The user's 4-digit discord-tag */ discriminator: string; + /** The user's avatar hash */ avatar: string | null; + /** Whether the user belongs to an OAuth2 application */ bot?: boolean; + /** Whether the user is an Official Discord System user (part of the urgent message system) */ system?: boolean; + /** Whether the user has two factor enabled on their account */ mfaEnabled?: boolean; + /** The user's chosen language option */ locale?: string; + /** Whether the email on this account has been verified */ verified?: boolean; - email?: string; - flags?: number; - premiumType?: number; + /** The user's email */ + email?: string | null; + /** The flags on a user's account */ + flags?: DiscordUserFlags; + /** The type of Nitro subscription on a user's account */ + premiumType?: DiscordPremiumTypes; + /** The public flags on a user's account */ + publicFlags?: DiscordUserFlags; } - -export type DiscordUser = SnakeCasedPropertiesDeep; ``` diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c8426b21a..d29e3c1e1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,9 +6,5 @@ jobs: steps: - uses: actions/checkout@v2 - uses: denoland/setup-deno@main - - name: Run fmt script - run: deno fmt - - name: Commit formatted files - uses: EndBug/add-and-commit@v7 - name: Run lint script - run: deno lint src/** test/** --unstable --ignore=./src/types + run: deno lint ./src ./tests diff --git a/.github/workflows/local_tests.yml b/.github/workflows/local_tests.yml new file mode 100644 index 000000000..64c3636fe --- /dev/null +++ b/.github/workflows/local_tests.yml @@ -0,0 +1,18 @@ +name: Local Tests Only +on: + push: +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + deno: ["v1.x"] + steps: + - uses: actions/checkout@v2 + - uses: denoland/setup-deno@main + with: + deno-version: ${{ matrix.deno }} + - name: Cache dependencies + run: deno cache mod.ts + - name: Run Local tests that don't need Discord's API + run: deno test -A tests/local.ts diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml new file mode 100644 index 000000000..bccd47892 --- /dev/null +++ b/.github/workflows/pr_tests.yml @@ -0,0 +1,29 @@ +name: Test Contributor Pull Requests +on: issue_comment +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + deno: ["v1.x"] + steps: + - uses: actions/checkout@v2 + - uses: denoland/setup-deno@main + with: + deno-version: ${{ matrix.deno }} + - name: Cache dependencies + run: deno cache mod.ts + - name: Run tests if requested by maintainers + if: ${{ github.event.issue.pull_request && github.event.comment.body == 'run-tests' && (github.actor == 'Skillz4Killz' || github.actor == 'itohatweb') }} + run: DISCORD_TOKEN=${{ env.DISCORD_TOKEN }} deno test --unstable --coverage=coverage -A tests/mod.ts + env: + DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} + # TODO: add coverage back when it is stable + # - name: Create coverage report + # run: deno --unstable --exclude=test coverage ./coverage --lcov > coverage.lcov + # - name: Collect and upload the coverage report + # uses: codecov/codecov-action@v1.0.10 + # with: + # file: ./coverage.lcov + env: + DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 000000000..baa973ede --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,26 @@ +name: Prettier + +on: [push, pull_request] + +jobs: + prettier: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # Make sure the actual branch is checked out when running on pull requests + ref: ${{ github.head_ref }} + # This is important to fetch the changes to the previous commit + fetch-depth: 0 + + - name: Prettify code + uses: creyD/prettier_action@v3.3 + with: + commit_message: "change: prettier code" + # This part is also where you can pass other options, for example: + prettier_options: --write **/* + only_changed: True + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cfbb40471..424d77d6d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,6 @@ name: Test on: push: - pull_request: - types: [labeled] jobs: test: runs-on: ubuntu-latest @@ -19,14 +17,14 @@ jobs: - name: Run test script for maintainers if: ${{ github.actor == 'Skillz4Killz' || github.actor == 'itohatweb' }} run: deno test --unstable --coverage=coverage -A tests/mod.ts - - name: Run test script if label added - if: ${{ github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'run-tests' }} - run: DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} deno test --unstable --coverage=coverage --allow-net tests/mod.ts - - name: Create coverage report - run: deno --unstable coverage ./coverage --lcov > coverage.lcov - - name: Collect and upload the coverage report - uses: codecov/codecov-action@v1.0.10 - with: - file: ./coverage.lcov + # TODO: add coverage back when it is stable + # - name: Create coverage report + # if: github.ref == 'refs/heads/main' + # run: deno --unstable coverage --exclude=test ./coverage --lcov > coverage.lcov + # - name: Collect and upload the coverage report + # if: github.ref == 'refs/heads/main' + # uses: codecov/codecov-action@v1.0.10 + # with: + # file: ./coverage.lcov env: DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} diff --git a/.gitignore b/.gitignore index 33cbff9b6..ef26aeb76 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ desktop.ini !.vscode/tasks.json !.vscode/launch.json *.code-workspace + +# Sublime Text +.sublime-project \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..963354f23 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 120 +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 35b46c0ce..492b7791a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "deno.enable": true, "deno.lint": true, - "editor.defaultFormatter": "denoland.vscode-deno", + "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": true, diff --git a/LICENSE b/LICENSE index d9af0aaea..3e5151fe5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) 2020-2021 Discordeno + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + 1. Definitions. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + "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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Discordeno + + 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/README.md b/README.md index 66aac30ef..3b99632e4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,11 @@ Discordeno follows [Semantic Versioning](https://semver.org/) [![Discord](https://img.shields.io/discord/785384884197392384?color=7289da&logo=discord&logoColor=dark)](https://discord.com/invite/5vBgXk3UcZ) ![Lint](https://github.com/discordeno/discordeno/workflows/Lint/badge.svg) ![Test](https://github.com/discordeno/discordeno/workflows/Test/badge.svg) + + ## Features @@ -37,16 +41,14 @@ Here is a minimal example to get started with: import { startBot } from "https://deno.land/x/discordeno/mod.ts"; startBot({ - token: "BOT TOKEN", - intents: ["GUILDS", "GUILD_MESSAGES"], + token: "BOT_TOKEN", + intents: ["Guilds", "GuildMessages"], eventHandlers: { ready() { console.log("Successfully connected to gateway"); }, messageCreate(message) { - if (message.content === "ping") { - message.reply("Pong using Discordeno!"); - } + // Process the message with your command handler here }, }, }); diff --git a/deps.ts b/deps.ts deleted file mode 100644 index 842c7c13b..000000000 --- a/deps.ts +++ /dev/null @@ -1 +0,0 @@ -export { encode } from "https://deno.land/std@0.90.0/encoding/base64.ts"; diff --git a/mod.ts b/mod.ts index c56b93ff0..131b9e0e1 100644 --- a/mod.ts +++ b/mod.ts @@ -3,15 +3,7 @@ export * from "./src/cache.ts"; export * from "./src/handlers/mod.ts"; export * from "./src/helpers/mod.ts"; export * from "./src/rest/mod.ts"; -export * from "./src/structures/channel.ts"; -export * from "./src/structures/guild.ts"; -export * from "./src/structures/member.ts"; -export * from "./src/structures/message.ts"; export * from "./src/structures/mod.ts"; -export * from "./src/structures/role.ts"; export * from "./src/types/mod.ts"; -export * from "./src/util/collection.ts"; -export * from "./src/util/constants.ts"; -export * from "./src/util/permissions.ts"; -export * from "./src/util/utils.ts"; +export * from "./src/util/mod.ts"; export * from "./src/ws/mod.ts"; diff --git a/src/bot.ts b/src/bot.ts index c36939832..637df063d 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,13 +1,15 @@ import { getGatewayBot } from "./helpers/misc/get_gateway_bot.ts"; import { rest } from "./rest/rest.ts"; -import { EventHandlers } from "./types/discordeno/eventHandlers.ts"; +import type { EventHandlers } from "./types/discordeno/event_handlers.ts"; import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts"; -import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts"; +import { snowflakeToBigint } from "./util/bigint.ts"; +import { GATEWAY_VERSION } from "./util/constants.ts"; import { ws } from "./ws/ws.ts"; +// deno-lint-ignore prefer-const export let secretKey = ""; -export let botId = ""; -export let applicationId = ""; +export let botId = 0n; +export let applicationId = 0n; export let eventHandlers: EventHandlers = {}; @@ -18,18 +20,14 @@ export async function startBot(config: BotConfig) { ws.identifyPayload.token = `Bot ${config.token}`; rest.token = `Bot ${config.token}`; ws.identifyPayload.intents = config.intents.reduce( - ( - bits, - next, - ) => (bits |= typeof next === "string" - ? DiscordGatewayIntents[next] - : next), - 0, + (bits, next) => (bits |= typeof next === "string" ? DiscordGatewayIntents[next] : next), + 0 ); // Initial API connection to get info about bots connection ws.botGatewayData = await getGatewayBot(); ws.maxShards = ws.maxShards || ws.botGatewayData.shards; + ws.lastShardId = ws.lastShardId === 1 ? ws.botGatewayData.shards - 1 : ws.lastShardId; // Explicitly append gateway version and encoding ws.botGatewayData.url += `?v=${GATEWAY_VERSION}&encoding=json`; @@ -39,69 +37,24 @@ export async function startBot(config: BotConfig) { ws.spawnShards(); } +export function replaceEventHandlers(newEventHandlers: EventHandlers) { + eventHandlers = newEventHandlers; +} + /** Allows you to dynamically update the event handlers by passing in new eventHandlers */ export function updateEventHandlers(newEventHandlers: EventHandlers) { - eventHandlers = { - ...eventHandlers, - ...newEventHandlers, - }; + // Object.assign instead of ... operator because of the Proxy used + Object.assign(eventHandlers, newEventHandlers); } /** INTERNAL LIB function used to set the bot Id once the READY event is sent by Discord. */ export function setBotId(id: string) { - if (botId !== id) botId = id; + botId = snowflakeToBigint(id); } /** INTERNAL LIB function used to set the application Id once the READY event is sent by Discord. */ export function setApplicationId(id: string) { - if (applicationId !== id) applicationId = id; -} - -// BIG BRAIN BOT STUFF ONLY BELOW THIS - -/** - * This function should be used only by bot developers whose bots are in over 25,000 servers. - * Please be aware if you are a beginner developer using this, things will not work as per the guides. This is for advanced developers only! - * - * Advanced Devs: This function will allow you to have an insane amount of customization potential as when you get to large bots you need to be able to optimize every tiny detail to make you bot work the way you need. - */ -export async function startBigBrainBot(options: BigBrainBotConfig) { - rest.token = `Bot ${options.token}`; - - if (options.secretKey) secretKey = options.secretKey; - if (options.restURL) baseEndpoints.BASE_URL = options.restURL; - if (options.cdnURL) baseEndpoints.CDN_URL = options.cdnURL; - if (options.eventHandlers) eventHandlers = options.eventHandlers; - - // PROXY DOESNT NEED US SPAWNING SHARDS - if (!options.wsPort) { - ws.identifyPayload.token = `Bot ${options.token}`; - - if (options.compress) { - ws.identifyPayload.compress = options.compress; - } - - ws.identifyPayload.intents = options.intents.reduce( - ( - bits, - next, - ) => (bits |= typeof next === "string" - ? DiscordGatewayIntents[next] - : next), - 0, - ); - - // Initial API connection to get info about bots connection - ws.botGatewayData = await getGatewayBot(); - ws.maxShards = ws.maxShards || - ws.botGatewayData.shards; - ws.lastShardId = options.lastShardId || ws.botGatewayData.shards; - // Explicitly append gateway version and encoding - ws.botGatewayData.url += `?v=${GATEWAY_VERSION}&encoding=json`; - proxyWSURL = ws.botGatewayData.url; - - ws.spawnShards(options.firstShardId); - } + applicationId = snowflakeToBigint(id); } export interface BotConfig { @@ -110,20 +63,3 @@ export interface BotConfig { intents: (DiscordGatewayIntents | keyof typeof DiscordGatewayIntents)[]; eventHandlers?: EventHandlers; } - -export interface BigBrainBotConfig extends BotConfig { - /** The first shard to start at for this worker. Use this to control which shards to run in each worker. */ - firstShardId: number; - /** The last shard to start for this worker. By default it will be 25 + the firstShardId. */ - lastShardId?: number; - /** The maximum shard Id number. Useful for zero-downtime updates or resharding. */ - maxShards?: number; - /** This can be used to forward the ws handling to a proxy. It will disable the sharding done by the bot side. */ - wsPort?: number; - /** This can be used to forward the REST handling to a proxy. */ - restURL?: string; - /** This can be used to forward the CDN handling to a proxy. */ - cdnURL?: string; - /** This is the authorization header that your servers will send. Helpful to prevent DDOS attacks and such. */ - secretKey?: string; -} diff --git a/src/cache.ts b/src/cache.ts index 317fa7dd0..02f30ca09 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,41 +1,34 @@ // deno-lint-ignore-file require-await no-explicit-any prefer-const -import { DiscordenoChannel } from "./structures/channel.ts"; -import { DiscordenoGuild } from "./structures/guild.ts"; -import { DiscordenoMember } from "./structures/member.ts"; -import { DiscordenoMessage } from "./structures/message.ts"; -import { Emoji } from "./types/emojis/emoji.ts"; -import { PresenceUpdate } from "./types/misc/presence_update.ts"; +import type { DiscordenoChannel } from "./structures/channel.ts"; +import type { DiscordenoGuild } from "./structures/guild.ts"; +import type { DiscordenoMember } from "./structures/member.ts"; +import type { DiscordenoMessage } from "./structures/message.ts"; +import type { PresenceUpdate } from "./types/activity/presence_update.ts"; +import type { Emoji } from "./types/emojis/emoji.ts"; import { Collection } from "./util/collection.ts"; export const cache = { isReady: false, /** All of the guild objects the bot has access to, mapped by their Ids */ - guilds: new Collection(), + guilds: new Collection(), /** All of the channel objects the bot has access to, mapped by their Ids */ - channels: new Collection(), + channels: new Collection(), /** All of the message objects the bot has cached since the bot acquired `READY` state, mapped by their Ids */ - messages: new Collection(), + messages: new Collection(), /** All of the member objects that have been cached since the bot acquired `READY` state, mapped by their Ids */ - members: new Collection(), + members: new Collection(), /** All of the unavailable guilds, mapped by their Ids (id, timestamp) */ - unavailableGuilds: new Collection(), + unavailableGuilds: new Collection(), /** All of the presence update objects received in PRESENCE_UPDATE gateway event, mapped by their user Id */ - presences: new Collection(), + presences: new Collection(), fetchAllMembersProcessingRequests: new Collection< string, - ( - value: - | Collection - | PromiseLike>, - ) => void + (value: Collection | PromiseLike>) => void >(), - executedSlashCommands: new Collection(), + executedSlashCommands: new Set(), get emojis() { - return new Collection( - this.guilds.reduce( - (a, b) => [...a, ...b.emojis.map((e) => [e.id, e])], - [] as any[], - ), + return new Collection( + this.guilds.reduce((a, b) => [...a, ...b.emojis.map((e) => [e.id, e])], [] as any[]) ); }, }; @@ -46,11 +39,11 @@ export let cacheHandlers = { return cache[table].clear(); }, /** Deletes 1 item from cache using the key */ - async delete(table: TableName, key: string) { + async delete(table: TableName, key: bigint) { return cache[table].delete(key); }, /** Check if something exists in cache with a key */ - async has(table: TableName, key: string) { + async has(table: TableName, key: bigint) { return cache[table].has(key); }, @@ -70,142 +63,72 @@ export let cacheHandlers = { filter, }; -export type TableName = - | "guilds" - | "unavailableGuilds" - | "channels" - | "messages" - | "members" - | "presences"; +export type TableName = "guilds" | "unavailableGuilds" | "channels" | "messages" | "members" | "presences"; -function set( - table: "guilds", - key: string, - value: DiscordenoGuild, -): Promise>; -function set( - table: "channels", - key: string, - value: DiscordenoChannel, -): Promise>; -function set( - table: "messages", - key: string, - value: DiscordenoMessage, -): Promise>; -function set( - table: "members", - key: string, - value: DiscordenoMember, -): Promise>; -function set( - table: "presences", - key: string, - value: PresenceUpdate, -): Promise>; -function set( - table: "unavailableGuilds", - key: string, - value: number, -): Promise>; -async function set(table: TableName, key: string, value: any) { +function set(table: "guilds", key: bigint, value: DiscordenoGuild): Promise>; +function set(table: "channels", key: bigint, value: DiscordenoChannel): Promise>; +function set(table: "messages", key: bigint, value: DiscordenoMessage): Promise>; +function set(table: "members", key: bigint, value: DiscordenoMember): Promise>; +function set(table: "presences", key: bigint, value: PresenceUpdate): Promise>; +function set(table: "unavailableGuilds", key: bigint, value: number): Promise>; +async function set(table: TableName, key: bigint, value: any) { return cache[table].set(key, value); } -function get( - table: "guilds", - key: string, -): Promise; -function get( - table: "channels", - key: string, -): Promise; -function get( - table: "messages", - key: string, -): Promise; -function get( - table: "members", - key: string, -): Promise; -function get( - table: "presences", - key: string, -): Promise; -function get( - table: "unavailableGuilds", - key: string, -): Promise; -async function get(table: TableName, key: string) { +function get(table: "guilds", key: bigint): Promise; +function get(table: "channels", key: bigint): Promise; +function get(table: "messages", key: bigint): Promise; +function get(table: "members", key: bigint): Promise; +function get(table: "presences", key: bigint): Promise; +function get(table: "unavailableGuilds", key: bigint): Promise; +async function get(table: TableName, key: bigint) { return cache[table].get(key); } function forEach( table: "guilds", - callback: ( - value: DiscordenoGuild, - key: string, - map: Map, - ) => unknown, + callback: (value: DiscordenoGuild, key: bigint, map: Map) => unknown ): void; function forEach( table: "unavailableGuilds", - callback: (value: number, key: string, map: Map) => unknown, + callback: (value: number, key: bigint, map: Map) => unknown ): void; function forEach( table: "channels", - callback: ( - value: DiscordenoChannel, - key: string, - map: Map, - ) => unknown, + callback: (value: DiscordenoChannel, key: bigint, map: Map) => unknown ): void; function forEach( table: "messages", - callback: ( - value: DiscordenoMessage, - key: string, - map: Map, - ) => unknown, + callback: (value: DiscordenoMessage, key: bigint, map: Map) => unknown ): void; function forEach( table: "members", - callback: ( - value: DiscordenoMember, - key: string, - map: Map, - ) => unknown, + callback: (value: DiscordenoMember, key: bigint, map: Map) => unknown ): void; -function forEach( - table: TableName, - callback: (value: any, key: string, map: Map) => unknown, -) { +function forEach(table: TableName, callback: (value: any, key: bigint, map: Map) => unknown) { return cache[table].forEach(callback); } function filter( table: "guilds", - callback: (value: DiscordenoGuild, key: string) => boolean, -): Promise>; + callback: (value: DiscordenoGuild, key: bigint) => boolean +): Promise>; function filter( table: "unavailableGuilds", - callback: (value: number, key: string) => boolean, -): Promise>; + callback: (value: number, key: bigint) => boolean +): Promise>; function filter( table: "channels", - callback: (value: DiscordenoChannel, key: string) => boolean, -): Promise>; + callback: (value: DiscordenoChannel, key: bigint) => boolean +): Promise>; function filter( table: "messages", - callback: (value: DiscordenoMessage, key: string) => boolean, -): Promise>; + callback: (value: DiscordenoMessage, key: bigint) => boolean +): Promise>; function filter( table: "members", - callback: (value: DiscordenoMember, key: string) => boolean, -): Promise>; -async function filter( - table: TableName, - callback: (value: any, key: string) => boolean, -) { + callback: (value: DiscordenoMember, key: bigint) => boolean +): Promise>; +async function filter(table: TableName, callback: (value: any, key: bigint) => boolean) { return cache[table].filter(callback); } diff --git a/src/handlers/channels/CHANNEL_CREATE.ts b/src/handlers/channels/CHANNEL_CREATE.ts index 135dbc0d9..679241f19 100644 --- a/src/handlers/channels/CHANNEL_CREATE.ts +++ b/src/handlers/channels/CHANNEL_CREATE.ts @@ -1,8 +1,8 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { Channel } from "../../types/channels/channel.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { Channel } from "../../types/channels/channel.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; export async function handleChannelCreate(data: DiscordGatewayPayload) { const payload = data.d as Channel; diff --git a/src/handlers/channels/CHANNEL_DELETE.ts b/src/handlers/channels/CHANNEL_DELETE.ts index f4c07229f..72feac12a 100644 --- a/src/handlers/channels/CHANNEL_DELETE.ts +++ b/src/handlers/channels/CHANNEL_DELETE.ts @@ -1,44 +1,54 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { Channel } from "../../types/channels/channel.ts"; +import type { Channel } from "../../types/channels/channel.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleChannelDelete(data: DiscordGatewayPayload) { const payload = data.d as Channel; - const cachedChannel = await cacheHandlers.get("channels", payload.id); + const cachedChannel = await cacheHandlers.get("channels", snowflakeToBigint(payload.id)); if (!cachedChannel) return; - if ( - cachedChannel.type === DiscordChannelTypes.GUILD_VOICE && payload.guildId - ) { - const guild = await cacheHandlers.get("guilds", payload.guildId); + if (cachedChannel.type === DiscordChannelTypes.GuildVoice && payload.guildId) { + const guild = await cacheHandlers.get("guilds", cachedChannel.guildId); if (guild) { - return Promise.all(guild.voiceStates.map(async (vs, key) => { - if (vs.channelId !== payload.id) return; + return Promise.all( + guild.voiceStates.map(async (vs, key) => { + if (vs.channelId !== cachedChannel.id) return; - // Since this channel was deleted all voice states for this channel should be deleted - guild.voiceStates.delete(key); + // Since this channel was deleted all voice states for this channel should be deleted + guild.voiceStates.delete(key); - const member = await cacheHandlers.get("members", vs.userId); - if (!member) return; + const member = await cacheHandlers.get("members", vs.userId); + if (!member) return; - eventHandlers.voiceChannelLeave?.(member, vs.channelId); - })); + eventHandlers.voiceChannelLeave?.(member, vs.channelId); + }) + ); } } - await cacheHandlers.delete("channels", payload.id); - cacheHandlers.forEach("messages", (message) => { - eventHandlers.debug?.( - "loop", - `Running forEach messages loop in CHANNEL_DELTE file.`, - ); - if (message.channelId === payload.id) { - cacheHandlers.delete("messages", message.id); - } - }); + if ( + [ + DiscordChannelTypes.GuildText, + DiscordChannelTypes.DM, + DiscordChannelTypes.GroupDm, + DiscordChannelTypes.GuildNews, + ].includes(payload.type) + ) { + await cacheHandlers.delete("channels", snowflakeToBigint(payload.id)); + cacheHandlers.forEach("messages", (message) => { + eventHandlers.debug?.("loop", `Running forEach messages loop in CHANNEL_DELTE file.`); + if (message.channelId === snowflakeToBigint(payload.id)) { + cacheHandlers.delete("messages", message.id); + } + }); + } + + await cacheHandlers.delete("channels", snowflakeToBigint(payload.id)); + eventHandlers.channelDelete?.(cachedChannel); } diff --git a/src/handlers/channels/CHANNEL_PINS_UPDATE.ts b/src/handlers/channels/CHANNEL_PINS_UPDATE.ts index 371274096..19dad784c 100644 --- a/src/handlers/channels/CHANNEL_PINS_UPDATE.ts +++ b/src/handlers/channels/CHANNEL_PINS_UPDATE.ts @@ -1,17 +1,16 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { ChannelPinsUpdate } from "../../types/channels/channel_pins_update.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { ChannelPinsUpdate } from "../../types/channels/channel_pins_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleChannelPinsUpdate(data: DiscordGatewayPayload) { const payload = data.d as ChannelPinsUpdate; - const channel = await cacheHandlers.get("channels", payload.channelId); + const channel = await cacheHandlers.get("channels", snowflakeToBigint(payload.channelId)); if (!channel) return; - const guild = payload.guildId - ? await cacheHandlers.get("guilds", payload.guildId) - : undefined; + const guild = payload.guildId ? await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)) : undefined; eventHandlers.channelPinsUpdate?.(channel, guild, payload.lastPinTimestamp); } diff --git a/src/handlers/channels/CHANNEL_UPDATE.ts b/src/handlers/channels/CHANNEL_UPDATE.ts index 1045545c5..b0a5209f0 100644 --- a/src/handlers/channels/CHANNEL_UPDATE.ts +++ b/src/handlers/channels/CHANNEL_UPDATE.ts @@ -1,17 +1,17 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { Channel } from "../../types/channels/channel.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { Channel } from "../../types/channels/channel.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleChannelUpdate(data: DiscordGatewayPayload) { const payload = data.d as Channel; - const cachedChannel = await cacheHandlers.get("channels", payload.id); + const cachedChannel = await cacheHandlers.get("channels", snowflakeToBigint(payload.id)); + if (!cachedChannel) return; const discordenoChannel = await structures.createDiscordenoChannel(payload); await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel); - if (!cachedChannel) return; - eventHandlers.channelUpdate?.(discordenoChannel, cachedChannel); } diff --git a/src/handlers/channels/STAGE_INSTANCE_CREATE.ts b/src/handlers/channels/STAGE_INSTANCE_CREATE.ts new file mode 100644 index 000000000..d99387dc5 --- /dev/null +++ b/src/handlers/channels/STAGE_INSTANCE_CREATE.ts @@ -0,0 +1,7 @@ +import { eventHandlers } from "../../bot.ts"; +import type { StageInstance } from "../../types/channels/stage_instance.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; + +export function handleStageInstanceCreate(data: DiscordGatewayPayload) { + eventHandlers.stageInstanceCreate?.(data.d as StageInstance); +} diff --git a/src/handlers/channels/STAGE_INSTANCE_DELETE.ts b/src/handlers/channels/STAGE_INSTANCE_DELETE.ts new file mode 100644 index 000000000..43e765ba2 --- /dev/null +++ b/src/handlers/channels/STAGE_INSTANCE_DELETE.ts @@ -0,0 +1,7 @@ +import { eventHandlers } from "../../bot.ts"; +import type { StageInstance } from "../../types/channels/stage_instance.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; + +export function handleStageInstanceDelete(data: DiscordGatewayPayload) { + eventHandlers.stageInstanceDelete?.(data.d as StageInstance); +} diff --git a/src/handlers/channels/STAGE_INSTANCE_UPDATE.ts b/src/handlers/channels/STAGE_INSTANCE_UPDATE.ts new file mode 100644 index 000000000..d0cf177bf --- /dev/null +++ b/src/handlers/channels/STAGE_INSTANCE_UPDATE.ts @@ -0,0 +1,7 @@ +import { eventHandlers } from "../../bot.ts"; +import type { StageInstance } from "../../types/channels/stage_instance.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; + +export function handleStageInstanceUpdate(data: DiscordGatewayPayload) { + eventHandlers.stageInstanceUpdate?.(data.d as StageInstance); +} diff --git a/src/handlers/channels/THREAD_CREATE.ts b/src/handlers/channels/THREAD_CREATE.ts new file mode 100644 index 000000000..ac24436b5 --- /dev/null +++ b/src/handlers/channels/THREAD_CREATE.ts @@ -0,0 +1,14 @@ +import { eventHandlers } from "../../bot.ts"; +import { cacheHandlers } from "../../cache.ts"; +import { structures } from "../../structures/mod.ts"; +import { Channel } from "../../types/channels/channel.ts"; +import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; + +export async function handleThreadCreate(data: DiscordGatewayPayload) { + const payload = data.d as Channel; + + const discordenoChannel = await structures.createDiscordenoChannel(payload); + await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel); + + eventHandlers.threadCreate?.(discordenoChannel); +} diff --git a/src/handlers/channels/THREAD_DELETE.ts b/src/handlers/channels/THREAD_DELETE.ts new file mode 100644 index 000000000..8ffc7bccc --- /dev/null +++ b/src/handlers/channels/THREAD_DELETE.ts @@ -0,0 +1,22 @@ +import { eventHandlers } from "../../bot.ts"; +import { cacheHandlers } from "../../cache.ts"; +import { Channel } from "../../types/channels/channel.ts"; +import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; + +export async function handleThreadDelete(data: DiscordGatewayPayload) { + const payload = data.d as Channel; + + const cachedChannel = await cacheHandlers.get("channels", snowflakeToBigint(payload.id)); + if (!cachedChannel) return; + + await cacheHandlers.delete("channels", snowflakeToBigint(payload.id)); + cacheHandlers.forEach("messages", (message) => { + eventHandlers.debug?.("loop", `Running forEach messages loop in CHANNEL_DELTE file.`); + if (message.channelId === snowflakeToBigint(payload.id)) { + cacheHandlers.delete("messages", message.id); + } + }); + + eventHandlers.threadDelete?.(cachedChannel); +} diff --git a/src/handlers/channels/THREAD_LIST_SYNC.ts b/src/handlers/channels/THREAD_LIST_SYNC.ts new file mode 100644 index 000000000..7745267e1 --- /dev/null +++ b/src/handlers/channels/THREAD_LIST_SYNC.ts @@ -0,0 +1,26 @@ +import { eventHandlers } from "../../bot.ts"; +import { cacheHandlers } from "../../cache.ts"; +import { DiscordenoChannel } from "../../structures/channel.ts"; +import { structures } from "../../structures/mod.ts"; +import { ThreadListSync } from "../../types/channels/threads/thread_list_sync.ts"; +import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; +import { Collection } from "../../util/collection.ts"; + +export async function handleThreadListSync(data: DiscordGatewayPayload) { + const payload = data.d as ThreadListSync; + + const discordenoChannels = await Promise.all( + payload.threads.map(async (thread) => { + const discordenoChannel = await structures.createDiscordenoChannel(thread, snowflakeToBigint(payload.guildId)); + + await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel); + + return discordenoChannel; + }) + ); + + const threads = new Collection(discordenoChannels.map((t) => [t.id, t])); + + eventHandlers.threadListSync?.(threads, payload.members, snowflakeToBigint(payload.guildId)); +} diff --git a/src/handlers/channels/THREAD_MEMBERS_UPDATE.ts b/src/handlers/channels/THREAD_MEMBERS_UPDATE.ts new file mode 100644 index 000000000..f779c8faa --- /dev/null +++ b/src/handlers/channels/THREAD_MEMBERS_UPDATE.ts @@ -0,0 +1,16 @@ +import { eventHandlers } from "../../bot.ts"; +import { cacheHandlers } from "../../cache.ts"; +import { ThreadMembersUpdate } from "../../types/channels/threads/thread_members_update.ts"; +import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; + +export async function handleThreadMembersUpdate(data: DiscordGatewayPayload) { + const payload = data.d as ThreadMembersUpdate; + const thread = await cacheHandlers.get("channels", snowflakeToBigint(payload.id)); + if (!thread) return; + + thread.memberCount = payload.memberCount; + await cacheHandlers.set("channels", thread.id, thread); + + eventHandlers.threadMembersUpdate?.(payload); +} diff --git a/src/handlers/channels/THREAD_MEMBER_UPDATE.ts b/src/handlers/channels/THREAD_MEMBER_UPDATE.ts new file mode 100644 index 000000000..70c5f1c75 --- /dev/null +++ b/src/handlers/channels/THREAD_MEMBER_UPDATE.ts @@ -0,0 +1,17 @@ +import { eventHandlers } from "../../bot.ts"; +import { cacheHandlers } from "../../cache.ts"; +import { ThreadMember } from "../../types/channels/threads/thread_member.ts"; +import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; + +export async function handleThreadMemberUpdate(data: DiscordGatewayPayload) { + const payload = data.d as ThreadMember; + const thread = await cacheHandlers.get("channels", snowflakeToBigint(payload.id)); + if (!thread) return; + + thread.member = payload; + + await cacheHandlers.set("channels", thread.id, thread); + + eventHandlers.threadMemberUpdate?.(payload); +} diff --git a/src/handlers/channels/THREAD_UPDATE.ts b/src/handlers/channels/THREAD_UPDATE.ts new file mode 100644 index 000000000..9c3756d02 --- /dev/null +++ b/src/handlers/channels/THREAD_UPDATE.ts @@ -0,0 +1,17 @@ +import { eventHandlers } from "../../bot.ts"; +import { cacheHandlers } from "../../cache.ts"; +import { structures } from "../../structures/mod.ts"; +import { Channel } from "../../types/channels/channel.ts"; +import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; + +export async function handleThreadUpdate(data: DiscordGatewayPayload) { + const payload = data.d as Channel; + const oldChannel = await cacheHandlers.get("channels", snowflakeToBigint(payload.id)); + if (!oldChannel) return; + + const discordenoChannel = await structures.createDiscordenoChannel(payload); + await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel); + + eventHandlers.threadUpdate?.(discordenoChannel, oldChannel); +} diff --git a/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts b/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts index 2ceae936b..06ff0d83d 100644 --- a/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts +++ b/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts @@ -1,13 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { - ApplicationCommandCreateUpdateDelete, -} from "../../types/interactions/application_command_create_update_delete.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { ApplicationCommandCreateUpdateDelete } from "../../types/interactions/commands/application_command_create_update_delete.ts"; -export function handleApplicationCommandCreate( - data: DiscordGatewayPayload, -) { - eventHandlers.applicationCommandCreate?.( - data.d as ApplicationCommandCreateUpdateDelete, - ); +export function handleApplicationCommandCreate(data: DiscordGatewayPayload) { + eventHandlers.applicationCommandCreate?.(data.d as ApplicationCommandCreateUpdateDelete); } diff --git a/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts b/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts index 63d96b23e..b37ace927 100644 --- a/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts +++ b/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts @@ -1,11 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { - ApplicationCommandCreateUpdateDelete, -} from "../../types/interactions/application_command_create_update_delete.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { ApplicationCommandCreateUpdateDelete } from "../../types/interactions/commands/application_command_create_update_delete.ts"; export function handleApplicationCommandDelete(data: DiscordGatewayPayload) { - eventHandlers.applicationCommandDelete?.( - data.d as ApplicationCommandCreateUpdateDelete, - ); + eventHandlers.applicationCommandDelete?.(data.d as ApplicationCommandCreateUpdateDelete); } diff --git a/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts b/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts index ad9ffde28..51fc2d063 100644 --- a/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts +++ b/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts @@ -1,11 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { - ApplicationCommandCreateUpdateDelete, -} from "../../types/interactions/application_command_create_update_delete.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { ApplicationCommandCreateUpdateDelete } from "../../types/interactions/commands/application_command_create_update_delete.ts"; export function handleApplicationCommandUpdate(data: DiscordGatewayPayload) { - eventHandlers.applicationCommandUpdate?.( - data.d as ApplicationCommandCreateUpdateDelete, - ); + eventHandlers.applicationCommandUpdate?.(data.d as ApplicationCommandCreateUpdateDelete); } diff --git a/src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts b/src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts index 40ae9f496..e5e27ed24 100644 --- a/src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts +++ b/src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts @@ -1,24 +1,19 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { GuildEmojisUpdate } from "../../types/emojis/guild_emojis_update.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildEmojisUpdate } from "../../types/emojis/guild_emojis_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; import { Collection } from "../../util/collection.ts"; export async function handleGuildEmojisUpdate(data: DiscordGatewayPayload) { const payload = data.d as GuildEmojisUpdate; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; const cachedEmojis = guild.emojis; - guild.emojis = new Collection( - payload.emojis.map((emoji) => [emoji.id!, emoji]), - ); + guild.emojis = new Collection(payload.emojis.map((emoji) => [snowflakeToBigint(emoji.id!), emoji])); - await cacheHandlers.set("guilds", payload.guildId, guild); + await cacheHandlers.set("guilds", guild.id, guild); - eventHandlers.guildEmojisUpdate?.( - guild, - guild.emojis, - cachedEmojis, - ); + eventHandlers.guildEmojisUpdate?.(guild, guild.emojis, cachedEmojis); } diff --git a/src/handlers/guilds/GUILD_BAN_ADD.ts b/src/handlers/guilds/GUILD_BAN_ADD.ts index 56df5c0e1..68070fcb1 100644 --- a/src/handlers/guilds/GUILD_BAN_ADD.ts +++ b/src/handlers/guilds/GUILD_BAN_ADD.ts @@ -1,13 +1,14 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildBanAddRemove } from "../../types/guilds/guild_ban_add_remove.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildBanAddRemove } from "../../types/guilds/guild_ban_add_remove.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleGuildBanAdd(data: DiscordGatewayPayload) { const payload = data.d as GuildBanAddRemove; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; - const member = await cacheHandlers.get("members", payload.user.id); + const member = await cacheHandlers.get("members", snowflakeToBigint(payload.user.id)); eventHandlers.guildBanAdd?.(guild, payload.user, member); } diff --git a/src/handlers/guilds/GUILD_BAN_REMOVE.ts b/src/handlers/guilds/GUILD_BAN_REMOVE.ts index 5c220588d..04a913b5c 100644 --- a/src/handlers/guilds/GUILD_BAN_REMOVE.ts +++ b/src/handlers/guilds/GUILD_BAN_REMOVE.ts @@ -1,13 +1,14 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildBanAddRemove } from "../../types/guilds/guild_ban_add_remove.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildBanAddRemove } from "../../types/guilds/guild_ban_add_remove.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleGuildBanRemove(data: DiscordGatewayPayload) { const payload = data.d as GuildBanAddRemove; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; - const member = await cacheHandlers.get("members", payload.user.id); + const member = await cacheHandlers.get("members", snowflakeToBigint(payload.user.id)); eventHandlers.guildBanRemove?.(guild, payload.user, member); } diff --git a/src/handlers/guilds/GUILD_CREATE.ts b/src/handlers/guilds/GUILD_CREATE.ts index d3fff22d1..2b6999194 100644 --- a/src/handlers/guilds/GUILD_CREATE.ts +++ b/src/handlers/guilds/GUILD_CREATE.ts @@ -1,34 +1,29 @@ import { eventHandlers } from "../../bot.ts"; import { cache, cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { Guild } from "../../types/guilds/guild.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { Guild } from "../../types/guilds/guild.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; import { ws } from "../../ws/ws.ts"; -export async function handleGuildCreate( - data: DiscordGatewayPayload, - shardId: number, -) { +export async function handleGuildCreate(data: DiscordGatewayPayload, shardId: number) { const payload = data.d as Guild; // When shards resume they emit GUILD_CREATE again. - if (await cacheHandlers.has("guilds", payload.id)) return; + if (await cacheHandlers.has("guilds", snowflakeToBigint(payload.id))) return; - const discordenoGuild = await structures.createDiscordenoGuild( - payload, - shardId, - ); - await cacheHandlers.set("guilds", discordenoGuild.id, discordenoGuild); + const guild = await structures.createDiscordenoGuild(payload, shardId); + await cacheHandlers.set("guilds", guild.id, guild); const shard = ws.shards.get(shardId); - if (shard?.unavailableGuildIds.has(payload.id)) { - await cacheHandlers.delete("unavailableGuilds", payload.id); + if (shard?.unavailableGuildIds.has(guild.id)) { + await cacheHandlers.delete("unavailableGuilds", guild.id); + shard.unavailableGuildIds.delete(guild.id); + shard.lastAvailable = Date.now(); - shard.unavailableGuildIds.delete(payload.id); - - return eventHandlers.guildAvailable?.(discordenoGuild); + return eventHandlers.guildAvailable?.(guild); } - if (!cache.isReady) return eventHandlers.guildLoaded?.(discordenoGuild); - eventHandlers.guildCreate?.(discordenoGuild); + if (!cache.isReady) return eventHandlers.guildLoaded?.(guild); + eventHandlers.guildCreate?.(guild); } diff --git a/src/handlers/guilds/GUILD_DELETE.ts b/src/handlers/guilds/GUILD_DELETE.ts index bf1672a0d..4235e351e 100644 --- a/src/handlers/guilds/GUILD_DELETE.ts +++ b/src/handlers/guilds/GUILD_DELETE.ts @@ -1,25 +1,22 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { UnavailableGuild } from "../../types/guilds/unavailable_guild.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { UnavailableGuild } from "../../types/guilds/unavailable_guild.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; import { ws } from "../../ws/ws.ts"; -export async function handleGuildDelete( - data: DiscordGatewayPayload, - shardId: number, -) { +export async function handleGuildDelete(data: DiscordGatewayPayload, shardId: number) { const payload = data.d as UnavailableGuild; - const guild = await cacheHandlers.get("guilds", payload.id); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.id)); if (!guild) return; - await cacheHandlers.delete("guilds", payload.id); + await cacheHandlers.delete("guilds", guild.id); if (payload.unavailable) { const shard = ws.shards.get(shardId); - if (shard) shard.unavailableGuildIds.add(payload.id); - - await cacheHandlers.set("unavailableGuilds", payload.id, Date.now()); + if (shard) shard.unavailableGuildIds.add(guild.id); + await cacheHandlers.set("unavailableGuilds", guild.id, Date.now()); eventHandlers.guildUnavailable?.(guild); } else { @@ -27,33 +24,24 @@ export async function handleGuildDelete( } cacheHandlers.forEach("messages", (message) => { - eventHandlers.debug?.( - "loop", - `1. Running forEach messages loop in CHANNEL_DELTE file.`, - ); - if (message.guildId === payload.id) { + eventHandlers.debug?.("loop", `1. Running forEach messages loop in CHANNEL_DELTE file.`); + if (message.guildId === guild.id) { cacheHandlers.delete("messages", message.id); } }); cacheHandlers.forEach("channels", (channel) => { - eventHandlers.debug?.( - "loop", - `2. Running forEach channels loop in CHANNEL_DELTE file.`, - ); - if (channel.guildId === payload.id) { + eventHandlers.debug?.("loop", `2. Running forEach channels loop in CHANNEL_DELTE file.`); + if (channel.guildId === guild.id) { cacheHandlers.delete("channels", channel.id); } }); cacheHandlers.forEach("members", (member) => { - eventHandlers.debug?.( - "loop", - `3. Running forEach members loop in CHANNEL_DELTE file.`, - ); - if (!member.guilds.has(payload.id)) return; + eventHandlers.debug?.("loop", `3. Running forEach members loop in CHANNEL_DELTE file.`); + if (!member.guilds.has(guild.id)) return; - member.guilds.delete(payload.id); + member.guilds.delete(guild.id); if (!member.guilds.size) { return cacheHandlers.delete("members", member.id); diff --git a/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts b/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts index 7c28c6520..57d6df937 100644 --- a/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts +++ b/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts @@ -1,14 +1,13 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildIntegrationsUpdate } from "../../types/integration/guild_integrations_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildIntegrationsUpdate } from "../../types/integrations/guild_integrations_update.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; -export async function handleGuildIntegrationsUpdate( - data: DiscordGatewayPayload, -) { +export async function handleGuildIntegrationsUpdate(data: DiscordGatewayPayload) { const payload = data.d as GuildIntegrationsUpdate; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; eventHandlers.guildIntegrationsUpdate?.(guild); diff --git a/src/handlers/guilds/GUILD_UPDATE.ts b/src/handlers/guilds/GUILD_UPDATE.ts index 607c16e76..41289d333 100644 --- a/src/handlers/guilds/GUILD_UPDATE.ts +++ b/src/handlers/guilds/GUILD_UPDATE.ts @@ -1,46 +1,44 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { GuildUpdateChange } from "../../types/discordeno/guild_update_change.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { Guild } from "../../types/guilds/guild.ts"; +import { structures } from "../../structures/mod.ts"; +import type { GuildUpdateChange } from "../../types/discordeno/guild_update_change.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { Guild } from "../../types/guilds/guild.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; -export async function handleGuildUpdate(data: DiscordGatewayPayload) { +export async function handleGuildUpdate(data: DiscordGatewayPayload, shardId: number) { const payload = data.d as Guild; - const newGuild = await cacheHandlers.get("guilds", payload.id); - if (!newGuild) return; + const oldGuild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.id)); + if (!oldGuild) return; - const keysToSkip = [ - "roles", - "guildHashes", - "guildId", - "maxMembers", - "emojis", - ]; + const keysToSkip = ["id", "roles", "guildHashes", "guildId", "maxMembers", "emojis"]; - const changes = Object.entries(payload) + const newGuild = await structures.createDiscordenoGuild(payload, shardId); + + const changes = Object.entries(newGuild) .map(([key, value]) => { if (keysToSkip.includes(key)) return; // @ts-ignore index signature - const cachedValue = newGuild[key]; - if (cachedValue !== value) { - // Guild create sends undefined and update sends false. - if (!cachedValue && !value) return; + const cachedValue = oldGuild[key]; - if (Array.isArray(cachedValue) && Array.isArray(value)) { - const different = (cachedValue.length !== value.length) || - cachedValue.find((val) => !value.includes(val)) || - value.find((val) => !cachedValue.includes(val)); - if (!different) return; - } + if (cachedValue === value) return; + // Guild create sends undefined and update sends false. + if (!cachedValue && !value) return; - // @ts-ignore index signature - newGuild[key] = value; - return { key, oldValue: cachedValue, value }; + if (Array.isArray(cachedValue) && Array.isArray(value)) { + const different = + cachedValue.length !== value.length || + cachedValue.find((val) => !value.includes(val)) || + value.find((val) => !cachedValue.includes(val)); + if (!different) return; } - }).filter((change) => change) as GuildUpdateChange[]; - await cacheHandlers.set("guilds", payload.id, newGuild); + return { key, oldValue: cachedValue, value }; + }) + .filter((change) => change) as GuildUpdateChange[]; + + await cacheHandlers.set("guilds", newGuild.id, newGuild); eventHandlers.guildUpdate?.(newGuild, changes); } diff --git a/src/handlers/integrations/INTEGRATION_CREATE.ts b/src/handlers/integrations/INTEGRATION_CREATE.ts index 1f6aede60..47776f8c8 100644 --- a/src/handlers/integrations/INTEGRATION_CREATE.ts +++ b/src/handlers/integrations/INTEGRATION_CREATE.ts @@ -1,13 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { - IntegrationCreateUpdate, -} from "../../types/integration/integration_create_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { IntegrationCreateUpdate } from "../../types/integrations/integration_create_update.ts"; -export function handleIntegrationCreate( - data: DiscordGatewayPayload, -) { - eventHandlers.integrationCreate?.( - data.d as IntegrationCreateUpdate, - ); +export function handleIntegrationCreate(data: DiscordGatewayPayload) { + eventHandlers.integrationCreate?.(data.d as IntegrationCreateUpdate); } diff --git a/src/handlers/integrations/INTEGRATION_DELETE.ts b/src/handlers/integrations/INTEGRATION_DELETE.ts index 1718d2a3c..6fc85523e 100644 --- a/src/handlers/integrations/INTEGRATION_DELETE.ts +++ b/src/handlers/integrations/INTEGRATION_DELETE.ts @@ -1,9 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { IntegrationDelete } from "../../types/integration/integration_delete.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { IntegrationDelete } from "../../types/integrations/integration_delete.ts"; export function handleIntegrationDelete(data: DiscordGatewayPayload) { - eventHandlers.integrationDelete?.( - data.d as IntegrationDelete, - ); + eventHandlers.integrationDelete?.(data.d as IntegrationDelete); } diff --git a/src/handlers/integrations/INTEGRATION_UPDATE.ts b/src/handlers/integrations/INTEGRATION_UPDATE.ts index c756ee2b3..1680110be 100644 --- a/src/handlers/integrations/INTEGRATION_UPDATE.ts +++ b/src/handlers/integrations/INTEGRATION_UPDATE.ts @@ -1,11 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { - IntegrationCreateUpdate, -} from "../../types/integration/integration_create_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { IntegrationCreateUpdate } from "../../types/integrations/integration_create_update.ts"; export function handleIntegrationUpdate(data: DiscordGatewayPayload) { - eventHandlers.integrationUpdate?.( - data.d as IntegrationCreateUpdate, - ); + eventHandlers.integrationUpdate?.(data.d as IntegrationCreateUpdate); } diff --git a/src/handlers/interactions/INTERACTION_CREATE.ts b/src/handlers/interactions/INTERACTION_CREATE.ts index 618cc1bdb..ce6b3211f 100644 --- a/src/handlers/interactions/INTERACTION_CREATE.ts +++ b/src/handlers/interactions/INTERACTION_CREATE.ts @@ -1,17 +1,15 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildMemberWithUser } from "../../types/guilds/guild_member.ts"; -import { Interaction } from "../../types/interactions/interaction.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { Interaction } from "../../types/interactions/interaction.ts"; +import type { GuildMemberWithUser } from "../../types/members/guild_member.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleInteractionCreate(data: DiscordGatewayPayload) { const payload = data.d as Interaction; const discordenoMember = payload.guildId - ? await structures.createDiscordenoMember( - payload.member as GuildMemberWithUser, - payload.guildId, - ) + ? await structures.createDiscordenoMember(payload.member as GuildMemberWithUser, snowflakeToBigint(payload.guildId)) : undefined; if (discordenoMember) { await cacheHandlers.set("members", discordenoMember.id, discordenoMember); diff --git a/src/handlers/invites/INVITE_CREATE.ts b/src/handlers/invites/INVITE_CREATE.ts index bb5a4d8c7..039ebc4c7 100644 --- a/src/handlers/invites/INVITE_CREATE.ts +++ b/src/handlers/invites/INVITE_CREATE.ts @@ -1,9 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { InviteCreate } from "../../types/invites/invite_create.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { InviteCreate } from "../../types/invites/invite_create.ts"; export function handleInviteCreate(data: DiscordGatewayPayload) { - eventHandlers.inviteCreate?.( - data.d as InviteCreate, - ); + eventHandlers.inviteCreate?.(data.d as InviteCreate); } diff --git a/src/handlers/invites/INVITE_DELETE.ts b/src/handlers/invites/INVITE_DELETE.ts index 6c7b137a8..b1f3be86b 100644 --- a/src/handlers/invites/INVITE_DELETE.ts +++ b/src/handlers/invites/INVITE_DELETE.ts @@ -1,9 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { InviteDelete } from "../../types/invites/invite_delete.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { InviteDelete } from "../../types/invites/invite_delete.ts"; export function handleInviteDelete(data: DiscordGatewayPayload) { - eventHandlers.inviteDelete?.( - data.d as InviteDelete, - ); + eventHandlers.inviteDelete?.(data.d as InviteDelete); } diff --git a/src/handlers/members/GUILD_MEMBERS_CHUNK.ts b/src/handlers/members/GUILD_MEMBERS_CHUNK.ts index 1057b666e..006eda9f8 100644 --- a/src/handlers/members/GUILD_MEMBERS_CHUNK.ts +++ b/src/handlers/members/GUILD_MEMBERS_CHUNK.ts @@ -1,28 +1,26 @@ import { cache, cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildMembersChunk } from "../../types/members/guild_members_chunk.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildMembersChunk } from "../../types/members/guild_members_chunk.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; import { Collection } from "../../util/collection.ts"; export async function handleGuildMembersChunk(data: DiscordGatewayPayload) { const payload = data.d as GuildMembersChunk; + const guildId = snowflakeToBigint(payload.guildId); + const members = await Promise.all( payload.members.map(async (member) => { - const discordenoMember = await structures.createDiscordenoMember( - member, - payload.guildId, - ); + const discordenoMember = await structures.createDiscordenoMember(member, guildId); await cacheHandlers.set("members", discordenoMember.id, discordenoMember); return discordenoMember; - }), + }) ); // Check if its necessary to resolve the fetchmembers promise for this chunk or if more chunks will be coming - if ( - payload.nonce - ) { + if (payload.nonce) { const resolve = cache.fetchAllMembersProcessingRequests.get(payload.nonce); if (!resolve) return; @@ -33,12 +31,7 @@ export async function handleGuildMembersChunk(data: DiscordGatewayPayload) { return resolve(new Collection(members.map((m) => [m.id, m]))); } - return resolve( - await cacheHandlers.filter( - "members", - (m) => m.guilds.has(payload.guildId), - ), - ); + return resolve(await cacheHandlers.filter("members", (m) => m.guilds.has(guildId))); } } } diff --git a/src/handlers/members/GUILD_MEMBER_ADD.ts b/src/handlers/members/GUILD_MEMBER_ADD.ts index 722bce40e..f75ad1555 100644 --- a/src/handlers/members/GUILD_MEMBER_ADD.ts +++ b/src/handlers/members/GUILD_MEMBER_ADD.ts @@ -1,19 +1,17 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildMemberAdd } from "../../types/members/guild_member_add.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildMemberAdd } from "../../types/members/guild_member_add.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleGuildMemberAdd(data: DiscordGatewayPayload) { const payload = data.d as GuildMemberAdd; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; guild.memberCount++; - const discordenoMember = await structures.createDiscordenoMember( - payload, - payload.guildId, - ); + const discordenoMember = await structures.createDiscordenoMember(payload, guild.id); await cacheHandlers.set("members", discordenoMember.id, discordenoMember); eventHandlers.guildMemberAdd?.(guild, discordenoMember); diff --git a/src/handlers/members/GUILD_MEMBER_REMOVE.ts b/src/handlers/members/GUILD_MEMBER_REMOVE.ts index cbf075a41..98cacb5ab 100644 --- a/src/handlers/members/GUILD_MEMBER_REMOVE.ts +++ b/src/handlers/members/GUILD_MEMBER_REMOVE.ts @@ -1,15 +1,16 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildMemberRemove } from "../../types/members/guild_member_remove.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildMemberRemove } from "../../types/members/guild_member_remove.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleGuildMemberRemove(data: DiscordGatewayPayload) { const payload = data.d as GuildMemberRemove; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; guild.memberCount--; - const member = await cacheHandlers.get("members", payload.user.id); + const member = await cacheHandlers.get("members", snowflakeToBigint(payload.user.id)); eventHandlers.guildMemberRemove?.(guild, payload.user, member); member?.guilds.delete(guild.id); diff --git a/src/handlers/members/GUILD_MEMBER_UPDATE.ts b/src/handlers/members/GUILD_MEMBER_UPDATE.ts index 6bcde3886..4f8ca7fe4 100644 --- a/src/handlers/members/GUILD_MEMBER_UPDATE.ts +++ b/src/handlers/members/GUILD_MEMBER_UPDATE.ts @@ -1,40 +1,32 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildMemberUpdate } from "../../types/members/guild_member_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildMemberUpdate } from "../../types/members/guild_member_update.ts"; +import { bigintToSnowflake, snowflakeToBigint } from "../../util/bigint.ts"; export async function handleGuildMemberUpdate(data: DiscordGatewayPayload) { const payload = data.d as GuildMemberUpdate; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; - const cachedMember = await cacheHandlers.get("members", payload.user.id); - const guildMember = cachedMember?.guilds.get(payload.guildId); + const cachedMember = await cacheHandlers.get("members", snowflakeToBigint(payload.user.id)); + const guildMember = cachedMember?.guilds.get(guild.id); const newMemberData = { ...payload, premiumSince: payload.premiumSince || undefined, - joinedAt: new Date(guildMember?.joinedAt || Date.now()) - .toISOString(), + joinedAt: new Date(guildMember?.joinedAt || Date.now()).toISOString(), deaf: guildMember?.deaf || false, mute: guildMember?.mute || false, roles: payload.roles, }; - const discordenoMember = await structures.createDiscordenoMember( - newMemberData, - payload.guildId, - ); + const discordenoMember = await structures.createDiscordenoMember(newMemberData, guild.id); await cacheHandlers.set("members", discordenoMember.id, discordenoMember); if (guildMember) { if (guildMember.nick !== payload.nick) { - eventHandlers.nicknameUpdate?.( - guild, - discordenoMember, - payload.nick!, - guildMember.nick ?? undefined, - ); + eventHandlers.nicknameUpdate?.(guild, discordenoMember, payload.nick!, guildMember.nick ?? undefined); } if (payload.pending === false && guildMember.pending === true) { @@ -44,22 +36,16 @@ export async function handleGuildMemberUpdate(data: DiscordGatewayPayload) { const roleIds = guildMember.roles || []; roleIds.forEach((id) => { - eventHandlers.debug?.( - "loop", - `1. Running forEach loop in GUILD_MEMBER_UPDATE file.`, - ); - if (!payload.roles.includes(id)) { + eventHandlers.debug?.("loop", `1. Running forEach loop in GUILD_MEMBER_UPDATE file.`); + if (!payload.roles.includes(bigintToSnowflake(id))) { eventHandlers.roleLost?.(guild, discordenoMember, id); } }); payload.roles.forEach((id) => { - eventHandlers.debug?.( - "loop", - `2. Running forEach loop in GUILD_MEMBER_UPDATE file.`, - ); - if (!roleIds.includes(id)) { - eventHandlers.roleGained?.(guild, discordenoMember, id); + eventHandlers.debug?.("loop", `2. Running forEach loop in GUILD_MEMBER_UPDATE file.`); + if (!roleIds.includes(snowflakeToBigint(id))) { + eventHandlers.roleGained?.(guild, discordenoMember, snowflakeToBigint(id)); } }); } diff --git a/src/handlers/messages/MESSAGE_CREATE.ts b/src/handlers/messages/MESSAGE_CREATE.ts index 3a3c9080d..b8c541c1c 100644 --- a/src/handlers/messages/MESSAGE_CREATE.ts +++ b/src/handlers/messages/MESSAGE_CREATE.ts @@ -1,51 +1,46 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildMemberWithUser } from "../../types/guilds/guild_member.ts"; -import { Message } from "../../types/messages/message.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildMemberWithUser } from "../../types/members/guild_member.ts"; +import type { Message } from "../../types/messages/message.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleMessageCreate(data: DiscordGatewayPayload) { const payload = data.d as Message; - const channel = await cacheHandlers.get("channels", payload.channelId); - if (channel) channel.lastMessageId = payload.id; + const channel = await cacheHandlers.get("channels", snowflakeToBigint(payload.channelId)); + if (channel) channel.lastMessageId = snowflakeToBigint(payload.id); - const guild = payload.guildId - ? await cacheHandlers.get("guilds", payload.guildId) - : undefined; + const guild = payload.guildId ? await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)) : undefined; if (payload.member && guild) { // If in a guild cache the author as a member const discordenoMember = await structures.createDiscordenoMember( { ...payload.member, user: payload.author } as GuildMemberWithUser, - guild.id, + guild.id ); await cacheHandlers.set("members", discordenoMember.id, discordenoMember); } - if (payload.mentions) { - await Promise.all(payload.mentions.map(async (mention) => { - // Cache the member if its a valid member - if (mention.member && guild) { - const discordenoMember = await structures.createDiscordenoMember( - { ...mention.member, user: mention } as GuildMemberWithUser, - guild.id, - ); + if (payload.mentions && guild) { + await Promise.all( + payload.mentions.map(async (mention) => { + // Cache the member if its a valid member + if (mention.member) { + const discordenoMember = await structures.createDiscordenoMember( + { ...mention.member, user: mention } as GuildMemberWithUser, + guild.id + ); - return cacheHandlers.set( - "members", - mention.id, - discordenoMember, - ); - } - })); + return cacheHandlers.set("members", snowflakeToBigint(mention.id), discordenoMember); + } + }) + ); } - const message = await structures.createDiscordenoMessage( - data.d as Message, - ); + const message = await structures.createDiscordenoMessage(data.d as Message); // Cache the message - await cacheHandlers.set("messages", payload.id, message); + await cacheHandlers.set("messages", snowflakeToBigint(payload.id), message); eventHandlers.messageCreate?.(message); } diff --git a/src/handlers/messages/MESSAGE_DELETE.ts b/src/handlers/messages/MESSAGE_DELETE.ts index f8bdc5e40..afbfc20a0 100644 --- a/src/handlers/messages/MESSAGE_DELETE.ts +++ b/src/handlers/messages/MESSAGE_DELETE.ts @@ -1,17 +1,18 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { MessageDelete } from "../../types/messages/message_delete.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { MessageDelete } from "../../types/messages/message_delete.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleMessageDelete(data: DiscordGatewayPayload) { const payload = data.d as MessageDelete; - const channel = await cacheHandlers.get("channels", payload.channelId); + const channel = await cacheHandlers.get("channels", snowflakeToBigint(payload.channelId)); if (!channel) return; eventHandlers.messageDelete?.( { id: payload.id, channel }, - await cacheHandlers.get("messages", payload.id), + await cacheHandlers.get("messages", snowflakeToBigint(payload.id)) ); - await cacheHandlers.delete("messages", payload.id); + await cacheHandlers.delete("messages", snowflakeToBigint(payload.id)); } diff --git a/src/handlers/messages/MESSAGE_DELETE_BULK.ts b/src/handlers/messages/MESSAGE_DELETE_BULK.ts index 058932208..64987feeb 100644 --- a/src/handlers/messages/MESSAGE_DELETE_BULK.ts +++ b/src/handlers/messages/MESSAGE_DELETE_BULK.ts @@ -1,18 +1,18 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { MessageDeleteBulk } from "../../types/messages/message_delete_bulk.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { MessageDeleteBulk } from "../../types/messages/message_delete_bulk.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleMessageDeleteBulk(data: DiscordGatewayPayload) { const payload = data.d as MessageDeleteBulk; - const channel = await cacheHandlers.get("channels", payload.channelId); + const channel = await cacheHandlers.get("channels", snowflakeToBigint(payload.channelId)); if (!channel) return; - return Promise.all(payload.ids.map(async (id) => { - eventHandlers.messageDelete?.( - { id, channel }, - await cacheHandlers.get("messages", id), - ); - await cacheHandlers.delete("messages", id); - })); + return Promise.all( + payload.ids.map(async (id) => { + eventHandlers.messageDelete?.({ id, channel }, await cacheHandlers.get("messages", snowflakeToBigint(id))); + await cacheHandlers.delete("messages", snowflakeToBigint(id)); + }) + ); } diff --git a/src/handlers/messages/MESSAGE_REACTION_ADD.ts b/src/handlers/messages/MESSAGE_REACTION_ADD.ts index 9ba917f7c..1dffcaf89 100644 --- a/src/handlers/messages/MESSAGE_REACTION_ADD.ts +++ b/src/handlers/messages/MESSAGE_REACTION_ADD.ts @@ -1,51 +1,39 @@ import { botId, eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { - MessageReactionAdd, -} from "../../types/messages/message_reaction_add.ts"; -import { snakeKeysToCamelCase } from "../../util/utils.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { MessageReactionAdd } from "../../types/messages/message_reaction_add.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleMessageReactionAdd(data: DiscordGatewayPayload) { const payload = data.d as MessageReactionAdd; - const message = await cacheHandlers.get("messages", payload.messageId); + const message = await cacheHandlers.get("messages", snowflakeToBigint(payload.messageId)); if (message) { const reactionExisted = message.reactions?.find( - (reaction) => - reaction.emoji.id === payload.emoji.id && - reaction.emoji.name === payload.emoji.name, + (reaction) => reaction.emoji.id === payload.emoji.id && reaction.emoji.name === payload.emoji.name ); if (reactionExisted) reactionExisted.count++; else { const newReaction = { count: 1, - me: payload.userId === botId, + me: snowflakeToBigint(payload.userId) === botId, emoji: { ...payload.emoji, id: payload.emoji.id || undefined }, }; - message.reactions = message.reactions - ? [...message.reactions, newReaction] - : [newReaction]; + message.reactions = message.reactions ? [...message.reactions, newReaction] : [newReaction]; } - await cacheHandlers.set("messages", payload.messageId, message); + await cacheHandlers.set("messages", snowflakeToBigint(payload.messageId), message); } if (payload.member && payload.guildId) { - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (guild) { - const discordenoMember = await structures.createDiscordenoMember( - payload.member, - guild.id, - ); + const discordenoMember = await structures.createDiscordenoMember(payload.member, guild.id); await cacheHandlers.set("members", discordenoMember.id, discordenoMember); } } - eventHandlers.reactionAdd?.( - snakeKeysToCamelCase(payload), - message, - ); + eventHandlers.reactionAdd?.(payload, message); } diff --git a/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts b/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts index 9d947f8e3..603568af7 100644 --- a/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts +++ b/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts @@ -1,21 +1,18 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { - MessageReactionRemove, -} from "../../types/messages/message_reaction_remove.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { MessageReactionRemove } from "../../types/messages/message_reaction_remove.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; -export async function handleMessageReactionRemove( - data: DiscordGatewayPayload, -) { +export async function handleMessageReactionRemove(data: DiscordGatewayPayload) { const payload = data.d as MessageReactionRemove; - const message = await cacheHandlers.get("messages", payload.messageId); + const message = await cacheHandlers.get("messages", snowflakeToBigint(payload.messageId)); if (message) { - const reaction = message.reactions?.find((reaction) => - // MUST USE == because discord sends null and we use undefined - reaction.emoji.id == payload.emoji.id && - reaction.emoji.name === payload.emoji.name + const reaction = message.reactions?.find( + (reaction) => + // MUST USE == because discord sends null and we use undefined + reaction.emoji.id == payload.emoji.id && reaction.emoji.name === payload.emoji.name ); if (reaction) { @@ -25,12 +22,9 @@ export async function handleMessageReactionRemove( } if (!message.reactions?.length) message.reactions = undefined; - await cacheHandlers.set("messages", payload.messageId, message); + await cacheHandlers.set("messages", message.id, message); } } - eventHandlers.reactionRemove?.( - payload, - message, - ); + eventHandlers.reactionRemove?.(payload, message); } diff --git a/src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts b/src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts index 328f7c369..79d84995e 100644 --- a/src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts +++ b/src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts @@ -1,24 +1,18 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { - MessageReactionRemoveAll, -} from "../../types/messages/message_reaction_remove_all.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { MessageReactionRemoveAll } from "../../types/messages/message_reaction_remove_all.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; -export async function handleMessageReactionRemoveAll( - data: DiscordGatewayPayload, -) { +export async function handleMessageReactionRemoveAll(data: DiscordGatewayPayload) { const payload = data.d as MessageReactionRemoveAll; - const message = await cacheHandlers.get("messages", payload.messageId); + const message = await cacheHandlers.get("messages", snowflakeToBigint(payload.messageId)); if (message?.reactions) { message.reactions = undefined; - await cacheHandlers.set("messages", payload.messageId, message); + await cacheHandlers.set("messages", snowflakeToBigint(payload.messageId), message); } - eventHandlers.reactionRemoveAll?.( - payload, - message, - ); + eventHandlers.reactionRemoveAll?.(payload, message); } diff --git a/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts b/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts index 34e42b0ca..422ab74be 100644 --- a/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts +++ b/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts @@ -1,33 +1,31 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { MessageReactionRemoveEmoji } from "../../types/messages/message_reaction_remove_emoji.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { MessageReactionRemoveEmoji } from "../../types/messages/message_reaction_remove_emoji.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; -export async function handleMessageReactionRemoveEmoji( - data: DiscordGatewayPayload, -) { +export async function handleMessageReactionRemoveEmoji(data: DiscordGatewayPayload) { const payload = data.d as MessageReactionRemoveEmoji; - const message = await cacheHandlers.get("messages", payload.messageId); + const message = await cacheHandlers.get("messages", snowflakeToBigint(payload.messageId)); if (message?.reactions) { message.reactions = message.reactions.filter( (reaction) => !( // MUST USE == because discord sends null and we use undefined - reaction.emoji.id == payload.emoji.id && - reaction.emoji.name === payload.emoji.name - ), + (reaction.emoji.id == payload.emoji.id && reaction.emoji.name === payload.emoji.name) + ) ); if (!message.reactions.length) message.reactions = undefined; - await cacheHandlers.set("messages", payload.messageId, message); + await cacheHandlers.set("messages", message.id, message); } eventHandlers.reactionRemoveEmoji?.( payload.emoji, - payload.messageId, - payload.channelId, - payload.guildId, + snowflakeToBigint(payload.messageId), + snowflakeToBigint(payload.channelId), + payload.guildId ? snowflakeToBigint(payload.guildId) : undefined ); } diff --git a/src/handlers/messages/MESSAGE_UPDATE.ts b/src/handlers/messages/MESSAGE_UPDATE.ts index 390746e04..32a6b21c0 100644 --- a/src/handlers/messages/MESSAGE_UPDATE.ts +++ b/src/handlers/messages/MESSAGE_UPDATE.ts @@ -1,28 +1,26 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { Message } from "../../types/messages/message.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { Message } from "../../types/messages/message.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleMessageUpdate(data: DiscordGatewayPayload) { const payload = data.d as Message; - const channel = await cacheHandlers.get("channels", payload.channelId); + const channel = await cacheHandlers.get("channels", snowflakeToBigint(payload.channelId)); if (!channel) return; - const oldMessage = await cacheHandlers.get("messages", payload.id); + const oldMessage = await cacheHandlers.get("messages", snowflakeToBigint(payload.id)); if (!oldMessage) return; // Messages with embeds can trigger update but they wont have edited_timestamp - if ( - !payload.editedTimestamp || - (oldMessage.content === payload.content) - ) { + if (!payload.editedTimestamp || oldMessage.content === payload.content) { return; } const message = await structures.createDiscordenoMessage(payload); - await cacheHandlers.set("messages", payload.id, message); + await cacheHandlers.set("messages", snowflakeToBigint(payload.id), message); eventHandlers.messageUpdate?.(message, oldMessage); } diff --git a/src/handlers/misc/PRESENCE_UPDATE.ts b/src/handlers/misc/PRESENCE_UPDATE.ts index 1e8d94657..ce801616a 100644 --- a/src/handlers/misc/PRESENCE_UPDATE.ts +++ b/src/handlers/misc/PRESENCE_UPDATE.ts @@ -1,13 +1,14 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { PresenceUpdate } from "../../types/misc/presence_update.ts"; +import type { PresenceUpdate } from "../../types/activity/presence_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handlePresenceUpdate(data: DiscordGatewayPayload) { const payload = data.d as PresenceUpdate; - const oldPresence = await cacheHandlers.get("presences", payload.user.id); - await cacheHandlers.set("presences", payload.user.id, payload); + const oldPresence = await cacheHandlers.get("presences", snowflakeToBigint(payload.user.id)); + await cacheHandlers.set("presences", snowflakeToBigint(payload.user.id), payload); eventHandlers.presenceUpdate?.(payload, oldPresence); } diff --git a/src/handlers/misc/READY.ts b/src/handlers/misc/READY.ts index 204ed07f2..8bee0fbdf 100644 --- a/src/handlers/misc/READY.ts +++ b/src/handlers/misc/READY.ts @@ -1,116 +1,73 @@ import { eventHandlers, setApplicationId, setBotId } from "../../bot.ts"; -import { cache, cacheHandlers } from "../../cache.ts"; -import { initialMemberLoadQueue } from "../../structures/guild.ts"; -import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { Ready } from "../../types/gateway/ready.ts"; -import { GuildMemberWithUser } from "../../types/mod.ts"; -import { ws } from "../../ws/ws.ts"; +import { cache } from "../../cache.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { Ready } from "../../types/gateway/ready.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; +import { DiscordenoShard, ws } from "../../ws/ws.ts"; + +export function handleReady(data: DiscordGatewayPayload, shardId: number) { + // Triggered on each shard + eventHandlers.shardReady?.(shardId); -export function handleReady( - data: DiscordGatewayPayload, - shardId: number, -) { // The bot has already started, the last shard is resumed, however. if (cache.isReady) return; + const shard = ws.shards.get(shardId); + if (!shard) return; + const payload = data.d as Ready; setBotId(payload.user.id); setApplicationId(payload.application.id); - // Triggered on each shard - eventHandlers.shardReady?.(shardId); - // Save when the READY event was received to prevent infinite load loops - const now = Date.now(); - - const shard = ws.shards.get(shardId); - if (!shard) return; - // Set ready to false just to go sure shard.ready = false; // All guilds are unavailable at first - shard.unavailableGuildIds = new Set(payload.guilds.map((g) => g.id)); + shard.unavailableGuildIds = new Set(payload.guilds.map((g) => snowflakeToBigint(g.id))); + // Set the last available to now + shard.lastAvailable = Date.now(); // Start ready check in 2 seconds - setTimeout(async () => { - eventHandlers.debug?.( - "loop", - `1. Running setTimeout in READY file.`, - ); - await checkReady(payload, shardId, now); + setTimeout(() => { + eventHandlers.debug?.("loop", `1. Running setTimeout in READY file.`); + checkReady(payload, shard); }, 2000); } -// Don't pass the shard itself because unavailableGuilds won't be updated by the GUILD_CREATE event /** This function checks if the shard is fully loaded */ -async function checkReady(payload: Ready, shardId: number, now: number) { - const shard = ws.shards.get(shardId); - if (!shard) return; - +function checkReady(payload: Ready, shard: DiscordenoShard) { // Check if all guilds were loaded - if (shard.unavailableGuildIds.size) { - if (Date.now() - now > 10000) { - eventHandlers.shardFailedToLoad?.(shardId, shard.unavailableGuildIds); - // Force execute the loaded function to prevent infinite loop - await loaded(shardId); - } else { - // Not all guilds were loaded but 10 seconds haven't passed so check again - setTimeout(async () => { - eventHandlers.debug?.( - "loop", - `2. Running setTimeout in READY file.`, - ); - await checkReady(payload, shardId, now); - }, 2000); - } - } else { - // All guilds were loaded - await loaded(shardId); + if (!shard.unavailableGuildIds.size) return loaded(shard); + + // If the last GUILD_CREATE has been received before 5 seconds if so most likely the remaining guilds are unavailable + if (shard.lastAvailable + 5000 < Date.now()) { + eventHandlers.shardFailedToLoad?.(shard.id, shard.unavailableGuildIds); + // Force execute the loaded function to prevent infinite loop + return loaded(shard); } + + // Not all guilds were loaded but 5 seconds haven't passed so check again + setTimeout(() => { + eventHandlers.debug?.("loop", `2. Running setTimeout in READY file.`); + checkReady(payload, shard); + }, 2000); } -async function loaded(shardId: number) { - const shard = ws.shards.get(shardId); - if (!shard) return; - +function loaded(shard: DiscordenoShard) { shard.ready = true; - // If it is the last shard we can go full ready - if (shardId === ws.lastShardId - 1) { - // Still some shards are loading so wait another 2 seconds for them - if (ws.shards.some((shard) => !shard.ready)) { - setTimeout(async () => { - eventHandlers.debug?.( - "loop", - `3. Running setTimeout in CHANNEL_DELTE file.`, - ); - await loaded(shardId); - }, 2000); - } else { - cache.isReady = true; - eventHandlers.ready?.(); + // If it is not the last shard we can't go full ready + if (shard.id !== ws.lastShardId) return; - // All the members that came in on guild creates should now be processed 1 by 1 - for (const [guildId, members] of initialMemberLoadQueue.entries()) { - eventHandlers.debug?.( - "loop", - "Running for of loop in READY file for loading members.", - ); - await Promise.allSettled( - members.map(async (member) => { - const discordenoMember = await structures.createDiscordenoMember( - member as GuildMemberWithUser, - guildId, - ); + // Still some shards are loading so wait another 2 seconds for them + if (ws.shards.some((shard) => !shard.ready)) { + setTimeout(() => { + eventHandlers.debug?.("loop", `3. Running setTimeout in READY file.`); + loaded(shard); + }, 2000); - return cacheHandlers.set( - "members", - discordenoMember.id, - discordenoMember, - ); - }), - ); - } - } + return; } + + cache.isReady = true; + eventHandlers.ready?.(); } diff --git a/src/handlers/misc/TYPING_START.ts b/src/handlers/misc/TYPING_START.ts index 1cfde4296..ccea2d5af 100644 --- a/src/handlers/misc/TYPING_START.ts +++ b/src/handlers/misc/TYPING_START.ts @@ -1,9 +1,7 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { TypingStart } from "../../types/misc/typing_start.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { TypingStart } from "../../types/misc/typing_start.ts"; export function handleTypingStart(data: DiscordGatewayPayload) { - eventHandlers.typingStart?.( - data.d as TypingStart, - ); + eventHandlers.typingStart?.(data.d as TypingStart); } diff --git a/src/handlers/misc/USER_UPDATE.ts b/src/handlers/misc/USER_UPDATE.ts index 5ed05a74b..19c29bab7 100644 --- a/src/handlers/misc/USER_UPDATE.ts +++ b/src/handlers/misc/USER_UPDATE.ts @@ -1,24 +1,35 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { User } from "../../types/users/user.ts"; +import { memberToggles } from "../../structures/member.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { User } from "../../types/users/user.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; +import { iconHashToBigInt } from "../../util/hash.ts"; export async function handleUserUpdate(data: DiscordGatewayPayload) { const userData = data.d as User; - const member = await cacheHandlers.get("members", userData.id); + const member = await cacheHandlers.get("members", snowflakeToBigint(userData.id)); if (!member) return; - Object.entries(userData).forEach(([key, value]) => { - eventHandlers.debug?.( - "loop", - `Running forEach loop in USER_UPDATE file.`, - ); - // @ts-ignore index signatures - if (member[key] !== value) return member[key] = value; - }); + // Update username + member.username = userData.username; + // Update discriminator + member.discriminator = Number(userData.discriminator); - await cacheHandlers.set("members", userData.id, member); + // Check if a avatar is available + const hash = userData.avatar ? iconHashToBigInt(userData.avatar) : undefined; + // Update the avatar + member.avatar = hash?.bigint || 0n; + // Update the animated status if its animated + if (hash?.animated) member.bitfield |= memberToggles.animatedAvatar; + else member.bitfield &= ~memberToggles.animatedAvatar; + + member.flags = userData.flags; + member.premiumType = userData.premiumType; + member.publicFlags = userData.publicFlags; + + await cacheHandlers.set("members", snowflakeToBigint(userData.id), member); eventHandlers.botUpdate?.(userData); } diff --git a/src/handlers/mod.ts b/src/handlers/mod.ts index 2eda4a9c7..9155ffdb6 100644 --- a/src/handlers/mod.ts +++ b/src/handlers/mod.ts @@ -2,6 +2,15 @@ import { handleChannelCreate } from "./channels/CHANNEL_CREATE.ts"; import { handleChannelDelete } from "./channels/CHANNEL_DELETE.ts"; import { handleChannelPinsUpdate } from "./channels/CHANNEL_PINS_UPDATE.ts"; import { handleChannelUpdate } from "./channels/CHANNEL_UPDATE.ts"; +import { handleStageInstanceCreate } from "./channels/STAGE_INSTANCE_CREATE.ts"; +import { handleStageInstanceUpdate } from "./channels/STAGE_INSTANCE_UPDATE.ts"; +import { handleStageInstanceDelete } from "./channels/STAGE_INSTANCE_DELETE.ts"; +import { handleThreadCreate } from "./channels/THREAD_CREATE.ts"; +import { handleThreadDelete } from "./channels/THREAD_DELETE.ts"; +import { handleThreadListSync } from "./channels/THREAD_LIST_SYNC.ts"; +import { handleThreadMembersUpdate } from "./channels/THREAD_MEMBERS_UPDATE.ts"; +import { handleThreadMemberUpdate } from "./channels/THREAD_MEMBER_UPDATE.ts"; +import { handleThreadUpdate } from "./channels/THREAD_UPDATE.ts"; import { handleApplicationCommandCreate } from "./commands/APPLICATION_COMMAND_CREATE.ts"; import { handleApplicationCommandDelete } from "./commands/APPLICATION_COMMAND_DELETE.ts"; import { handleApplicationCommandUpdate } from "./commands/APPLICATION_COMMAND_UPDATE.ts"; @@ -77,6 +86,15 @@ export { handleMessageUpdate, handlePresenceUpdate, handleReady, + handleStageInstanceCreate, + handleStageInstanceDelete, + handleStageInstanceUpdate, + handleThreadCreate, + handleThreadDelete, + handleThreadListSync, + handleThreadMembersUpdate, + handleThreadMemberUpdate, + handleThreadUpdate, handleTypingStart, handleUserUpdate, handleVoiceServerUpdate, @@ -92,6 +110,16 @@ export let handlers = { CHANNEL_DELETE: handleChannelDelete, CHANNEL_PINS_UPDATE: handleChannelPinsUpdate, CHANNEL_UPDATE: handleChannelUpdate, + THREAD_CREATE: handleThreadCreate, + THREAD_UPDATE: handleThreadUpdate, + THREAD_DELETE: handleThreadDelete, + THREAD_LIST_SYNC: handleThreadListSync, + THREAD_MEMBER_UPDATE: handleThreadMemberUpdate, + THREAD_MEMBERS_UPDATE: handleThreadMembersUpdate, + STAGE_INSTANCE_CREATE: handleStageInstanceCreate, + STAGE_INSTANCE_UPDATE: handleStageInstanceUpdate, + STAGE_INSTANCE_DELETE: handleStageInstanceDelete, + // commands APPLICATION_COMMAND_CREATE: handleApplicationCommandCreate, APPLICATION_COMMAND_DELETE: handleApplicationCommandDelete, diff --git a/src/handlers/roles/GUILD_ROLE_CREATE.ts b/src/handlers/roles/GUILD_ROLE_CREATE.ts index 8798e530f..82cf77a59 100644 --- a/src/handlers/roles/GUILD_ROLE_CREATE.ts +++ b/src/handlers/roles/GUILD_ROLE_CREATE.ts @@ -1,17 +1,21 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildRoleCreate } from "../../types/mod.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildRoleCreate } from "../../types/guilds/guild_role_create.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleGuildRoleCreate(data: DiscordGatewayPayload) { const payload = data.d as GuildRoleCreate; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; - const role = await structures.createDiscordenoRole(payload); - guild.roles = guild.roles.set(payload.role.id, role); - await cacheHandlers.set("guilds", payload.guildId, guild); + const role = await structures.createDiscordenoRole({ + ...payload, + guildId: guild.id, + }); + guild.roles = guild.roles.set(snowflakeToBigint(payload.role.id), role); + await cacheHandlers.set("guilds", guild.id, guild); eventHandlers.roleCreate?.(guild, role); } diff --git a/src/handlers/roles/GUILD_ROLE_DELETE.ts b/src/handlers/roles/GUILD_ROLE_DELETE.ts index ade6eff2a..171286373 100644 --- a/src/handlers/roles/GUILD_ROLE_DELETE.ts +++ b/src/handlers/roles/GUILD_ROLE_DELETE.ts @@ -1,36 +1,33 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildRoleDelete } from "../../types/guilds/guild_role_delete.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildRoleDelete } from "../../types/guilds/guild_role_delete.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleGuildRoleDelete(data: DiscordGatewayPayload) { const payload = data.d as GuildRoleDelete; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; - const cachedRole = guild.roles.get(payload.roleId)!; - guild.roles.delete(payload.roleId); + const roleId = snowflakeToBigint(payload.roleId); + + const cachedRole = guild.roles.get(roleId)!; + guild.roles.delete(roleId); if (cachedRole) eventHandlers.roleDelete?.(guild, cachedRole); // For bots without GUILD_MEMBERS member.roles is never updated breaking permissions checking. cacheHandlers.forEach("members", (member) => { - eventHandlers.debug?.( - "loop", - `1. Running forEach members loop in GUILD_ROLE_DELETE file.`, - ); + eventHandlers.debug?.("loop", `1. Running forEach members loop in GUILD_ROLE_DELETE file.`); // Not in the relevant guild so just skip. if (!member.guilds.has(guild.id)) return; member.guilds.forEach((g) => { - eventHandlers.debug?.( - "loop", - `2. Running forEach loop in CHANNEL_DELTE file.`, - ); + eventHandlers.debug?.("loop", `2. Running forEach loop in CHANNEL_DELTE file.`); // Member does not have this role - if (!g.roles.includes(payload.roleId)) return; + if (!g.roles.includes(roleId)) return; // Remove this role from the members cache - g.roles = g.roles.filter((id) => id !== payload.roleId); + g.roles = g.roles.filter((id) => id !== roleId); cacheHandlers.set("members", member.id, member); }); }); diff --git a/src/handlers/roles/GUILD_ROLE_UPDATE.ts b/src/handlers/roles/GUILD_ROLE_UPDATE.ts index 2ad390925..8c1a004a8 100644 --- a/src/handlers/roles/GUILD_ROLE_UPDATE.ts +++ b/src/handlers/roles/GUILD_ROLE_UPDATE.ts @@ -1,19 +1,23 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { GuildRoleUpdate } from "../../types/mod.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { GuildRoleUpdate } from "../../types/guilds/guild_role_update.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleGuildRoleUpdate(data: DiscordGatewayPayload) { const payload = data.d as GuildRoleUpdate; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; - const cachedRole = guild.roles.get(payload.role.id); + const cachedRole = guild.roles.get(snowflakeToBigint(payload.role.id)); if (!cachedRole) return; - const role = await structures.createDiscordenoRole(payload); - guild.roles.set(payload.role.id, role); + const role = await structures.createDiscordenoRole({ + ...payload, + guildId: guild.id, + }); + guild.roles.set(snowflakeToBigint(payload.role.id), role); await cacheHandlers.set("guilds", guild.id, guild); eventHandlers.roleUpdate?.(guild, role, cachedRole); diff --git a/src/handlers/voice/VOICE_SERVER_UPDATE.ts b/src/handlers/voice/VOICE_SERVER_UPDATE.ts index 9edb0052d..d93a41ded 100644 --- a/src/handlers/voice/VOICE_SERVER_UPDATE.ts +++ b/src/handlers/voice/VOICE_SERVER_UPDATE.ts @@ -1,12 +1,13 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { VoiceServerUpdate } from "../../types/voice/voice_server_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { VoiceServerUpdate } from "../../types/voice/voice_server_update.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleVoiceServerUpdate(data: DiscordGatewayPayload) { const payload = data.d as VoiceServerUpdate; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; eventHandlers.voiceServerUpdate?.(payload, guild); diff --git a/src/handlers/voice/VOICE_STATE_UPDATE.ts b/src/handlers/voice/VOICE_STATE_UPDATE.ts index 127d9619f..8e2f3b514 100644 --- a/src/handlers/voice/VOICE_STATE_UPDATE.ts +++ b/src/handlers/voice/VOICE_STATE_UPDATE.ts @@ -1,50 +1,45 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { VoiceState } from "../../types/voice/voice_state.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { VoiceState } from "../../types/voice/voice_state.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export async function handleVoiceStateUpdate(data: DiscordGatewayPayload) { const payload = data.d as VoiceState; - if (!payload.guildId) return; - const guild = await cacheHandlers.get("guilds", payload.guildId); + const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); if (!guild) return; const member = payload.member - ? await structures.createDiscordenoMember( - payload.member, - guild.id, - ) - : await cacheHandlers.get("members", payload.userId); + ? await structures.createDiscordenoMember(payload.member, guild.id) + : await cacheHandlers.get("members", snowflakeToBigint(payload.userId)); if (!member) return; // No cached state before so lets make one for em - const cachedState = guild.voiceStates.get(payload.userId); + const cachedState = guild.voiceStates.get(snowflakeToBigint(payload.userId)); guild.voiceStates.set( - payload.userId, - payload, + snowflakeToBigint(payload.userId), + await structures.createDiscordenoVoiceState(guild.id, payload) ); - await cacheHandlers.set("guilds", payload.guildId, guild); + await cacheHandlers.set("guilds", guild.id, guild); - if (cachedState?.channelId !== payload.channelId) { + if (cachedState?.channelId !== (payload.channelId ? snowflakeToBigint(payload.channelId) : null)) { // Either joined or moved channels if (payload.channelId) { - if (cachedState?.channelId) { // Was in a channel before - eventHandlers.voiceChannelSwitch?.( - member, - payload.channelId, - cachedState.channelId, - ); - } else { // Was not in a channel before so user just joined - eventHandlers.voiceChannelJoin?.(member, payload.channelId); + if (cachedState?.channelId) { + // Was in a channel before + eventHandlers.voiceChannelSwitch?.(member, snowflakeToBigint(payload.channelId), cachedState.channelId); + } else { + // Was not in a channel before so user just joined + eventHandlers.voiceChannelJoin?.(member, snowflakeToBigint(payload.channelId)); } } // Left the channel else if (cachedState?.channelId) { - guild.voiceStates.delete(payload.userId); + guild.voiceStates.delete(snowflakeToBigint(payload.userId)); eventHandlers.voiceChannelLeave?.(member, cachedState.channelId); } } diff --git a/src/handlers/webhooks/WEBHOOKS_UPDATE.ts b/src/handlers/webhooks/WEBHOOKS_UPDATE.ts index fef9ac28c..8b2e621d7 100644 --- a/src/handlers/webhooks/WEBHOOKS_UPDATE.ts +++ b/src/handlers/webhooks/WEBHOOKS_UPDATE.ts @@ -1,11 +1,9 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { WebhookUpdate } from "../../types/webhooks/webhooks_update.ts"; +import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import type { WebhookUpdate } from "../../types/webhooks/webhooks_update.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; export function handleWebhooksUpdate(data: DiscordGatewayPayload) { const options = data.d as WebhookUpdate; - eventHandlers.webhooksUpdate?.( - options.channelId, - options.guildId, - ); + eventHandlers.webhooksUpdate?.(snowflakeToBigint(options.channelId), snowflakeToBigint(options.guildId)); } diff --git a/src/helpers/channels/category_children.ts b/src/helpers/channels/category_children.ts index 4b1a83b84..b3c32e520 100644 --- a/src/helpers/channels/category_children.ts +++ b/src/helpers/channels/category_children.ts @@ -1,9 +1,6 @@ import { cacheHandlers } from "../../cache.ts"; /** Gets an array of all the channels ids that are the children of this category. */ -export function categoryChildren(id: string) { - return cacheHandlers.filter( - "channels", - (channel) => channel.parentId === id, - ); +export async function categoryChildren(id: bigint) { + return await cacheHandlers.filter("channels", (channel) => channel.parentId === id); } diff --git a/src/helpers/channels/channel_overwrite_has_permission.ts b/src/helpers/channels/channel_overwrite_has_permission.ts index cbf4cb650..f21fbc26a 100644 --- a/src/helpers/channels/channel_overwrite_has_permission.ts +++ b/src/helpers/channels/channel_overwrite_has_permission.ts @@ -1,32 +1,30 @@ -import { DiscordOverwrite } from "../../types/channels/overwrite.ts"; +import type { DiscordOverwrite } from "../../types/channels/overwrite.ts"; import { DiscordBitwisePermissionFlags } from "../../types/permissions/bitwise_permission_flags.ts"; -import { PermissionStrings } from "../../types/permissions/permission_strings.ts"; +import type { PermissionStrings } from "../../types/permissions/permission_strings.ts"; /** Checks if a channel overwrite for a user id or a role id has permission in this channel */ export function channelOverwriteHasPermission( - guildId: string, - id: string, - overwrites: DiscordOverwrite[], - permissions: PermissionStrings[], + guildId: bigint, + id: bigint, + overwrites: (Omit & { + id: bigint; + allow: bigint; + deny: bigint; + })[], + permissions: PermissionStrings[] ) { - const overwrite = overwrites.find((perm) => perm.id === id) || - overwrites.find((perm) => perm.id === guildId); + const overwrite = overwrites.find((perm) => perm.id === id) || overwrites.find((perm) => perm.id === guildId); + + if (!overwrite) return false; return permissions.every((perm) => { - if (overwrite) { - const allowBits = overwrite.allow; - const denyBits = overwrite.deny; - if ( - BigInt(denyBits) & BigInt(DiscordBitwisePermissionFlags[perm]) - ) { - return false; - } - if ( - BigInt(allowBits) & BigInt(DiscordBitwisePermissionFlags[perm]) - ) { - return true; - } + const allowBits = overwrite.allow; + const denyBits = overwrite.deny; + if (BigInt(denyBits) & BigInt(DiscordBitwisePermissionFlags[perm])) { + return false; + } + if (BigInt(allowBits) & BigInt(DiscordBitwisePermissionFlags[perm])) { + return true; } - return false; }); } diff --git a/src/helpers/channels/clone_channel.ts b/src/helpers/channels/clone_channel.ts index e84e039e0..70c2d41a6 100644 --- a/src/helpers/channels/clone_channel.ts +++ b/src/helpers/channels/clone_channel.ts @@ -1,21 +1,17 @@ import { cacheHandlers } from "../../cache.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; -import { CreateGuildChannel } from "../../types/guilds/create_guild_channel.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { CreateGuildChannel } from "../../types/guilds/create_guild_channel.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { calculatePermissions } from "../../util/permissions.ts"; -import { createChannel } from "./create_channel.ts"; +import { helpers } from "../mod.ts"; /** Create a copy of a channel */ -export async function cloneChannel(channelId: string, reason?: string) { +export async function cloneChannel(channelId: bigint, reason?: string) { const channelToClone = await cacheHandlers.get("channels", channelId); - //Return undefined if channel is not cached if (!channelToClone) throw new Error(Errors.CHANNEL_NOT_FOUND); //Check for DM channel - if ( - channelToClone.type === DiscordChannelTypes.DM || - channelToClone.type === DiscordChannelTypes.GROUP_DM - ) { + if (channelToClone.type === DiscordChannelTypes.DM || channelToClone.type === DiscordChannelTypes.GroupDm) { throw new Error(Errors.CHANNEL_NOT_IN_GUILD); } @@ -23,11 +19,8 @@ export async function cloneChannel(channelId: string, reason?: string) { ...channelToClone, name: channelToClone.name!, topic: channelToClone.topic || undefined, - parentId: channelToClone.parentId || undefined, - permissionOverwrites: channelToClone.permissionOverwrites.map(( - overwrite, - ) => ({ - id: overwrite.id, + permissionOverwrites: channelToClone.permissionOverwrites.map((overwrite) => ({ + id: overwrite.id.toString(), type: overwrite.type, allow: calculatePermissions(overwrite.allow), deny: calculatePermissions(overwrite.deny), @@ -35,5 +28,5 @@ export async function cloneChannel(channelId: string, reason?: string) { }; //Create the channel (also handles permissions) - return createChannel(channelToClone.guildId!, createChannelOptions, reason); + return await helpers.createChannel(channelToClone.guildId!, createChannelOptions, reason); } diff --git a/src/helpers/channels/create_channel.ts b/src/helpers/channels/create_channel.ts index 9d675ed60..8d6e251c3 100644 --- a/src/helpers/channels/create_channel.ts +++ b/src/helpers/channels/create_channel.ts @@ -1,57 +1,32 @@ -import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Channel } from "../../types/channels/channel.ts"; +import type { Channel } from "../../types/channels/channel.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; -import { - CreateGuildChannel, - DiscordCreateGuildChannel, -} from "../../types/guilds/create_guild_channel.ts"; -import { PermissionStrings } from "../../types/permissions/permission_strings.ts"; +import type { CreateGuildChannel, DiscordCreateGuildChannel } from "../../types/guilds/create_guild_channel.ts"; import { endpoints } from "../../util/constants.ts"; -import { - calculateBits, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { calculateBits, requireOverwritePermissions } from "../../util/permissions.ts"; +import { snakelize } from "../../util/utils.ts"; /** Create a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */ -export async function createChannel( - guildId: string, - options?: CreateGuildChannel, - reason?: string, -) { - const requiredPerms: Set = new Set(["MANAGE_CHANNELS"]); - - options?.permissionOverwrites?.forEach((overwrite) => { - eventHandlers.debug?.( - "loop", - `Running forEach loop in create_channel file.`, - ); - overwrite.allow.forEach(requiredPerms.add, requiredPerms); - overwrite.deny.forEach(requiredPerms.add, requiredPerms); - }); - - await requireBotGuildPermissions(guildId, [...requiredPerms]); +export async function createChannel(guildId: bigint, options?: CreateGuildChannel, reason?: string) { + if (options?.permissionOverwrites) { + await requireOverwritePermissions(guildId, options.permissionOverwrites); + } // BITRATES ARE IN THOUSANDS SO IF USER PROVIDES 32 WE CONVERT TO 32000 if (options?.bitrate && options.bitrate < 1000) options.bitrate *= 1000; - const result = await rest.runMethod( - "post", - endpoints.GUILD_CHANNELS(guildId), - { - ...camelKeysToSnakeCase(options ?? {}), - permission_overwrites: options?.permissionOverwrites?.map((perm) => ({ - ...perm, - allow: calculateBits(perm.allow), - deny: calculateBits(perm.deny), - })), - type: options?.type || DiscordChannelTypes.GUILD_TEXT, - reason, - }, - ); + const result = await rest.runMethod("post", endpoints.GUILD_CHANNELS(guildId), { + ...snakelize(options ?? {}), + permission_overwrites: options?.permissionOverwrites?.map((perm) => ({ + ...perm, + allow: calculateBits(perm.allow), + deny: calculateBits(perm.deny), + })), + type: options?.type || DiscordChannelTypes.GuildText, + reason, + }); const discordenoChannel = await structures.createDiscordenoChannel(result); await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel); diff --git a/src/helpers/channels/create_stage_instance.ts b/src/helpers/channels/create_stage_instance.ts new file mode 100644 index 000000000..17728e9a0 --- /dev/null +++ b/src/helpers/channels/create_stage_instance.ts @@ -0,0 +1,37 @@ +import { validateLength } from "../../util/validate_length.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import { rest } from "../../rest/rest.ts"; +import { endpoints } from "../../util/constants.ts"; +import type { StageInstance } from "../../types/channels/stage_instance.ts"; +import { cacheHandlers } from "../../cache.ts"; +import { ChannelTypes } from "../../types/channels/channel_types.ts"; +import { requireBotChannelPermissions } from "../../util/permissions.ts"; +import { PrivacyLevel } from "../../types/channels/privacy_level.ts"; +import { snakelize } from "../../util/utils.ts"; + +/** Creates a new Stage instance associated to a Stage channel. Requires the user to be a moderator of the Stage channel. */ +export async function createStageInstance(channelId: bigint, topic: string, privacyLevel?: PrivacyLevel) { + const channel = await cacheHandlers.get("channels", channelId); + + if (channel) { + if (channel.type !== ChannelTypes.GuildStageVoice) { + throw new Error(Errors.CHANNEL_NOT_STAGE_VOICE); + } + + await requireBotChannelPermissions(channel, ["MANAGE_CHANNELS", "MUTE_MEMBERS", "MOVE_MEMBERS"]); + } + + if (!validateLength(topic, { max: 120, min: 1 })) { + throw new Error(Errors.INVALID_TOPIC_LENGTH); + } + + return await rest.runMethod( + "post", + endpoints.STAGE_INSTANCES, + snakelize({ + channelId, + topic, + privacyLevel, + }) + ); +} diff --git a/src/helpers/channels/delete_channel.ts b/src/helpers/channels/delete_channel.ts index d934c8944..9a805b2bb 100644 --- a/src/helpers/channels/delete_channel.ts +++ b/src/helpers/channels/delete_channel.ts @@ -1,31 +1,35 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import { ChannelTypes } from "../../types/channels/channel_types.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Delete a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */ -export async function deleteChannel( - guildId: string, - channelId: string, - reason?: string, -): Promise { - await requireBotGuildPermissions(guildId, ["MANAGE_CHANNELS"]); +export async function deleteChannel(channelId: bigint, reason?: string) { + const channel = await cacheHandlers.get("channels", channelId); - const guild = await cacheHandlers.get("guilds", guildId); - if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); + if (channel?.guildId) { + const guild = await cacheHandlers.get("guilds", channel.guildId); + if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); - if (guild?.rulesChannelId === channelId) { - throw new Error(Errors.RULES_CHANNEL_CANNOT_BE_DELETED); + // TODO(threads): check if this requires guild perms or channel is enough + await requireBotGuildPermissions( + guild, + [ChannelTypes.GuildNewsThread, ChannelTypes.GuildPivateThread, ChannelTypes.GuildPublicThread].includes( + channel.type + ) + ? ["MANAGE_THREADS"] + : ["MANAGE_CHANNELS"] + ); + if (guild.rulesChannelId === channelId) { + throw new Error(Errors.RULES_CHANNEL_CANNOT_BE_DELETED); + } + + if (guild.publicUpdatesChannelId === channelId) { + throw new Error(Errors.UPDATES_CHANNEL_CANNOT_BE_DELETED); + } } - if (guild?.publicUpdatesChannelId === channelId) { - throw new Error(Errors.UPDATES_CHANNEL_CANNOT_BE_DELETED); - } - - return await rest.runMethod( - "delete", - endpoints.CHANNEL_BASE(channelId), - { reason }, - ); + return await rest.runMethod("delete", endpoints.CHANNEL_BASE(channelId), { reason }); } diff --git a/src/helpers/channels/delete_channel_overwrite.ts b/src/helpers/channels/delete_channel_overwrite.ts index ea92593cb..40dd141f7 100644 --- a/src/helpers/channels/delete_channel_overwrite.ts +++ b/src/helpers/channels/delete_channel_overwrite.ts @@ -4,14 +4,11 @@ import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Delete the channel permission overwrites for a user or role in this channel. Requires `MANAGE_ROLES` permission. */ export async function deleteChannelOverwrite( - guildId: string, - channelId: string, - overwriteId: string, + guildId: bigint, + channelId: bigint, + overwriteId: bigint ): Promise { await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); - return await rest.runMethod( - "delete", - endpoints.CHANNEL_OVERWRITE(channelId, overwriteId), - ); + return await rest.runMethod("delete", endpoints.CHANNEL_OVERWRITE(channelId, overwriteId)); } diff --git a/src/helpers/channels/delete_stage_instance.ts b/src/helpers/channels/delete_stage_instance.ts new file mode 100644 index 000000000..735da6353 --- /dev/null +++ b/src/helpers/channels/delete_stage_instance.ts @@ -0,0 +1,21 @@ +import { cacheHandlers } from "../../cache.ts"; +import { rest } from "../../rest/rest.ts"; +import { ChannelTypes } from "../../types/channels/channel_types.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import { endpoints } from "../../util/constants.ts"; +import { requireBotChannelPermissions } from "../../util/permissions.ts"; + +/** Deletes the Stage instance. Requires the user to be a moderator of the Stage channel. */ +export async function deleteStageInstance(channelId: bigint) { + const channel = await cacheHandlers.get("channels", channelId); + + if (channel) { + if (channel.type !== ChannelTypes.GuildStageVoice) { + throw new Error(Errors.CHANNEL_NOT_STAGE_VOICE); + } + + await requireBotChannelPermissions(channel, ["MUTE_MEMBERS", "MANAGE_CHANNELS", "MOVE_MEMBERS"]); + } + + return await rest.runMethod("delete", endpoints.STAGE_INSTANCE(channelId)); +} diff --git a/src/helpers/channels/edit_channel.ts b/src/helpers/channels/edit_channel.ts index dfb064c7d..75e59c266 100644 --- a/src/helpers/channels/edit_channel.ts +++ b/src/helpers/channels/edit_channel.ts @@ -1,22 +1,52 @@ import { eventHandlers } from "../../bot.ts"; +import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { ModifyChannel } from "../../types/channels/modify_channel.ts"; -import { Channel } from "../../types/mod.ts"; +import type { DiscordenoChannel } from "../../structures/channel.ts"; +import { structures } from "../../structures/mod.ts"; +import type { Channel } from "../../types/channels/channel.ts"; +import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; +import type { ModifyChannel } from "../../types/channels/modify_channel.ts"; +import type { ModifyThread } from "../../types/channels/threads/modify_thread.ts"; +import type { PermissionStrings } from "../../types/permissions/permission_strings.ts"; import { endpoints } from "../../util/constants.ts"; -import { - calculateBits, - requireBotChannelPermissions, -} from "../../util/permissions.ts"; +import { calculateBits, requireBotChannelPermissions, requireOverwritePermissions } from "../../util/permissions.ts"; +import { hasOwnProperty, snakelize } from "../../util/utils.ts"; +//TODO: implement DM group channel edit +//TODO(threads): check thread perms /** Update a channel's settings. Requires the `MANAGE_CHANNELS` permission for the guild. */ -export async function editChannel( - channelId: string, - options: ModifyChannel, - reason?: string, -) { - await requireBotChannelPermissions(channelId, ["MANAGE_CHANNELS"]); +export async function editChannel(channelId: bigint, options: ModifyChannel | ModifyThread, reason?: string) { + const channel = await cacheHandlers.get("channels", channelId); - if (options.name || options.topic) { + if (channel) { + if ( + [ + DiscordChannelTypes.GuildNewsThread, + DiscordChannelTypes.GuildPivateThread, + DiscordChannelTypes.GuildPublicThread, + ].includes(channel.type) + ) { + const permissions = new Set(); + + if (hasOwnProperty(options, "archive") && options.archive === false) { + permissions.add("SEND_MESSAGES"); + } + + // TODO(threads): change this to a better check + // hacky way of checking if more is being modified + if (Object.keys(options).length > 1) { + permissions.add("MANAGE_THREADS"); + } + + await requireBotChannelPermissions(channel.parentId ?? 0n, [...permissions]); + } + + if (hasOwnProperty(options, "permissionOverwrites") && Array.isArray(options.permissionOverwrites)) { + await requireOverwritePermissions(channel.guildId, options.permissionOverwrites); + } + } + + if (options.name || (options as ModifyChannel).topic) { const request = editChannelNameTopicQueue.get(channelId); if (!request) { // If this hasnt been done before simply add 1 for it @@ -32,66 +62,61 @@ export async function editChannel( request.amount = 2; request.timestamp = Date.now() + 600000; } else { - // 2 have already been used add to queue - request.items.push({ channelId, options }); - if (editChannelProcessing) return; - editChannelProcessing = true; - processEditChannelQueue(); - return; + return new Promise((resolve, reject) => { + // 2 have already been used add to queue + request.items.push({ channelId, options, resolve, reject }); + if (editChannelProcessing) return; + editChannelProcessing = true; + processEditChannelQueue(); + }); } } const payload = { - ...options, + ...snakelize>(options), // deno-lint-ignore camelcase - rate_limit_per_user: options.rateLimitPerUser, - // deno-lint-ignore camelcase - parent_id: options.parentId, - // deno-lint-ignore camelcase - user_limit: options.userLimit, - // deno-lint-ignore camelcase - permission_overwrites: options.permissionOverwrites?.map((overwrite) => { - return { - ...overwrite, - allow: calculateBits(overwrite.allow), - deny: calculateBits(overwrite.deny), - }; - }), + permission_overwrites: hasOwnProperty(options, "permissionOverwrites") + ? options.permissionOverwrites?.map((overwrite) => { + return { + ...overwrite, + allow: calculateBits(overwrite.allow), + deny: calculateBits(overwrite.deny), + }; + }) + : undefined, }; - return await rest.runMethod( - "patch", - endpoints.CHANNEL_BASE(channelId), - { - ...payload, - reason, - }, - ); + const result = await rest.runMethod("patch", endpoints.CHANNEL_BASE(channelId), { + ...payload, + reason, + }); + + return await structures.createDiscordenoChannel(result); } interface EditChannelRequest { amount: number; timestamp: number; - channelId: string; + channelId: bigint; items: { - channelId: string; + channelId: bigint; options: ModifyChannel; + resolve: (channel: DiscordenoChannel) => void; + // deno-lint-ignore no-explicit-any + reject: (error: any) => void; }[]; } -const editChannelNameTopicQueue = new Map(); +const editChannelNameTopicQueue = new Map(); let editChannelProcessing = false; function processEditChannelQueue() { if (!editChannelProcessing) return; const now = Date.now(); - editChannelNameTopicQueue.forEach((request) => { - eventHandlers.debug?.( - "loop", - `Running forEach loop in edit_channel file.`, - ); - if (now > request.timestamp) return; + editChannelNameTopicQueue.forEach(async (request) => { + eventHandlers.debug?.("loop", `Running forEach loop in edit_channel file.`); + if (now < request.timestamp) return; // 10 minutes have passed so we can reset this channel again if (!request.items.length) { return editChannelNameTopicQueue.delete(request.channelId); @@ -102,21 +127,23 @@ function processEditChannelQueue() { if (!details) return; - editChannel(details.channelId, details.options); + await editChannel(details.channelId, details.options) + .then((result) => details.resolve(result)) + .catch(details.reject); const secondDetails = request.items.shift(); if (!secondDetails) return; - return editChannel(secondDetails.channelId, secondDetails.options); + await editChannel(secondDetails.channelId, secondDetails.options) + .then((result) => secondDetails.resolve(result)) + .catch(secondDetails.reject); + return; }); if (editChannelNameTopicQueue.size) { setTimeout(() => { - eventHandlers.debug?.( - "loop", - `Running setTimeout in EDIT_CHANNEL file.`, - ); + eventHandlers.debug?.("loop", `Running setTimeout in EDIT_CHANNEL file.`); processEditChannelQueue(); - }, 600000); + }, 60000); } else { editChannelProcessing = false; } diff --git a/src/helpers/channels/edit_channel_overwrite.ts b/src/helpers/channels/edit_channel_overwrite.ts index 17f271528..c9b6c2db7 100644 --- a/src/helpers/channels/edit_channel_overwrite.ts +++ b/src/helpers/channels/edit_channel_overwrite.ts @@ -1,27 +1,20 @@ import { rest } from "../../rest/rest.ts"; -import { Overwrite } from "../../types/channels/overwrite.ts"; +import type { Overwrite } from "../../types/channels/overwrite.ts"; import { endpoints } from "../../util/constants.ts"; -import { - calculateBits, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; +import { calculateBits, requireBotGuildPermissions } from "../../util/permissions.ts"; /** Edit the channel permission overwrites for a user or role in this channel. Requires `MANAGE_ROLES` permission. */ export async function editChannelOverwrite( - guildId: string, - channelId: string, - overwriteId: string, - options: Omit, + guildId: bigint, + channelId: bigint, + overwriteId: bigint, + options: Omit ): Promise { await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); - return await rest.runMethod( - "put", - endpoints.CHANNEL_OVERWRITE(channelId, overwriteId), - { - allow: calculateBits(options.allow), - deny: calculateBits(options.deny), - type: options.type, - }, - ); + return await rest.runMethod("put", endpoints.CHANNEL_OVERWRITE(channelId, overwriteId), { + allow: calculateBits(options.allow), + deny: calculateBits(options.deny), + type: options.type, + }); } diff --git a/src/helpers/channels/follow_channel.ts b/src/helpers/channels/follow_channel.ts index 550bf9d0f..bd9d7d6ad 100644 --- a/src/helpers/channels/follow_channel.ts +++ b/src/helpers/channels/follow_channel.ts @@ -1,22 +1,15 @@ import { rest } from "../../rest/rest.ts"; -import { FollowedChannel } from "../../types/channels/followed_channel.ts"; +import type { FollowedChannel } from "../../types/channels/followed_channel.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Follow a News Channel to send messages to a target channel. Requires the `MANAGE_WEBHOOKS` permission in the target channel. Returns the webhook id. */ -export async function followChannel( - sourceChannelId: string, - targetChannelId: string, -) { +export async function followChannel(sourceChannelId: bigint, targetChannelId: bigint) { await requireBotChannelPermissions(targetChannelId, ["MANAGE_WEBHOOKS"]); - const data = await rest.runMethod( - "post", - endpoints.CHANNEL_FOLLOW(sourceChannelId), - { - webhook_channel_id: targetChannelId, - }, - ); + const data = await rest.runMethod("post", endpoints.CHANNEL_FOLLOW(sourceChannelId), { + webhook_channel_id: targetChannelId, + }); return data.webhookId; } diff --git a/src/helpers/channels/get_channel.ts b/src/helpers/channels/get_channel.ts index 9c27334aa..3b14c40c4 100644 --- a/src/helpers/channels/get_channel.ts +++ b/src/helpers/channels/get_channel.ts @@ -1,29 +1,23 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Channel } from "../../types/channels/channel.ts"; +import type { Channel } from "../../types/channels/channel.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; import { endpoints } from "../../util/constants.ts"; /** Fetches a single channel object from the api. * * ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your channels will be cached in your guild.** */ -export async function getChannel(channelId: string, addToCache = true) { - const result = await rest.runMethod( - "get", - endpoints.CHANNEL_BASE(channelId), - ); +export async function getChannel(channelId: bigint, addToCache = true) { + const result = await rest.runMethod("get", endpoints.CHANNEL_BASE(channelId)); const discordenoChannel = await structures.createDiscordenoChannel( result, - result.guildId, + result.guildId ? snowflakeToBigint(result.guildId) : undefined ); if (addToCache) { - await cacheHandlers.set( - "channels", - discordenoChannel.id, - discordenoChannel, - ); + await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel); } return discordenoChannel; diff --git a/src/helpers/channels/get_channel_webhooks.ts b/src/helpers/channels/get_channel_webhooks.ts index 69fcc7825..fc1bac9cb 100644 --- a/src/helpers/channels/get_channel_webhooks.ts +++ b/src/helpers/channels/get_channel_webhooks.ts @@ -1,19 +1,14 @@ import { rest } from "../../rest/rest.ts"; -import { Webhook } from "../../types/webhooks/webhook.ts"; +import type { Webhook } from "../../types/webhooks/webhook.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Gets the webhooks for this channel. Requires MANAGE_WEBHOOKS */ -export async function getChannelWebhooks(channelId: string) { +export async function getChannelWebhooks(channelId: bigint) { await requireBotChannelPermissions(channelId, ["MANAGE_WEBHOOKS"]); - const result = await rest.runMethod( - "get", - endpoints.CHANNEL_WEBHOOKS(channelId), - ); + const result = await rest.runMethod("get", endpoints.CHANNEL_WEBHOOKS(channelId)); - return new Collection( - result.map((webhook) => [webhook.id, webhook]), - ); + return new Collection(result.map((webhook) => [webhook.id, webhook])); } diff --git a/src/helpers/channels/get_channels.ts b/src/helpers/channels/get_channels.ts index c0271fb41..30c78ecfd 100644 --- a/src/helpers/channels/get_channels.ts +++ b/src/helpers/channels/get_channels.ts @@ -1,7 +1,7 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Channel } from "../../types/channels/channel.ts"; +import type { Channel } from "../../types/channels/channel.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; @@ -9,31 +9,21 @@ import { endpoints } from "../../util/constants.ts"; * * ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your channels will be cached in your guild.** */ -export async function getChannels(guildId: string, addToCache = true) { - const result = await rest.runMethod( - "get", - endpoints.GUILD_CHANNELS(guildId), - ); +export async function getChannels(guildId: bigint, addToCache = true) { + const result = await rest.runMethod("get", endpoints.GUILD_CHANNELS(guildId)); return new Collection( ( await Promise.all( result.map(async (res) => { - const discordenoChannel = await structures.createDiscordenoChannel( - res, - guildId, - ); + const discordenoChannel = await structures.createDiscordenoChannel(res, guildId); if (addToCache) { - await cacheHandlers.set( - "channels", - discordenoChannel.id, - discordenoChannel, - ); + await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel); } return discordenoChannel; - }), + }) ) - ).map((c) => [c.id, c]), + ).map((c) => [c.id, c]) ); } diff --git a/src/helpers/channels/get_pins.ts b/src/helpers/channels/get_pins.ts index 18b878b03..1b58916c1 100644 --- a/src/helpers/channels/get_pins.ts +++ b/src/helpers/channels/get_pins.ts @@ -1,16 +1,11 @@ import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Message } from "../../types/messages/message.ts"; +import type { Message } from "../../types/messages/message.ts"; import { endpoints } from "../../util/constants.ts"; /** Get pinned messages in this channel. */ -export async function getPins(channelId: string) { - const result = await rest.runMethod( - "get", - endpoints.CHANNEL_PINS(channelId), - ); +export async function getPins(channelId: bigint) { + const result = await rest.runMethod("get", endpoints.CHANNEL_PINS(channelId)); - return Promise.all( - result.map((res) => structures.createDiscordenoMessage(res)), - ); + return Promise.all(result.map((res) => structures.createDiscordenoMessage(res))); } diff --git a/src/helpers/channels/get_stage_instance.ts b/src/helpers/channels/get_stage_instance.ts new file mode 100644 index 000000000..5a3b4e633 --- /dev/null +++ b/src/helpers/channels/get_stage_instance.ts @@ -0,0 +1,19 @@ +import { cacheHandlers } from "../../cache.ts"; +import { rest } from "../../rest/rest.ts"; +import { ChannelTypes } from "../../types/channels/channel_types.ts"; +import type { StageInstance } from "../../types/channels/stage_instance.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import { endpoints } from "../../util/constants.ts"; + +/** Gets the stage instance associated with the Stage channel, if it exists. */ +export async function getStageInstance(channelId: bigint) { + const channel = await cacheHandlers.get("channels", channelId); + + if (channel) { + if (channel.type !== ChannelTypes.GuildStageVoice) { + throw new Error(Errors.CHANNEL_NOT_STAGE_VOICE); + } + } + + return await rest.runMethod("get", endpoints.STAGE_INSTANCE(channelId)); +} diff --git a/src/helpers/channels/is_channel_synced.ts b/src/helpers/channels/is_channel_synced.ts index 4f2dd0e0a..ed6798f78 100644 --- a/src/helpers/channels/is_channel_synced.ts +++ b/src/helpers/channels/is_channel_synced.ts @@ -1,7 +1,7 @@ import { cacheHandlers } from "../../cache.ts"; /** Checks whether a channel is synchronized with its parent/category channel or not. */ -export async function isChannelSynced(channelId: string) { +export async function isChannelSynced(channelId: bigint) { const channel = await cacheHandlers.get("channels", channelId); if (!channel?.parentId) return false; @@ -9,12 +9,8 @@ export async function isChannelSynced(channelId: string) { if (!parentChannel) return false; return channel.permissionOverwrites?.every((overwrite) => { - const permission = parentChannel.permissionOverwrites?.find( - (ow) => ow.id === overwrite.id, - ); + const permission = parentChannel.permissionOverwrites?.find((ow) => ow.id === overwrite.id); if (!permission) return false; - return !( - overwrite.allow !== permission.allow || overwrite.deny !== permission.deny - ); + return !(overwrite.allow !== permission.allow || overwrite.deny !== permission.deny); }); } diff --git a/src/helpers/channels/start_typing.ts b/src/helpers/channels/start_typing.ts index 013ad46e2..173a73f3b 100644 --- a/src/helpers/channels/start_typing.ts +++ b/src/helpers/channels/start_typing.ts @@ -1,7 +1,7 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; import { botHasChannelPermissions } from "../../util/permissions.ts"; @@ -10,33 +10,28 @@ import { botHasChannelPermissions } from "../../util/permissions.ts"; * However, if a bot is responding to a command and expects the computation to take a few seconds, * this endpoint may be called to let the user know that the bot is processing their message. */ -export async function startTyping(channelId: string) { +export async function startTyping(channelId: bigint) { const channel = await cacheHandlers.get("channels", channelId); // If the channel is cached, we can do extra checks/safety if (channel) { if ( ![ DiscordChannelTypes.DM, - DiscordChannelTypes.GUILD_NEWS, - DiscordChannelTypes.GUILD_TEXT, + DiscordChannelTypes.GuildNews, + DiscordChannelTypes.GuildText, + DiscordChannelTypes.GuildNewsThread, + DiscordChannelTypes.GuildPivateThread, + DiscordChannelTypes.GuildPublicThread, ].includes(channel.type) ) { throw new Error(Errors.CHANNEL_NOT_TEXT_BASED); } - const hasSendMessagesPerm = await botHasChannelPermissions( - channelId, - ["SEND_MESSAGES"], - ); - if ( - !hasSendMessagesPerm - ) { + const hasSendMessagesPerm = await botHasChannelPermissions(channelId, ["SEND_MESSAGES"]); + if (!hasSendMessagesPerm) { throw new Error(Errors.MISSING_SEND_MESSAGES); } } - return await rest.runMethod( - "post", - endpoints.CHANNEL_TYPING(channelId), - ); + return await rest.runMethod("post", endpoints.CHANNEL_TYPING(channelId)); } diff --git a/src/helpers/channels/swap_channels.ts b/src/helpers/channels/swap_channels.ts index c55b903d3..92cd14462 100644 --- a/src/helpers/channels/swap_channels.ts +++ b/src/helpers/channels/swap_channels.ts @@ -1,19 +1,12 @@ import { rest } from "../../rest/rest.ts"; -import { ModifyGuildChannelPositions } from "../../types/guilds/modify_guild_channel_position.ts"; +import type { ModifyGuildChannelPositions } from "../../types/guilds/modify_guild_channel_position.ts"; import { endpoints } from "../../util/constants.ts"; /** Modify the positions of channels on the guild. Requires MANAGE_CHANNELS permisison. */ -export async function swapChannels( - guildId: string, - channelPositions: ModifyGuildChannelPositions[], -) { +export async function swapChannels(guildId: bigint, channelPositions: ModifyGuildChannelPositions[]) { if (channelPositions.length < 2) { throw "You must provide at least two channels to be swapped."; } - return await rest.runMethod( - "patch", - endpoints.GUILD_CHANNELS(guildId), - channelPositions, - ); + return await rest.runMethod("patch", endpoints.GUILD_CHANNELS(guildId), channelPositions); } diff --git a/src/helpers/channels/threads/add_to_thread.ts b/src/helpers/channels/threads/add_to_thread.ts new file mode 100644 index 000000000..fe0c2138d --- /dev/null +++ b/src/helpers/channels/threads/add_to_thread.ts @@ -0,0 +1,27 @@ +import { cacheHandlers } from "../../../cache.ts"; +import { rest } from "../../../rest/rest.ts"; +import { ChannelTypes } from "../../../types/channels/channel_types.ts"; +import { Errors } from "../../../types/discordeno/errors.ts"; +import { endpoints } from "../../../util/constants.ts"; +//TODO(threads): this does not work rn +/** Adds the current user to a thread. Returns a 204 empty response on success. Also requires the thread is not archived. Fires a Thread Members Update Gateway event.Adds another user to a thread. Requires the ability to send messages in the thread. Also requires the thread is not archived. Returns a 204 empty response on success. Fires a Thread Members Update Gateway event. + * @param userId the user to add to the thread defaults to bot + */ +export async function addToThread(channelId: bigint, userId?: bigint) { + // TODO(threads): perm check + const channel = await cacheHandlers.get("channels", channelId); + if (channel) { + if ( + ![ChannelTypes.GuildNewsThread, ChannelTypes.GuildPivateThread, ChannelTypes.GuildPublicThread].includes( + channel.type + ) + ) { + throw new Error(Errors.NOT_A_THREAD_CHANNEL); + } + } + + return await rest.runMethod( + "put", + userId ? endpoints.THREAD_USER(channelId, userId) : endpoints.THREAD_ME(channelId) + ); +} diff --git a/src/helpers/channels/threads/get_active_threads.ts b/src/helpers/channels/threads/get_active_threads.ts new file mode 100644 index 000000000..eb7473f7d --- /dev/null +++ b/src/helpers/channels/threads/get_active_threads.ts @@ -0,0 +1,9 @@ +import { rest } from "../../../rest/rest.ts"; +import { endpoints } from "../../../util/constants.ts"; + +/** Returns all active threads in the channel, including public and private threads. Threads are ordered by their id, in descending order. Requires the READ_MESSAGE_HISTORY permission. */ +export async function getActiveThreads(channelId: bigint) { + // TODO(threads): perm check + // TODO(threads): test if it works + return await rest.runMethod("get", endpoints.THREAD_ACTIVE(channelId)); +} diff --git a/src/helpers/channels/threads/get_archived_threads.ts b/src/helpers/channels/threads/get_archived_threads.ts new file mode 100644 index 000000000..75078fd4f --- /dev/null +++ b/src/helpers/channels/threads/get_archived_threads.ts @@ -0,0 +1,24 @@ +import { rest } from "../../../rest/rest.ts"; +import { ListPublicArchivedThreads } from "../../../types/channels/threads/list_public_archived_threads.ts"; +import { endpoints } from "../../../util/constants.ts"; +import { snakelize } from "../../../util/utils.ts"; + +export async function getArchivedThreads( + channelId: bigint, + options?: ListPublicArchivedThreads & { + type?: "public" | "private" | "privateJoinedThreads"; + } +) { + // TODO(threads): perm check + // TODO(threads): check if this works + + return await rest.runMethod( + "get", + options?.type === "privateJoinedThreads" + ? endpoints.THREAD_ARCHIVED_PRIVATE_JOINED(channelId) + : options?.type === "private" + ? endpoints.THREAD_ARCHIVED_PRIVATE(channelId) + : endpoints.THREAD_ARCHIVED_PUBLIC(channelId), + snakelize(options ?? {}) + ); +} diff --git a/src/helpers/channels/threads/get_thread_members.ts b/src/helpers/channels/threads/get_thread_members.ts new file mode 100644 index 000000000..7aa90d6db --- /dev/null +++ b/src/helpers/channels/threads/get_thread_members.ts @@ -0,0 +1,24 @@ +import { cacheHandlers } from "../../../cache.ts"; +import { rest } from "../../../rest/rest.ts"; +import { ChannelTypes } from "../../../types/channels/channel_types.ts"; +import { Errors } from "../../../types/discordeno/errors.ts"; +import { endpoints } from "../../../util/constants.ts"; + +// TODO(threads): it seems like the documented return type is wrong +/** Returns array of thread members objects that are members of the thread. */ +export async function getThreadMembers(channelId: bigint) { + // TODO(threads): perm check + // TODO(threads): intents check + const channel = await cacheHandlers.get("channels", channelId); + if (channel) { + if ( + ![ChannelTypes.GuildNewsThread, ChannelTypes.GuildPivateThread, ChannelTypes.GuildPublicThread].includes( + channel.type + ) + ) { + throw new Error(Errors.NOT_A_THREAD_CHANNEL); + } + } + + return await rest.runMethod("get", endpoints.THREAD_MEMBERS(channelId)); +} diff --git a/src/helpers/channels/threads/remove_from_thread.ts b/src/helpers/channels/threads/remove_from_thread.ts new file mode 100644 index 000000000..fdd381022 --- /dev/null +++ b/src/helpers/channels/threads/remove_from_thread.ts @@ -0,0 +1,25 @@ +import { cacheHandlers } from "../../../cache.ts"; +import { rest } from "../../../rest/rest.ts"; +import { ChannelTypes } from "../../../types/channels/channel_types.ts"; +import { Errors } from "../../../types/discordeno/errors.ts"; +import { endpoints } from "../../../util/constants.ts"; + +/** Removes another user from a thread. Requires the MANAGE_THREADS permission or that you are the creator of the thread. Also requires the thread is not archived. Returns a 204 empty response on success. Fires a Thread Members Update Gateway event. */ +export async function removeFromThread(channelId: bigint, userId?: bigint) { + // TODO(threads): perm check + const channel = await cacheHandlers.get("channels", channelId); + if (channel) { + if ( + ![ChannelTypes.GuildNewsThread, ChannelTypes.GuildPivateThread, ChannelTypes.GuildPublicThread].includes( + channel.type + ) + ) { + throw new Error(Errors.NOT_A_THREAD_CHANNEL); + } + } + + return await rest.runMethod( + "delete", + userId ? endpoints.THREAD_USER(channelId, userId) : endpoints.THREAD_ME(channelId) + ); +} diff --git a/src/helpers/channels/threads/start_thread.ts b/src/helpers/channels/threads/start_thread.ts new file mode 100644 index 000000000..7202d7091 --- /dev/null +++ b/src/helpers/channels/threads/start_thread.ts @@ -0,0 +1,33 @@ +import { cacheHandlers } from "../../../cache.ts"; +import { rest } from "../../../rest/rest.ts"; +import { ChannelTypes } from "../../../types/channels/channel_types.ts"; +import { StartThread } from "../../../types/channels/threads/start_thread.ts"; +import { Errors } from "../../../types/discordeno/errors.ts"; +import { endpoints } from "../../../util/constants.ts"; +import { snakelize } from "../../../util/utils.ts"; + +/** + * Creates a new public thread from an existing message. Returns a channel on success, and a 400 BAD REQUEST on invalid parameters. Fires a Thread Create Gateway event. + * @param messageId when provided the thread will be public + */ +export async function startThread(channelId: bigint, options: StartThread & { messageId?: bigint }) { + const channel = await cacheHandlers.get("channels", channelId); + if (channel) { + // TODO(threads): perm check + if (![ChannelTypes.GuildText, ChannelTypes.GuildNews].includes(channel.type)) { + throw new Error(Errors.INVALID_THREAD_PARENT_CHANNEL_TYPE); + } + + if (!options.messageId && channel.type === ChannelTypes.GuildNews) { + throw new Error(Errors.GUILD_NEWS_CHANNEL_ONLY_SUPPORT_PUBLIC_THREADS); + } + } + + return await rest.runMethod( + "post", + options?.messageId + ? endpoints.THREAD_START_PUBLIC(channelId, options.messageId) + : endpoints.THREAD_START_PRIVATE(channelId), + snakelize(options) + ); +} diff --git a/src/helpers/channels/update_stage_instance.ts b/src/helpers/channels/update_stage_instance.ts new file mode 100644 index 000000000..c0cbe8e31 --- /dev/null +++ b/src/helpers/channels/update_stage_instance.ts @@ -0,0 +1,37 @@ +import { rest } from "../../rest/rest.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import type { StageInstance } from "../../types/channels/stage_instance.ts"; +import { endpoints } from "../../util/constants.ts"; +import { validateLength } from "../../util/validate_length.ts"; +import { cacheHandlers } from "../../cache.ts"; +import { requireBotChannelPermissions } from "../../util/permissions.ts"; +import { ChannelTypes } from "../../types/channels/channel_types.ts"; +import { snakelize } from "../../util/utils.ts"; + +/** Updates fields of an existing Stage instance. Requires the user to be a moderator of the Stage channel. */ +export async function updateStageInstance( + channelId: bigint, + data: Partial> = {} +) { + const channel = await cacheHandlers.get("channels", channelId); + + if (channel) { + if (channel.type !== ChannelTypes.GuildStageVoice) { + throw new Error(Errors.CHANNEL_NOT_STAGE_VOICE); + } + + await requireBotChannelPermissions(channel, ["MOVE_MEMBERS", "MUTE_MEMBERS", "MANAGE_CHANNELS"]); + } + + if ( + data?.topic && + !validateLength(data.topic, { + min: 1, + max: 120, + }) + ) { + throw new Error(Errors.INVALID_TOPIC_LENGTH); + } + + return await rest.runMethod("patch", endpoints.STAGE_INSTANCE(channelId), snakelize(data)); +} diff --git a/src/helpers/channels/update_voice_state.ts b/src/helpers/channels/update_voice_state.ts new file mode 100644 index 000000000..f2e37f9cf --- /dev/null +++ b/src/helpers/channels/update_voice_state.ts @@ -0,0 +1,27 @@ +import { rest } from "../../rest/rest.ts"; +import { UpdateOthersVoiceState } from "../../types/guilds/update_others_voice_state.ts"; +import type { UpdateSelfVoiceState } from "../../types/guilds/update_self_voice_state.ts"; +import { endpoints } from "../../util/constants.ts"; +import { hasOwnProperty, snakelize } from "../../util/utils.ts"; + +/** + * Updates the a user's voice state, defaults to the current user + * Caveats: + * - `channel_id` must currently point to a stage channel. + * - User must already have joined `channel_id`. + * - You must have the `MUTE_MEMBERS` permission. But can always suppress yourself. + * - When unsuppressed, non-bot users will have their `request_to_speak_timestamp` set to the current time. Bot users will not. + * - You must have the `REQUEST_TO_SPEAK` permission to request to speak. You can always clear your own request to speak. + * - You are able to set `request_to_speak_timestamp` to any present or future time. + * - When suppressed, the user will have their `request_to_speak_timestamp` removed. + */ +export async function updateBotVoiceState( + guildId: bigint, + options: UpdateSelfVoiceState | ({ userId: bigint } & UpdateOthersVoiceState) +) { + return await rest.runMethod( + "patch", + endpoints.UPDATE_VOICE_STATE(guildId, hasOwnProperty(options, "userId") ? options.userId : undefined), + snakelize(options) + ); +} diff --git a/src/helpers/commands/batch_edit_slash_command_permissions.ts b/src/helpers/commands/batch_edit_slash_command_permissions.ts deleted file mode 100644 index 514b546b7..000000000 --- a/src/helpers/commands/batch_edit_slash_command_permissions.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { ApplicationCommandPermissions } from "../../types/interactions/application_command_permissions.ts"; -import { endpoints } from "../../util/constants.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; - -/** Batch edits permissions for all commands in a guild. Takes an array of partial GuildApplicationCommandPermissions objects including `id` and `permissions`. */ -export async function batchEditSlashCommandPermissions( - guildId: string, - options: { id: string; permissions: ApplicationCommandPermissions[] }[], -) { - return await rest.runMethod( - "put", - endpoints.COMMANDS_PERMISSIONS(applicationId, guildId), - camelKeysToSnakeCase(options), - ); -} diff --git a/src/helpers/commands/delete_slash_command.ts b/src/helpers/commands/delete_slash_command.ts deleted file mode 100644 index 9f4c889d7..000000000 --- a/src/helpers/commands/delete_slash_command.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { endpoints } from "../../util/constants.ts"; - -/** Deletes a slash command. */ -export async function deleteSlashCommand( - id: string, - guildId?: string, -) { - return await rest.runMethod( - "delete", - guildId - ? endpoints.COMMANDS_GUILD_ID(applicationId, guildId, id) - : endpoints.COMMANDS_ID(applicationId, id), - ); -} diff --git a/src/helpers/commands/delete_slash_response.ts b/src/helpers/commands/delete_slash_response.ts deleted file mode 100644 index c0972f1cf..000000000 --- a/src/helpers/commands/delete_slash_response.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { endpoints } from "../../util/constants.ts"; - -/** To delete your response to a slash command. If a message id is not provided, it will default to deleting the original response. */ -export async function deleteSlashResponse( - token: string, - messageId?: string, -) { - return await rest.runMethod( - "delete", - messageId - ? endpoints.INTERACTION_ID_TOKEN_MESSAGE_ID( - applicationId, - token, - messageId, - ) - : endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationId, token), - ); -} diff --git a/src/helpers/commands/edit_slash_command_permissions.ts b/src/helpers/commands/edit_slash_command_permissions.ts deleted file mode 100644 index 1ea764d8a..000000000 --- a/src/helpers/commands/edit_slash_command_permissions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { ApplicationCommandPermissions } from "../../types/interactions/application_command_permissions.ts"; -import { endpoints } from "../../util/constants.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; - -/** Edits command permissions for a specific command for your application in a guild. */ -export async function editSlashCommandPermissions( - guildId: string, - commandId: string, - options: ApplicationCommandPermissions[], -) { - return await rest.runMethod( - "put", - endpoints.COMMANDS_PERMISSION(applicationId, guildId, commandId), - { permissions: camelKeysToSnakeCase(options) }, - ); -} diff --git a/src/helpers/commands/get_slash_command.ts b/src/helpers/commands/get_slash_command.ts deleted file mode 100644 index d207c577b..000000000 --- a/src/helpers/commands/get_slash_command.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { ApplicationCommand } from "../../types/interactions/application_command.ts"; -import { endpoints } from "../../util/constants.ts"; - -/** Fetchs the global command for the given Id. If a guildId is provided, the guild command will be fetched. */ -export async function getSlashCommand(commandId: string, guildId?: string) { - return await rest.runMethod( - "get", - guildId - ? endpoints.COMMANDS_GUILD_ID(applicationId, guildId, commandId) - : endpoints.COMMANDS_ID(applicationId, commandId), - ); -} diff --git a/src/helpers/commands/get_slash_command_permission.ts b/src/helpers/commands/get_slash_command_permission.ts deleted file mode 100644 index f34362b9f..000000000 --- a/src/helpers/commands/get_slash_command_permission.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { GuildApplicationCommandPermissions } from "../../types/interactions/guild_application_command_permissions.ts"; -import { endpoints } from "../../util/constants.ts"; - -/** Fetches command permissions for a specific command for your application in a guild. Returns a GuildApplicationCommandPermissions object. */ -export async function getSlashCommandPermission( - guildId: string, - commandId: string, -) { - return await rest.runMethod( - "get", - endpoints.COMMANDS_PERMISSION(applicationId, guildId, commandId), - ); -} diff --git a/src/helpers/commands/get_slash_command_permissions.ts b/src/helpers/commands/get_slash_command_permissions.ts deleted file mode 100644 index 4d69204bf..000000000 --- a/src/helpers/commands/get_slash_command_permissions.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { GuildApplicationCommandPermissions } from "../../types/interactions/guild_application_command_permissions.ts"; -import { endpoints } from "../../util/constants.ts"; - -/** Fetches command permissions for all commands for your application in a guild. Returns an array of GuildApplicationCommandPermissions objects. */ -export async function getSlashCommandPermissions(guildId: string) { - return await rest.runMethod( - "get", - endpoints.COMMANDS_PERMISSIONS(applicationId, guildId), - ); -} diff --git a/src/helpers/commands/get_slash_commands.ts b/src/helpers/commands/get_slash_commands.ts deleted file mode 100644 index 4b4de42d1..000000000 --- a/src/helpers/commands/get_slash_commands.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { ApplicationCommand } from "../../types/interactions/application_command.ts"; -import { Collection } from "../../util/collection.ts"; -import { endpoints } from "../../util/constants.ts"; - -/** Fetch all of the global commands for your application. */ -export async function getSlashCommands(guildId?: string) { - const result = await rest.runMethod( - "get", - guildId - ? endpoints.COMMANDS_GUILD(applicationId, guildId) - : endpoints.COMMANDS(applicationId), - ); - - return new Collection(result.map((command) => [command.name, command])); -} diff --git a/src/helpers/commands/upsert_slash_command.ts b/src/helpers/commands/upsert_slash_command.ts deleted file mode 100644 index 2fb71ae7c..000000000 --- a/src/helpers/commands/upsert_slash_command.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { EditGlobalApplicationCommand } from "../../types/interactions/edit_global_application_command.ts"; -import { ApplicationCommand } from "../../types/mod.ts"; -import { endpoints } from "../../util/constants.ts"; -import { validateSlashCommands } from "../../util/utils.ts"; - -/** - * Edit an existing slash command. If this command did not exist, it will create it. - */ -export async function upsertSlashCommand( - commandId: string, - options: EditGlobalApplicationCommand, - guildId?: string, -) { - validateSlashCommands([options]); - - return await rest.runMethod( - "patch", - guildId - ? endpoints.COMMANDS_GUILD_ID(applicationId, guildId, commandId) - : endpoints.COMMANDS_ID(applicationId, commandId), - options, - ); -} diff --git a/src/helpers/commands/upsert_slash_commands.ts b/src/helpers/commands/upsert_slash_commands.ts deleted file mode 100644 index bf2b78343..000000000 --- a/src/helpers/commands/upsert_slash_commands.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { EditGlobalApplicationCommand } from "../../types/interactions/edit_global_application_command.ts"; -import { ApplicationCommand } from "../../types/mod.ts"; -import { endpoints } from "../../util/constants.ts"; -import { validateSlashCommands } from "../../util/utils.ts"; - -/** - * Bulk edit existing slash commands. If a command does not exist, it will create it. - * - * **NOTE:** Any slash commands that are not specified in this function will be **deleted**. If you don't provide the commandId and rename your command, the command gets a new Id. - */ -export async function upsertSlashCommands( - options: EditGlobalApplicationCommand[], - guildId?: string, -) { - validateSlashCommands(options); - - return await rest.runMethod( - "put", - guildId - ? endpoints.COMMANDS_GUILD(applicationId, guildId) - : endpoints.COMMANDS(applicationId), - options, - ); -} diff --git a/src/helpers/discovery/add_discovery_subcategory.ts b/src/helpers/discovery/add_discovery_subcategory.ts index 93a124c91..038431dcf 100644 --- a/src/helpers/discovery/add_discovery_subcategory.ts +++ b/src/helpers/discovery/add_discovery_subcategory.ts @@ -1,19 +1,14 @@ import { rest } from "../../rest/rest.ts"; -import { - AddGuildDiscoverySubcategory, -} from "../../types/discovery/add_guild_discovery_subcategory.ts"; +import type { AddGuildDiscoverySubcategory } from "../../types/discovery/add_guild_discovery_subcategory.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Add a discovery subcategory to the guild. Requires the `MANAGE_GUILD` permission. */ -export async function addDiscoverySubcategory( - guildId: string, - categoryId: number, -) { +export async function addDiscoverySubcategory(guildId: bigint, categoryId: number) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); return await rest.runMethod( "post", - endpoints.DISCOVERY_SUBCATEGORY(guildId, categoryId), + endpoints.DISCOVERY_SUBCATEGORY(guildId, categoryId) ); } diff --git a/src/helpers/discovery/edit_discovery.ts b/src/helpers/discovery/edit_discovery.ts index 9976e04cd..d894477df 100644 --- a/src/helpers/discovery/edit_discovery.ts +++ b/src/helpers/discovery/edit_discovery.ts @@ -1,20 +1,13 @@ import { rest } from "../../rest/rest.ts"; -import { DiscoveryMetadata } from "../../types/discovery/discovery_metadata.ts"; -import { ModifyGuildDiscoveryMetadata } from "../../types/discovery/modify_guild_discovery_metadata.ts"; +import type { DiscoveryMetadata } from "../../types/discovery/discovery_metadata.ts"; +import type { ModifyGuildDiscoveryMetadata } from "../../types/discovery/modify_guild_discovery_metadata.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { snakelize } from "../../util/utils.ts"; /** Modify the discovery metadata for the guild. Requires the MANAGE_GUILD permission. Returns the updated discovery metadata object on success. */ -export async function editDiscovery( - guildId: string, - data: ModifyGuildDiscoveryMetadata, -) { +export async function editDiscovery(guildId: bigint, data: ModifyGuildDiscoveryMetadata) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); - return await rest.runMethod( - "patch", - endpoints.DISCOVERY_MODIFY(guildId), - camelKeysToSnakeCase(data), - ); + return await rest.runMethod("patch", endpoints.DISCOVERY_METADATA(guildId), snakelize(data)); } diff --git a/src/helpers/discovery/get_discovery.ts b/src/helpers/discovery/get_discovery.ts new file mode 100644 index 000000000..967235fa2 --- /dev/null +++ b/src/helpers/discovery/get_discovery.ts @@ -0,0 +1,11 @@ +import { rest } from "../../rest/rest.ts"; +import type { DiscoveryMetadata } from "../../types/discovery/discovery_metadata.ts"; +import { endpoints } from "../../util/constants.ts"; +import { requireBotGuildPermissions } from "../../util/permissions.ts"; + +/** Returns the discovery metadata object for the guild. Requires the `MANAGE_GUILD` permission. */ +export async function getDiscovery(guildId: bigint) { + await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); + + return await rest.runMethod("get", endpoints.DISCOVERY_METADATA(guildId)); +} diff --git a/src/helpers/discovery/get_discovery_categories.ts b/src/helpers/discovery/get_discovery_categories.ts index 8ec9858f5..012bafb64 100644 --- a/src/helpers/discovery/get_discovery_categories.ts +++ b/src/helpers/discovery/get_discovery_categories.ts @@ -1,18 +1,11 @@ import { rest } from "../../rest/rest.ts"; -import { DiscoveryCategory } from "../../types/discovery/discovery_category.ts"; +import type { DiscoveryCategory } from "../../types/discovery/discovery_category.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; -/** Returns an array of discovery category objects that can be used when editing guilds */ +/** Returns a Collection (mapped by Id of the discovery category object) of discovery category objects that can be used when editing guilds */ export async function getDiscoveryCategories() { - const result = await rest.runMethod( - "get", - endpoints.DISCOVERY_CATEGORIES, - ); + const result = await rest.runMethod("get", endpoints.DISCOVERY_CATEGORIES); - return new Collection( - result.map( - (category) => [category.id, category], - ), - ); + return new Collection(result.map((category) => [category.id, category])); } diff --git a/src/helpers/discovery/remove_discovery_subcategory.ts b/src/helpers/discovery/remove_discovery_subcategory.ts index 610254319..aaa9f219c 100644 --- a/src/helpers/discovery/remove_discovery_subcategory.ts +++ b/src/helpers/discovery/remove_discovery_subcategory.ts @@ -3,14 +3,8 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Removes a discovery subcategory from the guild. Requires the MANAGE_GUILD permission. Returns a 204 No Content on success. */ -export async function removeDiscoverySubcategory( - guildId: string, - categoryId: number, -) { +export async function removeDiscoverySubcategory(guildId: bigint, categoryId: number) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); - return await rest.runMethod( - "delete", - endpoints.DISCOVERY_SUBCATEGORY(guildId, categoryId), - ); + return await rest.runMethod("delete", endpoints.DISCOVERY_SUBCATEGORY(guildId, categoryId)); } diff --git a/src/helpers/discovery/valid_discovery_term.ts b/src/helpers/discovery/valid_discovery_term.ts index 5a96016af..9e2a850f6 100644 --- a/src/helpers/discovery/valid_discovery_term.ts +++ b/src/helpers/discovery/valid_discovery_term.ts @@ -1,13 +1,9 @@ import { rest } from "../../rest/rest.ts"; -import { ValidateDiscoverySearchTerm } from "../../types/discovery/validate_discovery_search_term.ts"; +import type { ValidateDiscoverySearchTerm } from "../../types/discovery/validate_discovery_search_term.ts"; import { endpoints } from "../../util/constants.ts"; export async function validDiscoveryTerm(term: string) { - const result = await rest.runMethod( - "get", - endpoints.DISCOVERY_VALID_TERM, - { term }, - ); + const result = await rest.runMethod("get", endpoints.DISCOVERY_VALID_TERM, { term }); return result.valid; } diff --git a/src/helpers/emojis/create_emoji.ts b/src/helpers/emojis/create_emoji.ts index 2b55f7ec4..e16adbe5d 100644 --- a/src/helpers/emojis/create_emoji.ts +++ b/src/helpers/emojis/create_emoji.ts @@ -1,26 +1,27 @@ import { rest } from "../../rest/rest.ts"; import { CreateGuildEmoji } from "../../types/emojis/create_guild_emoji.ts"; -import { Emoji } from "../../types/emojis/emoji.ts"; +import type { Emoji } from "../../types/emojis/emoji.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; import { urlToBase64 } from "../../util/utils.ts"; /** Create an emoji in the server. Emojis and animated emojis have a maximum file size of 256kb. Attempting to upload an emoji larger than this limit will fail and return 400 Bad Request and an error message, but not a JSON status code. If a URL is provided to the image parameter, Discordeno will automatically convert it to a base64 string internally. */ -export async function createEmoji( - guildId: string, - name: string, - image: string, - options: CreateGuildEmoji, -) { +export async function createEmoji(guildId: bigint, name: string, image: string, options: CreateGuildEmoji) { await requireBotGuildPermissions(guildId, ["MANAGE_EMOJIS"]); if (image && !image.startsWith("data:image/")) { image = await urlToBase64(image); } - return await rest.runMethod("post", endpoints.GUILD_EMOJIS(guildId), { + const emoji = await rest.runMethod("post", endpoints.GUILD_EMOJIS(guildId), { ...options, name, image, }); + + return { + ...emoji, + id: snowflakeToBigint(emoji.id!), + }; } diff --git a/src/helpers/emojis/delete_emoji.ts b/src/helpers/emojis/delete_emoji.ts index 40b43eaa3..402a095dd 100644 --- a/src/helpers/emojis/delete_emoji.ts +++ b/src/helpers/emojis/delete_emoji.ts @@ -3,16 +3,8 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */ -export async function deleteEmoji( - guildId: string, - id: string, - reason?: string, -) { +export async function deleteEmoji(guildId: bigint, id: bigint, reason?: string) { await requireBotGuildPermissions(guildId, ["MANAGE_EMOJIS"]); - return await rest.runMethod( - "delete", - endpoints.GUILD_EMOJI(guildId, id), - { reason }, - ); + return await rest.runMethod("delete", endpoints.GUILD_EMOJI(guildId, id), { reason }); } diff --git a/src/helpers/emojis/edit_emoji.ts b/src/helpers/emojis/edit_emoji.ts index 28ee8e15a..669efeed5 100644 --- a/src/helpers/emojis/edit_emoji.ts +++ b/src/helpers/emojis/edit_emoji.ts @@ -1,23 +1,15 @@ import { rest } from "../../rest/rest.ts"; -import { ModifyGuildEmoji } from "../../types/emojis/modify_guild_emoji.ts"; -import { Emoji } from "../../types/mod.ts"; +import type { Emoji } from "../../types/emojis/emoji.ts"; +import type { ModifyGuildEmoji } from "../../types/emojis/modify_guild_emoji.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */ -export async function editEmoji( - guildId: string, - id: string, - options: ModifyGuildEmoji, -) { +export async function editEmoji(guildId: bigint, id: bigint, options: ModifyGuildEmoji) { await requireBotGuildPermissions(guildId, ["MANAGE_EMOJIS"]); - return await rest.runMethod( - "patch", - endpoints.GUILD_EMOJI(guildId, id), - { - name: options.name, - roles: options.roles, - }, - ); + return await rest.runMethod("patch", endpoints.GUILD_EMOJI(guildId, id), { + name: options.name, + roles: options.roles, + }); } diff --git a/src/helpers/emojis/emoji_url.ts b/src/helpers/emojis/emoji_url.ts index 75360f838..0c61c311c 100644 --- a/src/helpers/emojis/emoji_url.ts +++ b/src/helpers/emojis/emoji_url.ts @@ -1,4 +1,4 @@ /** Creates a url to the emoji from the Discord CDN. */ -export function emojiURL(id: string, animated = false) { +export function emojiURL(id: bigint, animated = false) { return `https://cdn.discordapp.com/emojis/${id}.${animated ? "gif" : "png"}`; } diff --git a/src/helpers/emojis/get_emoji.ts b/src/helpers/emojis/get_emoji.ts index 09f2a3cdd..7bde027ef 100644 --- a/src/helpers/emojis/get_emoji.ts +++ b/src/helpers/emojis/get_emoji.ts @@ -1,7 +1,7 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { Emoji } from "../../types/emojis/emoji.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { Emoji } from "../../types/emojis/emoji.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; /** @@ -9,25 +9,14 @@ import { endpoints } from "../../util/constants.ts"; * * ⚠️ **If you need this, you are probably doing something wrong. Always use cache.guilds.get()?.emojis */ -export async function getEmoji( - guildId: string, - emojiId: string, - addToCache = true, -) { - const result = await rest.runMethod( - "get", - endpoints.GUILD_EMOJI(guildId, emojiId), - ); +export async function getEmoji(guildId: bigint, emojiId: bigint, addToCache = true) { + const result = await rest.runMethod("get", endpoints.GUILD_EMOJI(guildId, emojiId)); if (addToCache) { const guild = await cacheHandlers.get("guilds", guildId); if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); guild.emojis.set(emojiId, result); - await cacheHandlers.set( - "guilds", - guildId, - guild, - ); + await cacheHandlers.set("guilds", guildId, guild); } return result; diff --git a/src/helpers/emojis/get_emojis.ts b/src/helpers/emojis/get_emojis.ts index da1d8cfa1..babbeb3c7 100644 --- a/src/helpers/emojis/get_emojis.ts +++ b/src/helpers/emojis/get_emojis.ts @@ -1,8 +1,9 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { Emoji } from "../../types/emojis/emoji.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { Emoji } from "../../types/emojis/emoji.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; @@ -11,22 +12,16 @@ import { endpoints } from "../../util/constants.ts"; * * ⚠️ **If you need this, you are probably doing something wrong. Always use cache.guilds.get()?.emojis */ -export async function getEmojis(guildId: string, addToCache = true) { - const result = await rest.runMethod( - "get", - endpoints.GUILD_EMOJIS(guildId), - ); +export async function getEmojis(guildId: bigint, addToCache = true) { + const result = await rest.runMethod("get", endpoints.GUILD_EMOJIS(guildId)); if (addToCache) { const guild = await cacheHandlers.get("guilds", guildId); if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); result.forEach((emoji) => { - eventHandlers.debug?.( - "loop", - `Running forEach loop in get_emojis file.`, - ); - guild.emojis.set(emoji.id!, emoji); + eventHandlers.debug?.("loop", `Running forEach loop in get_emojis file.`); + guild.emojis.set(snowflakeToBigint(emoji.id!), emoji); }); await cacheHandlers.set("guilds", guildId, guild); diff --git a/src/helpers/guilds/create_guild.ts b/src/helpers/guilds/create_guild.ts index 40c28f3fa..dc065d839 100644 --- a/src/helpers/guilds/create_guild.ts +++ b/src/helpers/guilds/create_guild.ts @@ -2,18 +2,14 @@ import { botId } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { CreateGuild } from "../../types/guilds/create_guild.ts"; -import { Guild } from "../../types/guilds/guild.ts"; +import type { CreateGuild } from "../../types/guilds/create_guild.ts"; +import type { Guild } from "../../types/guilds/guild.ts"; import { endpoints } from "../../util/constants.ts"; import { getMember } from "../members/get_member.ts"; /** Create a new guild. Returns a guild object on success. Fires a Guild Create Gateway event. This endpoint can be used only by bots in less than 10 guilds. */ export async function createGuild(options: CreateGuild) { - const result = await rest.runMethod( - "post", - endpoints.GUILDS, - options, - ); + const result = await rest.runMethod("post", endpoints.GUILDS, options); const guild = await structures.createDiscordenoGuild(result, 0); // MANUALLY CACHE THE GUILD diff --git a/src/helpers/guilds/delete_guild.ts b/src/helpers/guilds/delete_guild.ts index 496ac3088..8e98de769 100644 --- a/src/helpers/guilds/delete_guild.ts +++ b/src/helpers/guilds/delete_guild.ts @@ -2,9 +2,6 @@ import { rest } from "../../rest/rest.ts"; import { endpoints } from "../../util/constants.ts"; /** Delete a guild permanently. User must be owner. Returns 204 No Content on success. Fires a Guild Delete Gateway event. */ -export async function deleteGuild(guildId: string) { - return await rest.runMethod( - "delete", - endpoints.GUILDS_BASE(guildId), - ); +export async function deleteGuild(guildId: bigint) { + return await rest.runMethod("delete", endpoints.GUILDS_BASE(guildId)); } diff --git a/src/helpers/guilds/edit_guild.ts b/src/helpers/guilds/edit_guild.ts index e17206c00..145efbd69 100644 --- a/src/helpers/guilds/edit_guild.ts +++ b/src/helpers/guilds/edit_guild.ts @@ -1,15 +1,15 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Guild } from "../../types/guilds/guild.ts"; -import { ModifyGuild } from "../../types/guilds/modify_guild.ts"; +import type { Guild } from "../../types/guilds/guild.ts"; +import type { ModifyGuild } from "../../types/guilds/modify_guild.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; import { urlToBase64 } from "../../util/utils.ts"; import { ws } from "../../ws/ws.ts"; /** Modify a guilds settings. Requires the MANAGE_GUILD permission. */ -export async function editGuild(guildId: string, options: ModifyGuild) { +export async function editGuild(guildId: bigint, options: ModifyGuild) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); if (options.icon && !options.icon.startsWith("data:image/")) { @@ -24,19 +24,11 @@ export async function editGuild(guildId: string, options: ModifyGuild) { options.splash = await urlToBase64(options.splash); } - const result = await rest.runMethod( - "patch", - endpoints.GUILDS_BASE(guildId), - options, - ); + const result = await rest.runMethod("patch", endpoints.GUILDS_BASE(guildId), options); const cached = await cacheHandlers.get("guilds", guildId); return structures.createDiscordenoGuild( result, - cached?.shardId || - Number( - (BigInt(result.id) >> 22n % BigInt(ws.botGatewayData.shards)) - .toString(), - ), + cached?.shardId || Number((BigInt(result.id) >> 22n % BigInt(ws.botGatewayData.shards)).toString()) ); } diff --git a/src/helpers/guilds/edit_welcome_screen.ts b/src/helpers/guilds/edit_welcome_screen.ts index ae2dae039..8ab1e4759 100644 --- a/src/helpers/guilds/edit_welcome_screen.ts +++ b/src/helpers/guilds/edit_welcome_screen.ts @@ -1,16 +1,9 @@ import { rest } from "../../rest/rest.ts"; -import { ModifyGuildWelcomeScreen } from "../../types/guilds/modify_guild_welcome_screen.ts"; -import { WelcomeScreen } from "../../types/mod.ts"; +import type { ModifyGuildWelcomeScreen } from "../../types/guilds/modify_guild_welcome_screen.ts"; +import type { WelcomeScreen } from "../../types/guilds/welcome_screen.ts"; import { endpoints } from "../../util/constants.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { snakelize } from "../../util/utils.ts"; -export async function editWelcomeScreen( - guildId: string, - options: ModifyGuildWelcomeScreen, -) { - return await rest.runMethod( - "patch", - endpoints.GUILD_WELCOME_SCREEN(guildId), - camelKeysToSnakeCase(options), - ); +export async function editWelcomeScreen(guildId: bigint, options: ModifyGuildWelcomeScreen) { + return await rest.runMethod("patch", endpoints.GUILD_WELCOME_SCREEN(guildId), snakelize(options)); } diff --git a/src/helpers/guilds/edit_widget.ts b/src/helpers/guilds/edit_widget.ts index 9259cf32d..66cd803e4 100644 --- a/src/helpers/guilds/edit_widget.ts +++ b/src/helpers/guilds/edit_widget.ts @@ -1,22 +1,14 @@ import { rest } from "../../rest/rest.ts"; -import { GuildWidget } from "../../types/guilds/guild_widget.ts"; +import type { GuildWidget } from "../../types/guilds/guild_widget.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Modify a guild widget object for the guild. Requires the MANAGE_GUILD permission. */ -export async function editWidget( - guildId: string, - enabled: boolean, - channelId?: string | null, -) { +export async function editWidget(guildId: bigint, enabled: boolean, channelId?: string | null) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); - return await rest.runMethod( - "patch", - endpoints.GUILD_WIDGET(guildId), - { - enabled, - channel_id: channelId, - }, - ); + return await rest.runMethod("patch", endpoints.GUILD_WIDGET(guildId), { + enabled, + channel_id: channelId, + }); } diff --git a/src/helpers/guilds/get_audit_logs.ts b/src/helpers/guilds/get_audit_logs.ts index 95b5b3aae..ed8e9ab0d 100644 --- a/src/helpers/guilds/get_audit_logs.ts +++ b/src/helpers/guilds/get_audit_logs.ts @@ -1,25 +1,20 @@ import { rest } from "../../rest/rest.ts"; -import { AuditLog } from "../../types/audit_log/audit_log.ts"; -import { GetGuildAuditLog } from "../../types/audit_log/get_guild_audit_log.ts"; +import type { AuditLog } from "../../types/audit_log/audit_log.ts"; +import type { GetGuildAuditLog } from "../../types/audit_log/get_guild_audit_log.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { snakelize } from "../../util/utils.ts"; /** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */ -export async function getAuditLogs( - guildId: string, - options: GetGuildAuditLog, -) { +export async function getAuditLogs(guildId: bigint, options?: GetGuildAuditLog) { await requireBotGuildPermissions(guildId, ["VIEW_AUDIT_LOG"]); return await rest.runMethod( "get", endpoints.GUILD_AUDIT_LOGS(guildId), - camelKeysToSnakeCase({ + snakelize({ ...options, - limit: options.limit && options.limit >= 1 && options.limit <= 100 - ? options.limit - : 50, - }), + limit: options?.limit && options.limit >= 1 && options.limit <= 100 ? options.limit : 50, + }) ); } diff --git a/src/helpers/guilds/get_available_voice_regions.ts b/src/helpers/guilds/get_available_voice_regions.ts index 5570e4916..2111f6a3d 100644 --- a/src/helpers/guilds/get_available_voice_regions.ts +++ b/src/helpers/guilds/get_available_voice_regions.ts @@ -1,5 +1,5 @@ import { rest } from "../../rest/rest.ts"; -import { VoiceRegion } from "../../types/voice/voice_region.ts"; +import type { VoiceRegion } from "../../types/voice/voice_region.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns an array of voice regions that can be used when creating servers. */ diff --git a/src/helpers/guilds/get_ban.ts b/src/helpers/guilds/get_ban.ts index 3fddcd1a8..125277417 100644 --- a/src/helpers/guilds/get_ban.ts +++ b/src/helpers/guilds/get_ban.ts @@ -1,14 +1,11 @@ import { rest } from "../../rest/rest.ts"; -import { Ban } from "../../types/guilds/ban.ts"; +import type { Ban } from "../../types/guilds/ban.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Returns a ban object for the given user or a 404 not found if the ban cannot be found. Requires the BAN_MEMBERS permission. */ -export async function getBan(guildId: string, memberId: string) { +export async function getBan(guildId: bigint, memberId: bigint) { await requireBotGuildPermissions(guildId, ["BAN_MEMBERS"]); - return await rest.runMethod( - "get", - endpoints.GUILD_BAN(guildId, memberId), - ); + return await rest.runMethod("get", endpoints.GUILD_BAN(guildId, memberId)); } diff --git a/src/helpers/guilds/get_bans.ts b/src/helpers/guilds/get_bans.ts index c9497f8b4..dd158e1aa 100644 --- a/src/helpers/guilds/get_bans.ts +++ b/src/helpers/guilds/get_bans.ts @@ -1,19 +1,15 @@ import { rest } from "../../rest/rest.ts"; -import { Ban } from "../../types/guilds/ban.ts"; +import type { Ban } from "../../types/guilds/ban.ts"; +import { snowflakeToBigint } from "../../util/bigint.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */ -export async function getBans(guildId: string) { +export async function getBans(guildId: bigint) { await requireBotGuildPermissions(guildId, ["BAN_MEMBERS"]); - const results = await rest.runMethod( - "get", - endpoints.GUILD_BANS(guildId), - ); + const results = await rest.runMethod("get", endpoints.GUILD_BANS(guildId)); - return new Collection( - results.map((res) => [res.user.id, res]), - ); + return new Collection(results.map((res) => [snowflakeToBigint(res.user.id), res])); } diff --git a/src/helpers/guilds/get_guild.ts b/src/helpers/guilds/get_guild.ts index 6e9dd06d3..4e38d934d 100644 --- a/src/helpers/guilds/get_guild.ts +++ b/src/helpers/guilds/get_guild.ts @@ -1,7 +1,7 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Guild } from "../../types/guilds/guild.ts"; +import type { Guild } from "../../types/guilds/guild.ts"; import { endpoints } from "../../util/constants.ts"; import { ws } from "../../ws/ws.ts"; @@ -13,28 +13,24 @@ import { ws } from "../../ws/ws.ts"; * So it does not cache the guild, you must do it manually. * */ export async function getGuild( - guildId: string, + guildId: bigint, options: { counts?: boolean; addToCache?: boolean } = { counts: true, addToCache: true, - }, + } ) { - const result = await rest.runMethod( - "get", - endpoints.GUILDS_BASE(guildId), - { - with_counts: options.counts, - }, - ); + const result = await rest.runMethod("get", endpoints.GUILDS_BASE(guildId), { + with_counts: options.counts, + }); - const structure = await structures.createDiscordenoGuild( + const guild = await structures.createDiscordenoGuild( result, - Number((BigInt(guildId) >> 22n) % BigInt(ws.botGatewayData.shards)), + Number((BigInt(guildId) >> 22n) % BigInt(ws.botGatewayData.shards)) ); if (options.addToCache) { - await cacheHandlers.set("guilds", guildId, structure); + await cacheHandlers.set("guilds", guild.id, guild); } - return structure; + return guild; } diff --git a/src/helpers/guilds/get_guild_preview.ts b/src/helpers/guilds/get_guild_preview.ts index 40873cf0f..04238da4d 100644 --- a/src/helpers/guilds/get_guild_preview.ts +++ b/src/helpers/guilds/get_guild_preview.ts @@ -1,11 +1,8 @@ import { rest } from "../../rest/rest.ts"; -import { GuildPreview } from "../../types/guilds/guild_preview.ts"; +import type { GuildPreview } from "../../types/guilds/guild_preview.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns the guild preview object for the given id. If the bot is not in the guild, then the guild must be Discoverable. */ -export async function getGuildPreview(guildId: string) { - return await rest.runMethod( - "get", - endpoints.GUILD_PREVIEW(guildId), - ); +export async function getGuildPreview(guildId: bigint) { + return await rest.runMethod("get", endpoints.GUILD_PREVIEW(guildId)); } diff --git a/src/helpers/guilds/get_prune_count.ts b/src/helpers/guilds/get_prune_count.ts index 12b35b418..3614aae14 100644 --- a/src/helpers/guilds/get_prune_count.ts +++ b/src/helpers/guilds/get_prune_count.ts @@ -1,15 +1,12 @@ import { rest } from "../../rest/rest.ts"; -import { GetGuildPruneCountQuery } from "../../types/guilds/get_guild_prune_count.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import type { GetGuildPruneCountQuery } from "../../types/guilds/get_guild_prune_count.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { snakelize } from "../../util/utils.ts"; /** Check how many members would be removed from the server in a prune operation. Requires the KICK_MEMBERS permission */ -export async function getPruneCount( - guildId: string, - options?: GetGuildPruneCountQuery, -) { +export async function getPruneCount(guildId: bigint, options?: GetGuildPruneCountQuery) { if (options?.days && options.days < 1) throw new Error(Errors.PRUNE_MIN_DAYS); if (options?.days && options.days > 30) { throw new Error(Errors.PRUNE_MAX_DAYS); @@ -17,11 +14,7 @@ export async function getPruneCount( await requireBotGuildPermissions(guildId, ["KICK_MEMBERS"]); - const result = await rest.runMethod( - "get", - endpoints.GUILD_PRUNE(guildId), - camelKeysToSnakeCase(options ?? {}), - ); + const result = await rest.runMethod("get", endpoints.GUILD_PRUNE(guildId), snakelize(options ?? {})); return result.pruned as number; } diff --git a/src/helpers/guilds/get_vainty_url.ts b/src/helpers/guilds/get_vainty_url.ts index 0dee86af7..7f5087556 100644 --- a/src/helpers/guilds/get_vainty_url.ts +++ b/src/helpers/guilds/get_vainty_url.ts @@ -1,15 +1,13 @@ import { rest } from "../../rest/rest.ts"; -import { InviteMetadata } from "../../types/invites/invite_metadata.ts"; +import type { InviteMetadata } from "../../types/invites/invite_metadata.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns the code and uses of the vanity url for this server if it is enabled else `code` will be null. Requires the `MANAGE_GUILD` permission. */ -export async function getVanityURL(guildId: string) { +export async function getVanityURL(guildId: bigint) { return await rest.runMethod< - (Partial & Pick) | { - code: null; - } - >( - "get", - endpoints.GUILD_VANITY_URL(guildId), - ); + | (Partial & Pick) + | { + code: null; + } + >("get", endpoints.GUILD_VANITY_URL(guildId)); } diff --git a/src/helpers/guilds/get_voice_regions.ts b/src/helpers/guilds/get_voice_regions.ts index 6ed926e52..756d876b3 100644 --- a/src/helpers/guilds/get_voice_regions.ts +++ b/src/helpers/guilds/get_voice_regions.ts @@ -1,16 +1,11 @@ import { rest } from "../../rest/rest.ts"; -import { VoiceRegion } from "../../types/voice/voice_region.ts"; +import type { VoiceRegion } from "../../types/voice/voice_region.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns a list of voice region objects for the guild. Unlike the similar /voice route, this returns VIP servers when the guild is VIP-enabled. */ -export async function getVoiceRegions(guildId: string) { - const result = await rest.runMethod( - "get", - endpoints.GUILD_REGIONS(guildId), - ); +export async function getVoiceRegions(guildId: bigint) { + const result = await rest.runMethod("get", endpoints.GUILD_REGIONS(guildId)); - return new Collection( - result.map((region) => [region.id, region]), - ); + return new Collection(result.map((region) => [region.id, region])); } diff --git a/src/helpers/guilds/get_welcome_screen.ts b/src/helpers/guilds/get_welcome_screen.ts index 36950e3eb..d048baf0b 100644 --- a/src/helpers/guilds/get_welcome_screen.ts +++ b/src/helpers/guilds/get_welcome_screen.ts @@ -1,10 +1,7 @@ import { rest } from "../../rest/rest.ts"; -import { WelcomeScreen } from "../../types/mod.ts"; +import type { WelcomeScreen } from "../../types/guilds/welcome_screen.ts"; import { endpoints } from "../../util/constants.ts"; -export async function getWelcomeScreen(guildId: string) { - return await rest.runMethod( - "get", - endpoints.GUILD_WELCOME_SCREEN(guildId), - ); +export async function getWelcomeScreen(guildId: bigint) { + return await rest.runMethod("get", endpoints.GUILD_WELCOME_SCREEN(guildId)); } diff --git a/src/helpers/guilds/get_widget.ts b/src/helpers/guilds/get_widget.ts index d98034f7e..c66020cee 100644 --- a/src/helpers/guilds/get_widget.ts +++ b/src/helpers/guilds/get_widget.ts @@ -1,19 +1,16 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { GuildWidgetDetails } from "../../types/guilds/guild_widget_details.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { GuildWidgetDetails } from "../../types/guilds/guild_widget_details.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns the widget for the guild. */ -export async function getWidget(guildId: string, options?: { force: boolean }) { +export async function getWidget(guildId: bigint, options?: { force: boolean }) { if (!options?.force) { const guild = await cacheHandlers.get("guilds", guildId); if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); if (!guild?.widgetEnabled) throw new Error(Errors.GUILD_WIDGET_NOT_ENABLED); } - return await rest.runMethod( - "get", - `${endpoints.GUILD_WIDGET(guildId)}.json`, - ); + return await rest.runMethod("get", `${endpoints.GUILD_WIDGET(guildId)}.json`); } diff --git a/src/helpers/guilds/get_widget_image_url.ts b/src/helpers/guilds/get_widget_image_url.ts index f552a2326..28cd5d94f 100644 --- a/src/helpers/guilds/get_widget_image_url.ts +++ b/src/helpers/guilds/get_widget_image_url.ts @@ -1,19 +1,15 @@ import { cacheHandlers } from "../../cache.ts"; -import { GetGuildWidgetImageQuery } from "../../types/guilds/get_guild_widget_image.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { GetGuildWidgetImageQuery } from "../../types/guilds/get_guild_widget_image.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns the widget image URL for the guild. */ -export async function getWidgetImageURL( - guildId: string, - options?: GetGuildWidgetImageQuery & { force?: boolean }, -) { +export async function getWidgetImageURL(guildId: bigint, options?: GetGuildWidgetImageQuery & { force?: boolean }) { if (!options?.force) { const guild = await cacheHandlers.get("guilds", guildId); if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); if (!guild.widgetEnabled) throw new Error(Errors.GUILD_WIDGET_NOT_ENABLED); } - return `${endpoints.GUILD_WIDGET(guildId)}.png?style=${options?.style ?? - "shield"}`; + return `${endpoints.GUILD_WIDGET(guildId)}.png?style=${options?.style ?? "shield"}`; } diff --git a/src/helpers/guilds/get_widget_settings.ts b/src/helpers/guilds/get_widget_settings.ts index 960fb83ec..23417a61a 100644 --- a/src/helpers/guilds/get_widget_settings.ts +++ b/src/helpers/guilds/get_widget_settings.ts @@ -1,14 +1,11 @@ import { rest } from "../../rest/rest.ts"; -import { GuildWidget } from "../../types/guilds/guild_widget.ts"; +import type { GuildWidget } from "../../types/guilds/guild_widget.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Returns the guild widget object. Requires the MANAGE_GUILD permission. */ -export async function getWidgetSettings(guildId: string) { +export async function getWidgetSettings(guildId: bigint) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); - return await rest.runMethod( - "get", - endpoints.GUILD_WIDGET(guildId), - ); + return await rest.runMethod("get", endpoints.GUILD_WIDGET(guildId)); } diff --git a/src/helpers/guilds/guild_banner_url.ts b/src/helpers/guilds/guild_banner_url.ts index db027d15b..e4167c3cc 100644 --- a/src/helpers/guilds/guild_banner_url.ts +++ b/src/helpers/guilds/guild_banner_url.ts @@ -1,16 +1,29 @@ -import { DiscordImageFormat } from "../../types/misc/image_format.ts"; -import { DiscordImageSize } from "../../types/misc/image_size.ts"; +import type { DiscordImageFormat } from "../../types/misc/image_format.ts"; +import type { DiscordImageSize } from "../../types/misc/image_size.ts"; import { endpoints } from "../../util/constants.ts"; +import { iconBigintToHash } from "../../util/hash.ts"; import { formatImageURL } from "../../util/utils.ts"; /** The full URL of the banner from Discords CDN. Undefined if no banner is set. */ export function guildBannerURL( - id: string, - banner?: string, - size: DiscordImageSize = 128, - format?: DiscordImageFormat, + id: bigint, + options: { + banner?: string | bigint; + size?: DiscordImageSize; + format?: DiscordImageFormat; + animated?: boolean; + } ) { - return banner - ? formatImageURL(endpoints.GUILD_BANNER(id, banner), size, format) + return options.banner + ? formatImageURL( + endpoints.GUILD_BANNER( + id, + typeof options.banner === "string" + ? options.banner + : iconBigintToHash(options.banner, options.animated ?? true) + ), + options.size || 128, + options.format + ) : undefined; } diff --git a/src/helpers/guilds/guild_icon_url.ts b/src/helpers/guilds/guild_icon_url.ts index b1e02e6f2..3d7af4a4d 100644 --- a/src/helpers/guilds/guild_icon_url.ts +++ b/src/helpers/guilds/guild_icon_url.ts @@ -1,16 +1,27 @@ -import { DiscordImageFormat } from "../../types/misc/image_format.ts"; -import { DiscordImageSize } from "../../types/misc/image_size.ts"; +import type { DiscordImageFormat } from "../../types/misc/image_format.ts"; +import type { DiscordImageSize } from "../../types/misc/image_size.ts"; import { endpoints } from "../../util/constants.ts"; +import { iconBigintToHash } from "../../util/hash.ts"; import { formatImageURL } from "../../util/utils.ts"; /** The full URL of the icon from Discords CDN. Undefined when no icon is set. */ export function guildIconURL( - id: string, - icon?: string, - size: DiscordImageSize = 128, - format?: DiscordImageFormat, + id: bigint, + options: { + icon?: string | bigint; + size?: DiscordImageSize; + format?: DiscordImageFormat; + animated?: boolean; + } ) { - return icon - ? formatImageURL(endpoints.GUILD_ICON(id, icon), size, format) + return options.icon + ? formatImageURL( + endpoints.GUILD_ICON( + id, + typeof options.icon === "string" ? options.icon : iconBigintToHash(options.icon, options.animated ?? true) + ), + options.size || 128, + options.format + ) : undefined; } diff --git a/src/helpers/guilds/guild_splash_url.ts b/src/helpers/guilds/guild_splash_url.ts index 9409a0e99..86e029982 100644 --- a/src/helpers/guilds/guild_splash_url.ts +++ b/src/helpers/guilds/guild_splash_url.ts @@ -1,20 +1,29 @@ -import { DiscordImageFormat } from "../../types/misc/image_format.ts"; -import { DiscordImageSize } from "../../types/misc/image_size.ts"; +import type { DiscordImageFormat } from "../../types/misc/image_format.ts"; +import type { DiscordImageSize } from "../../types/misc/image_size.ts"; import { endpoints } from "../../util/constants.ts"; +import { iconBigintToHash } from "../../util/hash.ts"; import { formatImageURL } from "../../util/utils.ts"; /** The full URL of the splash from Discords CDN. Undefined if no splash is set. */ export function guildSplashURL( - id: string, - splash?: string, - size: DiscordImageSize = 128, - format?: DiscordImageFormat, + id: bigint, + options: { + splash?: string | bigint; + size?: DiscordImageSize; + format?: DiscordImageFormat; + animated?: boolean; + } ) { - return splash + return options.splash ? formatImageURL( - endpoints.GUILD_SPLASH(id, splash), - size, - format, - ) + endpoints.GUILD_SPLASH( + id, + typeof options.splash === "string" + ? options.splash + : iconBigintToHash(options.splash, options.animated ?? true) + ), + options.size || 128, + options.format + ) : undefined; } diff --git a/src/helpers/guilds/leave_guild.ts b/src/helpers/guilds/leave_guild.ts index 9552ffe1a..3e8813535 100644 --- a/src/helpers/guilds/leave_guild.ts +++ b/src/helpers/guilds/leave_guild.ts @@ -2,9 +2,6 @@ import { rest } from "../../rest/rest.ts"; import { endpoints } from "../../util/constants.ts"; /** Leave a guild */ -export async function leaveGuild(guildId: string) { - return await rest.runMethod( - "delete", - endpoints.GUILD_LEAVE(guildId), - ); +export async function leaveGuild(guildId: bigint) { + return await rest.runMethod("delete", endpoints.GUILD_LEAVE(guildId)); } diff --git a/src/helpers/guilds/update_bot_voice_state.ts b/src/helpers/guilds/update_bot_voice_state.ts deleted file mode 100644 index 5fcf0849c..000000000 --- a/src/helpers/guilds/update_bot_voice_state.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { RequestManager } from "../../rest/request_manager.ts"; -import { - DiscordUpdateSelfVoiceState, - UpdateSelfVoiceState, -} from "../../types/guilds/update_self_voice_state.ts"; -import { endpoints } from "../../util/constants.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; - -/** - * Updates the current user's voice state. - * Caveats: - * - `channel_id` must currently point to a stage channel. - * - current user must already have joined `channel_id`. - * - You must have the `MUTE_MEMBERS` permission to unsuppress yourself. You can always suppress yourself. - * - You must have the `REQUEST_TO_SPEAK` permission to request to speak. You can always clear your own request to speak. - * - You are able to set `request_to_speak_timestamp` to any present or future time. - */ -export function updateBotVoiceState( - guildId: string, - data: UpdateSelfVoiceState, -) { - const payload = camelKeysToSnakeCase(data); - - return RequestManager.patch( - endpoints.UPDATE_VOICE_STATE(guildId), - payload, - ); -} diff --git a/src/helpers/guilds/update_user_voice_state.ts b/src/helpers/guilds/update_user_voice_state.ts deleted file mode 100644 index 30b87630f..000000000 --- a/src/helpers/guilds/update_user_voice_state.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { RequestManager } from "../../rest/request_manager.ts"; -import { - DiscordUpdateOthersVoiceState, - UpdateOthersVoiceState, -} from "../../types/guilds/update_others_voice_state.ts"; -import { endpoints } from "../../util/constants.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; - -/** - * Updates another user's voice state. - * Caveats: - * - `channel_id` must currently point to a stage channel. - * - User must already have joined `channel_id`. - * - You must have the `MUTE_MEMBERS` permission. (Since suppression is the only thing that is available currently.) - * - When unsuppressed, non-bot users will have their `request_to_speak_timestamp` set to the current time. Bot users will not. - * - When suppressed, the user will have their `request_to_speak_timestamp` removed. - */ -export function updateVoiceState( - guildId: string, - userId: string, - data: UpdateOthersVoiceState, -) { - const payload = camelKeysToSnakeCase(data); - - return RequestManager.patch( - endpoints.UPDATE_VOICE_STATE(guildId, userId), - payload, - ); -} diff --git a/src/helpers/integrations/delete_integration.ts b/src/helpers/integrations/delete_integration.ts index cd92ab5e1..8de324a4b 100644 --- a/src/helpers/integrations/delete_integration.ts +++ b/src/helpers/integrations/delete_integration.ts @@ -3,11 +3,8 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Delete the attached integration object for the guild with this id. Requires MANAGE_GUILD permission. */ -export async function deleteIntegration(guildId: string, id: string) { +export async function deleteIntegration(guildId: bigint, id: bigint) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); - return await rest.runMethod( - "delete", - endpoints.GUILD_INTEGRATION(guildId, id), - ); + return await rest.runMethod("delete", endpoints.GUILD_INTEGRATION(guildId, id)); } diff --git a/src/helpers/integrations/get_integrations.ts b/src/helpers/integrations/get_integrations.ts index f8c54d80d..c7070e582 100644 --- a/src/helpers/integrations/get_integrations.ts +++ b/src/helpers/integrations/get_integrations.ts @@ -1,14 +1,11 @@ import { rest } from "../../rest/rest.ts"; -import { Integration } from "../../types/mod.ts"; +import type { Integration } from "../../types/integrations/integration.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Returns a list of integrations for the guild. Requires the MANAGE_GUILD permission. */ -export async function getIntegrations(guildId: string) { +export async function getIntegrations(guildId: bigint) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); - return await rest.runMethod( - "get", - endpoints.GUILD_INTEGRATIONS(guildId), - ); + return await rest.runMethod("get", endpoints.GUILD_INTEGRATIONS(guildId)); } diff --git a/src/helpers/interactions/commands/batch_edit_slash_command_permissions.ts b/src/helpers/interactions/commands/batch_edit_slash_command_permissions.ts new file mode 100644 index 000000000..5671947d5 --- /dev/null +++ b/src/helpers/interactions/commands/batch_edit_slash_command_permissions.ts @@ -0,0 +1,13 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { ApplicationCommandPermissions } from "../../../types/interactions/commands/application_command_permissions.ts"; +import { endpoints } from "../../../util/constants.ts"; +import { snakelize } from "../../../util/utils.ts"; + +/** Batch edits permissions for all commands in a guild. Takes an array of partial GuildApplicationCommandPermissions objects including `id` and `permissions`. */ +export async function batchEditSlashCommandPermissions( + guildId: bigint, + options: { id: string; permissions: ApplicationCommandPermissions[] }[] +) { + return await rest.runMethod("put", endpoints.COMMANDS_PERMISSIONS(applicationId, guildId), snakelize(options)); +} diff --git a/src/helpers/commands/create_slash_command.ts b/src/helpers/interactions/commands/create_slash_command.ts similarity index 56% rename from src/helpers/commands/create_slash_command.ts rename to src/helpers/interactions/commands/create_slash_command.ts index c756570e5..26a989827 100644 --- a/src/helpers/commands/create_slash_command.ts +++ b/src/helpers/interactions/commands/create_slash_command.ts @@ -1,12 +1,9 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { CreateGlobalApplicationCommand } from "../../types/interactions/create_global_application_command.ts"; -import { ApplicationCommand } from "../../types/mod.ts"; -import { endpoints } from "../../util/constants.ts"; -import { - camelKeysToSnakeCase, - validateSlashCommands, -} from "../../util/utils.ts"; +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { ApplicationCommand } from "../../../types/interactions/commands/application_command.ts"; +import type { CreateGlobalApplicationCommand } from "../../../types/interactions/commands/create_global_application_command.ts"; +import { endpoints } from "../../../util/constants.ts"; +import { snakelize, validateSlashCommands } from "../../../util/utils.ts"; /** * There are two kinds of Slash Commands: global commands and guild commands. Global commands are available for every guild that adds your app; guild commands are specific to the guild you specify when making them. Command names are unique per application within each scope (global and guild). That means: @@ -19,17 +16,12 @@ import { * Global commands are cached for **1 hour**. That means that new global commands will fan out slowly across all guilds, and will be guaranteed to be updated in an hour. * Guild commands update **instantly**. We recommend you use guild commands for quick testing, and global commands when they're ready for public use. */ -export async function createSlashCommand( - options: CreateGlobalApplicationCommand, - guildId?: string, -) { - validateSlashCommands([options], true); +export async function createSlashCommand(options: CreateGlobalApplicationCommand, guildId?: bigint) { + [options] = validateSlashCommands([options], true) as CreateGlobalApplicationCommand[]; return await rest.runMethod( "post", - guildId - ? endpoints.COMMANDS_GUILD(applicationId, guildId) - : endpoints.COMMANDS(applicationId), - camelKeysToSnakeCase(options), + guildId ? endpoints.COMMANDS_GUILD(applicationId, guildId) : endpoints.COMMANDS(applicationId), + snakelize(options) ); } diff --git a/src/helpers/interactions/commands/delete_slash_command.ts b/src/helpers/interactions/commands/delete_slash_command.ts new file mode 100644 index 000000000..d4e8c71b2 --- /dev/null +++ b/src/helpers/interactions/commands/delete_slash_command.ts @@ -0,0 +1,11 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import { endpoints } from "../../../util/constants.ts"; + +/** Deletes a slash command. */ +export async function deleteSlashCommand(id: bigint, guildId?: bigint) { + return await rest.runMethod( + "delete", + guildId ? endpoints.COMMANDS_GUILD_ID(applicationId, guildId, id) : endpoints.COMMANDS_ID(applicationId, id) + ); +} diff --git a/src/helpers/interactions/commands/delete_slash_response.ts b/src/helpers/interactions/commands/delete_slash_response.ts new file mode 100644 index 000000000..25f1a1c59 --- /dev/null +++ b/src/helpers/interactions/commands/delete_slash_response.ts @@ -0,0 +1,13 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import { endpoints } from "../../../util/constants.ts"; + +/** To delete your response to a slash command. If a message id is not provided, it will default to deleting the original response. */ +export async function deleteSlashResponse(token: string, messageId?: bigint) { + return await rest.runMethod( + "delete", + messageId + ? endpoints.INTERACTION_ID_TOKEN_MESSAGE_ID(applicationId, token, messageId) + : endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationId, token) + ); +} diff --git a/src/helpers/interactions/commands/edit_slash_command_permissions.ts b/src/helpers/interactions/commands/edit_slash_command_permissions.ts new file mode 100644 index 000000000..d633d7080 --- /dev/null +++ b/src/helpers/interactions/commands/edit_slash_command_permissions.ts @@ -0,0 +1,16 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { ApplicationCommandPermissions } from "../../../types/interactions/commands/application_command_permissions.ts"; +import { endpoints } from "../../../util/constants.ts"; +import { snakelize } from "../../../util/utils.ts"; + +/** Edits command permissions for a specific command for your application in a guild. */ +export async function editSlashCommandPermissions( + guildId: bigint, + commandId: bigint, + options: ApplicationCommandPermissions[] +) { + return await rest.runMethod("put", endpoints.COMMANDS_PERMISSION(applicationId, guildId, commandId), { + permissions: snakelize(options), + }); +} diff --git a/src/helpers/commands/edit_slash_response.ts b/src/helpers/interactions/commands/edit_slash_response.ts similarity index 52% rename from src/helpers/commands/edit_slash_response.ts rename to src/helpers/interactions/commands/edit_slash_response.ts index 2c458f047..62920889c 100644 --- a/src/helpers/commands/edit_slash_response.ts +++ b/src/helpers/interactions/commands/edit_slash_response.ts @@ -1,16 +1,13 @@ -import { applicationId } from "../../bot.ts"; -import { rest } from "../../rest/rest.ts"; -import { structures } from "../../structures/mod.ts"; -import { DiscordenoEditWebhookMessage } from "../../types/discordeno/edit_webhook_message.ts"; -import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts"; -import { Errors } from "../../types/misc/errors.ts"; -import { endpoints } from "../../util/constants.ts"; +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import { structures } from "../../../structures/mod.ts"; +import type { DiscordenoEditWebhookMessage } from "../../../types/discordeno/edit_webhook_message.ts"; +import { Errors } from "../../../types/discordeno/errors.ts"; +import { DiscordAllowedMentionsTypes } from "../../../types/messages/allowed_mentions_types.ts"; +import { endpoints } from "../../../util/constants.ts"; /** To edit your response to a slash command. If a messageId is not provided it will default to editing the original response. */ -export async function editSlashResponse( - token: string, - options: DiscordenoEditWebhookMessage, -) { +export async function editSlashResponse(token: string, options: DiscordenoEditWebhookMessage) { if (options.content && options.content.length > 2000) { throw Error(Errors.MESSAGE_MAX_LENGTH); } @@ -21,40 +18,22 @@ export async function editSlashResponse( if (options.allowedMentions) { if (options.allowedMentions.users?.length) { - if ( - options.allowedMentions.parse?.includes( - DiscordAllowedMentionsTypes.UserMentions, - ) - ) { - options.allowedMentions.parse = options.allowedMentions.parse.filter( - (p) => p !== "users", - ); + if (options.allowedMentions.parse?.includes(DiscordAllowedMentionsTypes.UserMentions)) { + options.allowedMentions.parse = options.allowedMentions.parse.filter((p) => p !== "users"); } if (options.allowedMentions.users.length > 100) { - options.allowedMentions.users = options.allowedMentions.users.slice( - 0, - 100, - ); + options.allowedMentions.users = options.allowedMentions.users.slice(0, 100); } } if (options.allowedMentions.roles?.length) { - if ( - options.allowedMentions.parse?.includes( - DiscordAllowedMentionsTypes.RoleMentions, - ) - ) { - options.allowedMentions.parse = options.allowedMentions.parse.filter( - (p) => p !== "roles", - ); + if (options.allowedMentions.parse?.includes(DiscordAllowedMentionsTypes.RoleMentions)) { + options.allowedMentions.parse = options.allowedMentions.parse.filter((p) => p !== "roles"); } if (options.allowedMentions.roles.length > 100) { - options.allowedMentions.roles = options.allowedMentions.roles.slice( - 0, - 100, - ); + options.allowedMentions.roles = options.allowedMentions.roles.slice(0, 100); } } } @@ -64,14 +43,12 @@ export async function editSlashResponse( options.messageId ? endpoints.WEBHOOK_MESSAGE(applicationId, token, options.messageId) : endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationId, token), - options, + options ); // If the original message was edited, this will not return a message if (!options.messageId) return result as undefined; - const message = await structures.createDiscordenoMessage( - result, - ); + const message = await structures.createDiscordenoMessage(result); return message; } diff --git a/src/helpers/interactions/commands/get_slash_command.ts b/src/helpers/interactions/commands/get_slash_command.ts new file mode 100644 index 000000000..80b5fc3d6 --- /dev/null +++ b/src/helpers/interactions/commands/get_slash_command.ts @@ -0,0 +1,21 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { ApplicationCommand } from "../../../types/interactions/commands/application_command.ts"; +import { snowflakeToBigint } from "../../../util/bigint.ts"; +import { endpoints } from "../../../util/constants.ts"; + +/** Fetchs the global command for the given Id. If a guildId is provided, the guild command will be fetched. */ +export async function getSlashCommand(commandId: bigint, guildId?: bigint) { + const result = await rest.runMethod( + "get", + guildId + ? endpoints.COMMANDS_GUILD_ID(applicationId, guildId, commandId) + : endpoints.COMMANDS_ID(applicationId, commandId) + ); + + return { + ...result, + id: snowflakeToBigint(result.id), + applicationId: snowflakeToBigint(result.applicationId), + }; +} diff --git a/src/helpers/interactions/commands/get_slash_command_permission.ts b/src/helpers/interactions/commands/get_slash_command_permission.ts new file mode 100644 index 000000000..55bc8a7a7 --- /dev/null +++ b/src/helpers/interactions/commands/get_slash_command_permission.ts @@ -0,0 +1,12 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { GuildApplicationCommandPermissions } from "../../../types/interactions/commands/guild_application_command_permissions.ts"; +import { endpoints } from "../../../util/constants.ts"; + +/** Fetches command permissions for a specific command for your application in a guild. Returns a GuildApplicationCommandPermissions object. */ +export async function getSlashCommandPermission(guildId: bigint, commandId: bigint) { + return await rest.runMethod( + "get", + endpoints.COMMANDS_PERMISSION(applicationId, guildId, commandId) + ); +} diff --git a/src/helpers/interactions/commands/get_slash_command_permissions.ts b/src/helpers/interactions/commands/get_slash_command_permissions.ts new file mode 100644 index 000000000..1206371e2 --- /dev/null +++ b/src/helpers/interactions/commands/get_slash_command_permissions.ts @@ -0,0 +1,12 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { GuildApplicationCommandPermissions } from "../../../types/interactions/commands/guild_application_command_permissions.ts"; +import { endpoints } from "../../../util/constants.ts"; + +/** Fetches command permissions for all commands for your application in a guild. Returns an array of GuildApplicationCommandPermissions objects. */ +export async function getSlashCommandPermissions(guildId: bigint) { + return await rest.runMethod( + "get", + endpoints.COMMANDS_PERMISSIONS(applicationId, guildId) + ); +} diff --git a/src/helpers/interactions/commands/get_slash_commands.ts b/src/helpers/interactions/commands/get_slash_commands.ts new file mode 100644 index 000000000..11cf8f6d4 --- /dev/null +++ b/src/helpers/interactions/commands/get_slash_commands.ts @@ -0,0 +1,21 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { ApplicationCommand } from "../../../types/interactions/commands/application_command.ts"; +import { snowflakeToBigint } from "../../../util/bigint.ts"; +import { Collection } from "../../../util/collection.ts"; +import { endpoints } from "../../../util/constants.ts"; + +/** Fetch all of the global commands for your application. */ +export async function getSlashCommands(guildId?: bigint) { + const result = await rest.runMethod( + "get", + guildId ? endpoints.COMMANDS_GUILD(applicationId, guildId) : endpoints.COMMANDS(applicationId) + ); + + return new Collection( + result.map((command) => [ + command.name, + { ...command, id: snowflakeToBigint(command.id), applicationId: snowflakeToBigint(command.applicationId) }, + ]) + ); +} diff --git a/src/helpers/interactions/commands/upsert_slash_command.ts b/src/helpers/interactions/commands/upsert_slash_command.ts new file mode 100644 index 000000000..9b6c40e7e --- /dev/null +++ b/src/helpers/interactions/commands/upsert_slash_command.ts @@ -0,0 +1,21 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { ApplicationCommand } from "../../../types/interactions/commands/application_command.ts"; +import type { EditGlobalApplicationCommand } from "../../../types/interactions/commands/edit_global_application_command.ts"; +import { endpoints } from "../../../util/constants.ts"; +import { validateSlashCommands } from "../../../util/utils.ts"; + +/** + * Edit an existing slash command. If this command did not exist, it will create it. + */ +export async function upsertSlashCommand(commandId: bigint, options: EditGlobalApplicationCommand, guildId?: bigint) { + [options] = validateSlashCommands([options]); + + return await rest.runMethod( + "patch", + guildId + ? endpoints.COMMANDS_GUILD_ID(applicationId, guildId, commandId) + : endpoints.COMMANDS_ID(applicationId, commandId), + options + ); +} diff --git a/src/helpers/interactions/commands/upsert_slash_commands.ts b/src/helpers/interactions/commands/upsert_slash_commands.ts new file mode 100644 index 000000000..ff7bb29d3 --- /dev/null +++ b/src/helpers/interactions/commands/upsert_slash_commands.ts @@ -0,0 +1,21 @@ +import { applicationId } from "../../../bot.ts"; +import { rest } from "../../../rest/rest.ts"; +import type { ApplicationCommand } from "../../../types/interactions/commands/application_command.ts"; +import type { EditGlobalApplicationCommand } from "../../../types/interactions/commands/edit_global_application_command.ts"; +import { endpoints } from "../../../util/constants.ts"; +import { validateSlashCommands } from "../../../util/utils.ts"; + +/** + * Bulk edit existing slash commands. If a command does not exist, it will create it. + * + * **NOTE:** Any slash commands that are not specified in this function will be **deleted**. If you don't provide the commandId and rename your command, the command gets a new Id. + */ +export async function upsertSlashCommands(options: EditGlobalApplicationCommand[], guildId?: bigint) { + options = validateSlashCommands(options); + + return await rest.runMethod( + "put", + guildId ? endpoints.COMMANDS_GUILD(applicationId, guildId) : endpoints.COMMANDS(applicationId), + options + ); +} diff --git a/src/helpers/interactions/get_original_interaction_response.ts b/src/helpers/interactions/get_original_interaction_response.ts new file mode 100644 index 000000000..51a72d268 --- /dev/null +++ b/src/helpers/interactions/get_original_interaction_response.ts @@ -0,0 +1,12 @@ +import { applicationId } from "../../bot.ts"; +import { rest } from "../../rest/rest.ts"; +import { structures } from "../../structures/mod.ts"; +import type { Message } from "../../types/messages/message.ts"; +import { endpoints } from "../../util/constants.ts"; + +/** Returns the initial Interactio response. Functions the same as Get Webhook Message */ +export async function getOriginalInteractionResponse(token: string) { + const result = await rest.runMethod("get", endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationId, token)); + + return await structures.createDiscordenoMessage(result); +} diff --git a/src/helpers/commands/send_interaction_response.ts b/src/helpers/interactions/send_interaction_response.ts similarity index 54% rename from src/helpers/commands/send_interaction_response.ts rename to src/helpers/interactions/send_interaction_response.ts index 3bb2132cb..4e52fc171 100644 --- a/src/helpers/commands/send_interaction_response.ts +++ b/src/helpers/interactions/send_interaction_response.ts @@ -1,8 +1,9 @@ import { applicationId, eventHandlers } from "../../bot.ts"; import { cache } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { DiscordenoInteractionResponse } from "../../types/discordeno/interaction_response.ts"; +import type { DiscordenoInteractionResponse } from "../../types/discordeno/interaction_response.ts"; import { endpoints } from "../../util/constants.ts"; +import { snakelize, validateComponents } from "../../util/utils.ts"; /** * Send a response to a users slash command. The command data will have the id and token necessary to respond. @@ -10,34 +11,20 @@ import { endpoints } from "../../util/constants.ts"; * * NOTE: By default we will suppress mentions. To enable mentions, just pass any mentions object. */ -export async function sendInteractionResponse( - id: string, - token: string, - options: DiscordenoInteractionResponse, -) { +export async function sendInteractionResponse(id: bigint, token: string, options: DiscordenoInteractionResponse) { + // TODO: add more options validations + if (options.data?.components) validateComponents(options.data?.components); // If its already been executed, we need to send a followup response if (cache.executedSlashCommands.has(token)) { - return await rest.runMethod( - "post", - endpoints.WEBHOOK(applicationId, token), - { - ...options, - }, - ); + return await rest.runMethod("post", endpoints.WEBHOOK(applicationId, token), snakelize(options)); } // Expire in 15 minutes - cache.executedSlashCommands.set(token, id); - setTimeout( - () => { - eventHandlers.debug?.( - "loop", - `Running setTimeout in send_interaction_response file.`, - ); - cache.executedSlashCommands.delete(token); - }, - 900000, - ); + cache.executedSlashCommands.add(token); + setTimeout(() => { + eventHandlers.debug?.("loop", `Running setTimeout in send_interaction_response file.`); + cache.executedSlashCommands.delete(token); + }, 900000); // If the user wants this as a private message mark it ephemeral if (options.private) { @@ -49,9 +36,5 @@ export async function sendInteractionResponse( options.data = { ...options.data, allowedMentions: { parse: [] } }; } - return await rest.runMethod( - "post", - endpoints.INTERACTION_ID_TOKEN(id, token), - options, - ); + return await rest.runMethod("post", endpoints.INTERACTION_ID_TOKEN(id, token), snakelize(options)); } diff --git a/src/helpers/invites/create_invite.ts b/src/helpers/invites/create_invite.ts index 0edab07ba..c3354a3fe 100644 --- a/src/helpers/invites/create_invite.ts +++ b/src/helpers/invites/create_invite.ts @@ -1,15 +1,12 @@ import { rest } from "../../rest/rest.ts"; -import { CreateChannelInvite } from "../../types/invites/create_channel_invite.ts"; -import { Invite } from "../../types/invites/invite.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { CreateChannelInvite } from "../../types/invites/create_channel_invite.ts"; +import type { InviteMetadata } from "../../types/invites/invite_metadata.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Creates a new invite for this channel. Requires CREATE_INSTANT_INVITE */ -export async function createInvite( - channelId: string, - options: CreateChannelInvite, -) { +export async function createInvite(channelId: bigint, options: CreateChannelInvite = {}) { await requireBotChannelPermissions(channelId, ["CREATE_INSTANT_INVITE"]); if (options.maxAge && (options.maxAge < 0 || options.maxAge > 604800)) { @@ -19,9 +16,5 @@ export async function createInvite( throw new Error(Errors.INVITE_MAX_USES_INVALID); } - return await rest.runMethod( - "post", - endpoints.CHANNEL_INVITES(channelId), - options, - ); + return await rest.runMethod("post", endpoints.CHANNEL_INVITES(channelId), options); } diff --git a/src/helpers/invites/delete_invite.ts b/src/helpers/invites/delete_invite.ts index 0891c89ed..fe033ddf6 100644 --- a/src/helpers/invites/delete_invite.ts +++ b/src/helpers/invites/delete_invite.ts @@ -1,29 +1,19 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { Invite } from "../../types/invites/invite.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { InviteMetadata } from "../../types/invites/invite_metadata.ts"; import { endpoints } from "../../util/constants.ts"; -import { - botHasChannelPermissions, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; +import { botHasChannelPermissions, requireBotGuildPermissions } from "../../util/permissions.ts"; /** Deletes an invite for the given code. Requires `MANAGE_CHANNELS` or `MANAGE_GUILD` permission */ -export async function deleteInvite(channelId: string, inviteCode: string) { +export async function deleteInvite(channelId: bigint, inviteCode: string) { const channel = await cacheHandlers.get("channels", channelId); + if (channel) { + const hasPerm = await botHasChannelPermissions(channel, ["MANAGE_CHANNELS"]); - if (!channel) throw new Error(Errors.CHANNEL_NOT_FOUND); - - const hasPerm = await botHasChannelPermissions(channel, [ - "MANAGE_CHANNELS", - ]); - - if (!hasPerm) { - await requireBotGuildPermissions(channel!.guildId, ["MANAGE_GUILD"]); + if (!hasPerm) { + await requireBotGuildPermissions(channel.guildId, ["MANAGE_GUILD"]); + } } - return await rest.runMethod( - "delete", - endpoints.INVITE(inviteCode), - ); + return await rest.runMethod("delete", endpoints.INVITE(inviteCode)); } diff --git a/src/helpers/invites/get_channel_invites.ts b/src/helpers/invites/get_channel_invites.ts index 4c9ce95c6..3877b6c7f 100644 --- a/src/helpers/invites/get_channel_invites.ts +++ b/src/helpers/invites/get_channel_invites.ts @@ -1,19 +1,14 @@ import { rest } from "../../rest/rest.ts"; -import { Invite } from "../../types/invites/invite.ts"; +import type { InviteMetadata } from "../../types/invites/invite_metadata.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Gets the invites for this channel. Requires MANAGE_CHANNEL */ -export async function getChannelInvites(channelId: string) { +export async function getChannelInvites(channelId: bigint) { await requireBotChannelPermissions(channelId, ["MANAGE_CHANNELS"]); - const result = await rest.runMethod( - "get", - endpoints.CHANNEL_INVITES(channelId), - ); + const result = await rest.runMethod("get", endpoints.CHANNEL_INVITES(channelId)); - return new Collection( - result.map((invite) => [invite.code, invite]), - ); + return new Collection(result.map((invite) => [invite.code, invite])); } diff --git a/src/helpers/invites/get_invite.ts b/src/helpers/invites/get_invite.ts index de91249fe..dbe9c7062 100644 --- a/src/helpers/invites/get_invite.ts +++ b/src/helpers/invites/get_invite.ts @@ -1,11 +1,10 @@ import { rest } from "../../rest/rest.ts"; -import { Invite } from "../../types/invites/invite.ts"; +import { GetInvite } from "../../types/invites/get_invite.ts"; +import type { InviteMetadata } from "../../types/invites/invite_metadata.ts"; import { endpoints } from "../../util/constants.ts"; +import { snakelize } from "../../util/utils.ts"; /** Returns an invite for the given code or throws an error if the invite doesn't exists. */ -export async function getInvite(inviteCode: string) { - return await rest.runMethod( - "get", - endpoints.INVITE(inviteCode), - ); +export async function getInvite(inviteCode: string, options?: GetInvite) { + return await rest.runMethod("get", endpoints.INVITE(inviteCode), snakelize(options ?? {})); } diff --git a/src/helpers/invites/get_invites.ts b/src/helpers/invites/get_invites.ts index c4a72e7d6..59098bfbf 100644 --- a/src/helpers/invites/get_invites.ts +++ b/src/helpers/invites/get_invites.ts @@ -1,19 +1,14 @@ import { rest } from "../../rest/rest.ts"; -import { Invite } from "../../types/invites/invite.ts"; +import type { InviteMetadata } from "../../types/invites/invite_metadata.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Get all the invites for this guild. Requires MANAGE_GUILD permission */ -export async function getInvites(guildId: string) { +export async function getInvites(guildId: bigint) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); - const result = await rest.runMethod( - "get", - endpoints.GUILD_INVITES(guildId), - ); + const result = await rest.runMethod("get", endpoints.GUILD_INVITES(guildId)); - return new Collection( - result.map((invite) => [invite.code, invite]), - ); + return new Collection(result.map((invite) => [invite.code, invite])); } diff --git a/src/helpers/members/avatar_url.ts b/src/helpers/members/avatar_url.ts index 3bbc160de..c6c950cdc 100644 --- a/src/helpers/members/avatar_url.ts +++ b/src/helpers/members/avatar_url.ts @@ -1,17 +1,30 @@ -import { DiscordImageFormat } from "../../types/misc/image_format.ts"; -import { DiscordImageSize } from "../../types/misc/image_size.ts"; +import type { DiscordImageFormat } from "../../types/misc/image_format.ts"; +import type { DiscordImageSize } from "../../types/misc/image_size.ts"; import { endpoints } from "../../util/constants.ts"; +import { iconBigintToHash } from "../../util/hash.ts"; import { formatImageURL } from "../../util/utils.ts"; /** The users custom avatar or the default avatar if you don't have a member object. */ export function avatarURL( - userId: string, - discriminator: string, - avatar?: string | null, - size: DiscordImageSize = 128, - format?: DiscordImageFormat, + userId: bigint, + discriminator: number, + options: { + avatar?: string | bigint; + size?: DiscordImageSize; + format?: DiscordImageFormat; + animated?: boolean; + } ) { - return avatar - ? formatImageURL(endpoints.USER_AVATAR(userId, avatar), size, format) + return options.avatar + ? formatImageURL( + endpoints.USER_AVATAR( + userId, + typeof options.avatar === "string" + ? options.avatar + : iconBigintToHash(options.avatar, options.animated ?? true) + ), + options.size || 128, + options.format + ) : endpoints.USER_DEFAULT_AVATAR(Number(discriminator) % 5); } diff --git a/src/helpers/members/ban_member.ts b/src/helpers/members/ban_member.ts index c1257cf08..0f9ee8d4e 100644 --- a/src/helpers/members/ban_member.ts +++ b/src/helpers/members/ban_member.ts @@ -1,22 +1,14 @@ import { rest } from "../../rest/rest.ts"; -import { CreateGuildBan } from "../../types/guilds/create_guild_ban.ts"; +import type { CreateGuildBan } from "../../types/guilds/create_guild_ban.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { snakelize } from "../../util/utils.ts"; /** Ban a user from the guild and optionally delete previous messages sent by the user. Requires the BAN_MEMBERS permission. */ -export async function ban( - guildId: string, - id: string, - options: CreateGuildBan, -) { +export async function ban(guildId: bigint, id: bigint, options: CreateGuildBan) { await requireBotGuildPermissions(guildId, ["BAN_MEMBERS"]); - return await rest.runMethod( - "put", - endpoints.GUILD_BAN(guildId, id), - camelKeysToSnakeCase(options), - ); + return await rest.runMethod("put", endpoints.GUILD_BAN(guildId, id), snakelize(options)); } // aliases diff --git a/src/helpers/members/disconnect_member.ts b/src/helpers/members/disconnect_member.ts index 5c6c3a92e..a1d4b0b9c 100644 --- a/src/helpers/members/disconnect_member.ts +++ b/src/helpers/members/disconnect_member.ts @@ -1,6 +1,6 @@ import { editMember } from "./edit_member.ts"; /** Kicks a member from a voice channel */ -export function disconnectMember(guildId: string, memberId: string) { +export function disconnectMember(guildId: bigint, memberId: bigint) { return editMember(guildId, memberId, { channelId: null }); } diff --git a/src/helpers/members/edit_bot_nickname.ts b/src/helpers/members/edit_bot_nickname.ts index f9b41bb3c..9ab18893b 100644 --- a/src/helpers/members/edit_bot_nickname.ts +++ b/src/helpers/members/edit_bot_nickname.ts @@ -3,19 +3,12 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Edit the nickname of the bot in this guild */ -export async function editBotNickname( - guildId: string, - nickname: string | null, -) { +export async function editBotNickname(guildId: bigint, nickname: string | null) { await requireBotGuildPermissions(guildId, ["CHANGE_NICKNAME"]); - const response = await rest.runMethod<{ nick: string }>( - "patch", - endpoints.USER_NICK(guildId), - { - nick: nickname, - }, - ); + const response = await rest.runMethod<{ nick: string }>("patch", endpoints.USER_NICK(guildId), { + nick: nickname, + }); return response.nick; } diff --git a/src/helpers/members/edit_member.ts b/src/helpers/members/edit_member.ts index 73b3e6b6c..96432beb2 100644 --- a/src/helpers/members/edit_member.ts +++ b/src/helpers/members/edit_member.ts @@ -1,23 +1,17 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { GuildMemberWithUser } from "../../types/guilds/guild_member.ts"; -import { Errors } from "../../types/misc/errors.ts"; -import { ModifyGuildMember } from "../../types/mod.ts"; -import { PermissionStrings } from "../../types/permissions/permission_strings.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import type { ModifyGuildMember } from "../../types/guilds/modify_guild_member.ts"; +import type { GuildMemberWithUser } from "../../types/members/guild_member.ts"; +import type { PermissionStrings } from "../../types/permissions/permission_strings.ts"; +import { bigintToSnowflake } from "../../util/bigint.ts"; import { endpoints } from "../../util/constants.ts"; -import { - requireBotChannelPermissions, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { requireBotChannelPermissions, requireBotGuildPermissions } from "../../util/permissions.ts"; +import { snakelize } from "../../util/utils.ts"; /** Edit the member */ -export async function editMember( - guildId: string, - memberId: string, - options: ModifyGuildMember, -) { +export async function editMember(guildId: bigint, memberId: bigint, options: ModifyGuildMember) { const requiredPerms: Set = new Set(); if (options.nick) { @@ -29,41 +23,27 @@ export async function editMember( if (options.roles) requiredPerms.add("MANAGE_ROLES"); - if ( - typeof options.mute !== "undefined" || - typeof options.deaf !== "undefined" || - (typeof options.channelId !== "undefined" || "null") - ) { - const memberVoiceState = (await cacheHandlers.get("guilds", guildId)) - ?.voiceStates.get(memberId); + if (options.mute !== undefined || options.deaf !== undefined || options.channelId !== undefined) { + const memberVoiceState = (await cacheHandlers.get("guilds", guildId))?.voiceStates.get(memberId); if (!memberVoiceState?.channelId) { throw new Error(Errors.MEMBER_NOT_IN_VOICE_CHANNEL); } - if (typeof options.mute !== "undefined") { + if (options.mute !== undefined) { requiredPerms.add("MUTE_MEMBERS"); } - if (typeof options.deaf !== "undefined") { + if (options.deaf !== undefined) { requiredPerms.add("DEAFEN_MEMBERS"); } if (options.channelId) { - const requiredVoicePerms: Set = new Set([ - "CONNECT", - "MOVE_MEMBERS", - ]); + const requiredVoicePerms: Set = new Set(["CONNECT", "MOVE_MEMBERS"]); if (memberVoiceState) { - await requireBotChannelPermissions( - memberVoiceState?.channelId, - [...requiredVoicePerms], - ); + await requireBotChannelPermissions(memberVoiceState?.channelId, [...requiredVoicePerms]); } - await requireBotChannelPermissions( - options.channelId, - [...requiredVoicePerms], - ); + await requireBotChannelPermissions(options.channelId, [...requiredVoicePerms]); } } @@ -72,13 +52,13 @@ export async function editMember( const result = await rest.runMethod( "patch", endpoints.GUILD_MEMBER(guildId, memberId), - camelKeysToSnakeCase(options), + snakelize({ + ...options, + channelId: options.channelId ? bigintToSnowflake(options.channelId) : undefined, + }) as ModifyGuildMember ); - const member = await structures.createDiscordenoMember( - result, - guildId, - ); + const member = await structures.createDiscordenoMember(result, guildId); return member; } diff --git a/src/helpers/members/fetch_members.ts b/src/helpers/members/fetch_members.ts index aee4ed00c..8ce49fd29 100644 --- a/src/helpers/members/fetch_members.ts +++ b/src/helpers/members/fetch_members.ts @@ -1,11 +1,10 @@ import { cache } from "../../cache.ts"; import { DiscordenoMember } from "../../structures/member.ts"; import { DiscordGatewayOpcodes } from "../../types/codes/gateway_opcodes.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { DiscordGatewayIntents } from "../../types/gateway/gateway_intents.ts"; -import type { RequestGuildMembers } from "../../types/guilds/request_guild_members.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { RequestGuildMembers } from "../../types/members/request_guild_members.ts"; import { Collection } from "../../util/collection.ts"; -import { sendShardMessage } from "../../ws/send_shard_message.ts"; import { ws } from "../../ws/ws.ts"; /** @@ -16,16 +15,9 @@ import { ws } from "../../ws/ws.ts"; * REST: 50/s global(across all shards) rate limit with ALL requests this included * GW(this function): 120/m(PER shard) rate limit. Meaning if you have 8 shards your limit is now 960/m. */ -export function fetchMembers( - guildId: string, - shardId: number, - options?: Omit, -) { +export function fetchMembers(guildId: bigint, shardId: number, options?: Omit) { // You can request 1 member without the intent - if ( - (!options?.limit || options.limit > 1) && - !(ws.identifyPayload.intents & DiscordGatewayIntents.GUILD_MEMBERS) - ) { + if ((!options?.limit || options.limit > 1) && !(ws.identifyPayload.intents & DiscordGatewayIntents.GuildMembers)) { throw new Error(Errors.MISSING_INTENT_GUILD_MEMBERS); } @@ -37,7 +29,7 @@ export function fetchMembers( const nonce = `${guildId}-${Date.now()}`; cache.fetchAllMembersProcessingRequests.set(nonce, resolve); - sendShardMessage(shardId, { + ws.sendShardMessage(shardId, { op: DiscordGatewayOpcodes.RequestGuildMembers, d: { guild_id: guildId, @@ -49,5 +41,5 @@ export function fetchMembers( nonce, }, }); - }) as Promise>; + }) as Promise>; } diff --git a/src/helpers/members/get_member.ts b/src/helpers/members/get_member.ts index 8605fbe4c..0aff1bde9 100644 --- a/src/helpers/members/get_member.ts +++ b/src/helpers/members/get_member.ts @@ -1,30 +1,20 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { GuildMemberWithUser } from "../../types/guilds/guild_member.ts"; +import type { GuildMemberWithUser } from "../../types/members/guild_member.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns a guild member object for the specified user. * * ⚠️ **ADVANCED USE ONLY: Your members will be cached in your guild most likely. Only use this when you are absolutely sure the member is not cached.** */ -export async function getMember( - guildId: string, - id: string, - options?: { force?: boolean }, -) { +export async function getMember(guildId: bigint, id: bigint, options?: { force?: boolean }) { const guild = await cacheHandlers.get("guilds", guildId); if (!guild && !options?.force) return; - const data = (await rest.runMethod( - "get", - endpoints.GUILD_MEMBER(guildId, id), - )); + const data = await rest.runMethod("get", endpoints.GUILD_MEMBER(guildId, id)); - const discordenoMember = await structures.createDiscordenoMember( - data, - guildId, - ); + const discordenoMember = await structures.createDiscordenoMember(data, guildId); await cacheHandlers.set("members", discordenoMember.id, discordenoMember); return discordenoMember; diff --git a/src/helpers/members/get_members.ts b/src/helpers/members/get_members.ts index 202c4a70d..768cba54e 100644 --- a/src/helpers/members/get_members.ts +++ b/src/helpers/members/get_members.ts @@ -3,10 +3,11 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { DiscordenoMember } from "../../structures/member.ts"; import { structures } from "../../structures/mod.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { DiscordGatewayIntents } from "../../types/gateway/gateway_intents.ts"; -import { GuildMemberWithUser } from "../../types/guilds/guild_member.ts"; -import { ListGuildMembers } from "../../types/guilds/list_guild_members.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import type { GuildMemberWithUser } from "../../types/members/guild_member.ts"; +import type { ListGuildMembers } from "../../types/members/list_guild_members.ts"; +import { bigintToSnowflake } from "../../util/bigint.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; import { ws } from "../../ws/ws.ts"; @@ -19,76 +20,54 @@ import { ws } from "../../ws/ws.ts"; * REST(this function): 50/s global(across all shards) rate limit with ALL requests this included * GW(fetchMembers): 120/m(PER shard) rate limit. Meaning if you have 8 shards your limit is 960/m. */ -export async function getMembers( - guildId: string, - options?: ListGuildMembers & { addToCache?: boolean }, -) { - if (!(ws.identifyPayload.intents && DiscordGatewayIntents.GUILD_MEMBERS)) { +export async function getMembers(guildId: bigint, options?: ListGuildMembers & { addToCache?: boolean }) { + if (!(ws.identifyPayload.intents && DiscordGatewayIntents.GuildMembers)) { throw new Error(Errors.MISSING_INTENT_GUILD_MEMBERS); } const guild = await cacheHandlers.get("guilds", guildId); if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); - const members = new Collection(); + const members = new Collection(); let membersLeft = options?.limit ?? guild.memberCount; let loops = 1; - while ( - (options?.limit ?? guild.memberCount) > members.size && - membersLeft > 0 - ) { + while ((options?.limit ?? guild.memberCount) > members.size && membersLeft > 0) { eventHandlers.debug?.("loop", "Running while loop in getMembers function."); if (options?.limit && options.limit > 1000) { - console.log( - `Paginating get members from REST. #${loops} / ${ - Math.ceil( - (options?.limit ?? 1) / 1000, - ) - }`, - ); + console.log(`Paginating get members from REST. #${loops} / ${Math.ceil((options?.limit ?? 1) / 1000)}`); } - const result = (await rest.runMethod( + const result = await rest.runMethod( "get", - `${endpoints.GUILD_MEMBERS(guildId)}?limit=${ - membersLeft > 1000 ? 1000 : membersLeft - }${options?.after ? `&after=${options.after}` : ""}`, - )); + `${endpoints.GUILD_MEMBERS(guildId)}?limit=${membersLeft > 1000 ? 1000 : membersLeft}${ + options?.after ? `&after=${options.after}` : "" + }` + ); const discordenoMembers = await Promise.all( result.map(async (member) => { - const discordenoMember = await structures.createDiscordenoMember( - member, - guildId, - ); + const discordenoMember = await structures.createDiscordenoMember(member, guildId); if (options?.addToCache !== false) { - await cacheHandlers.set( - "members", - discordenoMember.id, - discordenoMember, - ); + await cacheHandlers.set("members", discordenoMember.id, discordenoMember); } return discordenoMember; - }), + }) ); if (!discordenoMembers.length) break; discordenoMembers.forEach((member) => { - eventHandlers.debug?.( - "loop", - `Running forEach loop in get_members file.`, - ); + eventHandlers.debug?.("loop", `Running forEach loop in get_members file.`); members.set(member.id, member); }); options = { limit: options?.limit, - after: discordenoMembers[discordenoMembers.length - 1].id, + after: bigintToSnowflake(discordenoMembers[discordenoMembers.length - 1].id), }; membersLeft -= 1000; diff --git a/src/helpers/members/kick_member.ts b/src/helpers/members/kick_member.ts index 8dbefc72e..4f9306a9a 100644 --- a/src/helpers/members/kick_member.ts +++ b/src/helpers/members/kick_member.ts @@ -1,30 +1,20 @@ import { botId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; -import { - highestRole, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; +import { highestRole, requireBotGuildPermissions } from "../../util/permissions.ts"; /** Kick a member from the server */ -export async function kick(guildId: string, memberId: string, reason?: string) { +export async function kick(guildId: bigint, memberId: bigint, reason?: string) { const botsHighestRole = await highestRole(guildId, botId); const membersHighestRole = await highestRole(guildId, memberId); - if ( - botsHighestRole && membersHighestRole && - botsHighestRole.position <= membersHighestRole.position - ) { + if (botsHighestRole && membersHighestRole && botsHighestRole.position <= membersHighestRole.position) { throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW); } await requireBotGuildPermissions(guildId, ["KICK_MEMBERS"]); - return await rest.runMethod( - "delete", - endpoints.GUILD_MEMBER(guildId, memberId), - { reason }, - ); + return await rest.runMethod("delete", endpoints.GUILD_MEMBER(guildId, memberId), { reason }); } // aliases diff --git a/src/helpers/members/move_member.ts b/src/helpers/members/move_member.ts index 678092454..fd1b871d1 100644 --- a/src/helpers/members/move_member.ts +++ b/src/helpers/members/move_member.ts @@ -6,10 +6,6 @@ import { editMember } from "./edit_member.ts"; * @param memberId the id of the member to move. * @param channelId id of channel to move user to (if they are connected to voice) */ -export function moveMember( - guildId: string, - memberId: string, - channelId: string, -) { +export function moveMember(guildId: bigint, memberId: bigint, channelId: bigint) { return editMember(guildId, memberId, { channelId }); } diff --git a/src/helpers/members/prune_members.ts b/src/helpers/members/prune_members.ts index ae8fc190c..c6ad13bc7 100644 --- a/src/helpers/members/prune_members.ts +++ b/src/helpers/members/prune_members.ts @@ -1,29 +1,22 @@ import { rest } from "../../rest/rest.ts"; -import { BeginGuildPrune } from "../../types/guilds/begin_guild_prune.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import type { BeginGuildPrune } from "../../types/guilds/begin_guild_prune.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { snakelize } from "../../util/utils.ts"; /** * Begin a prune operation. Requires the KICK_MEMBERS permission. Returns an object with one 'pruned' key indicating the number of members that were removed in the prune operation. For large guilds it's recommended to set the computePruneCount option to false, forcing 'pruned' to null. Fires multiple Guild Member Remove Gateway events. - * + * * By default, prune will not remove users with roles. You can optionally include specific roles in your prune by providing the roles (resolved to include_roles internally) parameter. Any inactive user that has a subset of the provided role(s) will be included in the prune and users with additional roles will not. */ -export async function pruneMembers( - guildId: string, - options: BeginGuildPrune, -) { +export async function pruneMembers(guildId: bigint, options: BeginGuildPrune) { if (options.days && options.days < 1) throw new Error(Errors.PRUNE_MIN_DAYS); if (options.days && options.days > 30) throw new Error(Errors.PRUNE_MAX_DAYS); await requireBotGuildPermissions(guildId, ["KICK_MEMBERS"]); - const result = await rest.runMethod<{ pruned: number }>( - "post", - endpoints.GUILD_PRUNE(guildId), - camelKeysToSnakeCase(options), - ); + const result = await rest.runMethod<{ pruned: number }>("post", endpoints.GUILD_PRUNE(guildId), snakelize(options)); return result.pruned; } diff --git a/src/helpers/members/search_members.ts b/src/helpers/members/search_members.ts index 12202aba2..06c389a23 100644 --- a/src/helpers/members/search_members.ts +++ b/src/helpers/members/search_members.ts @@ -2,9 +2,9 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { DiscordenoMember } from "../../structures/member.ts"; import { structures } from "../../structures/mod.ts"; -import { GuildMemberWithUser } from "../../types/guilds/guild_member.ts"; -import { SearchGuildMembers } from "../../types/members/search_guild_members.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import type { GuildMemberWithUser } from "../../types/members/guild_member.ts"; +import type { SearchGuildMembers } from "../../types/members/search_guild_members.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; @@ -13,9 +13,9 @@ import { endpoints } from "../../util/constants.ts"; * @param query Query string to match username(s) and nickname(s) against */ export async function searchMembers( - guildId: string, + guildId: bigint, query: string, - options?: Omit & { cache?: boolean }, + options?: Omit & { cache?: boolean } ) { if (options?.limit) { if (options.limit < 1) throw new Error(Errors.MEMBER_SEARCH_LIMIT_TOO_LOW); @@ -24,28 +24,21 @@ export async function searchMembers( } } - const result = await rest.runMethod( - "get", - endpoints.GUILD_MEMBERS_SEARCH(guildId), - { - ...options, - query, - }, + const result = await rest.runMethod("get", endpoints.GUILD_MEMBERS_SEARCH(guildId), { + ...options, + query, + }); + + const members = await Promise.all( + result.map(async (member) => { + const discordenoMember = await structures.createDiscordenoMember(member, guildId); + if (options?.cache) { + await cacheHandlers.set("members", discordenoMember.id, discordenoMember); + } + + return discordenoMember; + }) ); - const members = await Promise.all(result.map(async (member) => { - const discordenoMember = await structures.createDiscordenoMember( - member, - guildId, - ); - if (options?.cache) { - await cacheHandlers.set("members", discordenoMember.id, discordenoMember); - } - - return discordenoMember; - })); - - return new Collection( - members.map((member) => [member.id, member]), - ); + return new Collection(members.map((member) => [member.id, member])); } diff --git a/src/helpers/members/send_direct_message.ts b/src/helpers/members/send_direct_message.ts index ce01eaeda..e03cfa0ca 100644 --- a/src/helpers/members/send_direct_message.ts +++ b/src/helpers/members/send_direct_message.ts @@ -1,29 +1,20 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Channel } from "../../types/channels/channel.ts"; -import { CreateMessage } from "../../types/messages/create_message.ts"; +import type { Channel } from "../../types/channels/channel.ts"; +import type { CreateMessage } from "../../types/messages/create_message.ts"; import { endpoints } from "../../util/constants.ts"; import { sendMessage } from "../messages/send_message.ts"; /** Send a message to a users DM. Note: this takes 2 API calls. 1 is to fetch the users dm channel. 2 is to send a message to that channel. */ -export async function sendDirectMessage( - memberId: string, - content: string | CreateMessage, -) { +export async function sendDirectMessage(memberId: bigint, content: string | CreateMessage) { let dmChannel = await cacheHandlers.get("channels", memberId); if (!dmChannel) { // If not available in cache create a new one. - const dmChannelData = await rest.runMethod( - "post", - endpoints.USER_DM, - { - recipient_id: memberId, - }, - ); - const discordenoChannel = await structures.createDiscordenoChannel( - dmChannelData, - ); + const dmChannelData = await rest.runMethod("post", endpoints.USER_DM, { + recipient_id: memberId, + }); + const discordenoChannel = await structures.createDiscordenoChannel(dmChannelData); // Recreate the channel and add it undert he users id await cacheHandlers.set("channels", memberId, discordenoChannel); dmChannel = discordenoChannel; diff --git a/src/helpers/members/unban_member.ts b/src/helpers/members/unban_member.ts index a6c429fb8..75beb27a6 100644 --- a/src/helpers/members/unban_member.ts +++ b/src/helpers/members/unban_member.ts @@ -3,13 +3,10 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Remove the ban for a user. Requires BAN_MEMBERS permission */ -export async function unban(guildId: string, id: string) { +export async function unban(guildId: bigint, id: bigint) { await requireBotGuildPermissions(guildId, ["BAN_MEMBERS"]); - return await rest.runMethod( - "delete", - endpoints.GUILD_BAN(guildId, id), - ); + return await rest.runMethod("delete", endpoints.GUILD_BAN(guildId, id)); } // aliases diff --git a/src/helpers/messages/add_reaction.ts b/src/helpers/messages/add_reaction.ts index 6c2fe402b..cc8691179 100644 --- a/src/helpers/messages/add_reaction.ts +++ b/src/helpers/messages/add_reaction.ts @@ -3,15 +3,8 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Create a reaction for the message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. Requires READ_MESSAGE_HISTORY and ADD_REACTIONS */ -export async function addReaction( - channelId: string, - messageId: string, - reaction: string, -) { - await requireBotChannelPermissions(channelId, [ - "ADD_REACTIONS", - "READ_MESSAGE_HISTORY", - ]); +export async function addReaction(channelId: bigint, messageId: bigint, reaction: string) { + await requireBotChannelPermissions(channelId, ["ADD_REACTIONS", "READ_MESSAGE_HISTORY"]); if (reaction.startsWith("<:")) { reaction = reaction.substring(2, reaction.length - 1); @@ -19,8 +12,5 @@ export async function addReaction( reaction = reaction.substring(3, reaction.length - 1); } - return await rest.runMethod( - "put", - endpoints.CHANNEL_MESSAGE_REACTION_ME(channelId, messageId, reaction), - ); + return await rest.runMethod("put", endpoints.CHANNEL_MESSAGE_REACTION_ME(channelId, messageId, reaction)); } diff --git a/src/helpers/messages/add_reactions.ts b/src/helpers/messages/add_reactions.ts index 6a619c63e..00d3d6803 100644 --- a/src/helpers/messages/add_reactions.ts +++ b/src/helpers/messages/add_reactions.ts @@ -2,22 +2,12 @@ import { eventHandlers } from "../../bot.ts"; import { addReaction } from "./add_reaction.ts"; /** Adds multiple reactions to a message. If `ordered` is true(default is false), it will add the reactions one at a time in the order provided. Note: Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. Requires READ_MESSAGE_HISTORY and ADD_REACTIONS */ -export async function addReactions( - channelId: string, - messageId: string, - reactions: string[], - ordered = false, -) { +export async function addReactions(channelId: bigint, messageId: bigint, reactions: string[], ordered = false) { if (!ordered) { - await Promise.all( - reactions.map((reaction) => addReaction(channelId, messageId, reaction)), - ); + await Promise.all(reactions.map((reaction) => addReaction(channelId, messageId, reaction))); } else { for (const reaction of reactions) { - eventHandlers.debug?.( - "loop", - "Running for of loop in addReactions function.", - ); + eventHandlers.debug?.("loop", "Running for of loop in addReactions function."); await addReaction(channelId, messageId, reaction); } } diff --git a/src/helpers/messages/delete_message.ts b/src/helpers/messages/delete_message.ts index 9b84ae248..1753254cf 100644 --- a/src/helpers/messages/delete_message.ts +++ b/src/helpers/messages/delete_message.ts @@ -6,23 +6,14 @@ import { requireBotChannelPermissions } from "../../util/permissions.ts"; import { delay } from "../../util/utils.ts"; /** Delete a message with the channel id and message id only. */ -export async function deleteMessage( - channelId: string, - messageId: string, - reason?: string, - delayMilliseconds = 0, -) { +export async function deleteMessage(channelId: bigint, messageId: bigint, reason?: string, delayMilliseconds = 0) { const message = await cacheHandlers.get("messages", messageId); - if (message && message.author.id !== botId) { + if (message && message.authorId !== botId) { await requireBotChannelPermissions(message.channelId, ["MANAGE_MESSAGES"]); } if (delayMilliseconds) await delay(delayMilliseconds); - return await rest.runMethod( - "delete", - endpoints.CHANNEL_MESSAGE(channelId, messageId), - { reason }, - ); + return await rest.runMethod("delete", endpoints.CHANNEL_MESSAGE(channelId, messageId), { reason }); } diff --git a/src/helpers/messages/delete_messages.ts b/src/helpers/messages/delete_messages.ts index 5ff274215..f95ec54dd 100644 --- a/src/helpers/messages/delete_messages.ts +++ b/src/helpers/messages/delete_messages.ts @@ -1,14 +1,10 @@ import { rest } from "../../rest/rest.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Delete messages from the channel. 2-100. Requires the MANAGE_MESSAGES permission */ -export async function deleteMessages( - channelId: string, - ids: string[], - reason?: string, -) { +export async function deleteMessages(channelId: bigint, ids: bigint[], reason?: string) { await requireBotChannelPermissions(channelId, ["MANAGE_MESSAGES"]); if (ids.length < 2) { @@ -16,17 +12,11 @@ export async function deleteMessages( } if (ids.length > 100) { - console.warn( - `This endpoint only accepts a maximum of 100 messages. Deleting the first 100 message ids provided.`, - ); + console.warn(`This endpoint only accepts a maximum of 100 messages. Deleting the first 100 message ids provided.`); } - return await rest.runMethod( - "post", - endpoints.CHANNEL_BULK_DELETE(channelId), - { - messages: ids.splice(0, 100), - reason, - }, - ); + return await rest.runMethod("post", endpoints.CHANNEL_BULK_DELETE(channelId), { + messages: ids.splice(0, 100), + reason, + }); } diff --git a/src/helpers/messages/edit_message.ts b/src/helpers/messages/edit_message.ts index 860aa7616..c7f8f1522 100644 --- a/src/helpers/messages/edit_message.ts +++ b/src/helpers/messages/edit_message.ts @@ -2,24 +2,26 @@ import { botId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; import { DiscordenoMessage } from "../../structures/message.ts"; import { structures } from "../../structures/mod.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { EditMessage } from "../../types/messages/edit_message.ts"; -import { Message } from "../../types/messages/message.ts"; -import { Errors } from "../../types/misc/errors.ts"; -import { PermissionStrings } from "../../types/permissions/permission_strings.ts"; +import type { Message } from "../../types/messages/message.ts"; +import type { PermissionStrings } from "../../types/permissions/permission_strings.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; +import { validateComponents } from "../../util/utils.ts"; /** Edit the message. */ -export async function editMessage( - message: DiscordenoMessage, - content: string | EditMessage, -) { - if (message.author.id !== botId) { +export async function editMessage(message: DiscordenoMessage, content: string | EditMessage) { + if (message.authorId !== botId) { throw "You can only edit a message that was sent by the bot."; } if (typeof content === "string") content = { content }; + if (content.components?.length) { + validateComponents(content.components); + } + const requiredPerms: PermissionStrings[] = ["SEND_MESSAGES"]; await requireBotChannelPermissions(message.channelId, requiredPerms); @@ -31,7 +33,7 @@ export async function editMessage( const result = await rest.runMethod( "patch", endpoints.CHANNEL_MESSAGE(message.channelId, message.id), - content, + content ); return await structures.createDiscordenoMessage(result); diff --git a/src/helpers/messages/get_message.ts b/src/helpers/messages/get_message.ts index fc3f00cf6..b925abf98 100644 --- a/src/helpers/messages/get_message.ts +++ b/src/helpers/messages/get_message.ts @@ -1,23 +1,17 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Message } from "../../types/messages/message.ts"; +import type { Message } from "../../types/messages/message.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Fetch a single message from the server. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY */ -export async function getMessage(channelId: string, id: string) { +export async function getMessage(channelId: bigint, id: bigint) { if (await cacheHandlers.has("channels", channelId)) { - await requireBotChannelPermissions(channelId, [ - "VIEW_CHANNEL", - "READ_MESSAGE_HISTORY", - ]); + await requireBotChannelPermissions(channelId, ["VIEW_CHANNEL", "READ_MESSAGE_HISTORY"]); } - const result = await rest.runMethod( - "get", - endpoints.CHANNEL_MESSAGE(channelId, id), - ); + const result = await rest.runMethod("get", endpoints.CHANNEL_MESSAGE(channelId, id)); return await structures.createDiscordenoMessage(result); } diff --git a/src/helpers/messages/get_messages.ts b/src/helpers/messages/get_messages.ts index 1429b495b..38e9c2ea8 100644 --- a/src/helpers/messages/get_messages.ts +++ b/src/helpers/messages/get_messages.ts @@ -1,38 +1,28 @@ import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { GetMessagesAfter, GetMessagesAround, GetMessagesBefore, GetMessagesLimit, } from "../../types/messages/get_messages.ts"; -import { Message } from "../../types/messages/message.ts"; +import type { Message } from "../../types/messages/message.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Fetches between 2-100 messages. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY */ export async function getMessages( - channelId: string, - options?: - | GetMessagesAfter - | GetMessagesBefore - | GetMessagesAround - | GetMessagesLimit, + channelId: bigint, + options?: GetMessagesAfter | GetMessagesBefore | GetMessagesAround | GetMessagesLimit ) { - await requireBotChannelPermissions(channelId, [ - "VIEW_CHANNEL", - "READ_MESSAGE_HISTORY", - ]); + await requireBotChannelPermissions(channelId, ["VIEW_CHANNEL", "READ_MESSAGE_HISTORY"]); - if (options?.limit && options.limit > 100) return; + if (options?.limit && (options.limit < 0 || options.limit > 100)) { + throw new Error(Errors.INVALID_GET_MESSAGES_LIMIT); + } - const result = await rest.runMethod( - "get", - endpoints.CHANNEL_MESSAGES(channelId), - options, - ); + const result = await rest.runMethod("get", endpoints.CHANNEL_MESSAGES(channelId), options); - return await Promise.all( - result.map((res) => structures.createDiscordenoMessage(res)), - ); + return await Promise.all(result.map((res) => structures.createDiscordenoMessage(res))); } diff --git a/src/helpers/messages/get_reactions.ts b/src/helpers/messages/get_reactions.ts index bf10d2d6d..7bf1ba976 100644 --- a/src/helpers/messages/get_reactions.ts +++ b/src/helpers/messages/get_reactions.ts @@ -1,20 +1,15 @@ import { rest } from "../../rest/rest.ts"; -import { GetReactions } from "../../types/messages/message_get_reactions.ts"; -import { User } from "../../types/users/user.ts"; +import type { GetReactions } from "../../types/messages/message_get_reactions.ts"; +import type { User } from "../../types/users/user.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; /** Get a list of users that reacted with this emoji. */ -export async function getReactions( - channelId: string, - messageId: string, - reaction: string, - options?: GetReactions, -) { +export async function getReactions(channelId: bigint, messageId: bigint, reaction: string, options?: GetReactions) { const users = await rest.runMethod( "get", endpoints.CHANNEL_MESSAGE_REACTION(channelId, messageId, reaction), - options, + options ); return new Collection(users.map((user) => [user.id, user])); diff --git a/src/helpers/messages/pin_message.ts b/src/helpers/messages/pin_message.ts index f96cac8f0..9bc16270c 100644 --- a/src/helpers/messages/pin_message.ts +++ b/src/helpers/messages/pin_message.ts @@ -3,13 +3,10 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Pin a message in a channel. Requires MANAGE_MESSAGES. Max pins allowed in a channel = 50. */ -export async function pin(channelId: string, messageId: string) { +export async function pin(channelId: bigint, messageId: bigint) { await requireBotChannelPermissions(channelId, ["MANAGE_MESSAGES"]); - return await rest.runMethod( - "put", - endpoints.CHANNEL_PIN(channelId, messageId), - ); + return await rest.runMethod("put", endpoints.CHANNEL_PIN(channelId, messageId)); } // aliases diff --git a/src/helpers/messages/publish_message.ts b/src/helpers/messages/publish_message.ts index 7d9d2ca17..e2c858c43 100644 --- a/src/helpers/messages/publish_message.ts +++ b/src/helpers/messages/publish_message.ts @@ -1,14 +1,11 @@ import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { Message } from "../../types/messages/message.ts"; +import type { Message } from "../../types/messages/message.ts"; import { endpoints } from "../../util/constants.ts"; /** Crosspost a message in a News Channel to following channels. */ -export async function publishMessage(channelId: string, messageId: string) { - const data = await rest.runMethod( - "post", - endpoints.CHANNEL_MESSAGE_CROSSPOST(channelId, messageId), - ); +export async function publishMessage(channelId: bigint, messageId: bigint) { + const data = await rest.runMethod("post", endpoints.CHANNEL_MESSAGE_CROSSPOST(channelId, messageId)); return await structures.createDiscordenoMessage(data); } diff --git a/src/helpers/messages/remove_all_reactions.ts b/src/helpers/messages/remove_all_reactions.ts index a97b81d9e..a2682e71b 100644 --- a/src/helpers/messages/remove_all_reactions.ts +++ b/src/helpers/messages/remove_all_reactions.ts @@ -3,11 +3,8 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Removes all reactions for all emojis on this message. */ -export async function removeAllReactions(channelId: string, messageId: string) { +export async function removeAllReactions(channelId: bigint, messageId: bigint) { await requireBotChannelPermissions(channelId, ["MANAGE_MESSAGES"]); - return await rest.runMethod( - "delete", - endpoints.CHANNEL_MESSAGE_REACTIONS(channelId, messageId), - ); + return await rest.runMethod("delete", endpoints.CHANNEL_MESSAGE_REACTIONS(channelId, messageId)); } diff --git a/src/helpers/messages/remove_reaction.ts b/src/helpers/messages/remove_reaction.ts index 36866d010..081488cad 100644 --- a/src/helpers/messages/remove_reaction.ts +++ b/src/helpers/messages/remove_reaction.ts @@ -4,10 +4,10 @@ import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Removes a reaction from the given user on this message, defaults to bot. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. */ export async function removeReaction( - channelId: string, - messageId: string, + channelId: bigint, + messageId: bigint, reaction: string, - options?: { userId?: string }, + options?: { userId?: bigint } ) { if (options?.userId) { await requireBotChannelPermissions(channelId, ["MANAGE_MESSAGES"]); @@ -22,12 +22,7 @@ export async function removeReaction( return await rest.runMethod( "delete", options?.userId - ? endpoints.CHANNEL_MESSAGE_REACTION_USER( - channelId, - messageId, - reaction, - options.userId, - ) - : endpoints.CHANNEL_MESSAGE_REACTION_ME(channelId, messageId, reaction), + ? endpoints.CHANNEL_MESSAGE_REACTION_USER(channelId, messageId, reaction, options.userId) + : endpoints.CHANNEL_MESSAGE_REACTION_ME(channelId, messageId, reaction) ); } diff --git a/src/helpers/messages/remove_reaction_emoji.ts b/src/helpers/messages/remove_reaction_emoji.ts index 85a68a0be..69a70805d 100644 --- a/src/helpers/messages/remove_reaction_emoji.ts +++ b/src/helpers/messages/remove_reaction_emoji.ts @@ -3,11 +3,7 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Removes all reactions for a single emoji on this message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. */ -export async function removeReactionEmoji( - channelId: string, - messageId: string, - reaction: string, -) { +export async function removeReactionEmoji(channelId: bigint, messageId: bigint, reaction: string) { await requireBotChannelPermissions(channelId, ["MANAGE_MESSAGES"]); if (reaction.startsWith("<:")) { @@ -16,8 +12,5 @@ export async function removeReactionEmoji( reaction = reaction.substring(3, reaction.length - 1); } - return await rest.runMethod( - "delete", - endpoints.CHANNEL_MESSAGE_REACTION(channelId, messageId, reaction), - ); + return await rest.runMethod("delete", endpoints.CHANNEL_MESSAGE_REACTION(channelId, messageId, reaction)); } diff --git a/src/helpers/messages/send_message.ts b/src/helpers/messages/send_message.ts index 87dd3eb20..9bcb2372e 100644 --- a/src/helpers/messages/send_message.ts +++ b/src/helpers/messages/send_message.ts @@ -2,24 +2,18 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts"; -import { ButtonStyles } from "../../types/messages/components/button_styles.ts"; -import { CreateMessage } from "../../types/messages/create_message.ts"; -import { Message } from "../../types/messages/message.ts"; -import { Errors } from "../../types/misc/errors.ts"; -import { PermissionStrings } from "../../types/permissions/permission_strings.ts"; +import type { CreateMessage } from "../../types/messages/create_message.ts"; +import type { Message } from "../../types/messages/message.ts"; +import type { PermissionStrings } from "../../types/permissions/permission_strings.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; -import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { snakelize, validateComponents } from "../../util/utils.ts"; import { validateLength } from "../../util/validate_length.ts"; -import { isActionRow } from "../type_guards/is_action_row.ts"; -import { isButton } from "../type_guards/is_button.ts"; /** Send a message to the channel. Requires SEND_MESSAGES permission. */ -export async function sendMessage( - channelId: string, - content: string | CreateMessage, -) { +export async function sendMessage(channelId: bigint, content: string | CreateMessage) { if (typeof content === "string") content = { content }; const channel = await cacheHandlers.get("channels", channelId); @@ -27,24 +21,21 @@ export async function sendMessage( if ( ![ DiscordChannelTypes.DM, - DiscordChannelTypes.GUILD_NEWS, - DiscordChannelTypes.GUILD_TEXT, + DiscordChannelTypes.GuildNews, + DiscordChannelTypes.GuildText, + DiscordChannelTypes.GuildPublicThread, + DiscordChannelTypes.GuildPivateThread, + DiscordChannelTypes.GuildNewsThread, ].includes(channel.type) ) { throw new Error(Errors.CHANNEL_NOT_TEXT_BASED); } - const requiredPerms: Set = new Set([ - "SEND_MESSAGES", - "VIEW_CHANNEL", - ]); + const requiredPerms: Set = new Set(["SEND_MESSAGES", "VIEW_CHANNEL"]); if (content.tts) requiredPerms.add("SEND_TTS_MESSAGES"); if (content.embed) requiredPerms.add("EMBED_LINKS"); - if ( - content.messageReference?.messageId || - content.allowedMentions?.repliedUser - ) { + if (content.messageReference?.messageId || content.allowedMentions?.repliedUser) { requiredPerms.add("READ_MESSAGE_HISTORY"); } @@ -58,111 +49,44 @@ export async function sendMessage( if (content.allowedMentions) { if (content.allowedMentions.users?.length) { - if ( - content.allowedMentions.parse?.includes( - DiscordAllowedMentionsTypes.UserMentions, - ) - ) { - content.allowedMentions.parse = content.allowedMentions.parse.filter( - (p) => p !== "users", - ); + if (content.allowedMentions.parse?.includes(DiscordAllowedMentionsTypes.UserMentions)) { + content.allowedMentions.parse = content.allowedMentions.parse.filter((p) => p !== "users"); } if (content.allowedMentions.users.length > 100) { - content.allowedMentions.users = content.allowedMentions.users.slice( - 0, - 100, - ); + content.allowedMentions.users = content.allowedMentions.users.slice(0, 100); } } if (content.allowedMentions.roles?.length) { - if ( - content.allowedMentions.parse?.includes( - DiscordAllowedMentionsTypes.RoleMentions, - ) - ) { - content.allowedMentions.parse = content.allowedMentions.parse.filter( - (p) => p !== "roles", - ); + if (content.allowedMentions.parse?.includes(DiscordAllowedMentionsTypes.RoleMentions)) { + content.allowedMentions.parse = content.allowedMentions.parse.filter((p) => p !== "roles"); } if (content.allowedMentions.roles.length > 100) { - content.allowedMentions.roles = content.allowedMentions.roles.slice( - 0, - 100, - ); + content.allowedMentions.roles = content.allowedMentions.roles.slice(0, 100); } } } if (content.components?.length) { - let actionRowCounter = 0; - - for (const component of content.components) { - // 5 Link buttons can not have a customId - if (isButton(component)) { - if ( - component.type === ButtonStyles.Link && - component.customId - ) { - throw new Error(Errors.LINK_BUTTON_CANNOT_HAVE_CUSTOM_ID); - } - // Other buttons must have a customId - if ( - !component.customId && component.type !== ButtonStyles.Link - ) { - throw new Error(Errors.BUTTON_REQUIRES_CUSTOM_ID); - } - - if (!validateLength(component.label, { max: 80 })) { - throw new Error(Errors.COMPONENT_LABEL_TOO_BIG); - } - - if ( - component.customId && - !validateLength(component.customId, { max: 100 }) - ) { - throw new Error(Errors.COMPONENT_CUSTOM_ID_TOO_BIG); - } - } - - if (!isActionRow(component)) { - continue; - } - - actionRowCounter++; - // Max of 5 ActionRows per message - if (actionRowCounter > 5) throw new Error(Errors.TOO_MANY_ACTION_ROWS); - - // Max of 5 Buttons (or any component type) within an ActionRow - if (component.components?.length > 5) { - throw new Error(Errors.TOO_MANY_COMPONENTS); - } - } - } - - if ( - content.nonce && - !validateLength(content.nonce.toString(), { max: 25 }) - ) { - throw new Error(Errors.NONCE_TOO_LONG); + validateComponents(content.components); } const result = await rest.runMethod( "post", endpoints.CHANNEL_MESSAGES(channelId), - camelKeysToSnakeCase({ + snakelize({ ...content, ...(content.messageReference?.messageId ? { - messageReference: { - ...content.messageReference, - failIfNotExists: content.messageReference.failIfNotExists === true, - }, - } + messageReference: { + ...content.messageReference, + failIfNotExists: content.messageReference.failIfNotExists === true, + }, + } : {}), - }), + }) ); return structures.createDiscordenoMessage(result); diff --git a/src/helpers/messages/unpin_message.ts b/src/helpers/messages/unpin_message.ts index 1af948620..ee7542c42 100644 --- a/src/helpers/messages/unpin_message.ts +++ b/src/helpers/messages/unpin_message.ts @@ -3,16 +3,10 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Unpin a message in a channel. Requires MANAGE_MESSAGES. */ -export async function unpin( - channelId: string, - messageId: string, -): Promise { +export async function unpin(channelId: bigint, messageId: bigint): Promise { await requireBotChannelPermissions(channelId, ["MANAGE_MESSAGES"]); - return await rest.runMethod( - "delete", - endpoints.CHANNEL_PIN(channelId, messageId), - ); + return await rest.runMethod("delete", endpoints.CHANNEL_PIN(channelId, messageId)); } // aliases diff --git a/src/helpers/members/edit_bot_profile.ts b/src/helpers/misc/edit_bot_profile.ts similarity index 90% rename from src/helpers/members/edit_bot_profile.ts rename to src/helpers/misc/edit_bot_profile.ts index 2e4fd6c7c..11efed089 100644 --- a/src/helpers/members/edit_bot_profile.ts +++ b/src/helpers/misc/edit_bot_profile.ts @@ -1,6 +1,6 @@ import { rest } from "../../rest/rest.ts"; -import { Errors } from "../../types/misc/errors.ts"; -import { User } from "../../types/mod.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; +import type { User } from "../../types/users/user.ts"; import { endpoints } from "../../util/constants.ts"; import { urlToBase64 } from "../../util/utils.ts"; diff --git a/src/helpers/misc/edit_bot_status.ts b/src/helpers/misc/edit_bot_status.ts index bfed6b0bc..5c05cabce 100644 --- a/src/helpers/misc/edit_bot_status.ts +++ b/src/helpers/misc/edit_bot_status.ts @@ -1,17 +1,13 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayOpcodes } from "../../types/codes/gateway_opcodes.ts"; import type { StatusUpdate } from "../../types/gateway/status_update.ts"; -import { sendShardMessage } from "../../ws/send_shard_message.ts"; import { ws } from "../../ws/ws.ts"; export function editBotStatus(data: Omit) { ws.shards.forEach((shard) => { - eventHandlers.debug?.( - "loop", - `Running forEach loop in editBotStatus function.`, - ); + eventHandlers.debug?.("loop", `Running forEach loop in editBotStatus function.`); - sendShardMessage(shard, { + ws.sendShardMessage(shard, { op: DiscordGatewayOpcodes.StatusUpdate, d: { since: null, diff --git a/src/helpers/misc/get_gateway_bot.ts b/src/helpers/misc/get_gateway_bot.ts index d765780be..ac7594177 100644 --- a/src/helpers/misc/get_gateway_bot.ts +++ b/src/helpers/misc/get_gateway_bot.ts @@ -1,5 +1,5 @@ import { rest } from "../../rest/rest.ts"; -import { GetGatewayBot } from "../../types/gateway/get_gateway_bot.ts"; +import type { GetGatewayBot } from "../../types/gateway/get_gateway_bot.ts"; import { endpoints } from "../../util/constants.ts"; /** Get the bots Gateway metadata that can help during the operation of large or sharded bots. */ diff --git a/src/helpers/misc/get_user.ts b/src/helpers/misc/get_user.ts index 9913acddc..f4632beb5 100644 --- a/src/helpers/misc/get_user.ts +++ b/src/helpers/misc/get_user.ts @@ -1,8 +1,8 @@ import { rest } from "../../rest/rest.ts"; -import { User } from "../../types/users/user.ts"; +import type { User } from "../../types/users/user.ts"; import { endpoints } from "../../util/constants.ts"; /** This function will return the raw user payload in the rare cases you need to fetch a user directly from the API. */ -export async function getUser(userId: string) { +export async function getUser(userId: bigint) { return await rest.runMethod("get", endpoints.USER(userId)); } diff --git a/src/helpers/mod.ts b/src/helpers/mod.ts index 44bb6a467..5d928852c 100644 --- a/src/helpers/mod.ts +++ b/src/helpers/mod.ts @@ -13,19 +13,7 @@ import { getPins } from "./channels/get_pins.ts"; import { isChannelSynced } from "./channels/is_channel_synced.ts"; import { startTyping } from "./channels/start_typing.ts"; import { swapChannels } from "./channels/swap_channels.ts"; -import { batchEditSlashCommandPermissions } from "./commands/batch_edit_slash_command_permissions.ts"; -import { createSlashCommand } from "./commands/create_slash_command.ts"; -import { deleteSlashCommand } from "./commands/delete_slash_command.ts"; -import { deleteSlashResponse } from "./commands/delete_slash_response.ts"; -import { editSlashCommandPermissions } from "./commands/edit_slash_command_permissions.ts"; -import { editSlashResponse } from "./commands/edit_slash_response.ts"; -import { getSlashCommand } from "./commands/get_slash_command.ts"; -import { getSlashCommands } from "./commands/get_slash_commands.ts"; -import { getSlashCommandPermission } from "./commands/get_slash_command_permission.ts"; -import { getSlashCommandPermissions } from "./commands/get_slash_command_permissions.ts"; -import { sendInteractionResponse } from "./commands/send_interaction_response.ts"; -import { upsertSlashCommand } from "./commands/upsert_slash_command.ts"; -import { upsertSlashCommands } from "./commands/upsert_slash_commands.ts"; +import { updateBotVoiceState } from "./channels/update_voice_state.ts"; import { addDiscoverySubcategory } from "./discovery/add_discovery_subcategory.ts"; import { editDiscovery } from "./discovery/edit_discovery.ts"; import { getDiscoveryCategories } from "./discovery/get_discovery_categories.ts"; @@ -61,6 +49,20 @@ import { guildSplashURL } from "./guilds/guild_splash_url.ts"; import { leaveGuild } from "./guilds/leave_guild.ts"; import { deleteIntegration } from "./integrations/delete_integration.ts"; import { getIntegrations } from "./integrations/get_integrations.ts"; +import { batchEditSlashCommandPermissions } from "./interactions/commands/batch_edit_slash_command_permissions.ts"; +import { createSlashCommand } from "./interactions/commands/create_slash_command.ts"; +import { deleteSlashCommand } from "./interactions/commands/delete_slash_command.ts"; +import { deleteSlashResponse } from "./interactions/commands/delete_slash_response.ts"; +import { editSlashCommandPermissions } from "./interactions/commands/edit_slash_command_permissions.ts"; +import { editSlashResponse } from "./interactions/commands/edit_slash_response.ts"; +import { getSlashCommand } from "./interactions/commands/get_slash_command.ts"; +import { getSlashCommands } from "./interactions/commands/get_slash_commands.ts"; +import { getSlashCommandPermission } from "./interactions/commands/get_slash_command_permission.ts"; +import { getSlashCommandPermissions } from "./interactions/commands/get_slash_command_permissions.ts"; +import { upsertSlashCommand } from "./interactions/commands/upsert_slash_command.ts"; +import { upsertSlashCommands } from "./interactions/commands/upsert_slash_commands.ts"; +import { getOriginalInteractionResponse } from "./interactions/get_original_interaction_response.ts"; +import { sendInteractionResponse } from "./interactions/send_interaction_response.ts"; import { createInvite } from "./invites/create_invite.ts"; import { deleteInvite } from "./invites/delete_invite.ts"; import { getChannelInvites } from "./invites/get_channel_invites.ts"; @@ -70,7 +72,6 @@ import { avatarURL } from "./members/avatar_url.ts"; import { ban, banMember } from "./members/ban_member.ts"; import { disconnectMember } from "./members/disconnect_member.ts"; import { editBotNickname } from "./members/edit_bot_nickname.ts"; -import { editBotProfile } from "./members/edit_bot_profile.ts"; import { editMember } from "./members/edit_member.ts"; import { fetchMembers } from "./members/fetch_members.ts"; import { getMember } from "./members/get_member.ts"; @@ -95,6 +96,7 @@ import { removeReaction } from "./messages/remove_reaction.ts"; import { removeReactionEmoji } from "./messages/remove_reaction_emoji.ts"; import { sendMessage } from "./messages/send_message.ts"; import { unpin, unpinMessage } from "./messages/unpin_message.ts"; +import { editBotProfile } from "./misc/edit_bot_profile.ts"; import { editBotStatus } from "./misc/edit_bot_status.ts"; import { getGatewayBot } from "./misc/get_gateway_bot.ts"; import { getUser } from "./misc/get_user.ts"; @@ -111,6 +113,9 @@ import { editGuildTemplate } from "./templates/edit_guild_template.ts"; import { getGuildTemplates } from "./templates/get_guild_templates.ts"; import { getTemplate } from "./templates/get_template.ts"; import { syncGuildTemplate } from "./templates/sync_guild_template.ts"; +// Type Guards +import { isActionRow } from "./type_guards/is_action_row.ts"; +import { isButton } from "./type_guards/is_button.ts"; import { createWebhook } from "./webhooks/create_webhook.ts"; import { deleteWebhook } from "./webhooks/delete_webhook.ts"; import { deleteWebhookMessage } from "./webhooks/delete_webhook_message.ts"; @@ -118,13 +123,15 @@ import { deleteWebhookWithToken } from "./webhooks/delete_webhook_with_token.ts" import { editWebhook } from "./webhooks/edit_webhook.ts"; import { editWebhookMessage } from "./webhooks/edit_webhook_message.ts"; import { editWebhookWithToken } from "./webhooks/edit_webhook_with_token.ts"; -import { executeWebhook } from "./webhooks/execute_webhook.ts"; import { getWebhook } from "./webhooks/get_webhook.ts"; import { getWebhooks } from "./webhooks/get_webhooks.ts"; +import { getWebhookMessage } from "./webhooks/get_webhook_message.ts"; import { getWebhookWithToken } from "./webhooks/get_webhook_with_token.ts"; -// Type Guards -import { isActionRow } from "./type_guards/is_action_row.ts"; -import { isButton } from "./type_guards/is_button.ts"; +import { sendWebhook } from "./webhooks/send_webhook.ts"; +import { createStageInstance } from "./channels/create_stage_instance.ts"; +import { updateStageInstance } from "./channels/update_stage_instance.ts"; +import { getStageInstance } from "./channels/get_stage_instance.ts"; +import { deleteStageInstance } from "./channels/delete_stage_instance.ts"; export { addDiscoverySubcategory, @@ -145,6 +152,7 @@ export { createInvite, createRole, createSlashCommand, + createStageInstance, createWebhook, deleteChannel, deleteChannelOverwrite, @@ -158,6 +166,7 @@ export { deleteRole, deleteSlashCommand, deleteSlashResponse, + deleteStageInstance, deleteWebhook, deleteWebhookMessage, deleteWebhookWithToken, @@ -181,7 +190,6 @@ export { editWelcomeScreen, editWidget, emojiURL, - executeWebhook, fetchMembers, followChannel, getAuditLogs, @@ -206,6 +214,7 @@ export { getMembers, getMessage, getMessages, + getOriginalInteractionResponse, getPins, getPruneCount, getReactions, @@ -214,11 +223,13 @@ export { getSlashCommandPermission, getSlashCommandPermissions, getSlashCommands, + getStageInstance, getTemplate, getUser, getVanityURL, getVoiceRegions, getWebhook, + getWebhookMessage, getWebhooks, getWebhookWithToken, getWelcomeScreen, @@ -247,6 +258,7 @@ export { sendDirectMessage, sendInteractionResponse, sendMessage, + sendWebhook, startTyping, swapChannels, syncGuildTemplate, @@ -254,6 +266,8 @@ export { unbanMember, unpin, unpinMessage, + updateBotVoiceState, + updateStageInstance, upsertSlashCommand, upsertSlashCommands, validDiscoveryTerm, @@ -275,6 +289,11 @@ export let helpers = { isChannelSynced, startTyping, swapChannels, + updateBotVoiceState, + createStageInstance, + getStageInstance, + updateStageInstance, + deleteStageInstance, // commands createSlashCommand, deleteSlashCommand, @@ -289,6 +308,7 @@ export let helpers = { getSlashCommands, upsertSlashCommand, upsertSlashCommands, + getOriginalInteractionResponse, // emojis createEmoji, deleteEmoji, @@ -392,10 +412,11 @@ export let helpers = { editWebhookMessage, editWebhookWithToken, editWebhook, - executeWebhook, + sendWebhook, getWebhookWithToken, getWebhook, getWebhooks, + getWebhookMessage, }; export type Helpers = typeof helpers; diff --git a/src/helpers/roles/add_role.ts b/src/helpers/roles/add_role.ts index 6bf738ede..367e3911c 100644 --- a/src/helpers/roles/add_role.ts +++ b/src/helpers/roles/add_role.ts @@ -1,33 +1,17 @@ import { botId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; -import { - isHigherPosition, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; +import { isHigherPosition, requireBotGuildPermissions } from "../../util/permissions.ts"; /** Add a role to the member */ -export async function addRole( - guildId: string, - memberId: string, - roleId: string, - reason?: string, -) { - const isHigherRolePosition = await isHigherPosition( - guildId, - botId, - roleId, - ); +export async function addRole(guildId: bigint, memberId: bigint, roleId: bigint, reason?: string) { + const isHigherRolePosition = await isHigherPosition(guildId, botId, roleId); if (!isHigherRolePosition) { throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW); } await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); - return await rest.runMethod( - "put", - endpoints.GUILD_MEMBER_ROLE(guildId, memberId, roleId), - { reason }, - ); + return await rest.runMethod("put", endpoints.GUILD_MEMBER_ROLE(guildId, memberId, roleId), { reason }); } diff --git a/src/helpers/roles/create_role.ts b/src/helpers/roles/create_role.ts index 9f81b3911..978a790f3 100644 --- a/src/helpers/roles/create_role.ts +++ b/src/helpers/roles/create_role.ts @@ -2,38 +2,31 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; import { CreateGuildRole } from "../../types/guilds/create_guild_role.ts"; -import { Role } from "../../types/permissions/role.ts"; +import type { Role } from "../../types/permissions/role.ts"; import { endpoints } from "../../util/constants.ts"; -import { - calculateBits, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; +import { calculateBits, requireBotGuildPermissions } from "../../util/permissions.ts"; /** Create a new role for the guild. Requires the MANAGE_ROLES permission. */ -export async function createRole( - guildId: string, - options: CreateGuildRole, - reason?: string, -) { +export async function createRole(guildId: bigint, options: CreateGuildRole, reason?: string) { await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); - const result = await rest.runMethod( - "post", - endpoints.GUILD_ROLES(guildId), - { - ...options, - permissions: calculateBits(options?.permissions || []), - reason, - }, - ); + const result = await rest.runMethod("post", endpoints.GUILD_ROLES(guildId), { + ...options, + permissions: calculateBits(options?.permissions || []), + reason, + }); const role = await structures.createDiscordenoRole({ role: result, guildId, }); - const guild = await cacheHandlers.get("guilds", guildId); - guild?.roles.set(role.id, role); - // TODO: ADD TO CACHE? + const guild = await cacheHandlers.get("guilds", guildId); + if (guild) { + guild.roles.set(role.id, role); + + await cacheHandlers.set("guilds", guildId, guild); + } + return role; } diff --git a/src/helpers/roles/delete_role.ts b/src/helpers/roles/delete_role.ts index f9d134545..8825234f9 100644 --- a/src/helpers/roles/delete_role.ts +++ b/src/helpers/roles/delete_role.ts @@ -3,11 +3,8 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; /** Delete a guild role. Requires the MANAGE_ROLES permission. */ -export async function deleteRole(guildId: string, id: string) { +export async function deleteRole(guildId: bigint, id: bigint) { await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); - return await rest.runMethod( - "delete", - endpoints.GUILD_ROLE(guildId, id), - ); + return await rest.runMethod("delete", endpoints.GUILD_ROLE(guildId, id)); } diff --git a/src/helpers/roles/edit_role.ts b/src/helpers/roles/edit_role.ts index b3c6e8784..35ef00dbf 100644 --- a/src/helpers/roles/edit_role.ts +++ b/src/helpers/roles/edit_role.ts @@ -1,30 +1,18 @@ import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; -import { CreateGuildRole, Role } from "../../types/mod.ts"; +import type { CreateGuildRole } from "../../types/guilds/create_guild_role.ts"; +import type { Role } from "../../types/permissions/role.ts"; import { endpoints } from "../../util/constants.ts"; -import { - calculateBits, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; +import { calculateBits, requireBotGuildPermissions } from "../../util/permissions.ts"; /** Edit a guild role. Requires the MANAGE_ROLES permission. */ -export async function editRole( - guildId: string, - id: string, - options: CreateGuildRole, -) { +export async function editRole(guildId: bigint, id: bigint, options: CreateGuildRole) { await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); - const result = await rest.runMethod( - "patch", - endpoints.GUILD_ROLE(guildId, id), - { - ...options, - permissions: options.permissions - ? calculateBits(options.permissions) - : undefined, - }, - ); + const result = await rest.runMethod("patch", endpoints.GUILD_ROLE(guildId, id), { + ...options, + permissions: options.permissions ? calculateBits(options.permissions) : undefined, + }); return await structures.createDiscordenoRole({ role: result, guildId }); } diff --git a/src/helpers/roles/get_roles.ts b/src/helpers/roles/get_roles.ts index ace6fe351..5dc55535e 100644 --- a/src/helpers/roles/get_roles.ts +++ b/src/helpers/roles/get_roles.ts @@ -1,5 +1,7 @@ +import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { Role } from "../../types/permissions/role.ts"; +import { structures } from "../../structures/mod.ts"; +import type { Role } from "../../types/permissions/role.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; @@ -8,17 +10,24 @@ import { requireBotGuildPermissions } from "../../util/permissions.ts"; * * ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your roles will be cached in your guild.** */ -export async function getRoles(guildId: string) { +export async function getRoles(guildId: bigint, addToCache = true) { await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); - const result = await rest.runMethod( - "get", - endpoints.GUILD_ROLES(guildId), + const result = await rest.runMethod("get", endpoints.GUILD_ROLES(guildId)); + + const roleStructures = await Promise.all( + result.map(async (role) => await structures.createDiscordenoRole({ role, guildId })) ); - // TODO: addToCache + const roles = new Collection(roleStructures.map((role) => [role.id, role])); - return new Collection( - result.map((role) => [role.id, role]), - ); + if (addToCache) { + const guild = await cacheHandlers.get("guilds", guildId); + if (guild) { + guild.roles = roles; + await cacheHandlers.set("guilds", guild.id, guild); + } + } + + return roleStructures; } diff --git a/src/helpers/roles/remove_role.ts b/src/helpers/roles/remove_role.ts index 5a98501ef..d97c78314 100644 --- a/src/helpers/roles/remove_role.ts +++ b/src/helpers/roles/remove_role.ts @@ -1,35 +1,17 @@ import { botId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; -import { Errors } from "../../types/misc/errors.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { endpoints } from "../../util/constants.ts"; -import { - isHigherPosition, - requireBotGuildPermissions, -} from "../../util/permissions.ts"; +import { isHigherPosition, requireBotGuildPermissions } from "../../util/permissions.ts"; /** Remove a role from the member */ -export async function removeRole( - guildId: string, - memberId: string, - roleId: string, - reason?: string, -) { - const isHigherRolePosition = await isHigherPosition( - guildId, - botId, - roleId, - ); - if ( - !isHigherRolePosition - ) { +export async function removeRole(guildId: bigint, memberId: bigint, roleId: bigint, reason?: string) { + const isHigherRolePosition = await isHigherPosition(guildId, botId, roleId); + if (!isHigherRolePosition) { throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW); } await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); - return await rest.runMethod( - "delete", - endpoints.GUILD_MEMBER_ROLE(guildId, memberId, roleId), - { reason }, - ); + return await rest.runMethod("delete", endpoints.GUILD_MEMBER_ROLE(guildId, memberId, roleId), { reason }); } diff --git a/src/helpers/templates/create_guild_from_template.ts b/src/helpers/templates/create_guild_from_template.ts index 770f71299..f3ef0d468 100644 --- a/src/helpers/templates/create_guild_from_template.ts +++ b/src/helpers/templates/create_guild_from_template.ts @@ -1,33 +1,29 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; -import { Guild } from "../../types/guilds/guild.ts"; -import { CreateGuildFromTemplate } from "../../types/templates/create_guild_from_template.ts"; +import { structures } from "../../structures/mod.ts"; +import type { Guild } from "../../types/guilds/guild.ts"; +import type { CreateGuildFromTemplate } from "../../types/templates/create_guild_from_template.ts"; import { endpoints } from "../../util/constants.ts"; import { urlToBase64 } from "../../util/utils.ts"; +import { ws } from "../../ws/ws.ts"; /** * Create a new guild based on a template * NOTE: This endpoint can be used only by bots in less than 10 guilds. */ -export async function createGuildFromTemplate( - templateCode: string, - data: CreateGuildFromTemplate, -) { +export async function createGuildFromTemplate(templateCode: string, data: CreateGuildFromTemplate) { if ((await cacheHandlers.size("guilds")) >= 10) { - throw new Error( - "This function can only be used by bots in less than 10 guilds.", - ); + throw new Error("This function can only be used by bots in less than 10 guilds."); } if (data.icon) { data.icon = await urlToBase64(data.icon); } - // TODO: discordeno guild? + const createdGuild = await rest.runMethod("post", endpoints.GUILD_TEMPLATE(templateCode), data); - return await rest.runMethod( - "post", - endpoints.GUILD_TEMPLATE(templateCode), - data, + return await structures.createDiscordenoGuild( + createdGuild, + Number((BigInt(createdGuild.id) >> 22n % BigInt(ws.botGatewayData.shards)).toString()) ); } diff --git a/src/helpers/templates/create_guild_template.ts b/src/helpers/templates/create_guild_template.ts index a66d8f069..7dc10961c 100644 --- a/src/helpers/templates/create_guild_template.ts +++ b/src/helpers/templates/create_guild_template.ts @@ -1,5 +1,5 @@ import { rest } from "../../rest/rest.ts"; -import { Template } from "../../types/templates/template.ts"; +import type { Template } from "../../types/templates/template.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; @@ -9,10 +9,7 @@ import { requireBotGuildPermissions } from "../../util/permissions.ts"; * @param name name of the template (1-100 characters) * @param description description for the template (0-120 characters */ -export async function createGuildTemplate( - guildId: string, - data: Template, -) { +export async function createGuildTemplate(guildId: bigint, data: Template) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); if (data.name.length < 1 || data.name.length > 100) { @@ -23,9 +20,5 @@ export async function createGuildTemplate( throw new Error("The description can only be in between 0-120 characters."); } - return await rest.runMethod