diff --git a/.github/workflows/client-test.yml b/.github/workflows/client-test.yml deleted file mode 100644 index b71ad0204..000000000 --- a/.github/workflows/client-test.yml +++ /dev/null @@ -1,142 +0,0 @@ -name: Client Test - -on: - pull_request: - push: - -jobs: - build-type-and-test: - name: Build Type and Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - run: yarn install --immutable - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v3 - with: - path: .turbo - key: ${{ runner.os }}-turbo-build:type-${{ github.sha }} - - name: Build Type and Test - run: yarn build:type --cache-dir=".turbo" --filter=./packages/client - - build-dist: - name: Build Dist - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - run: yarn install --immutable - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v3 - with: - path: .turbo - key: ${{ runner.os }}-turbo-build-${{ github.sha }} - - name: Type Test - run: yarn build --cache-dir=".turbo" --filter=./packages/client - - format-unit-and-integration-test: - name: Format Test - runs-on: ubuntu-latest - needs: build-type-and-test - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - run: yarn install --immutable - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v3 - with: - path: .turbo - key: ${{ runner.os }}-turbo-lint-${{ github.sha }} - - name: Build type cache - if: steps.turbo-cache.outputs.cache-hit != 'true' - uses: actions/cache@v3 - with: - path: .turbo - key: ${{ runner.os }}-turbo-build:type-${{ github.sha }} - - name: Check Formatting - run: yarn lint --cache-dir=".turbo" --filter=./packages/client - - test-type-unit-and-integration-test: - name: Test Type Test - runs-on: ubuntu-latest - needs: build-type-and-test - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - run: yarn install --immutable - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v3 - with: - path: .turbo - key: ${{ runner.os }}-turbo-unit-and-integration-test:test-type-${{ github.sha }} - - name: Build type cache - if: steps.turbo-cache.outputs.cache-hit != 'true' - uses: actions/cache@v3 - with: - path: .turbo - key: ${{ runner.os }}-turbo-build:type-${{ github.sha }} - - name: Test Type Test - run: yarn test:test-type --cache-dir=".turbo" --filter=./packages/client - - # Not using matrix because test later on cant needs a specific job - client-unit-test: - name: Client - needs: build-dist - uses: ./.github/workflows/unit-test.yml - with: - package: client - client-other-runtime-test: - name: Client - needs: client-unit-test - uses: ./.github/workflows/other-runtime-unit-test.yml - with: - package: client diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e0d3ec953..11c776b0f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - package: ["gateway", "rest", "types", "utils", "bot", "client"] + package: ["gateway", "rest", "types", "utils", "bot"] steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/README.md b/README.md index 062e4316f..6b6d2f751 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,8 @@ Discordeno is actively maintained to guarantee **excellent performance, latest f - **Simple, Efficient, and Lightweight**: Discordeno is lightweight, simple to use, and adaptable. - By default: No caching. -- **Functional & Class API**: Discordeno is flexible enough to provide both methods. +- **Functional API**: - The functional API eliminates the challenges of extending built-in classes and inheritance while ensuring overall simple but performant code. - - The class based API, client package, provides a similar api as the [Eris](https://github.com/abalabahaha/eris) library to provide the best class based experience. - **Cross Runtime**: Supports the Node.js, Deno, and Bun runtimes. - **Standalone components**: Discordeno offers the option to have practically any component of a bot as a separate piece, including standalone REST, gateways, custom caches, and more. diff --git a/codecov.yml b/codecov.yml index d68eb3e8f..382119b89 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,9 +1,4 @@ flags: - client: - carryforward: false - client-unit: - carryforward: false - discordeno: carryforward: false discordeno-unit: diff --git a/denoImportMap.json b/denoImportMap.json index 93d032901..36dd25d67 100644 --- a/denoImportMap.json +++ b/denoImportMap.json @@ -17,7 +17,6 @@ "@discordeno/types": "./packages/types/dist/index.js", "@discordeno/rest": "./packages/rest/dist/index.js", "@discordeno/gateway": "./packages/gateway/dist/index.js", - "@discordeno/bot": "./packages/bot/dist/index.js", - "@discordeno/client": "./packages/client/dist/index.js" + "@discordeno/bot": "./packages/bot/dist/index.js" } } \ No newline at end of file diff --git a/packages/client/.c8rc.json b/packages/client/.c8rc.json deleted file mode 100644 index f135073e4..000000000 --- a/packages/client/.c8rc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "all": true, - "src": "src", - "reporter": ["text", "lcov"], - "include": ["src/**/*.ts"], - "exclude": ["tests"] -} diff --git a/packages/client/.mocharc.json b/packages/client/.mocharc.json deleted file mode 100644 index 4689d4a04..000000000 --- a/packages/client/.mocharc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "require": "ts-node/register", - "loader": "ts-node/esm", - "recursive": true, - "timeout": 2000, - "watch-extensions": "ts", - "watch-files": ["src", "tests"], - "enable-source-maps": true, - "parallel": false -} diff --git a/packages/client/.swcrc b/packages/client/.swcrc deleted file mode 100644 index 0000defd2..000000000 --- a/packages/client/.swcrc +++ /dev/null @@ -1,24 +0,0 @@ -{ - "jsc": { - "parser": { - "syntax": "typescript", - "decorators": true, - "dynamicImport": true - }, - "transform": { - "legacyDecorator": true, - "decoratorMetadata": true - }, - "target": "es2022", - "keepClassNames": true, - "loose": true - }, - "module": { - "type": "es6", - "strict": false, - "strictMode": true, - "lazy": false, - "noInterop": false - }, - "sourceMaps": true -} diff --git a/packages/client/README.md b/packages/client/README.md deleted file mode 100644 index 848e2bf39..000000000 --- a/packages/client/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# @discordeno/client - -This package is intended to provide a class based implementation on top of discordeno. It is based on Eris libraries API as it is just amazing! This also means since the API is near 1:1 the migration for Eris -> Discordeno is as simple as: - -```ts -npm uninstall eris && npm install @discordeno/client -``` - -Then replace all `from 'eris';` with `from '@discordeno/client'` and enjoy! - -## Credits - -https://github.com/abalabahaha/eris - -## Eris License - -The MIT License (MIT) - -Copyright (c) 2016-2021 abalabahaha - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. \ No newline at end of file diff --git a/packages/client/package.json b/packages/client/package.json deleted file mode 100644 index c1b7af0f1..000000000 --- a/packages/client/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@discordeno/client", - "version": "19.0.0-alpha.1", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "type": "module", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/discordeno/discordeno.git" - }, - "scripts": { - "build": "swc --delete-dir-on-start src --out-dir dist", - "build:type": "tsc --declaration --emitDeclarationOnly --declarationDir dist", - "release-build": "yarn build && yarn build:type", - "fmt": "eslint --fix \"src/**/*.ts*\"", - "lint": "eslint \"src/**/*.ts*\"", - "test:unit-coverage": "c8 mocha --no-warnings 'tests/**/*.spec.ts'", - "test:unit": "c8 --r lcov mocha --no-warnings 'tests/**/*.spec.ts' && node ../../scripts/coveragePathFixing.js client", - "test:deno-unit": "swc tests --delete-dir-on-start --out-dir denoTestsDist && node ../../scripts/fixDenoTestExtension.js && deno test -A --import-map ../../denoImportMap.json denoTestsDist", - "test:unit:watch": "mocha --no-warnings --watch --parallel 'tests/**/*.spec.ts'", - "test:type": "tsc --noEmit", - "test:test-type": "tsc --project tsconfig.test.json" - }, - "dependencies": { - "@discordeno/gateway": "19.0.0-alpha.1", - "@discordeno/rest": "19.0.0-alpha.1", - "@discordeno/types": "19.0.0-alpha.1", - "@discordeno/utils": "19.0.0-alpha.1" - }, - "devDependencies": { - "@swc/cli": "^0.1.62", - "@swc/core": "^1.3.40", - "@types/chai": "^4.3.4", - "@types/mocha": "^10.0.1", - "@types/node": "^18.15.3", - "@types/sinon": "^10.0.13", - "c8": "^7.13.0", - "chai": "^4.3.7", - "eslint": "^8.36.0", - "eslint-config-discordeno": "*", - "mocha": "^10.2.0", - "sinon": "^15.0.2", - "ts-node": "^10.9.1", - "tsconfig": "*", - "typescript": "^4.9.5" - } -} diff --git a/packages/client/src/Base.ts b/packages/client/src/Base.ts deleted file mode 100644 index f20717004..000000000 --- a/packages/client/src/Base.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { BigString } from './Client.js' - -export class Base { - /** Internal storage of the id done in bigint to preserve memory */ - _id: bigint - - constructor(id: BigString) { - this._id = BigInt(id) - } - - /** The snowflake id */ - get id(): string { - return this._id.toString() - } - - set id(value: BigString) { - this._id = BigInt(value) - } - - get createdAt(): number { - return Number(this._id / 4194304n + 1420070400000n) - } - - /** - * Calculates the timestamp in milliseconds associated with a Discord ID/snowflake. - * @deprecated Recommend using Object.createdAt or Client.snowflakeToTimestamp if you want to get a timestamp from a id. This is not desired but supported only to keep a similar api to eris. - */ - static getCreatedAt(id: string): number { - return Base.getDiscordEpoch(id) + 1420070400000 - } - - /** - * Gets the number of milliseconds since epoch represented by an ID/snowflake - * @deprecated Recommend using Object.createdAt or Client.snowflakeToTimestamp if you want to get a timestamp from a id. This is not desired but supported only to keep a similar api to eris. - */ - static getDiscordEpoch(id: string): number { - // @ts-expect-error some eris magic at play here - return Math.floor(id / 4194304) - } - - toString(): string { - return `[${this.constructor.name} ${this.id}]` - } - - toJSON(props: string[] = []): Record { - const json = { - id: this.id, - createdAt: this.createdAt, - } - for (const prop of props) { - // @ts-expect-error some eris magic at play here - const value = this[prop] - const type = typeof value - if (value === undefined) { - continue - } else if ((type !== 'object' && type !== 'function' && type !== 'bigint') || value === null) { - // @ts-expect-error some eris magic at play here - json[prop] = value - } else if (value.toJSON !== undefined) { - // @ts-expect-error some eris magic at play here - json[prop] = value.toJSON() - } else if (value.values !== undefined) { - // @ts-expect-error some eris magic at play here - json[prop] = [...value.values()] - } else if (type === 'bigint') { - // @ts-expect-error some eris magic at play here - json[prop] = value.toString() - } else if (type === 'object') { - // @ts-expect-error some eris magic at play here - json[prop] = value - } - } - return json - } -} - -export default Base diff --git a/packages/client/src/Client.ts b/packages/client/src/Client.ts deleted file mode 100644 index 6a396ff80..000000000 --- a/packages/client/src/Client.ts +++ /dev/null @@ -1,2486 +0,0 @@ -/* eslint-disable @typescript-eslint/no-confusing-void-expression */ -/* eslint-disable no-prototype-builtins */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { - AllowedMentionsTypes, - ChannelTypes, - type DiscordAllowedMentions, - type DiscordAuditLogEntry, - type DiscordChannel, - type DiscordGetGatewayBot, - type DiscordGuild, - type DiscordIntegration, - type DiscordInvite, - type DiscordMember, - type DiscordMemberWithUser, - type DiscordMessage, - type DiscordRole, - type DiscordTemplate, - type DiscordThreadMember, - type DiscordUser, - type GatewayIntents, - type GetMessagesOptions, - type OverwriteTypes, -} from '@discordeno/types' -import { delay, getBotIdFromToken, iconBigintToHash, iconHashToBigInt, snakelize } from '@discordeno/utils' -import EventEmitter from 'node:events' -import Base from './Base.js' -import Collection from './Collection.js' -import type { IntentStrings } from './Constants.js' -import { Intents } from './Constants.js' -import { - CHANNEL, - CHANNEL_BULK_DELETE, - CHANNEL_CROSSPOST, - CHANNEL_FOLLOW, - CHANNEL_INVITES, - CHANNEL_MESSAGE, - CHANNEL_MESSAGES, - CHANNEL_MESSAGE_REACTION, - CHANNEL_MESSAGE_REACTIONS, - CHANNEL_MESSAGE_REACTION_USER, - CHANNEL_PERMISSION, - CHANNEL_PIN, - CHANNEL_PINS, - CHANNEL_TYPING, - CHANNEL_WEBHOOKS, - COMMAND, - COMMANDS, - COMMAND_PERMISSIONS, - CUSTOM_EMOJI_GUILD, - DISCOVERY_CATEGORIES, - DISCOVERY_VALIDATION, - GATEWAY, - GATEWAY_BOT, - GUILD, - GUILDS, - GUILD_AUDIT_LOGS, - GUILD_BAN, - GUILD_BANS, - GUILD_CHANNELS, - GUILD_COMMAND, - GUILD_COMMANDS, - GUILD_COMMAND_PERMISSIONS, - GUILD_DISCOVERY, - GUILD_DISCOVERY_CATEGORY, - GUILD_EMOJI, - GUILD_EMOJIS, - GUILD_INTEGRATION, - GUILD_INTEGRATIONS, - GUILD_INTEGRATION_SYNC, - GUILD_INVITES, - GUILD_MEMBER, - GUILD_MEMBERS, - GUILD_MEMBERS_SEARCH, - GUILD_MEMBER_ROLE, - GUILD_PREVIEW, - GUILD_PRUNE, - GUILD_ROLE, - GUILD_ROLES, - GUILD_STICKER, - GUILD_STICKERS, - GUILD_TEMPLATE, - GUILD_TEMPLATES, - GUILD_TEMPLATE_GUILD, - GUILD_VANITY_URL, - GUILD_VOICE_REGIONS, - GUILD_VOICE_STATE, - GUILD_WEBHOOKS, - GUILD_WELCOME_SCREEN, - GUILD_WIDGET, - GUILD_WIDGET_SETTINGS, - INTERACTION_RESPOND, - INVITE, - OAUTH2_APPLICATION, - STAGE_INSTANCE, - STAGE_INSTANCES, - STICKER, - STICKER_PACKS, - THREADS_ARCHIVED, - THREADS_ARCHIVED_JOINED, - THREADS_GUILD_ACTIVE, - THREAD_MEMBER, - THREAD_MEMBERS, - THREAD_WITHOUT_MESSAGE, - THREAD_WITH_MESSAGE, - USER, - USER_CHANNELS, - USER_GUILD, - USER_GUILDS, - VOICE_REGIONS, - WEBHOOK, - WEBHOOK_MESSAGE, - WEBHOOK_TOKEN, - WEBHOOK_TOKEN_SLACK, -} from './Endpoints.js' -import ShardManager from './gateway/ShardManager.js' -import RequestHandler from './RequestHandler.js' -import type CategoryChannel from './Structures/channels/Category.js' -import type Channel from './Structures/channels/Channel.js' -import type GuildChannel from './Structures/channels/Guild.js' -import type NewsChannel from './Structures/channels/News.js' -import PrivateChannel from './Structures/channels/Private.js' -import type StageChannel from './Structures/channels/Stage.js' -import type TextChannel from './Structures/channels/Text.js' -import type TextVoiceChannel from './Structures/channels/TextVoice.js' -import ThreadMember from './Structures/channels/threads/Member.js' -import type NewsThreadChannel from './Structures/channels/threads/NewsThread.js' -import type PrivateThreadChannel from './Structures/channels/threads/PrivateThread.js' -import type PublicThreadChannel from './Structures/channels/threads/PublicThread.js' -import type ThreadChannel from './Structures/channels/threads/Thread.js' -import type VoiceChannel from './Structures/channels/Voice.js' -import GuildAuditLogEntry from './Structures/guilds/AuditLogEntry.js' -import Guild from './Structures/guilds/Guild.js' -import GuildIntegration from './Structures/guilds/Integration.js' -import Member from './Structures/guilds/Member.js' -import GuildPreview from './Structures/guilds/Preview.js' -import Role from './Structures/guilds/Role.js' -import StageInstance from './Structures/guilds/StageInstance.js' -import GuildTemplate from './Structures/guilds/Template.js' -import type UnavailableGuild from './Structures/guilds/Unavailable.js' -import type { VoiceState } from './Structures/guilds/VoiceState.js' -import type AutocompleteInteraction from './Structures/interactions/Autocomplete.js' -import type CommandInteraction from './Structures/interactions/Command.js' -import type ComponentInteraction from './Structures/interactions/Component.js' -import type Interaction from './Structures/interactions/Interaction.js' -import type PingInteraction from './Structures/interactions/Ping.js' -import type UnknownInteraction from './Structures/interactions/Unknown.js' -import Invite from './Structures/Invite.js' -import Message from './Structures/Message.js' -import Permission from './Structures/Permission.js' -import type PermissionOverwrite from './Structures/PermissionOverwrite.js' -import ExtendedUser from './Structures/users/Extended.js' -import User from './Structures/users/User.js' -import type { - ActivityPartial, - AllowedMentions, - AnyChannel, - AnyGuildChannel, - ApplicationCommand, - ApplicationCommandPermissions, - ApplicationCommandStructure, - BotActivityType, - ChannelFollow, - ChannelPosition, - ClientEvents, - CreateChannelInviteOptions, - CreateChannelOptions, - CreateGuildOptions, - CreateStickerOptions, - CreateThreadOptions, - CreateThreadWithoutMessageOptions, - DiscoveryCategory, - DiscoveryMetadata, - DiscoveryOptions, - DiscoverySubcategoryResponse, - EditChannelOptions, - EditChannelPositionOptions, - EditStickerOptions, - Emoji, - EmojiOptions, - FileContent, - GetArchivedThreadsOptions, - GetGuildAuditLogOptions, - GetGuildBansOptions, - GetMessageReactionOptions, - GetPruneOptions, - GetRESTGuildMembersOptions, - GetRESTGuildsOptions, - GuildApplicationCommandPermissions, - GuildAuditLog, - GuildBan, - GuildOptions, - GuildTemplateOptions, - GuildVanity, - IntegrationOptions, - InteractionResponse, - ListedChannelThreads, - ListedGuildThreads, - MemberOptions, - MessageContent, - MessageContentEdit, - MessageWebhookContent, - OAuthApplicationInfo, - PruneMemberOptions, - PurgeChannelOptions, - RoleOptions, - SelfStatus, - StageInstanceOptions, - Sticker, - StickerPack, - VoiceRegion, - VoiceStateOptions, - Webhook, - WebhookOptions, - WebhookPayload, - WelcomeScreen, - WelcomeScreenOptions, - Widget, - WidgetData, -} from './typings.js' -import { generateChannelFrom } from './utils/generate.js' - -// TODO: api version -const API_VERSION = 10 - -export class Client extends EventEmitter { - /** The cleaned up version of the provided configurations for the client. */ - options: ParsedClientOptions - /** The token used for this client. */ - token: string - /** The timestamp in milliseconds when this client was created. */ - startTime = Date.now() - - CDN_URL = 'https://cdn.discordapp.com' - CLIENT_URL = 'https://discord.com' - - guilds = new Collection() - unavailableGuilds = new Collection() - users = new Collection() - _channelGuildMap = new Collection() - _threadGuildMap = new Collection() - _privateChannelMap = new Collection() - privateChannels = new Collection() - - guildShardMap: Record - - /** Rest manager. Not recommended. */ - requestHandler: RequestHandler - /** Gateway manager. Not recommended */ - shards: ShardManager - /** The gateway url to connect to. */ - gatewayURL: string = '' - /** Whether or not the client is fully ready. */ - ready = false - - /** The reconnection delay from the last time it tried. */ - lastReconnectDelay: number = 0 - /** The amount of times it has already tried to reconnect. */ - reconnectAttempts: number = 0 - /** The client user */ - user!: ExtendedUser - - constructor(token: string, options: ClientOptions) { - super() - - this.token = token - - this.options = { - apiVersion: options.apiVersion ?? 10, - // This is set below, - allowedMentions: {}, - defaultImageFormat: options.defaultImageFormat ?? 'png', - defaultImageSize: options.defaultImageSize ?? 128, - proxyURL: options.proxyURL, - proxyRestAuthorization: options.proxyRestAuthorization, - applicationId: options.applicationId ?? this.id, - messageLimit: options.messageLimit, - seedVoiceConnections: options.seedVoiceConnections ?? true, - shardConcurrency: options.shardConcurrency ?? 'auto', - maxShards: options.maxShards ?? 'auto', - compress: false, - // compress: options.compress ?? false, - firstShardID: options.firstShardID ?? 0, - lastShardID: options.lastShardID, - maxResumeAttempts: options.maxResumeAttempts ?? Infinity, - // Set up below - intents: 0, - autoreconnect: options.autoreconnect ?? true, - guildCreateTimeout: options.guildCreateTimeout ?? 2000, - reconnectDelay: options.reconnectDelay ?? ((lastDelay, attempts) => Math.pow(attempts + 1, 0.7) * 20000), - } - - if (options.intents !== undefined) { - // Resolve intents option to the proper integer - if (Array.isArray(options.intents)) { - let bitmask = 0 - for (const intent of options.intents) { - if (typeof intent === 'number') { - bitmask |= intent - } else if (Intents[intent]) { - bitmask |= Intents[intent] - } else { - this.emit('warn', `Unknown intent: ${intent}`) - } - } - this.options.intents = bitmask - } - } - - this.options.allowedMentions = this._formatAllowedMentions(options.allowedMentions) - - this.guildShardMap = {} - this.requestHandler = new RequestHandler(this, {}) - - this.shards = new ShardManager(this, { - concurrency: this.options.shardConcurrency, - }) - - // NO PROXY REST START ALARMS - if (!this.proxyURL) this.requestHandler.warnUser() - - this.shards = new ShardManager(this, { - concurrency: typeof this.options.shardConcurrency === 'number' ? this.options.shardConcurrency : undefined, - }) - - // Class related annoyance bug hack - this.connect = this.connect.bind(this) - } - - /** The amount of time in milliseconds that this client has been online for. */ - get uptime(): number { - return Date.now() - this.startTime - } - - /** The api version to use. */ - get apiVersion(): ApiVersions { - return this.options.apiVersion - } - - /** Change the api version when making requests. */ - set apiVersion(version: ApiVersions) { - this.options.apiVersion = version - } - - /** The base url that will be used when making requests for discord api. */ - get BASE_URL(): string { - return `/api/v${this.apiVersion}` - } - - /** The url to the REST proxy to send requests to. */ - get proxyURL(): string { - return this.options.proxyURL ?? '' - } - - /** The password/authorization to confirm that these request made to your rest proxy are indeed from you and not a hacker. */ - get proxyRestAuthorization(): string { - return this.options.proxyRestAuthorization ?? '' - } - - /** The application id(NOT the bot id). The bot id and application id are the same for newer bots but older bots have different ids. */ - get applicationId(): BigString { - return this.options.applicationId - } - - /** Whether or not to seed voice connections. */ - get seedVoiceConnections(): boolean { - return this.options.seedVoiceConnections - } - - get id(): BigString { - return getBotIdFromToken(this.token) - } - - get channelGuildMap(): Record { - return this._channelGuildMap.toRecord() - } - - get threadGuildMap(): Record { - return this._threadGuildMap.toRecord() - } - - get privateChannelMap(): Record { - return this._privateChannelMap.toRecord() - } - - on(event: K, listener: (...args: ClientEvents[K]) => void): this - on(event: string, listener: (...args: any[]) => void): this { - return super.on(event, listener) - } - - /** Tells all shards to connect. This will call `getBotGateway()`, which is ratelimited. */ - async connect(): Promise { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - if (typeof this.token !== 'string') throw new Error(`Invalid token "${this.token}"`) - - try { - const data = await this.getBotGateway() - - if (data.url.includes('?')) { - data.url = data.url.substring(0, data.url.indexOf('?')) - } - if (!data.url.endsWith('/')) { - data.url += '/' - } - this.gatewayURL = `${data.url}?v=${API_VERSION}&encoding=${'json'}` - - if (this.options.compress) { - this.gatewayURL += '&compress=zlib-stream' - } - - if (this.options.maxShards === 'auto') { - this.options.maxShards = data.shards - } - - if (this.options.lastShardID === undefined) { - this.options.lastShardID = data.shards - 1 - } - - if (this.options.shardConcurrency === 'auto' && data.session_start_limit && typeof data.session_start_limit.max_concurrency === 'number') { - this.shards.setConcurrency(data.session_start_limit.max_concurrency) - } - - for (let i = this.options.firstShardID; i <= this.options.lastShardID; ++i) { - this.shards.spawn(i) - } - } catch (err) { - if (!this.options.autoreconnect) { - throw err - } - - const reconnectDelay = await this.options.reconnectDelay(this.lastReconnectDelay, this.reconnectAttempts) - - await delay(reconnectDelay) - this.lastReconnectDelay = reconnectDelay - this.reconnectAttempts = this.reconnectAttempts + 1 - - return await this.connect() - } - } - - /** Make a GET request to the discord api. */ - async get(url: string): Promise { - return snakelize(await this.requestHandler.discordeno.get(url)) - } - - /** Make a POST request to the discord api. */ - async post( - url: string, - payload?: { - body?: Record - reason?: string - file?: FileContent | FileContent[] - }, - ): Promise { - return snakelize( - await this.requestHandler.discordeno.post(url, { - reason: payload?.reason, - file: payload?.file, - ...payload?.body, - }), - ) - } - - /** Make a PATCH request to the discord api. */ - async patch( - url: string, - payload?: { - body?: Record | null | string | any[] - reason?: string - file?: FileContent | FileContent[] - }, - ): Promise { - return snakelize( - await this.requestHandler.discordeno.patch( - url, - payload?.file ?? payload?.reason - ? { - reason: payload.reason, - file: payload.file, - // @ts-expect-error js hacks plz stop - ...payload.body, - } - : payload?.body, - ), - ) - } - - /** Make a PUT request to the discord api. */ - async put( - url: string, - payload?: { - body?: Record | any[] - reason?: string - }, - ): Promise { - return snakelize( - await this.requestHandler.discordeno.put( - url, - Array.isArray(payload?.body) - ? payload!.body - : { - reason: payload?.reason, - ...payload?.body, - }, - ), - ) - } - - /** Make a DELETE request to the discord api. */ - async delete(url: string, payload?: { reason?: string }) { - return await this.requestHandler.discordeno.delete(url, payload) - } - - /** Add a guild discovery subcategory */ - async addGuildDiscoverySubcategory(guildID: BigString, categoryID: BigString, reason?: string): Promise { - return await this.post(GUILD_DISCOVERY_CATEGORY(guildID, categoryID), { - reason, - }) - } - - /** Add a role to a guild member */ - async addGuildMemberRole(guildID: BigString, memberID: BigString, roleID: BigString, reason?: string): Promise { - return await this.put(GUILD_MEMBER_ROLE(guildID, memberID, roleID), { - reason, - }) - } - - /** Add a reaction to a message */ - async addMessageReaction(channelID: BigString, messageID: BigString, reaction: string): Promise { - if (reaction === decodeURI(reaction)) { - reaction = encodeURIComponent(reaction) - } - - return await this.put(CHANNEL_MESSAGE_REACTION_USER(channelID, messageID, reaction, '@me')) - } - - /** Ban a user from a guild */ - async banGuildMember(guildID: BigString, userID: BigString, deleteMessageDays = 0, reason?: string): Promise { - if (deleteMessageDays < 0 || deleteMessageDays > 7) { - return await Promise.reject(new Error(`Invalid deleteMessageDays value (${deleteMessageDays}), should be a number between 0-7 inclusive`)) - } - - return await this.put(GUILD_BAN(guildID, userID), { - reason, - body: { delete_message_days: deleteMessageDays }, - }) - } - - /** Bulk create/edit global application commands */ - async bulkEditCommands(commands: ApplicationCommandStructure[]): Promise { - for (const command of commands) { - if (command.name !== undefined) { - if (command.type === 1 || command.type === undefined) { - command.name = command.name.toLowerCase() - if (!command.name.match(/^[\w-]{1,32}$/)) { - throw new Error('Slash Command names must match the regular expression "^[\\w-]{1,32}$"') - } - } - } - } - - return await this.put(COMMANDS(this.applicationId), { body: commands }) - } - - /** Bulk create/edit guild application commands */ - async bulkEditGuildCommands(guildID: BigString, commands: ApplicationCommand[]): Promise { - for (const command of commands) { - if (command.name !== undefined) { - if (command.type === 1 || command.type === undefined) { - command.name = command.name.toLowerCase() - if (!command.name.match(/^[\w-]{1,32}$/)) { - throw new Error('Slash Command names must match the regular expression "^[\\w-]{1,32}$"') - } - } - } - } - - return await this.put(GUILD_COMMANDS(this.applicationId, guildID), { - body: commands, - }) - } - - /** Create a channel in a guild */ - async createChannel(guildID: BigString, name: string): Promise - async createChannel(guildID: BigString, name: string, type: ChannelTypes.GuildText, options?: CreateChannelOptions): Promise - async createChannel(guildID: BigString, name: string, type: ChannelTypes.GuildVoice, options?: CreateChannelOptions): Promise - async createChannel(guildID: BigString, name: string, type: ChannelTypes.GuildCategory, options?: CreateChannelOptions): Promise - async createChannel(guildID: BigString, name: string, type: ChannelTypes.GuildAnnouncement, options?: CreateChannelOptions): Promise - async createChannel(guildID: BigString, name: string, type: ChannelTypes.GuildStageVoice, options?: CreateChannelOptions): Promise - async createChannel(guildID: BigString, name: string, type?: number, options?: CreateChannelOptions): Promise { - return await this.post(GUILD_CHANNELS(guildID), { - reason: options?.reason, - body: { - name, - type: type ?? ChannelTypes.GuildText, - bitrate: options?.bitrate, - nsfw: options?.nsfw, - parent_id: options?.parentID, - permission_overwrites: options?.permissionOverwrites, - position: options?.position, - rate_limit_per_user: options?.rateLimitPerUser, - topic: options?.topic, - user_limit: options?.userLimit, - }, - }).then((channel) => generateChannelFrom(channel, this)) - } - - /** Create an invite for a channel */ - async createChannelInvite(channelID: BigString, options: CreateChannelInviteOptions = {}, reason?: string): Promise { - return await this.post(CHANNEL_INVITES(channelID), { - body: { - max_age: options.maxAge, - max_uses: options.maxUses, - target_application_id: options.targetApplicationID, - target_type: options.targetType, - target_user_id: options.targetUserID, - temporary: options.temporary, - unique: options.unique, - }, - reason, - }).then((invite) => new Invite(invite, this)) - } - - /** Create a channel webhook */ - async createChannelWebhook(channelID: BigString, options: { name: string; avatar?: string | null }, reason?: string): Promise { - return await this.post(CHANNEL_WEBHOOKS(channelID), { - reason, - body: options, - }) - } - - /** Create a global application command */ - async createCommand(command: ApplicationCommandStructure): Promise { - if (command.name !== undefined) { - if (command.type === 1 || command.type === undefined) { - command.name = command.name.toLowerCase() - if (!command.name.match(/^[\w-]{1,32}$/)) { - throw new Error('Slash Command names must match the regular expression "^[\\w-]{1,32}$"') - } - } - } - - // @ts-expect-error some eris magic at play here - command.default_permission = command.defaultPermission - return await this.post(COMMANDS(this.applicationId), { body: command }) - } - - /** Create a guild */ - async createGuild(name: string, options?: CreateGuildOptions): Promise { - if (this.guilds.size > 9) { - throw new Error("This method can't be used when in 10 or more guilds.") - } - - return await this.post(GUILDS, { - body: { - name, - icon: options?.icon, - verification_level: options?.verificationLevel, - default_message_notifications: options?.defaultNotifications, - explicit_content_filter: options?.explicitContentFilter, - system_channel_id: options?.systemChannelID, - afk_channel_id: options?.afkChannelID, - afk_timeout: options?.afkTimeout, - roles: options?.roles, - channels: options?.channels, - }, - }).then((guild) => new Guild(guild, this)) - } - - /** Create a guild application command */ - async createGuildCommand(guildID: BigString, command: ApplicationCommandStructure): Promise { - if (command.name !== undefined) { - if (command.type === 1 || command.type === undefined) { - command.name = command.name.toLowerCase() - if (!command.name.match(/^[\w-]{1,32}$/)) { - throw new Error('Slash Command names must match the regular expression "^[\\w-]{1,32}$"') - } - } - } - // @ts-expect-error some eris magic at play here - command.default_permission = command.defaultPermission - - return await this.post(GUILD_COMMANDS(this.applicationId, guildID), { - body: command, - }) - } - - /** Create a guild emoji object */ - async createGuildEmoji(guildID: BigString, options: EmojiOptions, reason?: string): Promise { - return await this.post(GUILD_EMOJIS(guildID), { - body: { - name: options.name, - roles: options.roles, - image: options.image, - }, - reason, - }) - } - - /** Create a guild based on a template. This can only be used with bots in less than 10 guilds */ - async createGuildFromTemplate(code: string, name: string, icon?: string): Promise { - return await this.post(GUILD_TEMPLATE(code), { body: { name, icon } }).then((guild) => new Guild(guild, this)) - } - - /** Create a guild sticker */ - async createGuildSticker(guildID: BigString, options: CreateStickerOptions, reason?: string): Promise { - return await this.post(GUILD_STICKERS(guildID), { - body: { - // @ts-expect-error some eris magic at play here - description: options.description ?? '', - name: options.name, - tags: options.tags, - }, - reason, - file: options.file, - }) - } - - /** Create a template for a guild */ - async createGuildTemplate(guildID: BigString, name: string, description?: string): Promise { - return await this.post(GUILD_TEMPLATES(guildID), { - body: { - name, - description, - }, - }).then((template) => new GuildTemplate(template, this)) - } - - /** - * Respond to the interaction with a message - * Note: Use webhooks if you have already responded with an interaction response. - */ - async createInteractionResponse( - interactionID: BigString, - interactionToken: string, - options: InteractionResponse, - file?: FileContent | FileContent[], - ): Promise { - return await this.post(INTERACTION_RESPOND(interactionID, interactionToken), { - body: { - ...options, - data: { - ...options.data, - - allowed_mentions: options.data?.allowedMentions ? this._formatAllowedMentions(options.data.allowedMentions) : undefined, - allowedMentions: undefined, - }, - }, - file, - }) - } - - /** - * Create a message in a channel - * Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel for a user - */ - async createMessage(channelID: BigString, content: MessageContent, file?: FileContent | FileContent[]) { - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } else if (content.embed) { - if (!content.embeds) { - content.embeds = [] - } - content.embeds.push(content.embed) - } - } - - return await this.post(CHANNEL_MESSAGES(channelID), { - body: { - ...content, - allowed_mentions: this._formatAllowedMentions(content.allowedMentions), - sticker_ids: content.stickerIDs, - ...(content.messageReference?.messageID - ? { - message_reference: { - message_id: content.messageReference.messageID.toString(), - channel_id: content.messageReference.channelID?.toString(), - guild_id: content.messageReference.guildID?.toString(), - fail_if_not_exists: content.messageReference.failIfNotExists === true, - }, - } - : {}), - }, - file, - }).then((message) => new Message(message, this)) - } - - /** Create a guild role */ - async createRole(guildID: BigString, options: Role | RoleOptions, reason?: string) { - if (options.permissions !== undefined) { - options.permissions = options.permissions instanceof Permission ? String(options.permissions.allow) : String(options.permissions) - } - - return await this.post(GUILD_ROLES(guildID), { - body: { - name: options.name, - permissions: options.permissions, - color: options.color, - hoist: options.hoist, - icon: options.icon, - mentionable: options.mentionable, - unicode_emoji: options.unicodeEmoji, - }, - reason, - }).then((r) => { - const guild = this.guilds.get(guildID) - // @ts-expect-error some eris magic at play here - const role = new Role(r, guild) - - guild?.roles.set(role.id, role) - - return role - }) - } - - /** Create a stage instance */ - async createStageInstance(channelID: BigString, options: StageInstanceOptions): Promise { - return await this.post(STAGE_INSTANCES, { - body: { - channel_id: channelID, - privacy_level: options.privacyLevel, - topic: options.topic, - }, - }).then((instance) => new StageInstance(instance, this)) - } - - /** Create a thread with an existing message */ - async createThreadWithMessage( - channelID: BigString, - messageID: BigString, - options: CreateThreadOptions, - ): Promise { - return await this.post(THREAD_WITH_MESSAGE(channelID, messageID), { - body: { - name: options.name, - auto_archive_duration: options.autoArchiveDuration, - }, - }).then((channel) => generateChannelFrom(channel, this) as unknown as NewsThreadChannel | PublicThreadChannel) - } - - /** Create a thread without an existing message */ - async createThreadWithoutMessage(channelID: BigString, options: CreateThreadWithoutMessageOptions): Promise { - return (await this.post(THREAD_WITHOUT_MESSAGE(channelID), { - body: { - auto_archive_duration: options.autoArchiveDuration, - invitable: options.invitable, - name: options.name, - type: options.type, - }, - }).then((channel) => generateChannelFrom(channel, this))) as PrivateThreadChannel - } - - /** Crosspost (publish) a message to subscribed channels */ - async crosspostMessage(channelID: BigString, messageID: BigString): Promise { - return await this.post(CHANNEL_CROSSPOST(channelID, messageID)).then((message) => new Message(message, this)) - } - - /** Delete a guild channel, or leave a private or group channel */ - async deleteChannel(channelID: BigString, reason?: string): Promise { - return await this.delete(CHANNEL(channelID), { - reason, - }) - } - - /** Delete a channel permission overwrite */ - async deleteChannelPermission(channelID: BigString, overwriteID: BigString, reason?: string) { - return await this.delete(CHANNEL_PERMISSION(channelID, overwriteID), { - reason, - }) - } - - /** Delete a global application command */ - async deleteCommand(commandID: BigString): Promise { - return await this.delete(COMMAND(this.applicationId, commandID)) - } - - /** Delete a guild (bot user must be owner) */ - async deleteGuild(guildID: BigString): Promise { - return await this.delete(GUILD(guildID)) - } - - /** Delete a guild application command */ - async deleteGuildCommand(guildID: BigString, commandID: BigString): Promise { - return await this.delete(GUILD_COMMAND(this.applicationId, guildID, commandID)) - } - - /** Delete a guild discovery subcategory */ - async deleteGuildDiscoverySubcategory(guildID: BigString, categoryID: BigString, reason?: string) { - return await this.delete(GUILD_DISCOVERY_CATEGORY(guildID, categoryID), { - reason, - }) - } - - /** Delete a guild emoji object */ - async deleteGuildEmoji(guildID: BigString, emojiID: BigString, reason?: string): Promise { - return await this.delete(GUILD_EMOJI(guildID, emojiID), { - reason, - }) - } - - /** Delete a guild integration */ - async deleteGuildIntegration(guildID: BigString, integrationID: BigString): Promise { - return await this.delete(GUILD_INTEGRATION(guildID, integrationID)) - } - - /** Delete a guild sticker */ - async deleteGuildSticker(guildID: BigString, stickerID: BigString, reason?: string): Promise { - return await this.delete(GUILD_STICKER(guildID, stickerID), { - reason, - }) - } - - /** Delete a guild template */ - async deleteGuildTemplate(guildID: BigString, code: string): Promise { - return await this.delete(GUILD_TEMPLATE_GUILD(guildID, code)) - } - - /** Delete an invite */ - async deleteInvite(inviteID: string, reason?: string): Promise { - return await this.delete(INVITE(inviteID), { - reason, - }) - } - - /** Delete a message */ - async deleteMessage(channelID: BigString, messageID: BigString, reason?: string): Promise { - return await this.delete(CHANNEL_MESSAGE(channelID, messageID), { - reason, - }) - } - - /** Bulk delete messages (bot accounts only) */ - async deleteMessages(channelID: BigString, messageIDs: BigString[], reason?: string): Promise { - if (messageIDs.length === 0) { - return await Promise.resolve() - } - if (messageIDs.length === 1) { - return await this.deleteMessage(channelID, messageIDs[0], reason) - } - - const oldestAllowedSnowflake = (Date.now() - 1421280000000) * 4194304 - const invalidMessage = messageIDs.find((messageID) => this.snowflakeToTimestamp(messageID) < oldestAllowedSnowflake) - if (invalidMessage) { - return await Promise.reject(new Error(`Message ${invalidMessage} is more than 2 weeks old.`)) - } - - const chunks = this.chunkArray(messageIDs, 100) - for (const chunk of chunks) { - await this.post(CHANNEL_BULK_DELETE(channelID), { - body: { messages: chunk }, - reason, - }) - } - } - - /** Delete a guild role */ - async deleteRole(guildID: BigString, roleID: BigString, reason?: string): Promise { - return await this.delete(GUILD_ROLE(guildID, roleID), { - reason, - }) - } - - /** Delete a stage instance */ - async deleteStageInstance(channelID: BigString): Promise { - return await this.delete(STAGE_INSTANCE(channelID)) - } - - /** Delete a webhook */ - async deleteWebhook(webhookID: BigString, token?: string, reason?: string): Promise { - return await this.delete(token ? WEBHOOK_TOKEN(webhookID, token) : WEBHOOK(webhookID), { - reason, - }) - } - - /** Delete a webhook message */ - async deleteWebhookMessage(webhookID: BigString, token: string, messageID: BigString): Promise { - return await this.delete(WEBHOOK_MESSAGE(webhookID, token, messageID)) - } - - /** Edit a channel's properties */ - async editChannel(channelID: BigString, options: EditChannelOptions, reason?: string): Promise { - return await this.patch(CHANNEL(channelID), { - reason, - body: { - archived: options.archived, - auto_archive_duration: options.autoArchiveDuration, - bitrate: options.bitrate, - default_auto_archive_duration: options.defaultAutoArchiveDuration, - icon: options.icon, - invitable: options.invitable, - locked: options.locked, - name: options.name, - nsfw: options.nsfw, - owner_id: options.ownerID, - parent_id: options.parentID, - position: options.position, - rate_limit_per_user: options.rateLimitPerUser, - rtc_region: options.rtcRegion, - topic: options.topic, - user_limit: options.userLimit, - video_quality_mode: options.videoQualityMode, - permission_overwrites: options.permissionOverwrites, - }, - }).then((channel) => generateChannelFrom(channel, this) as unknown as AnyGuildChannel) - } - - /** Create a channel permission overwrite */ - async editChannelPermission( - channelID: BigString, - overwriteID: BigString, - allow: bigint | number, - deny: bigint | number, - type: OverwriteTypes, - reason?: string, - ): Promise { - return await this.put(CHANNEL_PERMISSION(channelID, overwriteID), { - body: { - allow: allow.toString(), - deny: deny.toString(), - type, - }, - reason, - }) - } - - /** - * Edit a guild channel's position. Note that channel position numbers are grouped by type (category, text, voice), then sorted in ascending order (lowest number is on top). - */ - async editChannelPosition(channelID: BigString, position: number, options: EditChannelPositionOptions = {}): Promise { - const guild = this.guilds.find((g) => g.channels.has(channelID)) - const channels = guild?.channels - if (!channels) { - return await Promise.reject(new Error(`Channel ${channelID} not found`)) - } - - const channel = channels.get(channelID) - if (!channel) { - return await Promise.reject(new Error(`Channel ${channelID} not found`)) - } - if (channel.position === position) { - return await Promise.resolve() - } - const min = Math.min(position, channel.position) - const max = Math.max(position, channel.position) - - const positions = channels - .filter((chan) => { - return chan.type === channel.type && min <= chan.position && chan.position <= max && chan.id !== channelID - }) - .sort((a, b) => a.position - b.position) - - if (position > channel.position) { - positions.push(channel) - } else { - positions.unshift(channel) - } - - return await this.patch(GUILD_CHANNELS(guild.id), { - body: channels.array().map((channel, index) => ({ - id: channel.id, - position: index + min, - lock_permissions: options.lockPermissions, - parent_id: options.parentID, - })), - }) - } - - /** - * Edit multiple guild channels' positions. Note that channel position numbers are grouped by type (category, text, voice), then sorted in ascending order (lowest number is on top). - */ - async editChannelPositions(guildID: BigString, channelPositions: ChannelPosition[]): Promise { - return await this.patch(GUILD_CHANNELS(guildID), { - body: channelPositions.map((channelPosition) => { - return { - id: channelPosition.id, - position: channelPosition.position, - lock_permissions: channelPosition.lockPermissions, - parent_id: channelPosition.parentID, - } - }), - }) - } - - /** Edit a global application command */ - async editCommand(commandID: BigString, command: ApplicationCommandStructure) { - if (command.name !== undefined) { - if (command.type === 1 || command.type === undefined) { - command.name = command.name.toLowerCase() - if (!command.name.match(/^[\w-]{1,32}$/)) { - throw new Error('Slash Command names must match the regular expression "^[\\w-]{1,32}$"') - } - } - } - // @ts-expect-error some eris magic at play here - command.default_permission = command.defaultPermission - return await this.patch(COMMAND(this.applicationId, commandID), { - body: command, - }) - } - - /** Edits command permissions for a specific command in a guild. */ - async editCommandPermissions( - guildID: BigString, - commandID: BigString, - permissions: ApplicationCommandPermissions[], - ): Promise { - return await this.put(COMMAND_PERMISSIONS(this.applicationId, guildID, commandID), { - body: permissions, - }) - } - - /** Edit a guild */ - async editGuild(guildID: BigString, options: GuildOptions, reason?: string): Promise { - return await this.patch(GUILD(guildID), { - body: { - name: options.name, - icon: options.icon, - verification_level: options.verificationLevel, - default_message_notifications: options.defaultNotifications, - explicit_content_filter: options.explicitContentFilter, - system_channel_id: options.systemChannelID, - system_channel_flags: options.systemChannelFlags, - rules_channel_id: options.rulesChannelID, - public_updates_channel_id: options.publicUpdatesChannelID, - preferred_locale: options.preferredLocale, - afk_channel_id: options.afkChannelID, - afk_timeout: options.afkTimeout, - owner_id: options.ownerID, - splash: options.splash, - banner: options.banner, - description: options.description, - discovery_splash: options.discoverySplash, - features: options.features, - }, - reason, - }).then((guild) => new Guild(guild, this)) - } - - /** Edit a guild application command */ - async editGuildCommand(guildID: BigString, commandID: BigString, command: ApplicationCommandStructure): Promise { - if (command.name !== undefined) { - if (command.type === 1 || command.type === undefined) { - command.name = command.name.toLowerCase() - if (!command.name.match(/^[\w-]{1,32}$/)) { - throw new Error('Slash Command names must match the regular expression "^[\\w-]{1,32}$"') - } - } - } - // @ts-expect-error some eris magic at play here - command.default_permission = command.defaultPermission - - return await this.patch(GUILD_COMMAND(this.applicationId, guildID, commandID), { - body: command, - }) - } - - /** Edit a guild's discovery data */ - async editGuildDiscovery(guildID: BigString, options: DiscoveryOptions = {}): Promise { - return await this.patch(GUILD_DISCOVERY(guildID), { - body: { - primary_category_id: options.primaryCategoryID, - keywords: options.keywords, - emoji_discoverability_enabled: options.emojiDiscoverabilityEnabled, - }, - reason: options.reason, - }) - } - - /** Edit a guild emoji object */ - async editGuildEmoji(guildID: BigString, emojiID: BigString, options: { name?: string; roles?: string[] }, reason?: string): Promise { - return await this.patch(GUILD_EMOJI(guildID, emojiID), { - body: options, - reason, - }) - } - - /** Edit a guild integration */ - async editGuildIntegration(guildID: BigString, integrationID: BigString, options: IntegrationOptions): Promise { - return await this.patch(GUILD_INTEGRATION(guildID, integrationID), { - body: { - expire_behavior: options.expireBehavior, - expire_grace_period: options.expireGracePeriod, - enable_emoticons: options.enableEmoticons, - }, - }) - } - - /** Edit a guild member */ - async editGuildMember(guildID: BigString, memberID: BigString, options: MemberOptions, reason?: string): Promise { - return await this.patch(GUILD_MEMBER(guildID, memberID), { - body: { - roles: options.roles?.filter((roleID, index) => options.roles!.indexOf(roleID) === index), - nick: options.nick, - mute: options.mute, - deaf: options.deaf, - channel_id: options.channelID, - communication_disabled_until: options.communicationDisabledUntil, - }, - reason, - }) - // @ts-expect-error some eris magic at play here - .then((member) => new Member(member, this.guilds.get(guildID), this)) - } - - /** Edit a guild sticker */ - async editGuildSticker(guildID: BigString, stickerID: BigString, options?: EditStickerOptions, reason?: string): Promise { - return await this.patch(GUILD_STICKER(guildID, stickerID), { - body: { ...options }, - reason, - }) - } - - /** Edit a guild template */ - async editGuildTemplate(guildID: BigString, code: string, options: GuildTemplateOptions): Promise { - return await this.patch(GUILD_TEMPLATE_GUILD(guildID, code), { - body: { ...options }, - }).then((template) => new GuildTemplate(template, this)) - } - - /** Modify a guild's vanity code */ - async editGuildVanity(guildID: BigString, code: string | null) { - return await this.patch(GUILD_VANITY_URL(guildID), { - body: code, - }) - } - - /** Update a user's voice state - See [caveats](https://discord.com/developers/docs/resources/guild#modify-user-voice-state-caveats) */ - async editGuildVoiceState(guildID: BigString, options: VoiceStateOptions, userID: BigString = '@me'): Promise { - return await this.patch(GUILD_VOICE_STATE(guildID, userID), { - body: { - channel_id: options.channelID, - request_to_speak_timestamp: options.requestToSpeakTimestamp, - suppress: options.suppress, - }, - }) - } - - /** Edit a guild welcome screen */ - async editGuildWelcomeScreen(guildID: BigString, options: WelcomeScreenOptions): Promise { - return await this.patch(GUILD_WELCOME_SCREEN(guildID), { - body: { - description: options.description, - enabled: options.enabled, - welcome_channels: options.welcomeChannels.map((c) => { - return { - channel_id: c.channelID, - description: c.description, - emoji_id: c.emojiID, - emoji_name: c.emojiName, - } - }), - }, - }) - } - - /** Modify a guild's widget */ - async editGuildWidget(guildID: BigString, options: Widget): Promise { - return await this.patch(GUILD_WIDGET(guildID), { body: { ...options } }) - } - - /** Edit a message */ - async editMessage(channelID: BigString, messageID: BigString, content: MessageContentEdit): Promise { - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } else if (content.embed) { - if (!content.embeds) { - content.embeds = [] - } - content.embeds.push(content.embed) - } - } - - return await this.patch(CHANNEL_MESSAGE(channelID, messageID), { - body: { - ...content, - allowed_mentions: this._formatAllowedMentions(content.allowedMentions), - }, - file: content.file, - }).then((message) => new Message(message, this)) - } - - /** Edit a guild role */ - async editRole(guildID: BigString, roleID: BigString, options: RoleOptions, reason?: string): Promise { - // @ts-expect-error some eris magic at play here - options.unicode_emoji = options.unicodeEmoji - - if (options.permissions !== undefined) { - options.permissions = options.permissions instanceof Permission ? String(options.permissions.allow) : String(options.permissions) - } - return await this.patch(GUILD_ROLE(guildID, roleID), { - body: { ...options }, - reason, - }) - // @ts-expect-error some eris magic at play here - .then((role) => new Role(role, this.guilds.get(guildID))) - } - - /** Edit a guild role's position. Note that role position numbers are highest on top and lowest at the bottom. */ - async editRolePosition(guildID: BigString, roleID: BigString, position: number): Promise { - if (guildID === roleID) { - return await Promise.reject(new Error('Cannot move default role')) - } - // @ts-expect-error some eris magic at play here - const roles = this.guilds.get(guildID).roles - const role = roles.get(roleID) - if (!role) { - return await Promise.reject(new Error(`Role ${roleID} not found`)) - } - if (role.position === position) { - return await Promise.resolve() - } - const min = Math.min(position, role.position) - const max = Math.max(position, role.position) - const positions = roles - .array() - .filter((role) => min <= role.position && role.position <= max && role.id !== roleID) - .sort((a, b) => a.position - b.position) - if (position > role.position) { - positions.push(role) - } else { - positions.unshift(role) - } - return await this.patch(GUILD_ROLES(guildID), { - body: positions.map((role, index) => ({ - id: role.id, - position: index + min, - })), - }) - } - - /** Edit properties of the bot user */ - async editSelf(options: { avatar?: string; username?: string }): Promise { - return await this.patch(USER('@me'), { body: { ...options } }).then((data) => new ExtendedUser(data, this)) - } - - /** Update a stage instance */ - async editStageInstance(channelID: BigString, options: StageInstanceOptions): Promise { - return await this.patch(STAGE_INSTANCE(channelID), { - body: { ...options }, - }).then((instance) => new StageInstance(instance, this)) - } - - /** - * Updates the bot's status on all guilds the shard is in - */ - async editStatus(status: SelfStatus, activities: Array> | ActivityPartial = []) { - return await Promise.all(this.shards.map(async (shard) => await shard.editStatus(status, activities))) - } - - /** Edit a webhook */ - async editWebhook(webhookID: BigString, options: WebhookOptions, token: string, reason?: string) { - return await this.patch(token ? WEBHOOK_TOKEN(webhookID, token) : WEBHOOK(webhookID), { - body: { - name: options.name, - avatar: options.avatar, - channel_id: options.channelID, - }, - reason, - }) - } - - /** Edit a webhook message */ - async editWebhookMessage(webhookID: BigString, token: string, messageID: BigString, options: MessageWebhookContent): Promise { - const { file, allowedMentions, ...body } = options - - return await this.patch(WEBHOOK_MESSAGE(webhookID, token, messageID), { - body: { - ...body, - allowed_mentions: this._formatAllowedMentions(allowedMentions), - }, - file, - }).then((response) => new Message(response, this)) - } - - /** Execute a slack-style webhook */ - async executeSlackWebhook( - webhookID: BigString, - token: string, - options: Record & { auth?: boolean; threadID?: string }, - ): Promise - async executeSlackWebhook( - webhookID: BigString, - token: string, - options: Record & { - auth?: boolean - threadID?: string - wait: true - }, - ): Promise - async executeSlackWebhook( - webhookID: BigString, - token: string, - options: Record & { - auth?: boolean - threadID?: string - wait?: true - }, - ): Promise { - const { wait, threadID, ...rest } = options - let qs = '' - if (wait) { - qs += '&wait=true' - } - if (threadID) { - qs += '&thread_id=' + threadID - } - return await this.post(WEBHOOK_TOKEN_SLACK(webhookID, token) + (qs ? '?' + qs : ''), { body: { ...rest } }) - } - - /** Execute a webhook */ - async executeWebhook(webhookID: BigString, token: string, options: WebhookPayload): Promise - async executeWebhook(webhookID: BigString, token: string, options: WebhookPayload & { wait: true }): Promise - async executeWebhook(webhookID: BigString, token: string, options: WebhookPayload & { wait?: boolean }): Promise { - let qs = '' - if (options.wait) { - qs += '&wait=true' - } - if (options.threadID) { - qs += '&thread_id=' + options.threadID - } - if (options.embed) { - if (!options.embeds) { - options.embeds = [] - } - options.embeds.push(options.embed) - } - - return await this.post(WEBHOOK_TOKEN(webhookID, token) + (qs ? '?' + qs : ''), { - body: { - content: options.content, - embeds: options.embeds, - username: options.username, - avatar_url: options.avatarURL, - tts: options.tts, - flags: options.flags, - allowed_mentions: this._formatAllowedMentions(options.allowedMentions), - components: options.components, - }, - file: options.file, - }).then((response) => (options.wait ? new Message(response, this) : undefined)) - } - - /** Follow a NewsChannel in another channel. This creates a webhook in the target channel */ - async followChannel(channelID: BigString, webhookChannelID: BigString): Promise { - return await this.post(CHANNEL_FOLLOW(channelID), { - body: { webhook_channel_id: webhookChannelID }, - }) - } - - /** Get all active threads in a guild */ - async getActiveGuildThreads(guildID: BigString): Promise { - return await this.get(THREADS_GUILD_ACTIVE(guildID)).then((response) => { - return { - members: response.members.map((member: DiscordThreadMember) => new ThreadMember(member, this)), - threads: response.threads.map((thread: DiscordChannel) => generateChannelFrom(thread, this)), - } - }) - } - - /** Get all archived threads in a channel */ - async getArchivedThreads( - channelID: BigString, - type: 'private', - options?: GetArchivedThreadsOptions, - ): Promise> - async getArchivedThreads( - channelID: BigString, - type: 'public', - options?: GetArchivedThreadsOptions, - ): Promise> - async getArchivedThreads(channelID: BigString, type: 'private' | 'public', options: GetArchivedThreadsOptions = {}): Promise { - let qs = '' - if (options.limit) { - qs += `&limit=${options.limit}` - } - if (options.before) { - qs += `&before=${options.before.toISOString()}` - } - - return await this.get(THREADS_ARCHIVED(channelID, type) + (qs ? '?' + qs : '')).then((response) => { - return { - hasMore: response.has_more, - members: response.members.map((member: DiscordThreadMember) => new ThreadMember(member, this)), - threads: response.threads.map((thread: DiscordChannel) => generateChannelFrom(thread, this)), - } - }) - } - - /** Get general and bot-specific info on connecting to the Discord gateway (e.g. connection ratelimit) */ - async getBotGateway(): Promise { - return await this.get(GATEWAY_BOT) - } - - /** Get a Channel object from a channel ID */ - getChannel(channelID: BigString): AnyChannel | undefined { - const id = channelID.toString() - - const guildID = this._channelGuildMap.get(channelID) ?? this._threadGuildMap.get(channelID) - - if (guildID) { - const guild = this.guilds.get(guildID) - if (guild) return guild.channels.get(channelID) as unknown as AnyChannel - } - - return this.privateChannels.get(id)! - } - - /** Get all invites in a channel */ - async getChannelInvites(channelID: BigString): Promise { - return await this.get(CHANNEL_INVITES(channelID)).then((invites) => invites.map((invite: DiscordInvite) => new Invite(invite, this))) - } - - /** Get all the webhooks in a channel */ - async getChannelWebhooks(channelID: BigString): Promise { - return await this.get(CHANNEL_WEBHOOKS(channelID)) - } - - /** Get a global application command */ - async getCommand(commandID: BigString): Promise { - return await this.get(COMMAND(this.applicationId, commandID)) - } - - /** Get the a guild's application command permissions */ - async getCommandPermissions(guildID: BigString, commandID: BigString): Promise { - return await this.get(COMMAND_PERMISSIONS(this.applicationId, guildID, commandID)) - } - - /** Get the global application commands */ - async getCommands(): Promise { - return await this.get(COMMANDS(this.applicationId)) - } - - /** Get a list of discovery categories */ - async getDiscoveryCategories(): Promise { - return await this.get(DISCOVERY_CATEGORIES) - } - - /** Get a DM channel with a user, or create one if it does not exist */ - async getDMChannel(userID: BigString): Promise { - if (this._privateChannelMap.has(userID)) { - return await Promise.resolve(this.privateChannels.get(this._privateChannelMap.get(userID)!)!) - } - return await this.post(USER_CHANNELS('@me'), { - body: { - recipients: [userID], - type: 1, - }, - }).then((privateChannel) => new PrivateChannel(privateChannel, this)) - } - - /** Get a guild from the guild's emoji ID */ - async getEmojiGuild(emojiID: BigString): Promise { - return await this.get(CUSTOM_EMOJI_GUILD(emojiID)).then((result) => new Guild(result, this)) - } - - /** Get info on connecting to the Discord gateway */ - async getGateway(): Promise<{ url: string }> { - return await this.get(GATEWAY) - } - - /** Get the audit log for a guild */ - async getGuildAuditLog(guildID: BigString, options: GetGuildAuditLogOptions = {}): Promise { - let qs = '' - if (options.actionType) { - qs += `&action_type=${options.actionType}` - } - if (options.userID) { - qs += `&user_id=${options.userID}` - } - if (options.before) { - qs += `&before=${options.before}` - } - if (options.limit) { - qs += `&limit=${options.limit}` - } - - return await this.get(GUILD_AUDIT_LOGS(guildID) + (qs ? '?' + qs : '')).then((data) => { - const guild = this.guilds.get(guildID) - const users = data.users.map((u: DiscordUser) => { - const user = new User(u, this) - this.users.set(user.id, user) - return user - }) - - const threads = data.threads.map((thread: DiscordChannel) => { - const channel = generateChannelFrom(thread, this) as unknown as ThreadChannel - guild?.threads.set(channel.id, channel) - return channel - }) - - return { - entries: guild ? data.audit_log_entries.map((entry: DiscordAuditLogEntry) => new GuildAuditLogEntry(entry, guild)) : [], - integrations: guild ? data.integrations.map((integration: DiscordIntegration) => new GuildIntegration(integration, guild)) : [], - threads, - users, - webhooks: data.webhooks, - } - }) - } - - /** Get a ban from the ban list of guild */ - async getGuildBan(guildID: BigString, userID: BigString): Promise { - return await this.get(GUILD_BAN(guildID, userID)).then((ban) => { - ban.user = new User(ban.user, this) - return ban - }) - } - - /** Get the ban list of a guild */ - async getGuildBans(guildID: BigString, options: GetGuildBansOptions = {}): Promise { - let qs = '' - if (options.after) { - qs += `&after=${options.after}` - } - if (options.before) { - qs += `&before=${options.before}` - } - if (options.limit) { - qs += `&limit=${options.limit && Math.min(options.limit, 1000)}` - } - - const bans = await this.get(GUILD_BANS(guildID) + (qs ? '?' + qs : '')) - - for (const ban of bans) { - const user = new User(ban.user, this) - this.users.set(user.id, user) - ban.user = user - } - - if (options.limit && options.limit > 1000 && bans.length >= 1000) { - const page = await this.getGuildBans(guildID, { - after: options.before ? undefined : bans[bans.length - 1].user.id, - before: options.before ? bans[0].user.id : undefined, - limit: options.limit - bans.length, - }) - - if (options.before) { - bans.unshift(...page) - } else { - bans.push(...page) - } - } - - return bans - } - - /** Get a guild application command */ - async getGuildCommand(guildID: BigString, commandID: BigString): Promise { - return await this.get(GUILD_COMMAND(this.applicationId, guildID, commandID)) - } - - /** Get the all of a guild's application command permissions */ - async getGuildCommandPermissions(guildID: BigString): Promise { - return await this.get(GUILD_COMMAND_PERMISSIONS(this.applicationId, guildID)) - } - - /** Get a guild's application commands */ - async getGuildCommands(guildID: BigString): Promise { - return await this.get(GUILD_COMMANDS(this.applicationId, guildID)) - } - - /** Get a guild's discovery object */ - async getGuildDiscovery(guildID: BigString): Promise { - return await this.get(GUILD_DISCOVERY(guildID)) - } - - /** Get a list of integrations for a guild */ - async getGuildIntegrations(guildID: BigString): Promise { - const guild = this.guilds.get(guildID) - return await this.get(GUILD_INTEGRATIONS(guildID)).then((integrations) => { - return guild ? integrations.map((integration: DiscordIntegration) => new GuildIntegration(integration, guild)) : [] - }) - } - - /** Get all invites in a guild */ - async getGuildInvites(guildID: BigString): Promise { - return await this.get(GUILD_INVITES(guildID)).then((invites) => invites.map((invite: DiscordInvite) => new Invite(invite, this))) - } - - /** Get a guild preview for a guild. Only available for community guilds. */ - async getGuildPreview(guildID: BigString): Promise { - return await this.get(GUILD_PREVIEW(guildID)).then((data) => new GuildPreview(data, this)) - } - - /** Get a guild template */ - async getGuildTemplate(code: string): Promise { - return await this.get(GUILD_TEMPLATE(code)).then((template) => new GuildTemplate(template, this)) - } - - /** Get a guild's templates */ - async getGuildTemplates(guildID: BigString): Promise { - return await this.get(GUILD_TEMPLATES(guildID)).then((templates) => templates.map((t: DiscordTemplate) => new GuildTemplate(t, this))) - } - - /** Returns the vanity url of the guild */ - async getGuildVanity(guildID: BigString): Promise { - return await this.get(GUILD_VANITY_URL(guildID)) - } - - /** Get all the webhooks in a guild */ - async getGuildWebhooks(guildID: BigString): Promise { - return await this.get(GUILD_WEBHOOKS(guildID)) - } - - /** Get the welcome screen of a Community guild, shown to new members */ - async getGuildWelcomeScreen(guildID: BigString): Promise { - return await this.get(GUILD_WELCOME_SCREEN(guildID)) - } - - /** Get a guild's widget object */ - async getGuildWidget(guildID: BigString): Promise { - return await this.get(GUILD_WIDGET(guildID)) - } - - /** Get a guild's widget settings object. Requires MANAGE_GUILD permission */ - async getGuildWidgetSettings(guildID: BigString): Promise { - return await this.get(GUILD_WIDGET_SETTINGS(guildID)) - } - - /** Get info on an invite */ - async getInvite(inviteID: string, withCounts?: boolean): Promise { - let qs = '' - if (withCounts) { - qs += '&with_counts=true' - } - - return await this.get(INVITE(inviteID) + (qs ? '?' + qs : '')).then((invite) => new Invite(invite, this)) - } - - /** Get joined private archived threads in a channel */ - async getJoinedPrivateArchivedThreads( - channelID: BigString, - options: GetArchivedThreadsOptions = {}, - ): Promise> { - let qs = '' - if (options.before) { - qs += `&before=${options.before.toISOString()}` - } - - if (options.limit) { - qs += `&limit=${options.limit}` - } - - return await this.get(THREADS_ARCHIVED_JOINED(channelID) + (qs ? '?' + qs : '')).then((response) => { - return { - hasMore: response.has_more, - members: response.members.map((member: DiscordThreadMember) => new ThreadMember(member, this)), - threads: response.threads.map((thread: DiscordChannel) => generateChannelFrom(thread, this)), - } - }) - } - - /** Get a previous message in a channel */ - async getMessage(channelID: BigString, messageID: BigString): Promise { - return await this.get(CHANNEL_MESSAGE(channelID, messageID)).then((message) => new Message(message, this)) - } - - /** Get a list of users who reacted with a specific reaction */ - async getMessageReaction(channelID: BigString, messageID: BigString, reaction: string, options: GetMessageReactionOptions = {}): Promise { - if (reaction === decodeURI(reaction)) { - reaction = encodeURIComponent(reaction) - } - if (!options || typeof options !== 'object') { - options = { - limit: options, - } - } - - let qs = '' - if (options.limit) { - qs += `&limit=${options.limit}` - } - if (options.after) { - qs += `&after=${options.after}` - } - - return await this.get(CHANNEL_MESSAGE_REACTION(channelID, messageID, reaction) + (qs ? '?' + qs : '')).then((users) => - users.map((user: DiscordUser) => new User(user, this)), - ) - } - - /** Get previous messages in a channel */ - async getMessages(channelID: BigString, options: GetMessagesOptions = {}): Promise { - if (!options || typeof options !== 'object') { - options = { - limit: options, - } - } - if (options.limit === undefined) { - // Legacy behavior - options.limit = 50 - } - - let limit = options.limit - if (limit && limit > 100) { - let logs: Message[] = [] - const get: (_before?: BigString, _after?: BigString) => Promise = async (_before?: BigString, _after?: BigString) => { - let qs = '' - qs += `&limit=${100}` - if (_before) qs += `&before=${_before}` - if (_after) qs += `&after=${_after}` - - const messages = await this.get(CHANNEL_MESSAGES(channelID) + (qs ? '?' + qs : '')) - if (limit <= messages.length) { - return _after - ? messages - .slice(messages.length - limit, messages.length) - .map((message: DiscordMessage) => new Message(message, this)) - .concat(logs) - : logs.concat(messages.slice(0, limit).map((message: DiscordMessage) => new Message(message, this))) - } - - limit -= messages.length - logs = _after - ? messages.map((message: DiscordMessage) => new Message(message, this)).concat(logs) - : logs.concat(messages.map((message: DiscordMessage) => new Message(message, this))) - if (messages.length < 100) { - return logs - } - - this.emit('debug', `Getting ${limit} more messages during getMessages for ${channelID}: ${_before} ${_after}`, -1) - - return await get((_before ?? !_after) && messages[messages.length - 1].id, _after && messages[0].id) - } - - // @ts-expect-error todo use typeguards here - return await get(options.before, options.after) - } - - const messages = await this.get(CHANNEL_MESSAGES(channelID)) - return messages.map((message: DiscordMessage) => { - try { - return new Message(message, this) - } catch (err: any) { - this.emit('error', `Error creating message from channel messages\n${err.stack}\n${JSON.stringify(messages)}`) - return null - } - }) - } - - /** Get the list of sticker packs available to Nitro subscribers */ - async getNitroStickerPacks(): Promise<{ sticker_packs: StickerPack[] }> { - return await this.get(STICKER_PACKS) - } - - /** Get data on an OAuth2 application */ - async getOAuthApplication(appID: BigString): Promise { - return await this.get(OAUTH2_APPLICATION(appID || '@me')) - } - - /** Get all the pins in a channel */ - async getPins(channelID: BigString): Promise { - return await this.get(CHANNEL_PINS(channelID)).then((messages) => messages.map((message: DiscordMessage) => new Message(message, this))) - } - - /** Get the prune count for a guild */ - async getPruneCount(guildID: BigString, options: GetPruneOptions = {}): Promise { - let qs = '' - if (options.days) { - qs += `&days=${options.days}` - } - // TODO: how to put array in query string - if (options.includeRoles) { - qs += `&include_roles=${options.includeRoles}` - } - - return await this.get(GUILD_PRUNE(guildID) + (qs ? '?' + qs : '')).then((data) => data.pruned) - } - - /** Get a channel's data via the REST API. */ - async getRESTChannel(channelID: BigString): Promise { - return await this.get(CHANNEL(channelID)).then((channel: DiscordChannel) => generateChannelFrom(channel, this)) - } - - /** Get a guild's data via the REST API. */ - async getRESTGuild(guildID: BigString, withCounts = false): Promise { - let qs = '' - if (withCounts) { - qs += `&with_conts=${withCounts}` - } - - return await this.get(GUILD(guildID) + (qs ? '?' + qs : '')).then((guild) => new Guild(guild, this)) - } - - /** Get a guild's channels via the REST API. */ - async getRESTGuildChannels(guildID: BigString): Promise { - return await this.get(GUILD_CHANNELS(guildID)).then((channels) => channels.map((channel: DiscordChannel) => generateChannelFrom(channel, this))) - } - - /** Get a guild emoji via the REST API. */ - async getRESTGuildEmoji(guildID: BigString, emojiID: BigString): Promise { - return await this.get(GUILD_EMOJI(guildID, emojiID)) - } - - /** Get a guild's emojis via the REST API. */ - async getRESTGuildEmojis(guildID: BigString): Promise { - return await this.get(GUILD_EMOJIS(guildID)) - } - - /** Get a guild's members via the REST API. */ - async getRESTGuildMember(guildID: BigString, memberID: BigString): Promise { - return await this.get(GUILD_MEMBER(guildID, memberID)).then( - (member: DiscordMemberWithUser) => new Member(member, this.guilds.get(guildID)!, this), - ) - } - - /** Get a guild's members via the REST API. */ - async getRESTGuildMembers(guildID: BigString, options: GetRESTGuildMembersOptions = {}): Promise { - if (!options || typeof options !== 'object') { - options = { - limit: options, - } - } - let qs = '' - - if (options.limit) { - qs += `&limit=${options.limit}` - } - if (options.after) { - qs += `&after=${options.after}` - } - - return await this.get(GUILD_MEMBERS(guildID) + (qs ? '?' + qs : '')).then((members) => - members.map((member: DiscordMemberWithUser) => new Member(member, this.guilds.get(guildID)!, this)), - ) - } - - /** Get a guild's roles via the REST API. */ - async getRESTGuildRoles(guildID: BigString): Promise { - return await this.get(GUILD_ROLES(guildID)).then((roles) => roles.map((role: DiscordRole) => new Role(role, this.guilds.get(guildID)!))) - } - - /** Get a list of the user's guilds via the REST API. */ - async getRESTGuilds(options: GetRESTGuildsOptions = {}) { - if (!options || typeof options !== 'object') { - options = { - limit: options, - } - } - let qs = '' - if (options.after) { - qs += `&after=${options.after}` - } - if (options.before) { - qs += `&before=${options.before}` - } - if (options.limit) { - qs += `&limit=${options.limit}` - } - - return await this.get(USER_GUILDS('@me') + (qs ? '?' + qs : '')).then((guilds) => guilds.map((guild: DiscordGuild) => new Guild(guild, this))) - } - - /** Get a guild sticker via the REST API. */ - async getRESTGuildSticker(guildID: BigString, stickerID: BigString): Promise { - return await this.get(GUILD_STICKER(guildID, stickerID)) - } - - /** Get a guild's stickers via the REST API. */ - async getRESTGuildStickers(guildID: BigString): Promise { - return await this.get(GUILD_STICKERS(guildID)) - } - - /** Get a sticker via the REST API. */ - async getRESTSticker(stickerID: BigString): Promise { - return await this.get(STICKER(stickerID)) - } - - /** Get a user's data via the REST API. */ - async getRESTUser(userID: BigString): Promise { - return await this.get(USER(userID)).then((user) => new User(user, this)) - } - - /** Get properties of the bot user */ - async getSelf(): Promise { - return await this.get(USER('@me')).then((data) => new ExtendedUser(data, this)) - } - - /** Get the stage instance associated with a stage channel */ - async getStageInstance(channelID: BigString): Promise { - return await this.get(STAGE_INSTANCE(channelID)).then((instance) => new StageInstance(instance, this)) - } - - /** Get a list of members that are part of a thread channel */ - async getThreadMembers(channelID: BigString): Promise { - return await this.get(THREAD_MEMBERS(channelID)).then((members) => members.map((member: DiscordThreadMember) => new ThreadMember(member, this))) - } - - /** Get a list of general/guild-specific voice regions */ - async getVoiceRegions(guildID: BigString): Promise { - return guildID ? await this.get(GUILD_VOICE_REGIONS(guildID)) : await this.get(VOICE_REGIONS) - } - - /** Get a webhook */ - async getWebhook(webhookID: BigString, token: string): Promise { - return await this.get(token ? WEBHOOK_TOKEN(webhookID, token) : WEBHOOK(webhookID)) - } - - /** Get a webhook message */ - async getWebhookMessage(webhookID: BigString, token: string, messageID: BigString): Promise { - return await this.get(WEBHOOK_MESSAGE(webhookID, token, messageID)).then((message) => new Message(message, this)) - } - - /** Join a thread */ - async joinThread(channelID: BigString, userID: BigString = '@me'): Promise { - return await this.put(THREAD_MEMBER(channelID, userID)) - } - - /** Kick a user from a guild */ - async kickGuildMember(guildID: BigString, userID: BigString, reason?: string): Promise { - return await this.delete(GUILD_MEMBER(guildID, userID), { - reason, - }) - } - - /** Leave a guild */ - async leaveGuild(guildID: BigString): Promise { - return await this.delete(USER_GUILD('@me', guildID)) - } - - /** Leave a thread */ - async leaveThread(channelID: BigString, userID: BigString = '@me'): Promise { - return await this.delete(THREAD_MEMBER(channelID, userID)) - } - - /** Pin a message */ - async pinMessage(channelID: BigString, messageID: BigString): Promise { - return await this.put(CHANNEL_PIN(channelID, messageID)) - } - - /** Begin pruning a guild */ - async pruneMembers(guildID: BigString, options: PruneMemberOptions = {}): Promise { - return await this.post(GUILD_PRUNE(guildID), { - body: { - days: options.days, - compute_prune_count: options.computePruneCount, - include_roles: options.includeRoles, - }, - reason: options.reason, - }).then((data) => data.pruned) - } - - /** Purge previous messages in a channel with an optional filter (bot accounts only) */ - async purgeChannel(channelID: BigString, options: PurgeChannelOptions): Promise { - let limit = options.limit - if (limit !== -1 && limit <= 0) { - return 0 - } - const toDelete: BigString[] = [] - let deleted = 0 - let done = false - const checkToDelete: () => Promise = async () => { - const messageIDs = (done && toDelete) || (toDelete.length >= 100 && toDelete.splice(0, 100)) - if (messageIDs) { - deleted += messageIDs.length - await this.deleteMessages(channelID, messageIDs, options.reason) - if (done) { - return deleted - } - await delay(1000) - return await checkToDelete() - } else if (done) { - return deleted - } else { - await delay(250) - return await checkToDelete() - } - } - const del = async (_before?: BigString, _after?: BigString) => { - const messages = await this.getMessages(channelID, { - limit: 100, - before: _before?.toString(), - after: _after?.toString(), - }) - if (limit !== -1 && limit <= 0) { - done = true - return - } - for (const message of messages) { - if (limit !== -1 && limit <= 0) { - break - } - if (message.timestamp < Date.now() - 1209600000) { - // 14d * 24h * 60m * 60s * 1000ms - done = true - return - } - if (!options.filter || options.filter(message)) { - toDelete.push(message.id) - } - if (limit !== -1) { - limit-- - } - } - if ((limit !== -1 && limit <= 0) || messages.length < 100) { - done = true - return - } - await del(_before ?? !_after ? messages[messages.length - 1].id : undefined, _after ? messages[0].id : undefined) - } - await del(options.before, options.after) - return await checkToDelete() - } - - /** Remove a role from a guild member */ - async removeGuildMemberRole(guildID: BigString, memberID: BigString, roleID: BigString, reason?: string): Promise { - return await this.delete(GUILD_MEMBER_ROLE(guildID, memberID, roleID), { - reason, - }) - } - - /** Remove a reaction from a message */ - async removeMessageReaction(channelID: BigString, messageID: BigString, reaction: string, userID?: BigString): Promise { - if (reaction === decodeURI(reaction)) { - reaction = encodeURIComponent(reaction) - } - return await this.delete(CHANNEL_MESSAGE_REACTION_USER(channelID, messageID, reaction, userID ?? '@me')) - } - - /** Remove all reactions from a message for a single emoji. */ - async removeMessageReactionEmoji(channelID: BigString, messageID: BigString, reaction: string): Promise { - if (reaction === decodeURI(reaction)) { - reaction = encodeURIComponent(reaction) - } - return await this.delete(CHANNEL_MESSAGE_REACTION(channelID, messageID, reaction)) - } - - /** Remove all reactions from a message */ - async removeMessageReactions(channelID: BigString, messageID: BigString): Promise { - return await this.delete(CHANNEL_MESSAGE_REACTIONS(channelID, messageID)) - } - - /** Search for guild members by partial nickname/username */ - async searchGuildMembers(guildID: BigString, query: string, limit?: number): Promise { - let qs = `?query=${query}` - if (limit) { - qs += `?limit=${limit}` - } - - return await this.get(GUILD_MEMBERS_SEARCH(guildID) + qs).then((members) => { - const guild = this.guilds.get(guildID) - - return guild ? members.map((member: DiscordMemberWithUser) => new Member(member, guild, this)) : [] - }) - } - - /** Send typing status in a channel */ - async sendChannelTyping(channelID: BigString): Promise { - return await this.post(CHANNEL_TYPING(channelID)) - } - - /** Force a guild integration to sync */ - async syncGuildIntegration(guildID: BigString, integrationID: BigString): Promise { - return await this.post(GUILD_INTEGRATION_SYNC(guildID, integrationID)) - } - - /** Force a guild template to sync */ - async syncGuildTemplate(guildID: BigString, code: string): Promise { - return await this.put(GUILD_TEMPLATE_GUILD(guildID, code)).then((template) => new GuildTemplate(template, this)) - } - - /** Unban a user from a guild */ - async unbanGuildMember(guildID: BigString, userID: BigString, reason?: string): Promise { - return await this.delete(GUILD_BAN(guildID, userID), { - reason, - }) - } - - /** Unpin a message */ - async unpinMessage(channelID: BigString, messageID: BigString): Promise { - return await this.delete(CHANNEL_PIN(channelID, messageID)) - } - - /** Validate discovery search term */ - async validateDiscoverySearchTerm(term: string): Promise<{ valid: boolean }> { - return await this.get(DISCOVERY_VALIDATION + `?term=${encodeURI(term)}`) - } - - /** Converts the easy to type allowed mentions to the format discord requires. */ - _formatAllowedMentions(allowed?: AllowedMentions): DiscordAllowedMentions { - if (!allowed) { - return this.options.allowedMentions - } - const result: DiscordAllowedMentions = {} - result.parse = [] - - if (allowed.everyone) { - result.parse.push(AllowedMentionsTypes.EveryoneMentions) - } - if (allowed.roles === true) { - result.parse.push(AllowedMentionsTypes.RoleMentions) - } else if (Array.isArray(allowed.roles)) { - if (allowed.roles.length > 100) { - throw new Error('Allowed role mentions cannot exceed 100.') - } - result.roles = allowed.roles - } - if (allowed.users === true) { - result.parse.push(AllowedMentionsTypes.UserMentions) - } else if (Array.isArray(allowed.users)) { - if (allowed.users.length > 100) { - throw new Error('Allowed user mentions cannot exceed 100.') - } - result.users = allowed.users - } - if (allowed.repliedUser !== undefined) { - result.replied_user = allowed.repliedUser - } - return result - } - - _formatImage(url: string, format?: ImageFormat, size?: ImageSize): string { - if (!format) { - format = url.includes('/a_') ? 'gif' : this.options.defaultImageFormat - } - - if (!size) { - size = this.options.defaultImageSize - } - return `${this.CDN_URL}${url}.${format}?size=${size}` - } - - /** Converts a snowflake(discord id) into a timestamp. */ - snowflakeToTimestamp(snowflake: BigString): number { - return Number(BigInt(snowflake) / 4194304n + 1420070400000n) - } - - /** Get the bot id from the bot token. WARNING: Discord staff has mentioned this may not be stable forever. Use at your own risk. However, note for over 5 years this has never broken. */ - getBotIdFromToken(token: string): string { - return getBotIdFromToken(token).toString() - } - - /** Convert a icon hash into a bigint. */ - iconHashToBigInt(hash: string): bigint { - return iconHashToBigInt(hash) - } - - /** Convert a icon bigint back into a hash. */ - iconBigintToHash(icon: bigint): string { - return iconBigintToHash(icon) - } - - /** Splits a large array into chunks of smaller arrays */ - chunkArray(array: T[], size = 100): T[][] { - const box: T[][] = [] - while (array.length > box.length) { - box.push(array.splice(0, 100)) - } - - return box - } - - toString() { - return `[Client ${this.id}]` - } - - toJSON(props: string[] = []): Record { - // TODO: Update this after Client is done - return Base.prototype.toJSON.call(this, [ - 'application', - 'bot', - 'channelGuildMap', - 'gatewayURL', - 'groupChannels', - 'guilds', - 'guildShardMap', - 'lastConnect', - 'lastReconnectDelay', - 'notes', - 'options', - 'presence', - 'privateChannelMap', - 'privateChannels', - 'ready', - 'reconnectAttempts', - 'relationships', - 'requestHandler', - 'shards', - 'startTime', - 'unavailableGuilds', - 'userGuildSettings', - 'users', - 'userSettings', - 'voiceConnections', - ...props, - ]) - } - - // Typescript is not so good as we developers so we need this little utility function to help it out - // Taken from https://fettblog.eu/typescript-hasownproperty/ - /** TS save way to check if a property exists in an object */ - // eslint-disable-next-line @typescript-eslint/ban-types - hasProperty(obj: T, prop: Y): obj is T & Record { - return obj.hasOwnProperty(prop) - } - - /** A typeguard that tells whether a member has the user property or not. */ - isDiscordMemberWithUser(member: DiscordMember | DiscordMemberWithUser): member is DiscordMemberWithUser { - return this.hasProperty(member, 'user') - } - - /** Removes properties from a Structure you don't want. For example, if your bot does not need Channel.topic you can remove it. */ - removeProperties< - T extends - | typeof Member - | typeof NewsThreadChannel - | typeof PrivateThreadChannel - | typeof PublicThreadChannel - | typeof ThreadChannel - | typeof CategoryChannel - | typeof Channel - | typeof GuildChannel - | typeof NewsChannel - | typeof PrivateChannel - | typeof StageChannel - | typeof TextChannel - | typeof TextVoiceChannel - | typeof VoiceChannel - | typeof GuildAuditLogEntry - | typeof Guild - | typeof GuildIntegration - | typeof Member - | typeof GuildPreview - | typeof Role - | typeof StageInstance - | typeof GuildTemplate - | typeof UnavailableGuild - | typeof VoiceState - | typeof AutocompleteInteraction - | typeof CommandInteraction - | typeof ComponentInteraction - | typeof Interaction - | typeof PingInteraction - | typeof UnknownInteraction - | typeof ExtendedUser - | typeof User - | typeof Invite - | typeof Message - | typeof Permission - | typeof PermissionOverwrite, - >(obj: T, props: string[]): this { - for (const prop of props) { - Object.defineProperty(obj.prototype, prop, { - // In case the user tries to use this property after having removed it. - get() { - throw new Error(`${obj.constructor.name}.${prop} was removed with Client.removeProperties().`) - }, - // {} makes noop so it will NOT set any values even internally - set() {}, - }) - } - - return this - } -} - -export default Client - -export interface ClientOptions { - /** - * @deprecated this property does absolutely nothing. Please delete from ur code. Thanks. - * Keeping this only to preserve 1:1 api with eris. - */ - restMode?: boolean; - /** The default allowed mentions you would like to use. */ - allowedMentions?: AllowedMentions - /** The default image format you would like to use. */ - defaultImageFormat?: ImageFormat - /** The default image size you would like to use. */ - defaultImageSize?: ImageSize - /** The message limit you would like to set. */ - messageLimit?: number - /** The api version you would like to use. */ - apiVersion?: ApiVersions - /** The url to the REST proxy to send requests to. This url should nly include the initial domain:port portion until api/v.... */ - proxyURL?: string - /** The password/authorization to confirm that these request made to your rest proxy are indeed from you and not a hacker. */ - proxyRestAuthorization?: string - /** The application id(NOT the bot id). The bot id and application id are the same for newer bots but older bots have different ids. */ - applicationId?: BigString - /** Whether or not to seed voice connections. */ - seedVoiceConnections?: boolean - /** The concurrency to use when starting the bot. */ - shardConcurrency?: 'auto' | number - /** How many shards to use max. */ - maxShards?: 'auto' | number - /** Whether or not to enable websocket compression. NOT REcOMMENDED. */ - compress?: boolean - /** The first shard id to use. */ - firstShardID?: number - /** The last shard id to use. */ - lastShardID?: number - /** How many times to attempt resuming. */ - maxResumeAttempts?: number - /** The intents to use when connection to gateway. */ - intents?: GatewayIntents | number | Array - /** Whether or not to automatically reconnect to gateway. */ - autoreconnect?: boolean - /** - * How long in milliseconds to wait for a GUILD_CREATE before "ready" is fired. Increase this value if you notice missing guilds - * @default 2000 - */ - guildCreateTimeout?: number - /** Handler to determine how many milliseconds to wait before reconnecting. */ - reconnectDelay?: (lastDelay: number, attempts: number) => Promise | number -} - -export interface ParsedClientOptions { - /** The discord api version to use. */ - apiVersion: ApiVersions - /** Allowed mentions */ - allowedMentions: DiscordAllowedMentions - /** The image format to use by default. */ - defaultImageFormat: ImageFormat - /** The image size to use by default. */ - defaultImageSize: ImageSize - /** The url to the REST proxy to send requests to. This url should nly include the initial domain:port portion until api/v.... */ - proxyURL?: string - /** The password/authorization to confirm that these request made to your rest proxy are indeed from you and not a hacker. */ - proxyRestAuthorization?: string - /** The application id(NOT the bot id). The bot id and application id are the same for newer bots but older bots have different ids. */ - applicationId: BigString - /** The message limit you would like to set. */ - messageLimit?: number - /** Whether or not to seed voice connections */ - seedVoiceConnections: boolean - /** The max concurrency for the bot */ - shardConcurrency: 'auto' | number - /** How many shards to use as max */ - maxShards: 'auto' | number - /** Whether or not to enable websocket compression. NOT REcOMMENDED. */ - compress: boolean - /** The first shard id to use. */ - firstShardID: number - /** The last shard id to use. */ - lastShardID?: number - /** How many times to attempt resuming. */ - maxResumeAttempts: number - /** The intents to use when connection to gateway. */ - intents: GatewayIntents - /** Whether or not to automatically reconnect to gateway. */ - autoreconnect: boolean - /** How long in milliseconds to wait for a GUILD_CREATE before "ready" is fired. Increase this value if you notice missing guilds */ - guildCreateTimeout: number - /** Handler to determine how many milliseconds to wait before reconnecting. */ - reconnectDelay: (lastDelay: number, attempts: number) => Promise | number -} - -// TODO: Switch bigstring to dd version in next dd release. -/** A union type of string or bigint to help make it easier for users to switch between one another. */ -export type BigString = bigint | string -/** The API versions that are supported. */ -export type ApiVersions = 10 -/** The sizes for images that are supported. */ -export type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 -/** The formats for images that are supported. */ -export type ImageFormat = 'jpg' | 'jpeg' | 'png' | 'webp' | 'gif' -/** The methods that are acceptable for REST. */ -export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' - -export interface RequestData { - /** The method which should be used to send this request. */ - method: RequestMethod - /** The url to send this request to. */ - url: string - /** The headers you can send which will override internal headers or add others ones. */ - headers?: Record - /** The reason to add to the audit logs for this request. */ - reason?: string - /** The payload this request should send. */ - body?: Record | string | null | any[] - /** The file contents that should be sent in this request. */ - file?: FileContent | FileContent[] -} diff --git a/packages/client/src/Collection.ts b/packages/client/src/Collection.ts deleted file mode 100644 index 1773be368..000000000 --- a/packages/client/src/Collection.ts +++ /dev/null @@ -1,140 +0,0 @@ -export class Collection extends Map { - limit: number | undefined - - set(key: K, value: V): this { - // When this collection is limitd make sure we can add first - if ((this.limit ?? this.limit === 0) && this.size >= this.limit) { - return this - } - - return super.set(key, value) - } - - forceSet(key: K, value: V): this { - return super.set(key, value) - } - - array(): V[] { - return [...this.values()] - } - - /** Retrieve the value of the first element in this collection */ - first(): V | undefined { - return this.values().next().value - } - - last(): V | undefined { - return [...this.values()][this.size - 1] - } - - random(): V | undefined { - const array = [...this.values()] - return array[Math.floor(Math.random() * array.length)] - } - - find(callback: (value: V, key: K) => boolean): V | undefined { - for (const key of this.keys()) { - const value = this.get(key)! - if (callback(value, key)) return value - } - } - - filter(callback: (value: V, key: K) => boolean, returnArray?: true): V[] - filter(callback: (value: V, key: K) => boolean, returnArray: false): Collection - filter(callback: (value: V, key: K) => boolean, returnArray = true): Collection | V[] { - const relevant = new Collection() - this.forEach((value, key) => { - if (callback(value, key)) relevant.set(key, value) - }) - - return returnArray ? relevant.array() : relevant - } - - map(callback: (value: V, key: K) => T): T[] { - const results = [] - for (const key of this.keys()) { - const value = this.get(key)! - results.push(callback(value, key)) - } - return results - } - - some(callback: (value: V, key: K) => boolean): boolean { - for (const key of this.keys()) { - const value = this.get(key)! - if (callback(value, key)) return true - } - - return false - } - - every(callback: (value: V, key: K) => boolean): boolean { - for (const key of this.keys()) { - const value = this.get(key)! - if (!callback(value, key)) return false - } - - return true - } - - reduce(callback: (accumulator: T, value: V, key: K) => T, initialValue?: T): T { - let accumulator: T = initialValue! - - for (const key of this.keys()) { - const value = this.get(key)! - accumulator = callback(accumulator, value, key) - } - - return accumulator - } - - /** - * Adds a object to the collection. - * @deprecated Recommend using Collection.set(). Keeping for the sake of Eris API. - * @deprecated extra parameter. No longer used, keeping for sake of Eris API. - */ - add(obj: V & { id: K }, extra?: unknown, replace?: boolean): V { - if (this.limit === 0) return obj - - const existing = this.get(obj.id) - if (existing && !replace) { - return existing - } - - this.set(obj.id, obj) - return obj - } - - remove(obj: { id: K }): V | undefined { - const item = this.get(obj.id) - if (!item) return - - this.delete(obj.id) - return item - } - - update(obj: V & { id: K }, extra?: unknown, replace?: boolean): V { - const item = this.get(obj.id) - if (!item) { - this.set(obj.id, obj) - return obj - } - - // @ts-expect-error some eris magic at play here - item.update?.(obj, extra) - return item - } - - toRecord(): Record { - const record: Record = {} - for (const [key, value] of this.entries()) { - // @ts-expect-error should work fine - const finalKey = typeof key === 'string' ? key : key.toString() - record[finalKey] = value - } - - return record - } -} - -export default Collection diff --git a/packages/client/src/Constants.ts b/packages/client/src/Constants.ts deleted file mode 100644 index 6fdb715c7..000000000 --- a/packages/client/src/Constants.ts +++ /dev/null @@ -1,650 +0,0 @@ -export const GATEWAY_VERSION = 10 -export const REST_VERSION = 10 - -export const MessageFlags = { - CROSSPOSTED: 1 << 0, - IS_CROSSPOST: 1 << 1, - SUPPRESS_EMBEDS: 1 << 2, - SOURCE_MESSAGE_DELETED: 1 << 3, - URGENT: 1 << 4, - HAS_THREAD: 1 << 5, - EPHEMERAL: 1 << 6, - LOADING: 1 << 7, -} - -export const ActivityTypes = { - GAME: 0, - STREAMING: 1, - LISTENING: 2, - WATCHING: 3, - CUSTOM: 4, - COMPETING: 5, -} - -export const ApplicationCommandOptionTypes = { - SUB_COMMAND: 1, - SUB_COMMAND_GROUP: 2, - STRING: 3, - INTEGER: 4, - BOOLEAN: 5, - USER: 6, - CHANNEL: 7, - ROLE: 8, - MENTIONABLE: 9, - NUMBER: 10, -} - -export const ApplicationCommandPermissionTypes = { - ROLE: 1, - USER: 2, -} - -export const ApplicationCommandTypes = { - CHAT_INPUT: 1, - USER: 2, - MESSAGE: 3, -} - -export const AuditLogActions = { - GUILD_UPDATE: 1, - - CHANNEL_CREATE: 10, - CHANNEL_UPDATE: 11, - CHANNEL_DELETE: 12, - CHANNEL_OVERWRITE_CREATE: 13, - CHANNEL_OVERWRITE_UPDATE: 14, - CHANNEL_OVERWRITE_DELETE: 15, - - MEMBER_KICK: 20, - MEMBER_PRUNE: 21, - MEMBER_BAN_ADD: 22, - MEMBER_BAN_REMOVE: 23, - MEMBER_UPDATE: 24, - MEMBER_ROLE_UPDATE: 25, - MEMBER_MOVE: 26, - MEMBER_DISCONNECT: 27, - BOT_ADD: 28, - - ROLE_CREATE: 30, - ROLE_UPDATE: 31, - ROLE_DELETE: 32, - - INVITE_CREATE: 40, - INVITE_UPDATE: 41, - INVITE_DELETE: 42, - - WEBHOOK_CREATE: 50, - WEBHOOK_UPDATE: 51, - WEBHOOK_DELETE: 52, - - EMOJI_CREATE: 60, - EMOJI_UPDATE: 61, - EMOJI_DELETE: 62, - - MESSAGE_DELETE: 72, - MESSAGE_BULK_DELETE: 73, - MESSAGE_PIN: 74, - MESSAGE_UNPIN: 75, - - INTEGRATION_CREATE: 80, - INTEGRATION_UPDATE: 81, - INTEGRATION_DELETE: 82, - - STAGE_INSTANCE_CREATE: 83, - STAGE_INSTANCE_UPDATE: 84, - STAGE_INSTANCE_DELETE: 85, - - STICKER_CREATE: 90, - STICKER_UPDATE: 91, - STICKER_DELETE: 92, - - GUILD_SCHEDULED_EVENT_CREATE: 100, - GUILD_SCHEDULED_EVENT_UPDATE: 101, - GUILD_SCHEDULED_EVENT_DELETE: 102, - - THREAD_CREATE: 110, - THREAD_UPDATE: 111, - THREAD_DELETE: 112, - - APPLICATION_COMMAND_PERMISSION_UPDATE: 121, -} - -export const ButtonStyles = { - PRIMARY: 1, - SECONDARY: 2, - SUCCESS: 3, - DANGER: 4, - LINK: 5, -} - -export enum ChannelTypes { - GUILD_TEXT = 0, - DM = 1, - GUILD_VOICE = 2, - GROUP_DM = 3, - GUILD_CATEGORY = 4, - GUILD_NEWS = 5, - GUILD_STORE = 6, - - GUILD_NEWS_THREAD = 10, - GUILD_PUBLIC_THREAD = 11, - GUILD_PRIVATE_THREAD = 12, - GUILD_STAGE_VOICE = 13, - GUILD_STAGE = 13, // [DEPRECATED] -} - -export const ComponentTypes = { - ACTION_ROW: 1, - BUTTON: 2, - SELECT_MENU: 3, -} - -export const ConnectionVisibilityTypes = { - NONE: 0, - EVERYONE: 1, -} - -export const DefaultMessageNotificationLevels = { - ALL_MESSAGES: 0, - ONLY_MENTIONS: 1, -} - -export const ExplicitContentFilterLevels = { - DISABLED: 0, - MEMBERS_WITHOUT_ROLES: 1, - ALL_MEMBERS: 2, -} - -export const GatewayOPCodes = { - DISPATCH: 0, - EVENT: 0, // [DEPRECATED] - HEARTBEAT: 1, - IDENTIFY: 2, - PRESENCE_UPDATE: 3, - STATUS_UPDATE: 3, // [DEPRECATED] - VOICE_STATE_UPDATE: 4, - VOICE_SERVER_PING: 5, - RESUME: 6, - RECONNECT: 7, - REQUEST_GUILD_MEMBERS: 8, - GET_GUILD_MEMBERS: 8, // [DEPRECATED] - INVALID_SESSION: 9, - HELLO: 10, - HEARTBEAT_ACK: 11, - SYNC_GUILD: 12, - SYNC_CALL: 13, -} - -export const GuildFeatures = [ - 'ANIMATED_ICON', - 'BANNER', - 'COMMERCE', - 'COMMUNITY', - 'CREATOR_MONETIZABLE_PROVISIONAL', - 'CREATOR_STORE_PAGE', - 'DISCOVERABLE', - 'FEATURABLE', - 'INVITE_SPLASH', - 'MEMBER_VERIFICATION_GATE_ENABLED', - 'MORE_STICKERS', - 'NEWS', - 'PARTNERED', - 'PREVIEW_ENABLED', - 'PRIVATE_THREADS', - 'ROLE_ICONS', - 'ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE', - 'ROLE_SUBSCRIPTIONS_ENABLED', - 'SEVEN_DAY_THREAD_ARCHIVE', - 'THREE_DAY_THREAD_ARCHIVE', - 'TICKETED_EVENTS_ENABLED', - 'VANITY_URL', - 'VERIFIED', - 'VIP_REGIONS', - 'WELCOME_SCREEN_ENABLED', -] - -export const GuildIntegrationExpireBehavior = { - REMOVE_ROLE: 0, - KICK: 1, -} -export const GuildIntegrationTypes = ['twitch', 'youtube', 'discord'] - -export const GuildNSFWLevels = { - DEFAULT: 0, - EXPLICIT: 1, - SAFE: 2, - AGE_RESTRICTED: 3, -} - -export const ImageFormats = ['jpg', 'jpeg', 'png', 'webp', 'gif'] - -export const ImageSizeBoundaries = { - MINIMUM: 16, - MAXIMUM: 4096, -} - -export const Intents = { - guilds: 1 << 0, - guildMembers: 1 << 1, - guildBans: 1 << 2, - guildEmojisAndStickers: 1 << 3, - guildEmojis: 1 << 3, // [DEPRECATED] - guildIntegrations: 1 << 4, - guildWebhooks: 1 << 5, - guildInvites: 1 << 6, - guildVoiceStates: 1 << 7, - guildPresences: 1 << 8, - guildMessages: 1 << 9, - guildMessageReactions: 1 << 10, - guildMessageTyping: 1 << 11, - directMessages: 1 << 12, - directMessageReactions: 1 << 13, - directMessageTyping: 1 << 14, - - guildScheduledEvents: 1 << 16, - // Override these below - allNonPrivileged: 0, - allPrivileged: 0, - all: 0, -} - -Intents.allNonPrivileged = - Intents.guilds | - Intents.guildBans | - Intents.guildEmojisAndStickers | - Intents.guildIntegrations | - Intents.guildWebhooks | - Intents.guildInvites | - Intents.guildVoiceStates | - Intents.guildMessages | - Intents.guildMessageReactions | - Intents.guildMessageTyping | - Intents.directMessages | - Intents.directMessageReactions | - Intents.directMessageTyping | - Intents.guildScheduledEvents -Intents.allPrivileged = Intents.guildMembers | Intents.guildPresences -Intents.all = Intents.allNonPrivileged | Intents.allPrivileged - -export const InteractionResponseTypes = { - PONG: 1, - CHANNEL_MESSAGE_WITH_SOURCE: 4, - DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE: 5, - DEFERRED_UPDATE_MESSAGE: 6, - UPDATE_MESSAGE: 7, - APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: 8, -} - -export const InteractionTypes = { - PING: 1, - APPLICATION_COMMAND: 2, - MESSAGE_COMPONENT: 3, - APPLICATION_COMMAND_AUTOCOMPLETE: 4, -} - -export const MFALevels = { - NONE: 0, - ELEVATED: 1, -} - -export const MessageActivityFlags = { - INSTANCE: 1 << 0, - JOIN: 1 << 1, - SPECTATE: 1 << 2, - JOIN_REQUEST: 1 << 3, - SYNC: 1 << 4, - PLAY: 1 << 5, - PARTY_PRIVACY_FRIENDS: 1 << 6, - PARTY_PRIVACY_VOICE_CHANNEL: 1 << 7, - EMBEDDED: 1 << 8, -} - -export const MessageActivityTypes = { - JOIN: 1, - SPECTATE: 2, - LISTEN: 3, - JOIN_REQUEST: 5, -} - -export const MessageTypes = { - DEFAULT: 0, - RECIPIENT_ADD: 1, - RECIPIENT_REMOVE: 2, - CALL: 3, - CHANNEL_NAME_CHANGE: 4, - CHANNEL_ICON_CHANGE: 5, - CHANNEL_PINNED_MESSAGE: 6, - GUILD_MEMBER_JOIN: 7, - USER_PREMIUM_GUILD_SUBSCRIPTION: 8, - USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1: 9, - USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2: 10, - USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3: 11, - CHANNEL_FOLLOW_ADD: 12, - - GUILD_DISCOVERY_DISQUALIFIED: 14, - GUILD_DISCOVERY_REQUALIFIED: 15, - GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING: 16, - GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING: 17, - THREAD_CREATED: 18, - REPLY: 19, - CHAT_INPUT_COMMAND: 20, - THREAD_STARTER_MESSAGE: 21, - GUILD_INVITE_REMINDER: 22, - CONTEXT_MENU_COMMAND: 23, - AUTO_MODERATION_ACTION: 24, - ROLE_SUBSCRIPTION_PURCHASE: 25, - INTERACTION_PREMIUM_UPSELL: 26, - STAGE_START: 27, - STAGE_END: 28, - STAGE_SPEAKER: 29, - - STAGE_TOPIC: 31, - GUILD_APPLICATION_PREMIUM_SUBSCRIPTION: 32, -} - -export const PermissionOverwriteTypes = { - ROLE: 0, - USER: 1, -} - -export const Permissions = { - createInstantInvite: 1n << 0n, - kickMembers: 1n << 1n, - banMembers: 1n << 2n, - administrator: 1n << 3n, - manageChannels: 1n << 4n, - manageGuild: 1n << 5n, - addReactions: 1n << 6n, - viewAuditLog: 1n << 7n, - viewAuditLogs: 1n << 7n, // [DEPRECATED] - voicePrioritySpeaker: 1n << 8n, - voiceStream: 1n << 9n, - stream: 1n << 9n, // [DEPRECATED] - viewChannel: 1n << 10n, - readMessages: 1n << 10n, // [DEPRECATED] - sendMessages: 1n << 11n, - sendTTSMessages: 1n << 12n, - manageMessages: 1n << 13n, - embedLinks: 1n << 14n, - attachFiles: 1n << 15n, - readMessageHistory: 1n << 16n, - mentionEveryone: 1n << 17n, - useExternalEmojis: 1n << 18n, - externalEmojis: 1n << 18n, // [DEPRECATED] - viewGuildInsights: 1n << 19n, - voiceConnect: 1n << 20n, - voiceSpeak: 1n << 21n, - voiceMuteMembers: 1n << 22n, - voiceDeafenMembers: 1n << 23n, - voiceMoveMembers: 1n << 24n, - voiceUseVAD: 1n << 25n, - changeNickname: 1n << 26n, - manageNicknames: 1n << 27n, - manageRoles: 1n << 28n, - manageWebhooks: 1n << 29n, - manageEmojisAndStickers: 1n << 30n, - manageEmojis: 1n << 30n, // [DEPRECATED] - useApplicationCommands: 1n << 31n, - useSlashCommands: 1n << 31n, // [DEPRECATED] - voiceRequestToSpeak: 1n << 32n, - manageEvents: 1n << 33n, - manageThreads: 1n << 34n, - createPublicThreads: 1n << 35n, - createPrivateThreads: 1n << 36n, - useExternalStickers: 1n << 37n, - sendMessagesInThreads: 1n << 38n, - startEmbeddedActivities: 1n << 39n, - moderateMembers: 1n << 40n, - // Override these below - all: 0n, - allText: 0n, - allVoice: 0n, - allGuild: 0n, -} -Permissions.allGuild = - Permissions.kickMembers | - Permissions.banMembers | - Permissions.administrator | - Permissions.manageChannels | - Permissions.manageGuild | - Permissions.viewAuditLog | - Permissions.viewGuildInsights | - Permissions.changeNickname | - Permissions.manageNicknames | - Permissions.manageRoles | - Permissions.manageWebhooks | - Permissions.manageEmojisAndStickers | - Permissions.manageEvents | - Permissions.moderateMembers -Permissions.allText = - Permissions.createInstantInvite | - Permissions.manageChannels | - Permissions.addReactions | - Permissions.viewChannel | - Permissions.sendMessages | - Permissions.sendTTSMessages | - Permissions.manageMessages | - Permissions.embedLinks | - Permissions.attachFiles | - Permissions.readMessageHistory | - Permissions.mentionEveryone | - Permissions.useExternalEmojis | - Permissions.manageRoles | - Permissions.manageWebhooks | - Permissions.useApplicationCommands | - Permissions.manageThreads | - Permissions.createPublicThreads | - Permissions.createPrivateThreads | - Permissions.useExternalStickers | - Permissions.sendMessagesInThreads -Permissions.allVoice = - Permissions.createInstantInvite | - Permissions.manageChannels | - Permissions.voicePrioritySpeaker | - Permissions.voiceStream | - Permissions.viewChannel | - Permissions.voiceConnect | - Permissions.voiceSpeak | - Permissions.voiceMuteMembers | - Permissions.voiceDeafenMembers | - Permissions.voiceMoveMembers | - Permissions.voiceUseVAD | - Permissions.manageRoles | - Permissions.voiceRequestToSpeak | - Permissions.startEmbeddedActivities -Permissions.all = Permissions.allGuild | Permissions.allText | Permissions.allVoice - -export const PremiumTiers = { - NONE: 0, - TIER_1: 1, - TIER_2: 2, - TIER_3: 3, -} - -export const GuildScheduledEventStatus = { - SCHEDULED: 1, - ACTIVE: 2, - COMPLETED: 3, - CANCELED: 4, -} - -export const GuildScheduledEventEntityTypes = { - STAGE_INSTANCE: 1, - VOICE: 2, - EXTERNAL: 3, -} - -export const GuildScheduledEventPrivacyLevel = { - PUBLIC: 1, - GUILD_ONLY: 2, -} - -export const PremiumTypes = { - NONE: 0, - NITRO_CLASSIC: 1, - NITRO: 2, -} - -export const StageInstancePrivacyLevel = { - PUBLIC: 1, - GUILD_ONLY: 2, -} - -export const StickerFormats = { - PNG: 1, - APNG: 2, - LOTTIE: 3, -} - -export const StickerTypes = { - STANDARD: 1, - GUILD: 2, -} - -export const SystemChannelFlags = { - SUPPRESS_JOIN_NOTIFICATIONS: 1 << 0, - SUPPRESS_PREMIUM_SUBSCRIPTIONS: 1 << 1, - SUPPRESS_GUILD_REMINDER_NOTIFICATIONS: 1 << 2, - SUPPRESS_JOIN_NOTIFICATION_REPLIES: 1 << 3, -} - -export const SystemJoinMessages = [ - '%user% joined the party.', - '%user% is here.', - 'Welcome, %user%. We hope you brought pizza.', - 'A wild %user% appeared.', - '%user% just landed.', - '%user% just slid into the server.', - '%user% just showed up!', - 'Welcome %user%. Say hi!', - '%user% hopped into the server.', - 'Everyone welcome %user%!', - "Glad you're here, %user%.", - 'Good to see you, %user%.', - 'Yay you made it, %user%!', -] - -export const ThreadMemberFlags = { - HAS_INTERACTED: 1 << 0, - ALL_MESSAGES: 1 << 1, - ONLY_MENTIONS: 1 << 2, - NO_MESSAGES: 1 << 3, -} - -export const UserFlags = { - NONE: 0, - DISCORD_STAFF: 1 << 0, - DISCORD_EMPLOYEE: 1 << 0, - PARTNER: 1 << 1, - PARTNERED_SERVER_OWNER: 1 << 1, - DISCORD_PARTNER: 1 << 1, // [DEPRECATED] - HYPESQUAD: 1 << 2, - HYPESQUAD_EVENTS: 1 << 2, - BUG_HUNTER_LEVEL_1: 1 << 3, - HOUSE_BRAVERY: 1 << 6, - HYPESQUAD_ONLINE_HOUSE_1: 1 << 6, - HOUSE_BRILLIANCE: 1 << 7, - HYPESQUAD_ONLINE_HOUSE_2: 1 << 7, - HOUSE_BALANCE: 1 << 8, - HYPESQUAD_ONLINE_HOUSE_3: 1 << 8, - PREMIUM_EARLY_SUPPORTER: 1 << 9, - EARLY_SUPPORTER: 1 << 9, - TEAM_PSEUDO_USER: 1 << 10, - TEAM_USER: 1 << 10, - SYSTEM: 1 << 12, - BUG_HUNTER_LEVEL_2: 1 << 14, - VERIFIED_BOT: 1 << 16, - VERIFIED_DEVELOPER: 1 << 17, - EARLY_VERIFIED_BOT_DEVELOPER: 1 << 17, - VERIFIED_BOT_DEVELOPER: 1 << 17, - CERTIFIED_MODERATOR: 1 << 18, - DISCORD_CERTIFIED_MODERATOR: 1 << 18, - BOT_HTTP_INTERACTIONS: 1 << 19, -} - -export const VerificationLevels = { - NONE: 0, - LOW: 1, - MEDIUM: 2, - HIGH: 3, - VERY_HIGH: 4, -} - -export const VideoQualityModes = { - AUTO: 1, - FULL: 2, -} - -export const VoiceOPCodes = { - IDENTIFY: 0, - SELECT_PROTOCOL: 1, - READY: 2, - HEARTBEAT: 3, - SESSION_DESCRIPTION: 4, - SPEAKING: 5, - HEARTBEAT_ACK: 6, - RESUME: 7, - HELLO: 8, - RESUMED: 9, - CLIENT_DISCONNECT: 13, - DISCONNECT: 13, // [DEPRECATED] -} - -export const WebhookTypes = { - INCOMING: 1, - CHANNEL_FOLLOWER: 2, - APPLICATION: 3, -} - -export type IntentStrings = keyof typeof Intents -export type PermissionClientStrings = keyof typeof Permissions - -export const Constants = { - GATEWAY_VERSION, - REST_VERSION, - MessageFlags, - ActivityTypes, - ApplicationCommandOptionTypes, - ApplicationCommandPermissionTypes, - ApplicationCommandTypes, - AuditLogActions, - ButtonStyles, - ComponentTypes, - ConnectionVisibilityTypes, - DefaultMessageNotificationLevels, - ExplicitContentFilterLevels, - GatewayOPCodes, - GuildFeatures, - GuildIntegrationExpireBehavior, - GuildIntegrationTypes, - GuildNSFWLevels, - ImageFormats, - ImageSizeBoundaries, - Intents, - InteractionResponseTypes, - InteractionTypes, - MFALevels, - MessageActivityFlags, - MessageActivityTypes, - MessageTypes, - PermissionOverwriteTypes, - Permissions, - PremiumTiers, - GuildScheduledEventStatus, - GuildScheduledEventEntityTypes, - GuildScheduledEventPrivacyLevel, - PremiumTypes, - StageInstancePrivacyLevel, - StickerFormats, - StickerTypes, - SystemChannelFlags, - SystemJoinMessages, - ThreadMemberFlags, - UserFlags, - VerificationLevels, - VideoQualityModes, - VoiceOPCodes, - WebhookTypes, -} -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type Constants = typeof Constants diff --git a/packages/client/src/Endpoints.ts b/packages/client/src/Endpoints.ts deleted file mode 100644 index 62a49a4e6..000000000 --- a/packages/client/src/Endpoints.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import type { BigString } from './Client.js' - -export const ORIGINAL_INTERACTION_RESPONSE = (appID: BigString, interactToken: string) => `/webhooks/${appID}/${interactToken}` -export const COMMAND = (applicationID: BigString, commandID: BigString) => `/applications/${applicationID}/commands/${commandID}` -export const COMMANDS = (applicationID: BigString) => `/applications/${applicationID}/commands` -export const COMMAND_PERMISSIONS = (applicationID: BigString, guildID: BigString, commandID: BigString) => - `/applications/${applicationID}/guilds/${guildID}/commands/${commandID}/permissions` -export const CHANNEL = (chanID: BigString) => `/channels/${chanID}` -export const CHANNEL_BULK_DELETE = (chanID: BigString) => `/channels/${chanID}/messages/bulk-delete` -export const CHANNEL_CALL_RING = (chanID: BigString) => `/channels/${chanID}/call/ring` -export const CHANNEL_CROSSPOST = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}/crosspost` -export const CHANNEL_FOLLOW = (chanID: BigString) => `/channels/${chanID}/followers` -export const CHANNEL_INVITES = (chanID: BigString) => `/channels/${chanID}/invites` -export const CHANNEL_MESSAGE_REACTION = (chanID: BigString, msgID: BigString, reaction: string) => - `/channels/${chanID}/messages/${msgID}/reactions/${reaction}` -export const CHANNEL_MESSAGE_REACTION_USER = (chanID: BigString, msgID: BigString, reaction: string, userID: BigString) => - `/channels/${chanID}/messages/${msgID}/reactions/${reaction}/${userID}` -export const CHANNEL_MESSAGE_REACTIONS = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}/reactions` -export const CHANNEL_MESSAGE = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}` -export const CHANNEL_MESSAGES = (chanID: BigString) => `/channels/${chanID}/messages` -export const CHANNEL_MESSAGES_SEARCH = (chanID: BigString) => `/channels/${chanID}/messages/search` -export const CHANNEL_PERMISSION = (chanID: BigString, overID: BigString) => `/channels/${chanID}/permissions/${overID}` -export const CHANNEL_PERMISSIONS = (chanID: BigString) => `/channels/${chanID}/permissions` -export const CHANNEL_PIN = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/pins/${msgID}` -export const CHANNEL_PINS = (chanID: BigString) => `/channels/${chanID}/pins` -export const CHANNEL_RECIPIENT = (groupID: BigString, userID: BigString) => `/channels/${groupID}/recipients/${userID}` -export const CHANNEL_TYPING = (chanID: BigString) => `/channels/${chanID}/typing` -export const CHANNEL_WEBHOOKS = (chanID: BigString) => `/channels/${chanID}/webhooks` -export const CHANNELS = '/channels' -export const CUSTOM_EMOJI_GUILD = (emojiID: BigString) => `/emojis/${emojiID}/guild` -export const DISCOVERY_CATEGORIES = '/discovery/categories' -export const DISCOVERY_VALIDATION = '/discovery/valid-term' -export const GATEWAY = '/gateway' -export const GATEWAY_BOT = '/gateway/bot' -export const GUILD = (guildID: BigString) => `/guilds/${guildID}` -export const GUILD_AUDIT_LOGS = (guildID: BigString) => `/guilds/${guildID}/audit-logs` -export const GUILD_BAN = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/bans/${memberID}` -export const GUILD_BANS = (guildID: BigString) => `/guilds/${guildID}/bans` -export const GUILD_CHANNELS = (guildID: BigString) => `/guilds/${guildID}/channels` -export const GUILD_COMMAND = (applicationID: BigString, guildID: BigString, commandID: BigString) => - `/applications/${applicationID}/guilds/${guildID}/commands/${commandID}` -export const GUILD_COMMAND_PERMISSIONS = (applicationID: BigString, guildID: BigString) => - `/applications/${applicationID}/guilds/${guildID}/commands/permissions` -export const GUILD_COMMANDS = (applicationID: BigString, guildID: BigString) => `/applications/${applicationID}/guilds/${guildID}/commands` -export const GUILD_DISCOVERY = (guildID: BigString) => `/guilds/${guildID}/discovery-metadata` -export const GUILD_DISCOVERY_CATEGORY = (guildID: BigString, categoryID: BigString) => `/guilds/${guildID}/discovery-categories/${categoryID}` -export const GUILD_EMOJI = (guildID: BigString, emojiID: BigString) => `/guilds/${guildID}/emojis/${emojiID}` -export const GUILD_EMOJIS = (guildID: BigString) => `/guilds/${guildID}/emojis` -export const GUILD_INTEGRATION = (guildID: BigString, inteID: BigString) => `/guilds/${guildID}/integrations/${inteID}` -export const GUILD_INTEGRATION_SYNC = (guildID: BigString, inteID: BigString) => `/guilds/${guildID}/integrations/${inteID}/sync` -export const GUILD_INTEGRATIONS = (guildID: BigString) => `/guilds/${guildID}/integrations` -export const GUILD_INVITES = (guildID: BigString) => `/guilds/${guildID}/invites` -export const GUILD_VANITY_URL = (guildID: BigString) => `/guilds/${guildID}/vanity-url` -export const GUILD_MEMBER = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/members/${memberID}` -export const GUILD_MEMBER_NICK = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/members/${memberID}/nick` -export const GUILD_MEMBER_ROLE = (guildID: BigString, memberID: BigString, roleID: BigString) => - `/guilds/${guildID}/members/${memberID}/roles/${roleID}` -export const GUILD_MEMBERS = (guildID: BigString) => `/guilds/${guildID}/members` -export const GUILD_MEMBERS_SEARCH = (guildID: BigString) => `/guilds/${guildID}/members/search` -export const GUILD_MESSAGES_SEARCH = (guildID: BigString) => `/guilds/${guildID}/messages/search` -export const GUILD_PREVIEW = (guildID: BigString) => `/guilds/${guildID}/preview` -export const GUILD_PRUNE = (guildID: BigString) => `/guilds/${guildID}/prune` -export const GUILD_ROLE = (guildID: BigString, roleID: BigString) => `/guilds/${guildID}/roles/${roleID}` -export const GUILD_ROLES = (guildID: BigString) => `/guilds/${guildID}/roles` -export const GUILD_STICKER = (guildID: BigString, stickerID: BigString) => `/guilds/${guildID}/stickers/${stickerID}` -export const GUILD_STICKERS = (guildID: BigString) => `/guilds/${guildID}/stickers` -export const GUILD_TEMPLATE = (code: string) => `/guilds/templates/${code}` -export const GUILD_TEMPLATES = (guildID: BigString) => `/guilds/${guildID}/templates` -export const GUILD_TEMPLATE_GUILD = (guildID: BigString, code: string) => `/guilds/${guildID}/templates/${code}` -export const GUILD_VOICE_REGIONS = (guildID: BigString) => `/guilds/${guildID}/regions` -export const GUILD_WEBHOOKS = (guildID: BigString) => `/guilds/${guildID}/webhooks` -export const GUILD_WELCOME_SCREEN = (guildID: BigString) => `/guilds/${guildID}/welcome-screen` -export const GUILD_WIDGET = (guildID: BigString) => `/guilds/${guildID}/widget.json` -export const GUILD_WIDGET_SETTINGS = (guildID: BigString) => `/guilds/${guildID}/widget` -export const GUILD_VOICE_STATE = (guildID: BigString, user: BigString) => `/guilds/${guildID}/voice-states/${user}` -export const GUILDS = '/guilds' -export const INTERACTION_RESPOND = (interactID: BigString, interactToken: string) => `/interactions/${interactID}/${interactToken}/callback` -export const INVITE = (inviteID: string) => `/invites/${inviteID}` -export const OAUTH2_APPLICATION = (appID: BigString) => `/oauth2/applications/${appID}` -export const STAGE_INSTANCE = (channelID: BigString) => `/stage-instances/${channelID}` -export const STAGE_INSTANCES = '/stage-instances' -export const STICKER = (stickerID: BigString) => `/stickers/${stickerID}` -export const STICKER_PACKS = '/sticker-packs' -export const THREAD_MEMBER = (channelID: BigString, userID: BigString) => `/channels/${channelID}/thread-members/${userID}` -export const THREAD_MEMBERS = (channelID: BigString) => `/channels/${channelID}/thread-members` -export const THREAD_WITH_MESSAGE = (channelID: BigString, msgID: BigString) => `/channels/${channelID}/messages/${msgID}/threads` -export const THREAD_WITHOUT_MESSAGE = (channelID: BigString) => `/channels/${channelID}/threads` -export const THREADS_ACTIVE = (channelID: BigString) => `/channels/${channelID}/threads/active` -export const THREADS_ARCHIVED = (channelID: BigString, type: string) => `/channels/${channelID}/threads/archived/${type}` -export const THREADS_ARCHIVED_JOINED = (channelID: BigString) => `/channels/${channelID}/users/@me/threads/archived/private` -export const THREADS_GUILD_ACTIVE = (guildID: BigString) => `/guilds/${guildID}/threads/active` -export const USER = (userID: BigString) => `/users/${userID}` -export const USER_BILLING = (userID: BigString) => `/users/${userID}/billing` -export const USER_BILLING_PAYMENTS = (userID: BigString) => `/users/${userID}/billing/payments` -export const USER_BILLING_PREMIUM_SUBSCRIPTION = (userID: BigString) => `/users/${userID}/billing/premium-subscription` -export const USER_CHANNELS = (userID: BigString) => `/users/${userID}/channels` -export const USER_CONNECTIONS = (userID: BigString) => `/users/${userID}/connections` -export const USER_CONNECTION_PLATFORM = (userID: BigString, platform: string, id: string) => `/users/${userID}/connections/${platform}/${id}` -export const USER_GUILD = (userID: BigString, guildID: BigString) => `/users/${userID}/guilds/${guildID}` -export const USER_GUILDS = (userID: BigString) => `/users/${userID}/guilds` -export const USER_MFA_CODES = (userID: BigString) => `/users/${userID}/mfa/codes` -export const USER_MFA_TOTP_DISABLE = (userID: BigString) => `/users/${userID}/mfa/totp/disable` -export const USER_MFA_TOTP_ENABLE = (userID: BigString) => `/users/${userID}/mfa/totp/enable` -export const USER_NOTE = (userID: BigString, targetID: BigString) => `/users/${userID}/note/${targetID}` -export const USER_PROFILE = (userID: BigString) => `/users/${userID}/profile` -export const USER_RELATIONSHIP = (userID: BigString, relID: BigString) => `/users/${userID}/relationships/${relID}` -export const USER_SETTINGS = (userID: BigString) => `/users/${userID}/settings` -export const USERS = '/users' -export const VOICE_REGIONS = '/voice/regions' -export const WEBHOOK = (hookID: BigString) => `/webhooks/${hookID}` -export const WEBHOOK_MESSAGE = (hookID: BigString, token: string, msgID: BigString) => `/webhooks/${hookID}/${token}/messages/${msgID}` -export const WEBHOOK_SLACK = (hookID: BigString) => `/webhooks/${hookID}/slack` -export const WEBHOOK_TOKEN = (hookID: BigString, token: string) => `/webhooks/${hookID}/${token}` -export const WEBHOOK_TOKEN_SLACK = (hookID: BigString, token: string) => `/webhooks/${hookID}/${token}/slack` - -// CDN Endpoints -export const ACHIEVEMENT_ICON = (applicationID: BigString, achievementID: BigString, icon: string) => - `/app-assets/${applicationID}/achievements/${achievementID}/icons/${icon}` -export const APPLICATION_ASSET = (applicationID: BigString, asset: string) => `/app-assets/${applicationID}/${asset}` -export const APPLICATION_ICON = (applicationID: BigString, icon: string) => `/app-icons/${applicationID}/${icon}` -export const BANNER = (guildOrUserID: BigString, hash: string) => `/banners/${guildOrUserID}/${hash}` -export const CHANNEL_ICON = (chanID: BigString, chanIcon: string) => `/channel-icons/${chanID}/${chanIcon}` -export const CUSTOM_EMOJI = (emojiID: BigString) => `/emojis/${emojiID}` -export const DEFAULT_USER_AVATAR = (userDiscriminator: string) => `/embed/avatars/${userDiscriminator}` -export const GUILD_AVATAR = (guildID: BigString, userID: BigString, guildAvatar: string) => - `/guilds/${guildID}/users/${userID}/avatars/${guildAvatar}` -export const GUILD_DISCOVERY_SPLASH = (guildID: BigString, guildDiscoverySplash: string) => `/discovery-splashes/${guildID}/${guildDiscoverySplash}` -export const GUILD_ICON = (guildID: BigString, guildIcon: string) => `/icons/${guildID}/${guildIcon}` -export const GUILD_SPLASH = (guildID: BigString, guildSplash: string) => `/splashes/${guildID}/${guildSplash}` -export const ROLE_ICON = (roleID: BigString, roleIcon: string) => `/role-icons/${roleID}/${roleIcon}` -export const TEAM_ICON = (teamID: BigString, teamIcon: string) => `/team-icons/${teamID}/${teamIcon}` -export const USER_AVATAR = (userID: BigString, userAvatar: string) => `/avatars/${userID}/${userAvatar}` - -// Client Endpoints -export const MESSAGE_LINK = (guildID: BigString, channelID: BigString, messageID: BigString) => `/channels/${guildID}/${channelID}/${messageID}` diff --git a/packages/client/src/RequestHandler.ts b/packages/client/src/RequestHandler.ts deleted file mode 100644 index beb40beef..000000000 --- a/packages/client/src/RequestHandler.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { RequestMethods, RestManager } from '@discordeno/rest' -import { createRestManager } from '@discordeno/rest' -import Base from './Base.js' -import type Client from './Client.js' -import type { FileContent, RequestHandlerOptions } from './typings.js' - -// TODO: make dynamic based on package.json file -const version = '19.0.0-alpha.1' - -export class RequestHandler { - /** The client manager. */ - client: Client - /** The options this manager was configured with. */ - options: RequestHandlerOptions - /** The user agent used to make requests. */ - userAgent: string - /** The rate limits currently in cache. */ - ratelimits: Record - /** The latency information for this manager. */ - latencyRef: { - latency: number - raw: number[] - timeOffset: number - timeOffsets: number[] - lastTimeOffsetCheck: number - } - - /** Whether or not the manager is globally blocked. */ - globalBlock: boolean - /** The ready queue */ - readyQueue: unknown[] - /** The internal rest manager from dd. */ - discordeno: RestManager - - constructor(client: Client, options: RequestHandlerOptions) { - this.options = options = Object.assign( - { - // agent: client.options.agent || null, - agent: null, - baseURL: 'https://discord.com/api', - decodeReasons: true, - disableLatencyCompensation: false, - domain: 'discord.com', - // latencyThreshold: client.options.latencyThreshold || 30000, - latencyThreshold: 30000, - // ratelimiterOffset: client.options.ratelimiterOffset || 0, - ratelimiterOffset: 0, - // requestTimeout: client.options.requestTimeout || 15000, - requestTimeout: 15000, - }, - options, - ) - - this.client = client - this.discordeno = createRestManager({ - token: this.client.token, - proxy: - options.baseURL ?? this.client.options.proxyURL - ? { - baseUrl: options.baseURL ?? this.client.options.proxyURL!, - authorization: this.client.token, - } - : undefined, - }) - - this.userAgent = `DiscordBot (https://github.com/discordeno/discordeno, ${version})` - this.ratelimits = {} - this.latencyRef = { - latency: this.options.ratelimiterOffset ?? 0, - raw: new Array(10).fill(this.options.ratelimiterOffset), - timeOffset: 0, - timeOffsets: new Array(10).fill(0), - lastTimeOffsetCheck: 0, - } - this.globalBlock = false - this.readyQueue = [] - } - - /** - * @deprecated Use `.client` instead - */ - get _client(): Client { - return this.client - } - - /** - * @deprecated Useless, handled by discordeno itself. Kept for Eris api compatibility. - */ - globalUnblock(): void {} - - warnUser(): void {} - - /** - * Make an API request - * @deprecated Use a proxy rest instead. - */ - async request(method: RequestMethods, url: string, auth?: boolean, body?: any, file?: FileContent): Promise { - if (file) body.file = file - - return await this.discordeno.makeRequest(method, url, body) - } - - routefy(url: string, method: RequestMethods): string { - return this.discordeno.simplifyUrl(url, method) - } - - toString(): string { - return '[RequestHandler]' - } - - toJSON(props: string[] = []): Record { - return Base.prototype.toJSON.call(this, ['globalBlock', 'latencyRef', 'options', 'ratelimits', 'readyQueue', 'userAgent', ...props]) - } -} - -export default RequestHandler diff --git a/packages/client/src/Structures/Invite.ts b/packages/client/src/Structures/Invite.ts deleted file mode 100644 index ce589ab27..000000000 --- a/packages/client/src/Structures/Invite.ts +++ /dev/null @@ -1,149 +0,0 @@ -/* eslint-disable no-useless-call */ - -import type { DiscordInvite, DiscordInviteCreate, DiscordMemberWithUser, TargetTypes } from '@discordeno/types' -import Base from '../Base.js' -import type Client from '../Client.js' -import type Channel from './channels/Channel.js' -import Guild from './guilds/Guild.js' -import Member from './guilds/Member.js' -import User from './users/User.js' - -export class Invite { - /** The client object. */ - client: Client - /** The invite code (unique Id) */ - code: string - /** The channel this invite is for */ - channel?: Channel - /** The guild this invite is for. */ - guild?: Guild - /** The user who created this invite. */ - inviter?: User - /** The amount of times this invite has been used. */ - uses: number | null = null - /** The amount of times this invite can be used. */ - maxUses: number | null = null - /** How long the invite is valid for (in seconds) */ - maxAge: number | null = null - /** Whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) */ - temporary: boolean = false - /** The time at which the invite was created */ - createdAt?: number - - presenceCount?: number | null - memberCount?: number | null - - stageInstance?: { - members: Member[] - participantCount: number - speakerCount: number - topic: string - } | null - - targetApplicationID?: string | null - targetType?: TargetTypes | null - targetUser?: User | null - - constructor(data: DiscordInvite | DiscordInviteCreate, client: Client) { - // super(); - this.client = client - this.code = data.code - // @ts-expect-error js hacks - this.channel = data.channel - - if (data.inviter) { - this.inviter = new User(data.inviter, client) - client.users.set(this.inviter.id, this.inviter) - } - - if (this.isInviteCreate(data)) { - this.uses = data.uses !== undefined ? data.uses : null - this.maxUses = data.max_uses !== undefined ? data.max_uses : null - this.maxAge = data.max_age !== undefined ? data.max_age : null - this.temporary = data.temporary !== undefined ? data.temporary : false - this.createdAt = Date.parse(data.created_at) - } else { - if (data.guild) { - if (client.guilds.has(data.guild.id!)) { - if (data.channel) { - // @ts-expect-error should work i think dumb partials - const channel = new GuildChannel(data.channel, client) - client.guilds.get(data.guild.id!)?.channels.set(channel.id, channel) - } - } else { - // @ts-expect-error js hacks - this.guild = new Guild(data.guild, client) - } - } - - this.presenceCount = data.approximate_presence_count !== undefined ? data.approximate_presence_count : null - this.memberCount = data.approximate_member_count !== undefined ? data.approximate_member_count : null - if (data.stage_instance !== undefined) { - this.stageInstance = { - members: data.stage_instance.members.map((m) => { - // @ts-expect-error js hacks - const member = new Member(m as DiscordMemberWithUser, this.guild, client) - this.guild?.members.set(member.id, member) - return member - }), - participantCount: data.stage_instance.participant_count, - speakerCount: data.stage_instance.speaker_count, - topic: data.stage_instance.topic, - } - } else { - this.stageInstance = null - } - } - - this.targetApplicationID = data.target_application !== undefined ? data.target_application.id : null - this.targetType = data.target_type !== undefined ? data.target_type : null - this.targetUser = data.target_user !== undefined ? new User(data.target_user, client) : null - if (this.targetUser) client.users.set(this.targetUser.id, this.targetUser) - } - - /** - * @deprecated Use .client - */ - get _client(): Client { - return this.client - } - - /** - * @deprecated Use .createdAt - */ - get _createdAt(): number | undefined { - return this.createdAt - } - - /** Delete the invite */ - async delete(reason?: string): Promise { - return await this.client.deleteInvite.call(this.client, this.code, reason) - } - - toString(): string { - return `[Invite ${this.code}]` - } - - toJSON(props = []): Record { - return Base.prototype.toJSON([ - 'channel', - 'code', - 'createdAt', - 'guild', - 'maxAge', - 'maxUses', - 'memberCount', - 'presenceCount', - 'revoked', - 'temporary', - 'uses', - ...props, - ]) - } - - isInviteCreate(data: DiscordInvite | DiscordInviteCreate): data is DiscordInviteCreate { - return Reflect.has(data, 'created_at') - } -} - -export default Invite diff --git a/packages/client/src/Structures/Message.ts b/packages/client/src/Structures/Message.ts deleted file mode 100644 index edae6ee72..000000000 --- a/packages/client/src/Structures/Message.ts +++ /dev/null @@ -1,444 +0,0 @@ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ - -import { - MessageTypes, - type DiscordApplication, - type DiscordAttachment, - type DiscordEmbed, - type DiscordMemberWithUser, - type DiscordMessage, - type DiscordMessageActivity, - type DiscordMessageComponents, - type DiscordStickerItem, - type InteractionTypes, -} from '@discordeno/types' -import Base from '../Base.js' -import type Client from '../Client.js' -import { MessageFlags } from '../Constants.js' -import { MESSAGE_LINK } from '../Endpoints.js' -import type { GetMessageReactionOptions, MessageContentEdit, MessageWebhookContent } from '../typings.js' -import type NewsChannel from './channels/News.js' -import type PrivateChannel from './channels/Private.js' -import type TextChannel from './channels/Text.js' -import type TextVoiceChannel from './channels/TextVoice.js' -import type NewsThreadChannel from './channels/threads/NewsThread.js' -import type PrivateThreadChannel from './channels/threads/PrivateThread.js' -import type PublicThreadChannel from './channels/threads/PublicThread.js' -import type Guild from './guilds/Guild.js' -import Member from './guilds/Member.js' -import User from './users/User.js' - -export class Message extends Base { - /** The client manager. */ - client: Client - /** Timestamp of message creation */ - timestamp: number - /** The type of the message */ - type: MessageTypes - /** The channel the message is in. Can be partial with only the id if the channel is not cached. */ - channel: PrivateChannel | TextChannel | NewsChannel | NewsThreadChannel | PublicThreadChannel | PrivateThreadChannel | TextVoiceChannel - /** The message content. */ - content: string - /** An object containing the reactions on the message. Each key is a reaction emoji and each value is an object with properties `me` (Boolean) and `count` (Number) for that specific reaction emoji. */ - reactions: Record - /** The ID of the guild this message is in (undefined if in DMs) */ - guildID?: string - /** ID of the webhook that sent the message */ - webhookID?: string - /** An object containing the reference to the original message if it is a crossposted message or reply */ - messageReference?: { - /** The id of the original message this message was crossposted from */ - messageID?: string - /** The id of the channel this message was crossposted from */ - channelID?: string - /** The id of the guild this message was crossposted from */ - guildID?: string - } | null - - /** The flags that are enabled on this message. */ - flags: number - /** The message author */ - author: User - /** The message author with server-specific data */ - member?: Member - /** The message that was replied to. If undefined, message data was not received. If null, the message was deleted. */ - referencedMessage?: Message | null - /** An object containing info about the interaction the message is responding to, if applicable */ - interaction?: { - /** The id of the interaction */ - id: string - /** The type of interaction */ - type: InteractionTypes - /** The name of the command */ - name: string - /** The user who invoked the interaction */ - user: User - /** The member who invoked the interaction */ - member?: Member - } - - /** Array of mentioned users */ - mentions: User[] = [] - /** Array of mentioned roles' ids */ - roleMentions: string[] = [] - /** Array of attachments */ - attachments: DiscordAttachment[] = [] - /** Array of embeds */ - embeds: DiscordEmbed[] = [] - /** The stickers sent with the message */ - stickerItems: DiscordStickerItem[] = [] - /** An array of component objects */ - components: DiscordMessageComponents = [] - /** The activity specified in the message */ - activity?: DiscordMessageActivity - /** The application of the activity in the message */ - application?: Partial - /** The ID of the interaction's application */ - applicationID?: string - /** Timestamp of latest message edit */ - editedTimestamp?: number - /** Whether the message mentions everyone/here or not */ - mentionEveryone: boolean = false - /** Whether the message is pinned or not */ - pinned: boolean = false - /** Whether to play the message using TTS or not */ - tts: boolean = false - - constructor(data: DiscordMessage, client: Client) { - super(data.id) - - this.client = client - this.timestamp = Date.parse(data.timestamp) - - this.type = data.type || MessageTypes.Default - this.timestamp = Date.parse(data.timestamp) - // @ts-expect-error eris js hack - this.channel = this.client.getChannel(data.channel_id) ?? { - id: data.channel_id, - } - this.content = '' - this.reactions = {} - this.guildID = data.guild_id - this.webhookID = data.webhook_id - - if (data.message_reference) { - this.messageReference = { - messageID: data.message_reference.message_id, - channelID: data.message_reference.channel_id, - guildID: data.message_reference.guild_id, - } - } else { - this.messageReference = null - } - - this.flags = data.flags ?? 0 - - this.author = new User(data.author, client) - if (!data.webhook_id) { - this.client.users.set(this.author.id, this.author) - } - - if (data.referenced_message) { - const channel = this.client.getChannel(data.referenced_message.channel_id) as TextChannel - this.referencedMessage = new Message(data.referenced_message, this.client) - - if (channel) { - channel.messages.set(this.referencedMessage.id, this.referencedMessage) - } - } else { - this.referencedMessage = data.referenced_message - } - - if (data.interaction) { - this.interaction = { - id: data.interaction.id, - type: data.interaction.type, - name: data.interaction.name, - user: new User(data.interaction.user, client), - } - - if (data.interaction.member) { - data.interaction.member.user = data.interaction.user - - if (this.guild) { - this.interaction.member = new Member( - // @ts-expect-error some eris magic at play here - data.interaction.member, - this.guild, - client, - ) - this.guild.members.set(this.interaction.member.id, this.interaction.member) - } else { - // @ts-expect-error some eris magic at play here - interactionMember = data.interaction.member - } - } else if (this.guild?.members.has(data.interaction.user.id)) { - this.interaction.member = this.guild.members.get(data.interaction.user.id) - } - } - - if (this.guild) { - if (data.member) { - data.member.user = data.author - this.member = new Member(data.member as DiscordMemberWithUser, this.guild, client) - this.guild.members.set(this.member.id, this.member) - } else if (this.guild.members.has(this.author.id)) { - this.member = this.guild.members.get(this.author.id) - } - } - - this.update(data) - } - - /** - * @deprecated Use `.client` instead. - */ - get _client(): Client { - return this.client - } - - get guild(): Guild | undefined { - return this.guildID ? this.client.guilds.get(this.guildID) : undefined - } - - update(data: DiscordMessage) { - if (data.pinned !== undefined) this.pinned = !!data.pinned - - if (data.tts !== undefined) this.tts = data.tts - if (data.attachments !== undefined) this.attachments = data.attachments - if (data.embeds !== undefined) this.embeds = data.embeds - if (data.flags !== undefined) this.flags = data.flags - if (data.activity !== undefined) this.activity = data.activity - if (data.components !== undefined) this.components = data.components - if (data.application !== undefined) this.application = data.application - - if (data.edited_timestamp) this.editedTimestamp = Date.parse(data.edited_timestamp) - if (data.application_id !== undefined) this.applicationID = data.application_id - if (data.sticker_items !== undefined) this.stickerItems = data.sticker_items - - if (data.content !== undefined) { - this.content = data.content || '' - this.mentionEveryone = !!data.mention_everyone - this.mentions = [] - - for (const mention of data.mentions ?? []) { - const user = new User(mention, this.client) - this.client.users.set(user.id, user) - - if (mention.member && this.guild) { - mention.member.user = mention - this.guild.members.set(mention.id, new Member(mention.member as DiscordMemberWithUser, this.guild, this.client)) - } - } - - if (data.mention_roles) this.roleMentions = data.mention_roles - } - - if (data.reactions) { - for (const reaction of data.reactions ?? []) { - this.reactions[reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name!] = { - count: reaction.count, - me: reaction.me, - } - } - } - } - - get channelMentions(): string[] { - return (this.content.match(/<#[0-9]+>/g) ?? []).map((mention) => mention.substring(2, mention.length - 1)) - } - - get cleanContent() { - let cleanContent = this.content?.replace(//g, '$1') || '' - - let authorName = this.author.username - if (this.guild) { - const member = this.guild.members.get(this.author.id) - if (member?.nick) { - authorName = member.nick - } - } - cleanContent = cleanContent.replace(new RegExp(`<@!?${this.author.id}>`, 'g'), '@\u200b' + authorName) - - if (this.mentions) { - this.mentions.forEach((mention) => { - if (this.guild) { - const member = this.guild.members.get(mention.id) - if (member?.nick) { - cleanContent = cleanContent.replace(new RegExp(`<@!?${mention.id}>`, 'g'), '@\u200b' + member.nick) - } - } - cleanContent = cleanContent.replace(new RegExp(`<@!?${mention.id}>`, 'g'), '@\u200b' + mention.username) - }) - } - - if (this.guild && this.roleMentions) { - for (const roleID of this.roleMentions) { - const role = this.guild.roles.get(roleID) - const roleName = role ? role.name : 'deleted-role' - cleanContent = cleanContent.replace(new RegExp(`<@&${roleID}>`, 'g'), '@\u200b' + roleName) - } - } - - this.channelMentions.forEach((id) => { - const channel = this.client.getChannel(id) as TextChannel - if (channel?.name && channel.mention) { - cleanContent = cleanContent.replace(channel.mention, '#' + channel.name) - } - }) - - return cleanContent.replace(/@everyone/g, '@\u200beveryone').replace(/@here/g, '@\u200bhere') - } - - get jumpLink() { - return `${this.client.CLIENT_URL}${MESSAGE_LINK(this.guildID ?? '@me', this.channel.id, this.id)}` - } - - /** Add a reaction to a message */ - async addReaction(reaction: string): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot have reactions') - } - return await this.client.addMessageReaction.call(this.client, this.channel.id, this.id, reaction) - } - - /** Create a thread with this message */ - async createThreadWithMessage(options: { - name: string - autoArchiveDuration: 60 | 1440 | 4320 | 10080 - }): Promise { - return await this.client.createThreadWithMessage.call(this.client, this.channel.id, this.id, options) - } - - /** Crosspost (publish) a message to subscribed channels (NewsChannel only) */ - async crosspost(): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot be crossposted') - } - - return await this.client.crosspostMessage.call(this.client, this.channel.id, this.id) - } - - /** Delete the message */ - async delete(reason?: string): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot be deleted') - } - - return await this.client.deleteMessage.call(this.client, this.channel.id, this.id, reason) - } - - /** Delete the message as a webhook */ - async deleteWebhook(token: string): Promise { - if (!this.webhookID) throw new Error('Message is not a webhook') - if (this.flags & MessageFlags.EPHEMERAL) throw new Error('Ephemeral messages cannot be deleted') - - return await this.client.deleteWebhookMessage.call(this.client, this.webhookID, token, this.id) - } - - /** Edit the message */ - async edit(content: MessageContentEdit): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot be edited via this method') - } - - return await this.client.editMessage.call(this.client, this.channel.id, this.id, content) - } - - /** Edit the message as a webhook */ - async editWebhook(token: string, options: MessageWebhookContent): Promise { - if (!this.webhookID) { - throw new Error('Message is not a webhook') - } - - return await this.client.editWebhookMessage.call(this.client, this.webhookID, token, this.id, options) - } - - /** Get a list of users who reacted with a specific reaction */ - async getReaction(reaction: string, options?: GetMessageReactionOptions): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot have reactions') - } - - return await this.client.getMessageReaction.call(this.client, this.channel.id, this.id, reaction, options) - } - - /** Pin the message */ - async pin(): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot be pinned') - } - - return await this.client.pinMessage.call(this.client, this.channel.id, this.id) - } - - /** Remove a reaction from a message */ - async removeReaction(reaction: string): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot have reactions') - } - - return await this.client.removeMessageReaction.call(this.client, this.channel.id, this.id, reaction) - } - - /** Remove all reactions from a message for a single emoji */ - async removeReactionEmoji(reaction: string): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot have reactions') - } - - return await this.client.removeMessageReactionEmoji.call(this.client, this.channel.id, this.id, reaction) - } - - /** Remove all reactions from a message */ - async removeReactions(): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot have reactions') - } - - return await this.client.removeMessageReactions.call(this.client, this.channel.id, this.id) - } - - /** Unpin the message */ - async unpin(): Promise { - if (this.flags & MessageFlags.EPHEMERAL) { - throw new Error('Ephemeral messages cannot be pinned') - } - - return await this.client.unpinMessage.call(this.client, this.channel.id, this.id) - } - - toJSON(props: string[] = []): Record { - return super.toJSON([ - 'activity', - 'application', - 'attachments', - 'author', - 'content', - 'editedTimestamp', - 'embeds', - 'flags', - 'guildID', - 'hit', - 'member', - 'mentionEveryone', - 'mentions', - 'messageReference', - 'pinned', - 'reactions', - 'referencedMesssage', - 'roleMentions', - 'stickers', - 'stickerItems', - 'timestamp', - 'tts', - 'type', - 'webhookID', - ...props, - ]) - } -} - -export default Message diff --git a/packages/client/src/Structures/Permission.ts b/packages/client/src/Structures/Permission.ts deleted file mode 100644 index 475a9ff6b..000000000 --- a/packages/client/src/Structures/Permission.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { BitwisePermissionFlags } from '@discordeno/types' -import { Base } from '../Base.js' -import type { BigString } from '../Client.js' -import type { PermissionClientStrings } from '../Constants.js' -import { Permissions } from '../Constants.js' - -export class Permission { - allow: bigint - deny: bigint - _json?: Record - - constructor(allow: BigString | number = 0, deny: BigString | number = 0) { - this.allow = BigInt(allow) - this.deny = BigInt(deny) - } - - get isAdmin(): boolean { - return !!(this.allow & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) - } - - get json() { - if (!this._json) { - this._json = {} - for (const key of Object.keys(BitwisePermissionFlags)) { - if (typeof key === 'number') continue - - const perm = key as keyof typeof BitwisePermissionFlags - - if (this.allow & BigInt(BitwisePermissionFlags[perm])) { - this._json[perm] = true - } else if (this.deny & BigInt(BitwisePermissionFlags[perm])) { - this._json[perm] = false - } - } - } - return this._json - } - - /** Check if this permission allows a specific permission */ - has(permission: bigint | PermissionClientStrings): boolean { - if (this.isAdmin) return true - - if (typeof permission === 'bigint') { - return (this.allow & permission) === permission - } - return !!(this.allow & Permissions[permission]) - } - - toString() { - return `[${this.constructor.name} +${this.allow} -${this.deny}]` - } - - toJSON(props: string[] = []): Record { - return Base.prototype.toJSON.call(['allow', 'deny', ...props]) - } -} - -export default Permission diff --git a/packages/client/src/Structures/PermissionOverwrite.ts b/packages/client/src/Structures/PermissionOverwrite.ts deleted file mode 100644 index c59e07b3f..000000000 --- a/packages/client/src/Structures/PermissionOverwrite.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { DiscordOverwrite, OverwriteTypes } from '@discordeno/types' -import { Base } from '../Base.js' -import Permission from './Permission.js' - -export class PermissionOverwrite extends Permission { - id: string - type: OverwriteTypes - - constructor(data: DiscordOverwrite) { - super(data.allow, data.deny) - - this.id = data.id - this.type = data.type - } - - toJSON(props: string[] = []): Record { - return Base.prototype.toJSON.call(['id', 'type', ...props]) - } -} - -export default PermissionOverwrite diff --git a/packages/client/src/Structures/channels/Category.ts b/packages/client/src/Structures/channels/Category.ts deleted file mode 100644 index af2fce989..000000000 --- a/packages/client/src/Structures/channels/Category.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { BigString } from '@discordeno/types' -import type Collection from '../../Collection.js' -import type { AnyGuildChannel } from '../../typings.js' -import GuildChannel from './Guild.js' - -export class CategoryChannel extends GuildChannel { - get channels(): Collection> { - return this.guild?.channels.filter((c) => c.parentID === this.id) as unknown as Collection> - } -} - -export default CategoryChannel diff --git a/packages/client/src/Structures/channels/Channel.ts b/packages/client/src/Structures/channels/Channel.ts deleted file mode 100644 index 0f8a77a23..000000000 --- a/packages/client/src/Structures/channels/Channel.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ChannelTypes, DiscordChannel } from '@discordeno/types' -import Base from '../../Base.js' -import type Client from '../../Client.js' - -export class Channel extends Base { - type: ChannelTypes - client: Client - - constructor(data: DiscordChannel | Pick, client: Client) { - super(data.id) - this.type = data.type - this.client = client - } - - get mention(): string { - return `<#${this.id}>` - } - - /** - * @deprecated Removed this circular dependency hack for better alternative. - */ - static from(_data: DiscordChannel, client: Client): void { - console.error('Usage of channel.from is deprecated, please use generateChannelFrom(data, client)') - client.emit('warn', new Error(`Usage of "Channel.from" is deprecated. Use "generateChanneFrom()"`)) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['type', ...props]) - } -} - -export default Channel diff --git a/packages/client/src/Structures/channels/Guild.ts b/packages/client/src/Structures/channels/Guild.ts deleted file mode 100644 index 9f3d0dcc7..000000000 --- a/packages/client/src/Structures/channels/Guild.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ -import type { BigString, DiscordChannel, OverwriteTypes } from '@discordeno/types' -import { BitwisePermissionFlags, ChannelTypes } from '@discordeno/types' -import { Collection } from '@discordeno/utils' -import type Client from '../../Client.js' -import type { EditChannelOptions, EditChannelPositionOptions } from '../../typings.js' -import type Guild from '../guilds/Guild.js' -import type Member from '../guilds/Member.js' -import Permission from '../Permission.js' -import PermissionOverwrite from '../PermissionOverwrite.js' -import Channel from './Channel.js' - -export class GuildChannel extends Channel { - position: number - name: string - parentID?: string | null - guild: Guild - nsfw: boolean - permissionOverwrites = new Collection() - /** The RTC region ID of the channel (automatic if `null`) (guild voice channels only) */ - rtcRegion: string | null = null - - constructor(data: DiscordChannel, client: Client) { - super(data, client) - - this.position = data.position ?? 0 - this.guild = client.guilds.get(data.guild_id!)! - this.name = data.name ?? '' - this.parentID = data.parent_id - this.nsfw = !!data.nsfw - } - - update(data: DiscordChannel): void { - if (data.type !== undefined) { - this.type = data.type - } - if (data.name !== undefined) { - this.name = data.name - } - if (data.position !== undefined) { - this.position = data.position - } - if (data.parent_id !== undefined) { - this.parentID = data.parent_id - } - this.nsfw = !!data.nsfw - if (data.permission_overwrites) { - data.permission_overwrites.forEach((overwrite) => { - const perms = new PermissionOverwrite(overwrite) - this.permissionOverwrites.set(perms.id, perms) - }) - } - } - - /** Delete the channel */ - async delete(reason?: string): Promise { - return await this.client.deleteChannel.call(this.client, this.id, reason) - } - - /** Delete a channel permission overwrite */ - async deletePermission(overwriteID: BigString, reason?: string): Promise { - return await this.client.deleteChannelPermission.call(this.client, this.id, overwriteID, reason) - } - - /** Edit the channel's properties */ - async edit(options: EditChannelOptions, reason?: string) { - return await this.client.editChannel.call(this.client, this.id, options, reason) - } - - /** Create a channel permission overwrite */ - async editPermission(overwriteID: BigString, allow: bigint | number, deny: bigint | number, type: OverwriteTypes, reason?: string): Promise { - return await this.client.editChannelPermission.call(this.client, this.id, overwriteID, allow, deny, type, reason) - } - - /** Edit the channel's position. Note that channel position numbers are lowest on top and highest at the bottom. */ - async editPosition(position: number, options?: EditChannelPositionOptions): Promise { - return await this.client.editChannelPosition.call(this.client, this.id, position, options) - } - - /** Get the channel-specific permissions of a member */ - permissionsOf(memberID: BigString | Member): Permission { - const member = ['string', 'bigint'].includes(typeof memberID) ? this.guild.members.get(memberID as BigString)! : (memberID as Member) - let permission = this.guild.permissionsOf(member).allow - if (permission & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) { - return new Permission(BitwisePermissionFlags.ADMINISTRATOR) - } - const channel = - [ChannelTypes.PublicThread, ChannelTypes.PrivateThread, ChannelTypes.AnnouncementThread].includes(this.type) && this.parentID - ? this.guild.channels.get(this.parentID) - : this - let overwrite = channel?.permissionOverwrites.get(this.guild.id) - if (overwrite) { - permission = (permission & ~overwrite.deny) | overwrite.allow - } - let deny = 0n - let allow = 0n - for (const roleID of member.roles) { - if ((overwrite = channel?.permissionOverwrites.get(roleID))) { - deny |= overwrite.deny - allow |= overwrite.allow - } - } - permission = (permission & ~deny) | allow - overwrite = channel?.permissionOverwrites.get(member.id) - if (overwrite) { - permission = (permission & ~overwrite.deny) | overwrite.allow - } - return new Permission(permission) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['name', 'nsfw', 'parentID', 'permissionOverwrites', 'position', ...props]) - } -} - -export default GuildChannel diff --git a/packages/client/src/Structures/channels/News.ts b/packages/client/src/Structures/channels/News.ts deleted file mode 100644 index 844383136..000000000 --- a/packages/client/src/Structures/channels/News.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/return-await */ -import type { BigString, DiscordChannel } from '@discordeno/types' -import type Client from '../../Client.js' -import type { ChannelFollow } from '../../typings.js' -import type Message from '../Message.js' -import TextChannel from './Text.js' - -export class NewsChannel extends TextChannel { - constructor(data: DiscordChannel, client: Client, messageLimit?: number) { - super(data, client, messageLimit) - - this.rateLimitPerUser = 0 - this.update(data) - } - - /** Crosspost (publish) a message to subscribed channels */ - async crosspostMessage(messageID: BigString): Promise { - return await this.client.crosspostMessage.call(this.client, this.id, messageID) - } - - /** Follow this channel in another channel. This creates a webhook in the target channel */ - async follow(webhookChannelID: BigString): Promise { - return await this.client.followChannel.call(this.client, this.id, webhookChannelID) - } -} - -export default NewsChannel diff --git a/packages/client/src/Structures/channels/Private.ts b/packages/client/src/Structures/channels/Private.ts deleted file mode 100644 index 0802c1b4a..000000000 --- a/packages/client/src/Structures/channels/Private.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* eslint-disable no-useless-call */ -import { ChannelTypes, type BigString, type DiscordChannel, type GetMessagesOptions } from '@discordeno/types' -import type Client from '../../Client.js' -import Collection from '../../Collection.js' -import type { FileContent, GetMessageReactionOptions, MessageContent, MessageContentEdit } from '../../typings.js' -import type Message from '../Message.js' -import User from '../users/User.js' -import Channel from './Channel.js' - -export class PrivateChannel extends Channel { - /** The ID of the last message in this channel */ - lastMessageID = "" - // TODO: THIS A THING IN DMS???? - /** The rate limit per user. */ - rateLimitPerUser?: number - /** Collection of Messages in this channel */ - messages: Collection - /** The recipient in this private channel */ - recipient?: User - - constructor(data: DiscordChannel, client: Client) { - super(data, client) - - this.lastMessageID = data.last_message_id ?? "" - this.rateLimitPerUser = data.rate_limit_per_user - if (this.type === ChannelTypes.DM || this.type === undefined) { - if (data.recipients?.[0]) this.recipient = new User(data.recipients[0], client) - } - this.messages = new Collection() - this.messages.limit = client.options.messageLimit - } - - /** Add a reaction to a message */ - async addMessageReaction(messageID: BigString, reaction: string): Promise { - return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction) - } - - /** Create a message in a text channel */ - async createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise { - return await this.client.createMessage.call(this.client, this.id, content, file) - } - - // TODO: REASONS ARE A THING FOR AUDIT LOGS IN DMS??? - /** Delete a message */ - async deleteMessage(messageID: BigString, reason?: string): Promise { - return await this.client.deleteMessage.call(this.client, this.id, messageID, reason) - } - - /** Edit a message */ - async editMessage(messageID: BigString, content: MessageContentEdit): Promise { - return await this.client.editMessage.call(this.client, this.id, messageID, content) - } - - /** Get a previous message in a text channel */ - async getMessage(messageID: BigString): Promise { - return await this.client.getMessage.call(this.client, this.id, messageID) - } - - /** Get a list of users who reacted with a specific reaction */ - async getMessageReaction(messageID: BigString, reaction: string, options: GetMessageReactionOptions): Promise { - return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options) - } - - /** Get a previous message in a text channel */ - async getMessages(options: GetMessagesOptions): Promise { - return await this.client.getMessages.call(this.client, this.id, options) - } - - /** Get all the pins in a text channel */ - async getPins(): Promise { - return await this.client.getPins.call(this.client, this.id) - } - - /** Leave the channel */ - async leave(): Promise { - return await this.client.deleteChannel.call(this.client, this.id) - } - - /** Pin a message */ - async pinMessage(messageID: BigString): Promise { - return await this.client.pinMessage.call(this.client, this.id, messageID) - } - - /** Remove a reaction from a message */ - async removeMessageReaction(messageID: BigString, reaction: string): Promise { - return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction) - } - - /** Send typing status in a text channel */ - async sendTyping(): Promise { - return await this.client.sendChannelTyping.call(this.client, this.id) - } - - /** Unpin a message */ - async unpinMessage(messageID: BigString): Promise { - return await this.client.unpinMessage.call(this.client, this.id, messageID) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['call', 'lastCall', 'lastMessageID', 'messages', 'recipient', ...props]) - } -} - -export default PrivateChannel diff --git a/packages/client/src/Structures/channels/Stage.ts b/packages/client/src/Structures/channels/Stage.ts deleted file mode 100644 index bfce0e2b0..000000000 --- a/packages/client/src/Structures/channels/Stage.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/return-await */ -import type { DiscordChannel } from '@discordeno/types' -import type { StageInstanceOptions } from '../../typings.js' -import type StageInstance from '../guilds/StageInstance.js' -import VoiceChannel from './Voice.js' - -export class StageChannel extends VoiceChannel { - /** The topic of the channel */ - topic?: string | null - - update(data: DiscordChannel): void { - super.update(data) - - if (data.topic !== undefined) { - this.topic = data.topic - } - } - - /** Create a stage instance */ - async createInstance(options: StageInstanceOptions): Promise { - return await this.client.createStageInstance.call(this.client, this.id, options) - } - - /** Delete the stage instance for this channel */ - async deleteInstance(): Promise { - return await this.client.deleteStageInstance.call(this.client, this.id) - } - - /** Update the stage instance for this channel */ - async editInstance(options: StageInstanceOptions): Promise { - return await this.client.editStageInstance.call(this.client, this.id, options) - } - - /** Get the stage instance for this channel */ - async getInstance(): Promise { - return await this.client.getStageInstance.call(this.client, this.id) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['topic', ...props]) - } -} - -export default StageChannel diff --git a/packages/client/src/Structures/channels/Text.ts b/packages/client/src/Structures/channels/Text.ts deleted file mode 100644 index f3dc5f797..000000000 --- a/packages/client/src/Structures/channels/Text.ts +++ /dev/null @@ -1,376 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/return-await */ -import type { BigString, DiscordChannel, GetMessagesOptions } from '@discordeno/types' -import type Client from '../../Client.js' -import Collection from '../../Collection.js' -import type { - CreateChannelInviteOptions, - CreateThreadOptions, - CreateThreadWithoutMessageOptions, - FileContent, - GetArchivedThreadsOptions, - GetMessageReactionOptions, - ListedChannelThreads, - MessageContent, - MessageContentEdit, - PurgeChannelOptions, -} from '../../typings.js' -import type Invite from '../Invite.js' -import type Message from '../Message.js' -import GuildChannel from './Guild.js' -import type PrivateThreadChannel from './threads/PrivateThread.js' -import type PublicThreadChannel from './threads/PublicThread.js' - -export class TextChannel extends GuildChannel { - /** Collection of Messages in this channel */ - messages: Collection - /** The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled */ - rateLimitPerUser: number | null - /** The ID of the last message in this channel */ - lastMessageID = "" - /** The timestamp of the last pinned message */ - lastPinTimestamp?: number | null - /** Default duration for newly created threads, in minutes, to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 */ - defaultAutoArchiveDuration?: number - /** The channel topic (0-4096 characters for GUILD_FORUM channels, 0-1024 characters for all others) */ - topic?: string | null - - constructor(data: DiscordChannel, client: Client, messageLimit?: number) { - super(data, client) - - this.messages = new Collection() - if (messageLimit == null) this.messages.limit = client.options.messageLimit - else this.messages.limit = messageLimit - - this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user - - this.lastMessageID = data.last_message_id ?? "" - this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null - - this.update(data) - } - - update(data: DiscordChannel): void { - super.update(data) - - if (data.rate_limit_per_user !== undefined) this.rateLimitPerUser = data.rate_limit_per_user - if (data.topic !== undefined) this.topic = data.topic - if (data.default_auto_archive_duration !== undefined) this.defaultAutoArchiveDuration = data.default_auto_archive_duration - } - - /** Add a reaction to a message */ - async addMessageReaction(messageID: BigString, reaction: string): Promise { - return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction) - } - - /** Create an invite for the channel */ - async createInvite(options?: CreateChannelInviteOptions, reason?: string): Promise { - return await this.client.createChannelInvite.call(this.client, this.id, options, reason) - } - - /** - * Create a message in the channel - * @arg {String | Object} content A string or object. If an object is passed: - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to. - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} [content.content] A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Array} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Object} [content.messageReference] The message reference, used when replying to messages - * @arg {String} [content.messageReference.channelID] The channel ID of the referenced message - * @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message - * @arg {String} [content.messageReference.guildID] The guild ID of the referenced message - * @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message - * @arg {String} [content.messageReferenceID] [DEPRECATED] The ID of the message should be replied to. Use `messageReference` instead - * @arg {Array} [content.stickerIDs] An array of IDs corresponding to stickers to send - * @arg {Boolean} [content.tts] Set the message TTS flag - * @arg {Object | Array} [file] A file object (or an Array of them) - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - async createMessage(content: MessageContent, file?: FileContent | FileContent[]) { - return this.client.createMessage.call(this.client, this.id, content, file) - } - - /** - * Create a thread with an existing message - * @arg {String} messageID The ID of the message to create the thread from - * @arg {Object} options The thread options - * @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 - * @arg {String} options.name The thread channel name - * @returns {Promise} - */ - async createThreadWithMessage(messageID: string, options: CreateThreadOptions) { - return this.client.createThreadWithMessage.call(this.client, this.id, messageID, options) - } - - /** - * Create a thread without an existing message - * @arg {Object} options The thread options - * @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 - * @arg {boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only) - * @arg {String} options.name The thread channel name - * @arg {Number} [options.type] The channel type of the thread to create. It is recommended to explicitly set this property as this will be a required property in API v10 - * @returns {Promise} - */ - async createThreadWithoutMessage(options: CreateThreadWithoutMessageOptions) { - return this.client.createThreadWithoutMessage.call(this.client, this.id, options) - } - - /** - * Create a channel webhook - * @arg {Object} options Webhook options - * @arg {String} [options.avatar] The default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings - * @arg {String} options.name The default name - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} Resolves with a webhook object - */ - async createWebhook(options: { name: string; avatar?: string | null }, reason: string) { - return this.client.createChannelWebhook.call(this.client, this.id, options, reason) - } - - /** - * Delete a message - * @arg {String} messageID The ID of the message - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - async deleteMessage(messageID: string, reason: string) { - return this.client.deleteMessage.call(this.client, this.id, messageID, reason) - } - - /** - * Bulk delete messages (bot accounts only) - * @arg {Array} messageIDs Array of message IDs to delete - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - async deleteMessages(messageIDs: string[], reason: string) { - return this.client.deleteMessages.call(this.client, this.id, messageIDs, reason) - } - - /** - * Edit a message - * @arg {String} messageID The ID of the message - * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} [content.content] A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Array} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Object | Array} [content.file] A file object (or an Array of them) - * @arg {Buffer} content.file[].file A buffer containing file data - * @arg {String} content.file[].name What to name the file - * @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference - * @returns {Promise} - */ - async editMessage(messageID: string, content: MessageContentEdit) { - return this.client.editMessage.call(this.client, this.id, messageID, content) - } - - /** Get all archived threads in this channel */ - async getArchivedThreads(type: 'private', options?: GetArchivedThreadsOptions): Promise> - async getArchivedThreads(type: 'public', options?: GetArchivedThreadsOptions): Promise> - async getArchivedThreads( - type: 'public' | 'private', - options?: GetArchivedThreadsOptions, - ): Promise> { - return await this.client.getArchivedThreads.call(this.client, this.id, type as 'public', options) - } - - /** - * Get all invites in the channel - * @returns {Promise>} - */ - async getInvites() { - return this.client.getChannelInvites.call(this.client, this.id) - } - - /** - * Get joined private archived threads in this channel - * @arg {Object} [options] Additional options when requesting archived threads - * @arg {Date} [options.before] List of threads to return before the timestamp - * @arg {Number} [options.limit] Maximum number of threads to return - * @returns {Promise} An object containing an array of `threads`, an array of `members` and whether the response `hasMore` threads that could be returned in a subsequent call - */ - async getJoinedPrivateArchivedThreads(options: GetArchivedThreadsOptions) { - return this.client.getJoinedPrivateArchivedThreads.call(this.client, this.id, options) - } - - /** - * Get a previous message in the channel - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - async getMessage(messageID: string) { - return this.client.getMessage.call(this.client, this.id, messageID) - } - - /** - * Get a list of users who reacted with a specific reaction - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior) - * @arg {Number} [options.limit=100] The maximum number of users to get - * @arg {String} [options.after] Get users after this user ID - * @returns {Promise>} - */ - async getMessageReaction(messageID: string, reaction: string, options: GetMessageReactionOptions) { - return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options) - } - - /** - * Get previous messages in the channel - * @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit` - * @arg {String} [options.after] Get messages after this message ID - * @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100) - * @arg {String} [options.before] Get messages before this message ID - * @arg {Number} [options.limit=50] The max number of messages to get - * @returns {Promise>} - */ - async getMessages(options: GetMessagesOptions) { - return this.client.getMessages.call(this.client, this.id, options) - } - - /** - * Get all the pins in the channel - * @returns {Promise>} - */ - async getPins() { - return this.client.getPins.call(this.client, this.id) - } - - /** - * Get all the webhooks in the channel - * @returns {Promise>} Resolves with an array of webhook objects - */ - async getWebhooks() { - return this.client.getChannelWebhooks.call(this.client, this.id) - } - - /** - * Pin a message - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - async pinMessage(messageID: string) { - return this.client.pinMessage.call(this.client, this.id, messageID) - } - - /** - * Purge previous messages in the channel with an optional filter (bot accounts only) - * @arg {Object} options Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit` - * @arg {String} [options.after] Get messages after this message ID - * @arg {String} [options.before] Get messages before this message ID - * @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object - * @arg {Number} options.limit The max number of messages to search through, -1 for no limit - * @arg {String} [options.reason] The reason to be displayed in audit logs - * @returns {Promise} Resolves with the number of messages deleted - */ - async purge(limit: PurgeChannelOptions) { - return this.client.purgeChannel.call(this.client, this.id, limit) - } - - /** - * Remove a reaction from a message - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to remove the reaction for - * @returns {Promise} - */ - async removeMessageReaction(messageID: string, reaction: string, userID: string) { - return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID) - } - - /** - * Remove all reactions from a message for a single emoji - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @returns {Promise} - */ - async removeMessageReactionEmoji(messageID: string, reaction: string) { - return this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction) - } - - /** - * Remove all reactions from a message - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - async removeMessageReactions(messageID: string) { - return this.client.removeMessageReactions.call(this.client, this.id, messageID) - } - - /** - * Send typing status in the channel - * @returns {Promise} - */ - async sendTyping() { - return this.client.sendChannelTyping.call(this.client, this.id) - } - - /** - * Unpin a message - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - async unpinMessage(messageID: string) { - return this.client.unpinMessage.call(this.client, this.id, messageID) - } - - /** - * Un-send a message. You're welcome Programmix - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - async unsendMessage(messageID: string) { - return this.client.deleteMessage.call(this.client, this.id, messageID) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['lastMessageID', 'lastPinTimestamp', 'messages', 'rateLimitPerUser', 'topic', ...props]) - } -} - -export default TextChannel diff --git a/packages/client/src/Structures/channels/TextVoice.ts b/packages/client/src/Structures/channels/TextVoice.ts deleted file mode 100644 index c92171415..000000000 --- a/packages/client/src/Structures/channels/TextVoice.ts +++ /dev/null @@ -1,271 +0,0 @@ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import type { DiscordChannel, GetMessagesOptions } from '@discordeno/types' -import type Client from '../../Client.js' -import Collection from '../../Collection.js' -import type { - CreateInviteOptions, - FileContent, - GetMessageReactionOptions, - MessageContent, - MessageContentEdit, - PurgeChannelOptions, -} from '../../typings.js' -import type Message from '../Message.js' -import VoiceChannel from './Voice.js' - -/** - * Represents a Text-in-Voice channel. See VoiceChannel for more properties and methods. - * @extends VoiceChannel - * @prop {String} lastMessageID The ID of the last message in this channel - * @prop {Collection} messages Collection of Messages in this channel - * @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled - */ -export class TextVoiceChannel extends VoiceChannel { - lastMessageID = "" - messages: Collection - rateLimitPerUser: number | null - - constructor(data: DiscordChannel, client: Client, messageLimit?: number) { - super(data, client) - - this.messages = new Collection() - if (messageLimit == null) this.messages.limit = client.options.messageLimit - else this.messages.limit = messageLimit - - this.lastMessageID = data.last_message_id ?? "" - this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user - } - - update(data: DiscordChannel) { - super.update(data) - // "not yet, possibly TBD" - if (data.rate_limit_per_user !== undefined) { - this.rateLimitPerUser = data.rate_limit_per_user - } - } - - /** - * Add a reaction to a message - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @returns {Promise} - */ - async addMessageReaction(messageID: string, reaction: string) { - return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction) - } - - /** - * Create an invite for the channel - * @arg {Object} [options] Invite generation options - * @arg {Number} [options.maxAge] How long the invite should last in seconds - * @arg {Number} [options.maxUses] How many uses the invite should last for - * @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not - * @arg {Boolean} [options.unique] Whether the invite is unique or not - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - async createInvite(options: CreateInviteOptions, reason: string) { - return await this.client.createChannelInvite.call(this.client, this.id, options, reason) - } - - /** - * Create a message in the channel - * Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel ID for a user - * @arg {String | Object} content A string or object. If an object is passed: - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} content.content A content string - * @arg {Array} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Object} [content.messageReference] The message reference, used when replying to messages - * @arg {String} [content.messageReference.channelID] The channel ID of the referenced message - * @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message - * @arg {String} [content.messageReference.guildID] The guild ID of the referenced message - * @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message - * @arg {Array} [content.stickerIDs] An array of IDs corresponding to the stickers to send - * @arg {Boolean} [content.tts] Set the message TTS flag - * @arg {Object} [file] A file object - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - async createMessage(content: MessageContent, file?: FileContent | FileContent[]) { - return await this.client.createMessage.call(this.client, this.id, content, file) - } - - /** - * Delete a message - * @arg {String} messageID The ID of the message - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - async deleteMessage(messageID: string, reason: string) { - return await this.client.deleteMessage.call(this.client, this.id, messageID, reason) - } - - /** - * Bulk delete messages (bot accounts only) - * @arg {Array} messageIDs Array of message IDs to delete - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - async deleteMessages(messageIDs: string[], reason: string) { - return await this.client.deleteMessages.call(this.client, this.id, messageIDs, reason) - } - - /** - * Edit a message - * @arg {String} messageID The ID of the message - * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} content.content A content string - * @arg {Boolean} [content.disableEveryone] Whether to filter @everyone/@here or not (overrides default) - * @arg {Array} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference - * @returns {Promise} - */ - async editMessage(messageID: string, content: MessageContentEdit) { - return await this.client.editMessage.call(this.client, this.id, messageID, content) - } - - /** - * Get all invites in the channel - * @returns {Promise>} - */ - async getInvites() { - return await this.client.getChannelInvites.call(this.client, this.id) - } - - /** - * Get a previous message in the channel - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - async getMessage(messageID: string) { - return await this.client.getMessage.call(this.client, this.id, messageID) - } - - /** - * Get a list of users who reacted with a specific reaction - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior) - * @arg {Number} [options.limit=100] The maximum number of users to get - * @arg {String} [options.after] Get users after this user ID - * @returns {Promise>} - */ - async getMessageReaction(messageID: string, reaction: string, options: GetMessageReactionOptions) { - return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options) - } - - /** - * Get previous messages in the channel - * @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit` - * @arg {String} [options.after] Get messages after this message ID - * @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100) - * @arg {String} [options.before] Get messages before this message ID - * @arg {Number} [options.limit=50] The max number of messages to get - * @returns {Promise>} - */ - async getMessages(options: GetMessagesOptions) { - return await this.client.getMessages.call(this.client, this.id, options) - } - - /** - * Purge previous messages in the channel with an optional filter (bot accounts only) - * @arg {Object} options Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit` - * @arg {String} [options.after] Get messages after this message ID - * @arg {String} [options.before] Get messages before this message ID - * @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object - * @arg {Number} options.limit The max number of messages to search through, -1 for no limit - * @arg {String} [options.reason] The reason to be displayed in audit logs - * @returns {Promise} Resolves with the number of messages deleted - */ - async purge(options: PurgeChannelOptions) { - return await this.client.purgeChannel.call(this.client, this.id, options) - } - - /** - * Remove a reaction from a message - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to remove the reaction for - * @returns {Promise} - */ - async removeMessageReaction(messageID: string, reaction: string, userID: string) { - return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID) - } - - /** - * Remove all reactions from a message for a single emoji - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @returns {Promise} - */ - async removeMessageReactionEmoji(messageID: string, reaction: string) { - return await this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction) - } - - /** - * Remove all reactions from a message - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - async removeMessageReactions(messageID: string) { - return await this.client.removeMessageReactions.call(this.client, this.id, messageID) - } - - /** - * Send typing status in the channel - * @returns {Promise} - */ - async sendTyping() { - return await this.client.sendChannelTyping.call(this.client, this.id) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['lastMessageID', 'messages', 'rateLimitPerUser', ...props]) - } -} - -export default TextVoiceChannel diff --git a/packages/client/src/Structures/channels/Voice.ts b/packages/client/src/Structures/channels/Voice.ts deleted file mode 100644 index cb7040acd..000000000 --- a/packages/client/src/Structures/channels/Voice.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import type { DiscordChannel } from '@discordeno/types' -import type Client from '../../Client.js' -import Collection from '../../Collection.js' -import type { CreateInviteOptions, TextVoiceChannelTypes, VideoQualityMode } from '../../typings.js' -import type Member from '../guilds/Member.js' -import GuildChannel from './Guild.js' - -export class VoiceChannel extends GuildChannel { - bitrate: number = 0 - rtcRegion: string | null = null - type: TextVoiceChannelTypes = 0 - userLimit: number = 0 - videoQualityMode: VideoQualityMode = 0 - voiceMembers: Collection - - constructor(data: DiscordChannel, client: Client) { - super(data, client) - - this.voiceMembers = new Collection() - this.update(data) - } - - update(data: DiscordChannel): void { - super.update(data) - - if (data.bitrate !== undefined) { - this.bitrate = data.bitrate - } - if (data.rtc_region !== undefined) { - this.rtcRegion = data.rtc_region - } - if (data.user_limit !== undefined) { - this.userLimit = data.user_limit - } - if (data.video_quality_mode !== undefined) { - this.videoQualityMode = data.video_quality_mode - } - } - - /** - * Create an invite for the channel - * @arg {Object} [options] Invite generation options - * @arg {Number} [options.maxAge] How long the invite should last in seconds - * @arg {Number} [options.maxUses] How many uses the invite should last for - * @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not - * @arg {Boolean} [options.unique] Whether the invite is unique or not - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - async createInvite(options: CreateInviteOptions, reason: string) { - return await this.client.createChannelInvite.call(this.client, this.id, options, reason) - } - - /** - * Get all invites in the channel - * @returns {Promise>} - */ - async getInvites() { - return await this.client.getChannelInvites.call(this.client, this.id) - } - - // TODO: gateway - // /** - // * Joins the channel. - // * @arg {Object} [options] VoiceConnection constructor options - // * @arg {Object} [options.opusOnly] Skip opus encoder initialization. You should not enable this unless you know what you are doing - // * @arg {Object} [options.shared] Whether the VoiceConnection will be part of a SharedStream or not - // * @arg {Boolean} [options.selfMute] Whether the bot joins the channel muted or not - // * @arg {Boolean} [options.selfDeaf] Whether the bot joins the channel deafened or not - // * @returns {Promise} Resolves with a VoiceConnection - // */ - // join(options: JoinVoiceChannelOptions) { - // return this.client.joinVoiceChannel.call(this.client, this.id, options); - // } - - // TODO: gateway - // /** - // * Leaves the channel. - // */ - // leave() { - // return this.client.leaveVoiceChannel.call(this.client, this.id); - // } - - toJSON(props: string[] = []): Record { - return super.toJSON(['bitrate', 'rtcRegion', 'userLimit', 'videoQualityMode', 'voiceMembers', ...props]) - } -} - -export default VoiceChannel diff --git a/packages/client/src/Structures/channels/threads/Member.ts b/packages/client/src/Structures/channels/threads/Member.ts deleted file mode 100644 index 57a636b4d..000000000 --- a/packages/client/src/Structures/channels/threads/Member.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable no-useless-call */ -import type { DiscordThreadMember } from '@discordeno/types' -import Base from '../../../Base.js' -import type Client from '../../../Client.js' -import type Member from '../../guilds/Member.js' - -export class ThreadMember extends Base { - client: Client - /** The user-thread settings of this member */ - flags: number - /** The ID of the thread this member is a part of */ - threadID: string - /** Timestamp of when the member joined the thread */ - joinTimestamp: number - /** The guild member that this thread member belongs to. This will never be present when fetching over REST */ - guildMember?: Member - - constructor(data: DiscordThreadMember, client: Client) { - super(data.user_id) - - this.client = client - this.flags = data.flags - this.threadID = data.id - this.joinTimestamp = Date.parse(data.join_timestamp) - } - - get _client(): Client { - return this.client - } - - /** Remove the member from the thread */ - async leave(): Promise { - return await this._client.leaveThread.call(this._client, this.threadID, this.id) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['threadID', 'joinTimestamp', ...props]) - } -} - -export default ThreadMember diff --git a/packages/client/src/Structures/channels/threads/NewsThread.ts b/packages/client/src/Structures/channels/threads/NewsThread.ts deleted file mode 100644 index 4ff199862..000000000 --- a/packages/client/src/Structures/channels/threads/NewsThread.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ThreadChannel from './Thread.js' - -export class NewsThreadChannel extends ThreadChannel {} - -export default NewsThreadChannel diff --git a/packages/client/src/Structures/channels/threads/PrivateThread.ts b/packages/client/src/Structures/channels/threads/PrivateThread.ts deleted file mode 100644 index 0dd7a2e3e..000000000 --- a/packages/client/src/Structures/channels/threads/PrivateThread.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { DiscordChannel } from '@discordeno/types' -import type Client from '../../../Client.js' -import ThreadChannel from './Thread.js' - -export class PrivateThreadChannel extends ThreadChannel { - constructor(data: DiscordChannel, client: Client, messageLimit?: number) { - super(data, client, messageLimit) - - this.update(data) - } - - update(data: DiscordChannel): void { - if (data.thread_metadata !== undefined) { - this.threadMetadata = { - archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp), - archived: data.thread_metadata.archived, - autoArchiveDuration: data.thread_metadata.auto_archive_duration, - invitable: data.thread_metadata.invitable, - locked: data.thread_metadata.locked, - } - } - } -} - -export default PrivateThreadChannel diff --git a/packages/client/src/Structures/channels/threads/PublicThread.ts b/packages/client/src/Structures/channels/threads/PublicThread.ts deleted file mode 100644 index dcc1ad9fc..000000000 --- a/packages/client/src/Structures/channels/threads/PublicThread.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ThreadChannel from './Thread.js' - -export class PublicThreadChannel extends ThreadChannel {} - -export default PublicThreadChannel diff --git a/packages/client/src/Structures/channels/threads/Thread.ts b/packages/client/src/Structures/channels/threads/Thread.ts deleted file mode 100644 index d2e32c20a..000000000 --- a/packages/client/src/Structures/channels/threads/Thread.ts +++ /dev/null @@ -1,180 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ -import type { BigString, DiscordChannel, GetMessagesOptions } from '@discordeno/types' -import type Client from '../../../Client.js' -import Collection from '../../../Collection.js' -import type { FileContent, GetMessageReactionOptions, MessageContent, MessageContentEdit, PurgeChannelOptions } from '../../../typings.js' -import type Message from '../../Message.js' -import type User from '../../users/User.js' -import GuildChannel from '../Guild.js' -import ThreadMember from './Member.js' - -export class ThreadChannel extends GuildChannel { - /** The cached messages that were sent in this channel. */ - messages: Collection - /** The cached thread members that are in this channel. */ - members: Collection - /** The id of the last message in this channel. */ - lastMessageID: string - /** The id of the user who created this thread. */ - ownerID: string - /** The approximate amount of members that have joined this thread. */ - memberCount?: number - /** The approximate amount of messages in this channel. */ - messageCount?: number - /** The rate limit that users can send messages in this channel. 0 means no rate limit has been enabled. */ - rateLimitPerUser?: number - /** The data relevant to this thread. */ - threadMetadata?: { - /** Timestamp when the thread's archive status was last changed, used for calculating recent activity */ - archiveTimestamp: number - /** Whether the thread is archived. */ - archived: boolean - /** Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 */ - autoArchiveDuration: number - /** Whether the thread is locked. */ - locked: boolean - /** Whether or not the thread is inviteable. */ - invitable?: boolean - } - - /** The bot's thread member object if it has joined the thread. */ - member?: ThreadMember - - constructor(data: DiscordChannel, client: Client, messageLimit?: number) { - super(data, client) - - this.members = new Collection() - this.messages = new Collection() - - this.messages.limit = messageLimit ?? client.options.messageLimit - this.lastMessageID = data.last_message_id ?? "" - this.ownerID = data.owner_id! - - this.update(data) - } - - update(data: DiscordChannel): void { - super.update(data) - - if (data.member_count !== undefined) { - this.memberCount = data.member_count - } - if (data.message_count !== undefined) { - this.messageCount = data.message_count - } - if (data.rate_limit_per_user !== undefined) { - this.rateLimitPerUser = data.rate_limit_per_user - } - if (data.thread_metadata !== undefined) { - this.threadMetadata = { - archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp), - archived: data.thread_metadata.archived, - autoArchiveDuration: data.thread_metadata.auto_archive_duration, - locked: data.thread_metadata.locked, - } - } - if (data.member !== undefined) { - this.member = new ThreadMember(data.member, this.client) - } - } - - async addMessageReaction(messageID: BigString, reaction: string): Promise { - return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction) - } - - async createMessage(content: MessageContent, file?: FileContent | FileContent[]) { - return await this.client.createMessage.call(this.client, this.id, content, file) - } - - async deleteMessage(messageID: BigString, reason?: string): Promise { - return await this.client.deleteMessage.call(this.client, this.id, messageID, reason) - } - - async deleteMessages(messageIDs: BigString[], reason?: string): Promise { - return await this.client.deleteMessages.call(this.client, this.id, messageIDs, reason) - } - - async editMessage(messageID: BigString, content: MessageContentEdit) { - return await this.client.editMessage.call(this.client, this.id, messageID, content) - } - - async getMembers(): Promise { - return await this.client.getThreadMembers.call(this.client, this.id) - } - - async getMessage(messageID: BigString): Promise { - return await this.client.getMessage.call(this.client, this.id, messageID) - } - - async getMessageReaction(messageID: BigString, reaction: string, options?: GetMessageReactionOptions): Promise { - return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options) - } - - async getMessages(options: GetMessagesOptions) { - return await this.client.getMessages.call(this.client, this.id, options) - } - - async getPins(): Promise { - return await this.client.getPins.call(this.client, this.id) - } - - async join(userID: BigString = '@me'): Promise { - return await this.client.joinThread.call(this.client, this.id, userID) - } - - async leave(userID: BigString): Promise { - return await this.client.leaveThread.call(this.client, this.id, userID) - } - - async pinMessage(messageID: BigString): Promise { - return await this.client.pinMessage.call(this.client, this.id, messageID) - } - - async purge(options: PurgeChannelOptions): Promise { - return await this.client.purgeChannel.call(this.client, this.id, options) - } - - async removeMessageReaction(messageID: BigString, reaction: string, userID: BigString = '@me') { - return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID) - } - - async removeMessageReactionEmoji(messageID: BigString, reaction: string): Promise { - return await this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction) - } - - async removeMessageReactions(messageID: BigString): Promise { - return await this.client.removeMessageReactions.call(this.client, this.id, messageID) - } - - async sendTyping(): Promise { - return await this.client.sendChannelTyping.call(this.client, this.id) - } - - async unpinMessage(messageID: BigString): Promise { - return await this.client.unpinMessage.call(this.client, this.id, messageID) - } - - /** - * @deprecated Use deleteMessage instead - */ - async unsendMessage(messageID: BigString): Promise { - return await this.client.deleteMessage.call(this.client, this.id, messageID) - } - - toJSON(props: string[] = []): Record { - return super.toJSON([ - 'lastMessageID', - 'memberCount', - 'messageCount', - 'messages', - 'ownerID', - 'rateLimitPerUser', - 'threadMetadata', - 'member', - ...props, - ]) - } -} - -export default ThreadChannel diff --git a/packages/client/src/Structures/guilds/AuditLogEntry.ts b/packages/client/src/Structures/guilds/AuditLogEntry.ts deleted file mode 100644 index 87a5da161..000000000 --- a/packages/client/src/Structures/guilds/AuditLogEntry.ts +++ /dev/null @@ -1,181 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import type { DiscordAuditLogChange, DiscordAuditLogEntry } from '@discordeno/types' -import { AuditLogEvents } from '@discordeno/types' -import Base from '../../Base.js' -import type GuildChannel from '../channels/Guild.js' -import type TextChannel from '../channels/Text.js' -import Invite from '../Invite.js' -import type Message from '../Message.js' -import type User from '../users/User.js' -import type Guild from './Guild.js' -import type Member from './Member.js' -import type Role from './Role.js' - -export class GuildAuditLogEntry extends Base { - /** The guild to which this entry belongs. */ - guild: Guild - /** The action type of the entry. */ - actionType: AuditLogEvents - /** The reason for the action. */ - reason: string | null - /** The user that performed the action. */ - user?: User - /** The properties of the targeted object before the action was taken. For example, if a channel was renamed from #general to #potato, this would be `{name: "general"}`` */ - before: Record - - /** The properties of the targeted object after the action was taken. For example, if a channel was renamed from #general to #potato, this would be `{name: "potato"}`` */ - after: Record - - /** The ID of the action target */ - targetID?: string - /** The number of entities targeted. For example, for action type 26 (MEMBER_MOVE), this is the number of members that were moved/disconnected from the voice channel */ - count?: number - /** The channel targeted in the entry, action types 26 (MEMBER_MOVE), 72/74/75 (MESSAGE_DELETE/PIN/UNPIN) and 83/84/85 (STAGE_INSTANCE_CREATE/UPDATE/DELETE) only */ - channel?: GuildChannel - /** The message that was (un)pinned, action types 74/75 (MESSAGE_PIN/UNPIN) only. If the message is not cached, this will be an object with an `id` key. No other property is guaranteed. */ - message?: Message | { id: string } - /** The number of days of inactivity to prune for, action type 21 (MEMBER_PRUNE) only */ - deleteMemberDays?: number - /** The number of members pruned from the server, action type 21 (MEMBER_PRUNE) only */ - membersRemoved?: number - /** The member described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the member is not cached, this could be {id: String} */ - member?: Member | { id: string } - /** The role described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the role is not cached, this could be {id: String, name: String} */ - role?: Role | { id: string; name: string } - - constructor(data: DiscordAuditLogEntry, guild: Guild) { - super(data.id) - - this.guild = guild - this.actionType = data.action_type - this.reason = data.reason ?? null - this.user = data.user_id ? guild.client.users.get(data.user_id) : undefined - this.before = {} as any - this.after = {} as any - if (data.changes) { - data.changes.forEach((change) => { - if (change.old_value !== undefined) { - this.before[change.key] = change.old_value - } - if (change.new_value !== undefined) { - this.after[change.key] = change.new_value - } - }) - } - - if (data.target_id) { - this.targetID = data.target_id - } - if (data.options) { - if (data.options.count) { - this.count = +data.options.count - } - if (data.options.channel_id) { - if (this.actionType >= 83) { - this.channel = guild.threads.get(data.options.channel_id) - } else { - this.channel = guild.channels.get(data.options.channel_id) - } - if (data.options.message_id) { - this.message = (this.channel && (this.channel as TextChannel).messages.get(data.options.message_id)) ?? { - id: data.options.message_id, - } - } - } - if (data.options.delete_member_days) { - this.deleteMemberDays = +data.options.delete_member_days - this.membersRemoved = +data.options.members_removed - } - if (data.options.type) { - if (data.options.type === '1') { - this.member = guild.members.get(data.options.id) ?? { - id: data.options.id, - } - } else if (data.options.type === '0') { - this.role = guild.roles.get(data.options.id) ?? { - id: data.options.id, - name: data.options.role_name, - } - } - } - } - } - - get target() { - // pay more, get less - if (this.actionType < 10) { - // Guild - return this.guild - } else if (this.actionType < 20) { - // Channel - return this.guild?.channels.get(this.targetID!) - } else if (this.actionType < 30) { - // Member - if (this.actionType === AuditLogEvents.MemberMove || this.actionType === AuditLogEvents.MemberDisconnect) { - // MEMBER_MOVE / MEMBER_DISCONNECT - return null - } - return this.guild?.members.get(this.targetID!) - } else if (this.actionType < 40) { - // Role - return this.guild?.roles.get(this.targetID!) - } else if (this.actionType < 50) { - // Invite - const changes = this.actionType === 42 ? this.before : this.after // Apparently the meaning of life is a deleted invite - return new Invite( - { - code: changes.code as string, - // @ts-expect-error idk why this is happening - channel: changes.channel, - guild: this.guild.toJSON(), - uses: changes.uses as number, - max_uses: changes.max_uses as number, - max_age: changes.max_age as number, - temporary: changes.temporary as boolean, - }, - this.guild?.client, - ) - } else if (this.actionType < 60) { - // Webhook - return null // Go get the webhook yourself - } else if (this.actionType < 70) { - // Emoji - return this.guild?.emojis?.find((emoji) => emoji.id === this.targetID) - } else if (this.actionType < 80) { - // Message - return this.guild?.client.users.get(this.targetID!) - } else if (this.actionType < 83) { - // Integrations - return null - } else if (this.actionType < 90) { - // Stage Instances - return this.guild?.threads.get(this.targetID!) - } else if (this.actionType < 100) { - // Sticker - return this.guild?.stickers?.find((sticker) => sticker.id === this.targetID) - } else { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - throw new Error('Unrecognized action type: ' + this.actionType) - } - } - - toJSON(props: string[] = []): Record { - return super.toJSON([ - 'actionType', - 'after', - 'before', - 'channel', - 'count', - 'deleteMemberDays', - 'member', - 'membersRemoved', - 'reason', - 'role', - 'targetID', - 'user', - ...props, - ]) - } -} - -export default GuildAuditLogEntry diff --git a/packages/client/src/Structures/guilds/Guild.ts b/packages/client/src/Structures/guilds/Guild.ts deleted file mode 100644 index 839d2de04..000000000 --- a/packages/client/src/Structures/guilds/Guild.ts +++ /dev/null @@ -1,952 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ -import { - BitwisePermissionFlags, - ChannelTypes, - type ApplicationCommandTypes, - type BigString, - type DefaultMessageNotificationLevels, - type DiscordEmoji, - type DiscordGuild, - type DiscordMemberWithUser, - type DiscordSticker, - type ExplicitContentFilterLevels, - type GuildFeatures, - type GuildNsfwLevel, - type MfaLevels, - type PremiumTiers, - type RequestGuildMembers, - type SystemChannelFlags, - type VerificationLevels, -} from '@discordeno/types' -import Base from '../../Base.js' -import type Client from '../../Client.js' -import type { ImageFormat, ImageSize } from '../../Client.js' -import Collection from '../../Collection.js' -import { BANNER, GUILD_DISCOVERY_SPLASH, GUILD_ICON, GUILD_SPLASH } from '../../Endpoints.js' -import type Shard from '../../gateway/Shard.js' -import type { - AnyGuildChannel, - AnyThreadChannel, - ApplicationCommand, - ApplicationCommandPermissions, - ApplicationCommandStructure, - ChannelPosition, - CreateChannelOptions, - CreateStickerOptions, - DiscoveryMetadata, - DiscoveryOptions, - DiscoverySubcategoryResponse, - EditStickerOptions, - Emoji, - EmojiOptions, - GetGuildAuditLogOptions, - GetGuildBansOptions, - GetPruneOptions, - GetRESTGuildMembersOptions, - GuildApplicationCommandPermissions, - GuildAuditLog, - GuildBan, - GuildOptions, - GuildTemplateOptions, - GuildVanity, - IntegrationOptions, - ListedGuildThreads, - MemberOptions, - PruneMemberOptions, - RoleOptions, - Sticker, - VoiceRegion, - VoiceStateOptions, - Webhook, - WelcomeScreen, - WelcomeScreenOptions, - Widget, - WidgetData, -} from '../../typings.js' -import { generateChannelFrom } from '../../utils/generate.js' -import type CategoryChannel from '../channels/Category.js' -import type GuildChannel from '../channels/Guild.js' -import type StageChannel from '../channels/Stage.js' -import type TextChannel from '../channels/Text.js' -import type TextVoiceChannel from '../channels/TextVoice.js' -import type ThreadChannel from '../channels/threads/Thread.js' -import type VoiceChannel from '../channels/Voice.js' -import type Invite from '../Invite.js' -import Permission from '../Permission.js' -import User from '../users/User.js' -import type GuildIntegration from './Integration.js' -import Member from './Member.js' -import Role from './Role.js' -import StageInstance from './StageInstance.js' -import type GuildTemplate from './Template.js' -import type { VoiceState } from './VoiceState.js' - -export class Guild extends Base { - /** The client object */ - client: Client - /** The id of the guild owner. */ - ownerID: string - /** The id of the application. */ - applicationID?: string | null - /** The id of the widget channel. */ - widgetChannelID?: string | null - /** The afk channel id if one is set. */ - afkChannelID?: string | null - /** The system channel id if one is set. */ - systemChannelID?: string | null - /** The public updates channel id if one is set. */ - publicUpdatesChannelID?: string | null - /** The rules channel id if one is set. */ - rulesChannelID?: string | null - /** The name of the guild. */ - name?: string - /** The description of the guild. */ - description?: string | null - /** The vanity url if one is set. */ - vanityURL?: string | null - /** The preferred locale of the server. */ - preferredLocale?: string - /** The system channel flags. */ - systemChannelFlags?: SystemChannelFlags - /** The verification level of the guild. */ - verificationLevel?: VerificationLevels - /** The default notification level. */ - defaultNotifications?: DefaultMessageNotificationLevels - /** The explicit content filter setting for this guild. */ - explicitContentFilter?: ExplicitContentFilterLevels - /** Array of guild features */ - features: GuildFeatures[] = [] - /** The premium tier of the guild. */ - premiumTier?: PremiumTiers - /** The MFA level of the guild. */ - mfaLevel?: MfaLevels - /** The NSFW level of the guild. */ - nsfwLevel?: GuildNsfwLevel - /** The compressed form of the guild splash image. */ - _splash?: bigint - /** The compressed form of the guild's discovery splash image. */ - _discoverySplash?: bigint - /** The compressed form of the guild's banner image. */ - _banner?: bigint - /** The compressed form of the guild's icon image. */ - _icon?: bigint - /** The cached emojis in the guild. */ - emojis?: DiscordEmoji[] - /** The cached stickers in the guild. */ - stickers?: DiscordSticker[] - /** The afk timeout in seconds. */ - afkTimeout?: number - /** When this guild was joined at. */ - joinedAt: number - /** The amount of members in the guild. */ - memberCount: number - /** The approximate member count in the guild. */ - approximateMemberCount?: number - /** The approximate presence count in the guild. */ - approximatePresenceCount?: number - /** The amount of subscribers to the server. */ - premiumSubscriptionCount?: number - /** The maximum amount of presences that can be in a guild. */ - maxPresences?: number | null - /** The maximum amount of members that can be in the guild. */ - maxMembers?: number - /** The maximum amount of members that can be in a video channel. */ - maxVideoChannelUsers?: number | null - /** Whether or not this guild is unavailable. */ - unavailable: boolean - /** Whether or not the widget is enabled in this guild. */ - widgetEnabled: boolean - /** Whether or not this guild is considered large. */ - large?: boolean - /** Whether or not the premium progress bar is enabled. */ - premiumProgressBarEnabled?: boolean - /** Whether or not this server is nsfw. */ - nsfw?: boolean - /** The welcome screen settings. */ - welcomeScreen?: { - description: string | null - welcomeChannels?: Array<{ - channelID: string - description: string - emojiID: string | null - emojiName: string | null - }> - } - - /** The cached members in this guild. */ - members = new Collection() - /** The cached roles in this guild. */ - roles = new Collection() - /** The cached channels in this guild. */ - channels = new Collection() - /** The cached threads in this guild. */ - threads = new Collection() - /** The cached voice states in this guild. */ - voiceStates = new Collection() - /** The cached stage instances in this guild. */ - stageInstances = new Collection() - /** The shard that manages this guild. */ - shard: Shard; - - constructor(data: DiscordGuild, client: Client) { - super(data.id) - this.client = client - this.shard = client.shards.get(client.guildShardMap[this.id] || (Base.getDiscordEpoch(data.id) % (client.options.maxShards as number)) || 0)!; - - this.ownerID = data.owner_id - - this.unavailable = !!data.unavailable - this.joinedAt = Date.parse(data.joined_at!) - this.memberCount = data.member_count ?? 0 - this.applicationID = data.application_id - this.widgetEnabled = !!data.widget_enabled - - if (data.widget_channel_id !== undefined) { - this.widgetChannelID = data.widget_channel_id - } - - if (data.approximate_member_count !== undefined) { - this.approximateMemberCount = data.approximate_member_count - } - if (data.approximate_presence_count !== undefined) { - this.approximatePresenceCount = data.approximate_presence_count - } - - if (data.roles) { - for (const r of data.roles) { - const role = new Role(r, this) - this.roles.set(role.id, role) - } - } - - if (data.channels) { - for (const channelData of data.channels) { - channelData.guild_id = this.id.toString() - const channel = generateChannelFrom(channelData, client) as GuildChannel - this.channels.set(channel.id, channel) - client._channelGuildMap.set(channel.id, this.id) - } - } - - if (data.threads) { - for (const threadData of data.threads) { - threadData.guild_id = this.id.toString() - const thread = generateChannelFrom(threadData, client) as unknown as ThreadChannel - this.threads.set(thread.id, thread) - client._threadGuildMap.set(thread.id, this.id) - } - } - - if (data.members) { - for (const m of data.members) { - const member = new Member(m as DiscordMemberWithUser, this, client) - this.members.set(member.id, member) - } - } - - if (data.stage_instances) { - for (const stageInstance of data.stage_instances) { - stageInstance.guild_id = this.id - - const instance = new StageInstance(stageInstance, client) - this.stageInstances.set(instance.id, instance) - } - } - - if (data.presences) { - for (const presence of data.presences) { - if (presence.user?.id) { - const cached = this.client.users.get(presence.user.id) - if (cached) cached.update(presence.user) - else { - const user = new User(presence.user, this.client) - this.client.users.set(user.id, user) - } - } - } - } - - if (data.voice_states) { - for (const voiceState of data.voice_states) { - if (!this.members.get(voiceState.user_id)) continue - - if (voiceState.member) { - const member = new Member(voiceState.member, this, client) - this.members.set(member.id, member) - const user = new User(voiceState.member.user, client) - this.client.users.set(user.id, user) - - // TODO: check channel type maybe voice channel? - ;(this.channels.get(voiceState.channel_id!) as VoiceChannel)?.voiceMembers.set(member.id, member) - } - - // TODO: voice support - // if ( - // client.options.seedVoiceConnections && - // voiceState.user_id === client.id && - // !client.voiceConnections.get(this.id) - // ) { - // process.nextTick(() => - // this.client.joinVoiceChannel(voiceState.channel_id) - // ); - // } - } - } - this.update(data) - } - - /** - * @deprecated - please use .client - */ - get _client() { - return this.client - } - - update(data: DiscordGuild) { - if (data.name !== undefined) { - this.name = data.name - } - if (data.verification_level !== undefined) { - this.verificationLevel = data.verification_level - } - if (data.splash !== undefined) { - this._splash = data.splash ? this.client.iconHashToBigInt(data.splash) : undefined - } - - if (data.discovery_splash !== undefined) { - this._discoverySplash = data.discovery_splash ? this.client.iconHashToBigInt(data.discovery_splash) : undefined - } - - if (data.banner !== undefined) { - this._banner = data.banner ? this.client.iconHashToBigInt(data.banner) : undefined - } - - if (data.owner_id !== undefined) { - this.ownerID = data.owner_id - } - - if (data.icon !== undefined) { - this._icon = data.icon ? this.client.iconHashToBigInt(data.icon) : undefined - } - - // TODO: compress features. - if (data.features !== undefined) { - this.features = data.features - } - - if (data.emojis !== undefined) { - this.emojis = data.emojis - } - - if (data.stickers !== undefined) { - this.stickers = data.stickers - } - - if (data.afk_channel_id !== undefined) { - this.afkChannelID = data.afk_channel_id - } - - if (data.afk_timeout !== undefined) { - this.afkTimeout = data.afk_timeout - } - - if (data.default_message_notifications !== undefined) { - this.defaultNotifications = data.default_message_notifications - } - - if (data.mfa_level !== undefined) { - this.mfaLevel = data.mfa_level - } - - if (data.large !== undefined) { - this.large = data.large - } - - if (data.max_presences !== undefined) { - this.maxPresences = data.max_presences - } - - if (data.explicit_content_filter !== undefined) { - this.explicitContentFilter = data.explicit_content_filter - } - - if (data.system_channel_id !== undefined) { - this.systemChannelID = data.system_channel_id - } - - if (data.system_channel_flags !== undefined) { - this.systemChannelFlags = data.system_channel_flags - } - if (data.premium_progress_bar_enabled !== undefined) { - this.premiumProgressBarEnabled = data.premium_progress_bar_enabled - } - if (data.premium_tier !== undefined) { - this.premiumTier = data.premium_tier - } - if (data.premium_subscription_count !== undefined) { - this.premiumSubscriptionCount = data.premium_subscription_count - } - if (data.vanity_url_code !== undefined) { - this.vanityURL = data.vanity_url_code - } - if (data.preferred_locale !== undefined) { - this.preferredLocale = data.preferred_locale - } - if (data.description !== undefined) { - this.description = data.description - } - if (data.max_members !== undefined) { - this.maxMembers = data.max_members - } - if (data.public_updates_channel_id !== undefined) { - this.publicUpdatesChannelID = data.public_updates_channel_id - } - if (data.rules_channel_id !== undefined) { - this.rulesChannelID = data.rules_channel_id - } - if (data.max_video_channel_users !== undefined) { - this.maxVideoChannelUsers = data.max_video_channel_users - } - if (data.welcome_screen !== undefined) { - this.welcomeScreen = { - description: data.welcome_screen.description, - welcomeChannels: data.welcome_screen.welcome_channels?.map((c) => { - return { - channelID: c.channel_id, - description: c.description, - emojiID: c.emoji_id, - emojiName: c.emoji_name, - } - }), - } - } - // if (data.nsfw !== undefined) { - // this.nsfw = data.nsfw; - // } - if (data.nsfw_level !== undefined) { - this.nsfwLevel = data.nsfw_level - } - } - - get banner(): string | undefined { - return this._banner ? this.client.iconBigintToHash(this._banner) : undefined - } - - get bannerURL(): string | null { - return this.banner ? this.client._formatImage(BANNER(this.id, this.banner)) : null - } - - get icon(): string | undefined { - return this._icon ? this.client.iconBigintToHash(this._icon) : undefined - } - - get iconURL(): string | null { - return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon)) : null - } - - get splash(): string | undefined { - return this._splash ? this.client.iconBigintToHash(this._splash) : undefined - } - - get splashURL(): string | null { - return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash)) : null - } - - get discoverySplash(): string | undefined { - return this._discoverySplash ? this.client.iconBigintToHash(this._discoverySplash) : undefined - } - - get discoverySplashURL(): string | null { - return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash)) : null - } - - /** Add a discovery subcategory */ - async addDiscoverySubcategory(categoryID: BigString, reason?: string): Promise { - return await this.client.addGuildDiscoverySubcategory.call(this.client, this.id, categoryID, reason) - } - - /** Add a role to a guild member */ - async addMemberRole(memberID: BigString, roleID: BigString, reason?: string): Promise { - return await this.client.addGuildMemberRole.call(this.client, this.id, memberID, roleID, reason) - } - - /** Ban a user from the guild */ - async banMember(userID: BigString, deleteMessageDays = 0, reason?: string): Promise { - return await this.client.banGuildMember.call(this.client, this.id, userID, deleteMessageDays, reason) - } - - /** Bulk create/edit guild application commands */ - async bulkEditCommands(commands: Array>): Promise>> { - return await this.client.bulkEditGuildCommands.call(this.client, this.id, commands) - } - - /** Create a channel in the guild */ - async createChannel( - name: string, - type = ChannelTypes.GuildText, - options: CreateChannelOptions, - ): Promise { - return await this.client.createChannel.call(this.client, this.id, name, type as number, options) - } - - /** Create a guild application command */ - async createCommand(command: ApplicationCommandStructure): Promise> { - return await this.client.createGuildCommand.call(this.client, this.id, command) - } - - /** Create a emoji in the guild */ - async createEmoji(options: EmojiOptions, reason?: string): Promise { - return await this.client.createGuildEmoji.call(this.client, this.id, options, reason) - } - - /** Create a guild role */ - async createRole(options: Role | RoleOptions, reason?: string): Promise { - return await this.client.createRole.call(this.client, this.id, options, reason) - } - - /** Create a guild sticker */ - async createSticker(options: CreateStickerOptions, reason?: string): Promise { - return await this.client.createGuildSticker.call(this.client, this.id, options, reason) - } - - /** Create a template for this guild */ - async createTemplate(name: string, description?: string): Promise { - return await this.client.createGuildTemplate.call(this.client, this.id, name, description) - } - - /** Delete the guild (bot user must be owner) */ - async delete(): Promise { - if (this.ownerID !== this.client.id) throw new Error('To delete a guild, the bot must be the owner of the guild.') - - return await this.client.deleteGuild.call(this.client, this.id) - } - - /** Delete a guild application command */ - async deleteCommand(commandID: BigString): Promise { - return await this.client.deleteGuildCommand.call(this.client, this.id, commandID) - } - - /** Delete a discovery subcategory */ - async deleteDiscoverySubcategory(categoryID: BigString, reason?: string): Promise { - return await this.client.deleteGuildDiscoverySubcategory.call(this.client, this.id, categoryID, reason) - } - - /** Delete a emoji in the guild */ - async deleteEmoji(emojiID: BigString, reason?: string): Promise { - return await this.client.deleteGuildEmoji.call(this.client, this.id, emojiID, reason) - } - - /** Delete a guild integration */ - async deleteIntegration(integrationID: BigString): Promise { - return await this.client.deleteGuildIntegration.call(this.client, this.id, integrationID) - } - - /** Delete a role */ - async deleteRole(roleID: BigString, reason?: string): Promise { - return await this.client.deleteRole.call(this.client, this.id, roleID, reason) - } - - /** Delete a guild sticker */ - async deleteSticker(stickerID: BigString, reason?: string): Promise { - return await this.client.deleteGuildSticker.call(this.client, this.id, stickerID, reason) - } - - /** Delete a guild template */ - async deleteTemplate(code: string): Promise { - return await this.client.deleteGuildTemplate.call(this.client, this.id, code) - } - - /** Get the guild's banner with the given format and size */ - dynamicBannerURL(format?: ImageFormat, size?: ImageSize): string | null { - return this.banner ? this.client._formatImage(BANNER(this.id, this.banner), format, size) : null - } - - /** Get the guild's discovery splash with the given format and size */ - dynamicDiscoverySplashURL(format?: ImageFormat, size?: ImageSize): string | null { - return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), format, size) : null - } - - /** Get the guild's icon with the given format and size */ - dynamicIconURL(format?: ImageFormat, size?: ImageSize): string | null { - return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon), format, size) : null - } - - /** Get the guild's splash with the given format and size */ - dynamicSplashURL(format?: ImageFormat, size?: ImageSize): string | null { - return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash), format, size) : null - } - - /** Edit the guild */ - async edit(options: GuildOptions, reason?: string): Promise { - return await this.client.editGuild.call(this.client, this.id, options, reason) - } - - /** Edit multiple channels' positions. Note that channel position numbers are grouped by type (category, text, voice), then sorted in ascending order (lowest number is on top). */ - async editChannelPositions(channelPositions: ChannelPosition[]): Promise { - return await this.client.editChannelPositions.call(this.client, this.id, channelPositions) - } - - /** Edit a guild application command */ - async editCommand(commandID: BigString, commands: ApplicationCommandStructure): Promise> { - return await this.client.editGuildCommand.call(this.client, this.id, commandID, commands) - } - - /** - * Edits command permissions for a specific command in a guild. - * Note: You can only add up to 10 permission overwrites for a command. - */ - async editCommandPermissions(commandID: BigString, permissions: ApplicationCommandPermissions[]): Promise { - return await this.client.editCommandPermissions.call(this.client, this.id, commandID, permissions) - } - - /** Edit the guild's discovery data */ - async editDiscovery(options: DiscoveryOptions): Promise { - return await this.client.editGuildDiscovery.call(this.client, this.id, options) - } - - /** - * Edit a emoji in the guild - * @arg {String} emojiID The ID of the emoji you want to modify - * @arg {Object} options Emoji options - * @arg {String} [options.name] The name of emoji - * @arg {Array} [options.roles] An array containing authorized role IDs - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} A guild emoji object - */ - async editEmoji( - emojiID: BigString, - options: { - name?: string | undefined - roles?: string[] | undefined - }, - reason?: string, - ): Promise { - return await this.client.editGuildEmoji.call(this.client, this.id, emojiID, options, reason) - } - - /** Edit a guild integration */ - async editIntegration(integrationID: BigString, options: IntegrationOptions): Promise { - return await this.client.editGuildIntegration.call(this.client, this.id, integrationID, options) - } - - /** Edit a guild member */ - async editMember(memberID: BigString, options: MemberOptions, reason?: string): Promise { - return await this.client.editGuildMember.call(this.client, this.id, memberID, options, reason) - } - - /** Edit the guild role */ - async editRole(roleID: BigString, options: RoleOptions, reason?: string): Promise { - return await this.client.editRole.call(this.client, this.id, roleID, options, reason) - } - - /** Edit a guild sticker */ - async editSticker(stickerID: BigString, options: EditStickerOptions, reason?: string): Promise { - return await this.client.editGuildSticker.call(this.client, this.id, stickerID, options, reason) - } - - /** Edit a guild template */ - async editTemplate(code: string, options: GuildTemplateOptions): Promise { - return await this.client.editGuildTemplate.call(this.client, this.id, code, options) - } - - /** Modify the guild's vanity code */ - async editVanity(code: string | null): Promise { - return await this.client.editGuildVanity.call(this.client, this.id, code) - } - - /** Update a user's voice state - See [caveats](https://discord.com/developers/docs/resources/guild#modify-user-voice-state-caveats) */ - async editVoiceState(options: VoiceStateOptions, userID: BigString = '@me'): Promise { - return await this.client.editGuildVoiceState.call(this.client, this.id, options, userID) - } - - /** Edit the guild welcome screen */ - async editWelcomeScreen(options: WelcomeScreenOptions): Promise { - return await this.client.editGuildWelcomeScreen.call(this.client, this.id, options) - } - - /** Modify a guild's widget */ - async editWidget(options: Widget): Promise { - return await this.client.editGuildWidget.call(this.client, this.id, options) - } - - /** Request all guild members from Discord */ - async fetchAllMembers(timeout?: number): Promise { - return await this.fetchMembers({ - guildId: this.id, - limit: 0, - }).then((m: any[]) => m.length) - } - - /** Request specific guild members through the gateway connection */ - async fetchMembers(options: RequestGuildMembers): Promise { - // TODO: Use gateway fetch - return await this.client.getRESTGuildMembers(this.id, options) - } - - /** Get all active threads in this guild */ - async getActiveThreads(): Promise> { - return await this.client.getActiveGuildThreads.call(this.client, this.id) - } - - /** Get the audit log for the guild */ - async getAuditLog(options: GetGuildAuditLogOptions): Promise { - return await this.client.getGuildAuditLog.call(this.client, this.id, options) - } - - /** Get a ban from the ban list of a guild */ - async getBan(userID: BigString): Promise { - return await this.client.getGuildBan.call(this.client, this.id, userID) - } - - /** Get the ban list of the guild */ - async getBans(options?: GetGuildBansOptions): Promise { - return await this.client.getGuildBans.call(this.client, this.id, options) - } - - /** Get a guild application command */ - async getCommand(commandID: BigString): Promise> { - return await this.client.getGuildCommand.call(this.client, this.id, commandID) - } - - /** Get the a guild's application command permissions */ - async getCommandPermissions(commandID: BigString): Promise { - return await this.client.getCommandPermissions.call(this.client, this.id, commandID) - } - - /** Get the guild's application commands */ - async getCommands(): Promise> { - return await this.client.getGuildCommands.call(this.client, this.id) - } - - /** Get the guild's discovery object */ - async getDiscovery(): Promise { - return await this.client.getGuildDiscovery.call(this.client, this.id) - } - - /** Get the all of a guild's application command permissions */ - async getGuildCommandPermissions(): Promise { - return await this.client.getGuildCommandPermissions.call(this.client, this.id) - } - - /** Get a list of integrations for the guild */ - async getIntegrations(): Promise { - return await this.client.getGuildIntegrations.call(this.client, this.id) - } - - /** Get all invites in the guild */ - async getInvites(): Promise { - return await this.client.getGuildInvites.call(this.client, this.id) - } - - /** Get the prune count for the guild */ - async getPruneCount(options: GetPruneOptions): Promise { - return await this.client.getPruneCount.call(this.client, this.id, options) - } - - /** Get a guild's channels via the REST API. REST mode is required to use this endpoint. */ - async getRESTChannels(): Promise { - return await this.client.getRESTGuildChannels.call(this.client, this.id) - } - - /** Get a guild emoji via the REST API. REST mode is required to use this endpoint. */ - async getRESTEmoji(emojiID: BigString): Promise { - return await this.client.getRESTGuildEmoji.call(this.client, this.id, emojiID) - } - - /** Get a guild's emojis via the REST API. REST mode is required to use this endpoint. */ - async getRESTEmojis(): Promise { - return await this.client.getRESTGuildEmojis.call(this.client, this.id) - } - - /** Get a guild's members via the REST API. REST mode is required to use this endpoint. */ - async getRESTMember(memberID: BigString): Promise { - return await this.client.getRESTGuildMember.call(this.client, this.id, memberID) - } - - /** Get a guild's members via the REST API. REST mode is required to use this endpoint. */ - async getRESTMembers(options?: GetRESTGuildMembersOptions): Promise { - return await this.client.getRESTGuildMembers.call(this.client, this.id, options) - } - - /** Get a guild's roles via the REST API. REST mode is required to use this endpoint. */ - async getRESTRoles(): Promise { - return await this.client.getRESTGuildRoles.call(this.client, this.id) - } - - /** Get a guild sticker via the REST API. REST mode is required to use this endpoint. */ - async getRESTSticker(stickerID: BigString): Promise { - return await this.client.getRESTGuildSticker.call(this.client, this.id, stickerID) - } - - /** Get a guild's stickers via the REST API. REST mode is required to use this endpoint. */ - async getRESTStickers(): Promise { - return await this.client.getRESTGuildStickers.call(this.client, this.id) - } - - /** Get the guild's templates */ - async getTemplates(): Promise { - return await this.client.getGuildTemplates.call(this.client, this.id) - } - - /** Returns the vanity url of the guild */ - async getVanity(): Promise { - return await this.client.getGuildVanity.call(this.client, this.id) - } - - /** Get possible voice regions for a guild */ - async getVoiceRegions(): Promise { - return await this.client.getVoiceRegions.call(this.client, this.id) - } - - /** Get all the webhooks in the guild */ - async getWebhooks(): Promise { - return await this.client.getGuildWebhooks.call(this.client, this.id) - } - - /** Get the welcome screen of the Community guild, shown to new members */ - async getWelcomeScreen(): Promise { - return await this.client.getGuildWelcomeScreen.call(this.client, this.id) - } - - /** Get a guild's widget object */ - async getWidget(): Promise { - return await this.client.getGuildWidget.call(this.client, this.id) - } - - /** Get a guild's widget settings object */ - async getWidgetSettings(): Promise { - return await this.client.getGuildWidgetSettings.call(this.client, this.id) - } - - /** Kick a member from the guild */ - async kickMember(userID: BigString, reason?: string): Promise { - return await this.client.kickGuildMember.call(this.client, this.id, userID, reason) - } - - /** Leave the guild */ - async leave(): Promise { - return await this.client.leaveGuild.call(this.client, this.id) - } - - // TODO: gateway voice - // /** Leaves the voice channel in this guild */ - // async leaveVoiceChannel(): Promise { - // return await this.client.closeVoiceConnection.call(this.client, this.id); - // } - - /** Get the guild permissions of a member */ - permissionsOf(memberID: BigString | Member): Permission { - const member = ['string', 'bigint'].includes(typeof memberID) ? this.members.get(memberID as BigString)! : (memberID as Member) - if (member.id === this.ownerID) { - return new Permission(BitwisePermissionFlags.ADMINISTRATOR) - } else { - let permissions = this.roles.get(this.id)!.permissions.allow - if (permissions & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) { - return new Permission(BitwisePermissionFlags.ADMINISTRATOR) - } - for (const id of member.roles) { - const role = this.roles.get(id) - if (!role) { - continue - } - - const { allow: perm } = role.permissions - if (perm & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) { - permissions = BigInt(BitwisePermissionFlags.ADMINISTRATOR) - break - } else { - permissions |= perm - } - } - return new Permission(permissions) - } - } - - /** Begin pruning the guild */ - async pruneMembers(options?: PruneMemberOptions): Promise { - return await this.client.pruneMembers.call(this.client, this.id, options) - } - - /** Remove a role from a guild member */ - async removeMemberRole(memberID: BigString, roleID: BigString, reason?: string): Promise { - return await this.client.removeGuildMemberRole.call(this.client, this.id, memberID, roleID, reason) - } - - /** Search for guild members by partial nickname/username */ - async searchMembers(query: string, limit = 1): Promise { - return await this.client.searchGuildMembers.call(this.client, this.id, query, limit) - } - - /** Force a guild integration to sync */ - async syncIntegration(integrationID: BigString): Promise { - return await this.client.syncGuildIntegration.call(this.client, this.id, integrationID) - } - - /** Force a guild template to sync */ - async syncTemplate(code: string): Promise { - return await this.client.syncGuildTemplate.call(this.client, this.id, code) - } - - /** Unban a user from the guild */ - async unbanMember(userID: BigString, reason?: string): Promise { - return await this.client.unbanGuildMember.call(this.client, this.id, userID, reason) - } - - toJSON(props: string[] = []): Record { - return super.toJSON([ - 'afkChannelID', - 'afkTimeout', - 'applicationID', - 'approximateMemberCount', - 'approximatePresenceCount', - 'autoRemoved', - 'banner', - 'categories', - 'channels', - 'defaultNotifications', - 'description', - 'discoverySplash', - 'emojiCount', - 'emojis', - 'explicitContentFilter', - 'features', - 'icon', - 'joinedAt', - 'keywords', - 'large', - 'maxMembers', - 'maxPresences', - 'maxVideoChannelUsers', - 'memberCount', - 'members', - 'mfaLevel', - 'name', - 'ownerID', - 'pendingVoiceStates', - 'preferredLocale', - 'premiumProgressBarEnabled', - 'premiumSubscriptionCount', - 'premiumTier', - 'primaryCategory', - 'primaryCategoryID', - 'publicUpdatesChannelID', - 'roles', - 'rulesChannelID', - 'splash', - 'stickers', - 'systemChannelFlags', - 'systemChannelID', - 'unavailable', - 'vanityURL', - 'verificationLevel', - 'voiceStates', - 'welcomeScreen', - 'widgetChannelID', - 'widgetEnabled', - ...props, - ]) - } -} - -export default Guild diff --git a/packages/client/src/Structures/guilds/Integration.ts b/packages/client/src/Structures/guilds/Integration.ts deleted file mode 100644 index 1eed1873d..000000000 --- a/packages/client/src/Structures/guilds/Integration.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* eslint-disable no-useless-call */ -import type { DiscordIntegration, DiscordIntegrationApplication } from '@discordeno/types' -import Base from '../../Base.js' -import type { IntegrationOptions } from '../../typings.js' -import User from '../users/User.js' -import type Guild from './Guild.js' - -export class GuildIntegration extends Base { - /** The guild where this integration exists. */ - guild: Guild - /** The name of the integration. */ - name: string - /** The type of integration. */ - type: string - /** The user connected to the integration. */ - user?: User - /** Whether the integration is syncing or not. */ - syncing?: boolean - /** THe Unix timestamp of last integration sync. */ - syncedAt?: number - /** The number of subscribers. */ - subscriberCount?: number - /** The role id of the role connected to the integration. */ - roleID?: string - /** WHether or not the application was revoked. */ - revoked?: boolean - /** Whether integration emoticons are enabled or not. */ - enableEmoticons?: boolean - /** Behavior of expired subscriptions */ - expireBehavior?: number - /** Grace period for expired subscriptions. */ - expireGracePeriod?: number - /** Whether the integration is enabled or not. */ - enabled?: boolean - /** The bot/oauth2 application for integration. */ - application?: DiscordIntegrationApplication - - /** Info on the integration account */ - account: { - /** The id of the integration account. */ - id: string - /** The name of the integration account. */ - name: string - } - - constructor(data: DiscordIntegration, guild: Guild) { - super(data.id) - - this.guild = guild - this.name = data.name - this.type = data.type - - if (data.role_id !== undefined) { - this.roleID = data.role_id - } - - if (data.user) { - this.user = new User(data.user, guild.client) - guild.client.users.set(this.user.id, this.user) - } - - this.account = data.account // not worth making a class for - - this.update(data) - } - - update(data: DiscordIntegration): void { - this.enabled = data.enabled - if (data.syncing !== undefined) { - this.syncing = data.syncing - } - if (data.expire_behavior !== undefined) { - this.expireBehavior = data.expire_behavior - } - if (data.expire_behavior !== undefined) { - this.expireGracePeriod = data.expire_grace_period - } - if (data.enable_emoticons) { - this.enableEmoticons = data.enable_emoticons - } - if (data.subscriber_count !== undefined) { - this.subscriberCount = data.subscriber_count - } - if (data.synced_at !== undefined) { - this.syncedAt = Date.parse(data.synced_at) - } - if (data.revoked !== undefined) { - this.revoked = data.revoked - } - if (data.application !== undefined) { - this.application = data.application - } - } - - /** Delete the guild integration */ - async delete(): Promise { - return await this.guild.client.deleteGuildIntegration.call(this.guild.client, this.guild.id, this.id) - } - - /** Edit the guild integration */ - async edit(options: IntegrationOptions): Promise { - return await this.guild.client.editGuildIntegration.call(this.guild.client, this.guild.id, this.id, options) - } - - /** Force the guild integration to sync */ - async sync(): Promise { - return await this.guild.client.syncGuildIntegration.call(this.guild.client, this.guild.id, this.id) - } - - toJSON(props: string[] = []): Record { - return super.toJSON([ - 'account', - 'application', - 'enabled', - 'enableEmoticons', - 'expireBehavior', - 'expireGracePeriod', - 'name', - 'revoked', - 'roleID', - 'subscriberCount', - 'syncedAt', - 'syncing', - 'type', - 'user', - ...props, - ]) - } -} - -export default GuildIntegration diff --git a/packages/client/src/Structures/guilds/Member.ts b/packages/client/src/Structures/guilds/Member.ts deleted file mode 100644 index 8e2901b1a..000000000 --- a/packages/client/src/Structures/guilds/Member.ts +++ /dev/null @@ -1,222 +0,0 @@ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/return-await */ - -import type { BigString, DiscordMember, DiscordMemberWithUser } from '@discordeno/types' -import Base from '../../Base.js' -import type Client from '../../Client.js' -import type { ImageFormat, ImageSize } from '../../Client.js' -import { GUILD_AVATAR } from '../../Endpoints.js' -import type { MemberOptions, Status } from '../../typings.js' -import User from '../users/User.js' -import type Guild from './Guild.js' - -export class Member extends Base { - /** The client manager */ - client: Client - /** An array of role IDs this member is a part of */ - roles: string[] - /** The guild the member is in */ - guild: Guild - /** The user object of the member */ - user: User - /** The server nickname of the member. */ - nick: string | null - /** The timestamp when this member joined the server. */ - joinedAt?: number - /** Timestamp of when the member boosted the guild */ - premiumSince?: number - /** Whether the user has not yet passed the guild's Membership Screening requirements */ - pending?: boolean - /** Timestamp of timeout expiry. If `null`, the member is not timed out */ - communicationDisabledUntil?: number | null - /** The members current status */ - status?: Status; - - /** The compressed form of the members avatar. */ - _avatar?: bigint - - constructor(data: (DiscordMember & { id: BigString }) | DiscordMemberWithUser, guild: Guild, client: Client) { - super(client.isDiscordMemberWithUser(data) ? data.user.id : data.id) - this.client = client - this.guild = guild - this.nick = null - this.roles = data.roles ?? [] - - const userID = client.isDiscordMemberWithUser(data) ? data.user.id : data.id - - this.user = client.users.get(userID)! - if (data.user) { - this.user = new User(data.user, client) - client.users.set(this.user.id, this.user) - } - - if (!this.user) { - throw new Error('User associated with Member not found: ' + userID) - } - - this.update(data) - } - - update(data: (DiscordMember & { id: BigString }) | DiscordMemberWithUser) { - if (data.joined_at !== undefined) { - this.joinedAt = data.joined_at ? Date.parse(data.joined_at) : undefined - } - - if (data.premium_since !== undefined) { - this.premiumSince = data.premium_since === null ? undefined : Date.parse(data.premium_since) - } - - // eslint-disable-next-line no-prototype-builtins - if (data.hasOwnProperty('mute') && this.guild) { - // TODO: voice stuff - // const state = this.guild.voiceStates.get(this.id); - // if ( - // data.channel_id === null && - // !data.mute && - // !data.deaf && - // !data.suppress - // ) { - // this.guild.voiceStates.delete(this.id); - // } else if (state) { - // state.update(data); - // } else if (data.channel_id || data.mute || data.deaf || data.suppress) { - // this.guild.voiceStates.update(data); - // } - } - - if (data.nick !== undefined) this.nick = data.nick - if (data.roles !== undefined) this.roles = data.roles - if (data.pending !== undefined) this.pending = data.pending - if (data.avatar !== undefined) this._avatar = data.avatar ? this.client.iconHashToBigInt(data.avatar) : undefined - - if (data.communication_disabled_until !== undefined) { - if (data.communication_disabled_until !== null) { - this.communicationDisabledUntil = Date.parse(data.communication_disabled_until) - } else { - this.communicationDisabledUntil = data.communication_disabled_until - } - } - } - - get avatar(): string | undefined { - return this._avatar ? this.client.iconBigintToHash(this._avatar) : undefined - } - - get accentColor() { - return this.user.accentColor - } - - get avatarURL() { - return this.avatar ? this.client._formatImage(GUILD_AVATAR(this.guild.id, this.id, this.avatar)) : this.user.avatarURL - } - - get banner() { - return this.user.banner - } - - get bannerURL() { - return this.user.bannerURL - } - - get bot() { - return this.user.bot - } - - get createdAt() { - return this.user.createdAt - } - - get defaultAvatar() { - return this.user.defaultAvatar - } - - get defaultAvatarURL() { - return this.user.defaultAvatarURL - } - - get discriminator() { - return this.user.discriminator - } - - get mention() { - return `<@!${this.id}>` - } - - get permissions() { - return this.guild.permissionsOf(this) - } - - get staticAvatarURL() { - return this.user.staticAvatarURL - } - - get username() { - return this.user.username - } - - get voiceState() { - if (this.guild?.voiceStates.has(this.id)) { - return this.guild.voiceStates.get(this.id) - } else { - // @ts-expect-error some eris magic at play here - return new VoiceState({ id: this.id }) - } - } - - /** Add a role to the guild member */ - async addRole(roleID: BigString, reason?: string): Promise { - return await this.client.addGuildMemberRole.call(this.client, this.guild.id, this.id, roleID, reason) - } - - /** Ban the user from the guild */ - async ban(deleteMessageDays = 0, reason?: string): Promise { - return await this.client.banGuildMember.call(this.client, this.guild.id, this.id, deleteMessageDays, reason) - } - - /** Edit the guild member */ - async edit(options: MemberOptions, reason?: string): Promise { - return await this.client.editGuildMember.call(this.client, this.guild.id, this.id, options, reason) - } - - /** Get the member's avatar with the given format and size */ - dynamicAvatarURL(format?: ImageFormat, size?: ImageSize): string { - return this.avatar - ? this.client._formatImage(GUILD_AVATAR(this.guild.id, this.id, this.avatar), format, size) - : this.user.dynamicAvatarURL(format, size) - } - - /** Kick the member from the guild */ - async kick(reason?: string): Promise { - return await this.client.kickGuildMember.call(this.client, this.guild.id, this.id, reason) - } - - /** Remove a role from the guild member */ - async removeRole(roleID: BigString, reason?: string): Promise { - return await this.client.removeGuildMemberRole.call(this.client, this.guild.id, this.id, roleID, reason) - } - - /** Unban the user from the guild */ - async unban(reason?: string): Promise { - return await this.client.unbanGuildMember.call(this.client, this.guild.id, this.id, reason) - } - - toJSON(props: string[] = []): Record { - return super.toJSON([ - 'activities', - 'communicationDisabledUntil', - 'joinedAt', - 'nick', - 'pending', - 'premiumSince', - 'roles', - 'status', - 'user', - 'voiceState', - ...props, - ]) - } -} - -export default Member diff --git a/packages/client/src/Structures/guilds/Preview.ts b/packages/client/src/Structures/guilds/Preview.ts deleted file mode 100644 index 0968cbf86..000000000 --- a/packages/client/src/Structures/guilds/Preview.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { DiscordEmoji, DiscordGuildPreview } from '@discordeno/types' -import Base from '../../Base.js' -import type Client from '../../Client.js' -import type { ImageFormat, ImageSize } from '../../Client.js' -import { GUILD_DISCOVERY_SPLASH, GUILD_ICON, GUILD_SPLASH } from '../../Endpoints.js' -import { GuildToggles } from '../toggles/Guild.js' - -export class GuildPreview extends Base { - /** The client object */ - client: Client - /** The name of the guild. */ - name: string - /** The description of the guild. */ - description: string | null - /** An array of guild emojis. */ - emojis: DiscordEmoji[] - /** The approximate number of members in the guild. */ - approximateMemberCount: number - /** The approximate number of presences in the guild. */ - approximatePresenceCount: number - - /** The guild's icon image url. */ - _icon: bigint | null - /** The guild's splash image url. */ - _splash: bigint | null - /** The guild's discovery splash image url. */ - _discoverySplash: bigint | null - /** The guild's features. */ - _features: GuildToggles - - constructor(data: DiscordGuildPreview, client: Client) { - super(data.id) - - this.client = client - this.name = data.name - this.description = data.description - this._icon = data.icon ? client.iconHashToBigInt(data.icon) : null - this._splash = data.splash ? client.iconHashToBigInt(data.splash) : null - this._discoverySplash = data.discovery_splash ? client.iconHashToBigInt(data.discovery_splash) : null - this.approximateMemberCount = data.approximate_member_count - this.approximatePresenceCount = data.approximate_presence_count - this.emojis = data.emojis - // TODO: make dd version accept a specific subset of discord guild here - // @ts-expect-error this should not cause an issue - this._features = new GuildToggles(data) - } - - /** - * @deprecated Use .client - */ - get _client(): Client { - return this.client - } - - get icon(): string | undefined { - return this._icon ? this.client.iconBigintToHash(this._icon) : undefined - } - - get iconURL(): string | null { - return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon)) : null - } - - get splash(): string | undefined { - return this._splash ? this.client.iconBigintToHash(this._splash) : undefined - } - - get splashURL(): string | null { - return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash)) : null - } - - get discoverySplash(): string | undefined { - return this._discoverySplash ? this.client.iconBigintToHash(this._discoverySplash) : undefined - } - - get discoverySplashURL(): string | null { - return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash)) : null - } - - get features(): string[] { - return this._features.features.map((feature) => feature.replace(/([a-z])([A-Z])/, '$1_$2').toUpperCase()) - } - - /** Get the guild's splash with the given format and size */ - dynamicDiscoverySplashURL(format?: ImageFormat, size?: ImageSize): string | null { - return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), format, size) : null - } - - /** Get the guild's icon with the given format and size */ - dynamicIconURL(format?: ImageFormat, size?: ImageSize): string | null { - return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon), format, size) : null - } - - /** Get the guild's splash with the given format and size */ - dynamicSplashURL(format?: ImageFormat, size?: ImageSize): string | null { - return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash), format, size) : null - } - - toJSON(props: string[] = []): Record { - return super.toJSON([ - 'approximateMemberCount', - 'approximatePresenceCount', - 'description', - 'discoverySplash', - 'emojis', - 'features', - 'icon', - 'name', - 'splash', - ...props, - ]) - } -} - -export default GuildPreview diff --git a/packages/client/src/Structures/guilds/Role.ts b/packages/client/src/Structures/guilds/Role.ts deleted file mode 100644 index 627a6c820..000000000 --- a/packages/client/src/Structures/guilds/Role.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/return-await */ - -import type { DiscordRole, DiscordRoleTags } from '@discordeno/types' -import Base from '../../Base.js' -import { ROLE_ICON } from '../../Endpoints.js' -import type { RoleOptions } from '../../typings.js' -import Permission from '../Permission.js' -import type Guild from './Guild.js' - -export class Role extends Base { - permissions: Permission - name: string - color: number - hoist: boolean - mentionable: boolean - managed: boolean - icon?: string - unicodeEmoji?: string - position: number - guild: Guild - tags?: Omit & { - premium_subscriber?: boolean - available_for_purchase?: boolean - guild_connections?: boolean - } - - constructor(data: DiscordRole, guild: Guild) { - super(data.id) - this.guild = guild - - this.name = data.name - this.permissions = new Permission(data.permissions) - this.color = data.color - this.hoist = !!data.hoist - this.mentionable = !!data.mentionable - this.managed = !!data.managed - this.icon = data.icon - this.unicodeEmoji = data.unicode_emoji - this.position = data.position - this.tags = data.tags - ? { - bot_id: data.tags.bot_id, - integration_id: data.tags.integration_id, - premium_subscriber: data.tags.premium_subscriber === null, - subscription_listing_id: data.tags.subscription_listing_id, - available_for_purchase: data.tags.available_for_purchase === null, - guild_connections: data.tags.guild_connections === null, - } - : undefined - } - - update(data: DiscordRole) { - if (data.name !== undefined) { - this.name = data.name - } - if (data.mentionable !== undefined) { - this.mentionable = data.mentionable - } - if (data.managed !== undefined) { - this.managed = data.managed - } - if (data.hoist !== undefined) { - this.hoist = data.hoist - } - if (data.color !== undefined) { - this.color = data.color - } - if (data.position !== undefined) { - this.position = data.position - } - if (data.permissions !== undefined) { - this.permissions = new Permission(data.permissions) - } - if (data.tags !== undefined) { - this.tags = { - bot_id: data.tags.bot_id, - integration_id: data.tags.integration_id, - subscription_listing_id: data.tags.subscription_listing_id, - available_for_purchase: data.tags.available_for_purchase === null, - premium_subscriber: data.tags.premium_subscriber === null, - } - } - if (data.icon !== undefined) { - this.icon = data.icon - } - if (data.unicode_emoji !== undefined) { - this.unicodeEmoji = data.unicode_emoji - } - } - - get iconURL() { - return this.icon ? this.guild.client._formatImage(ROLE_ICON(this.id, this.icon)) : null - } - - get json() { - return this.permissions.json - } - - get mention() { - return `<@&${this.id}>` - } - - /** Delete the role */ - async delete(reason: string): Promise { - return await this.guild.client.deleteRole.call(this.guild.client, this.guild.id, this.id, reason) - } - - /** Edit the guild role */ - async edit(options: RoleOptions, reason?: string): Promise { - return await this.guild.client.editRole.call(this.guild.client, this.guild.id, this.id, options, reason) - } - - /** Edit the role's position. Note that role position numbers are highest on top and lowest at the bottom. */ - async editPosition(position: number): Promise { - return await this.guild.client.editRolePosition.call(this.guild.client, this.guild.id, this.id, position) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['color', 'hoist', 'icon', 'managed', 'mentionable', 'name', 'permissions', 'position', 'tags', 'unicodeEmoji', ...props]) - } -} - -export default Role diff --git a/packages/client/src/Structures/guilds/StageInstance.ts b/packages/client/src/Structures/guilds/StageInstance.ts deleted file mode 100644 index a31cdb97d..000000000 --- a/packages/client/src/Structures/guilds/StageInstance.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ -import Base from '../../Base.js' - -import type { DiscordStageInstance } from '@discordeno/types' -import type Client from '../../Client.js' -import type { StageInstanceOptions } from '../../typings.js' -import type StageChannel from '../channels/Stage.js' -import type Guild from './Guild.js' - -export class StageInstance extends Base { - /** The client manager. */ - client: Client - /** The associated stage channel */ - channel: StageChannel | { id: string } - /** The guild of the associated stage channel */ - guild: Guild | { id: string } - /** The stage channel topic */ - topic?: string | null - - constructor(data: DiscordStageInstance, client: Client) { - super(data.id) - - this.client = client - this.channel = client.getChannel(data.channel_id) ?? { - id: data.channel_id, - } - this.guild = client.guilds.get(data.guild_id) ?? { id: data.guild_id } - this.update(data) - } - - /** - * @deprecated Use `.client` instead. - */ - get _client(): Client { - return this.client - } - - update(data: DiscordStageInstance) { - if (data.topic !== undefined) { - this.topic = data.topic - } - } - - /** Delete this stage instance */ - async delete(): Promise { - return await this.client.deleteStageInstance.call(this.client, this.channel.id) - } - - /** Update this stage instance */ - async edit(options: StageInstanceOptions): Promise { - return await this.client.editStageInstance.call(this.client, this.channel.id, options) - } -} - -export default StageInstance diff --git a/packages/client/src/Structures/guilds/Template.ts b/packages/client/src/Structures/guilds/Template.ts deleted file mode 100644 index cb2f00a4f..000000000 --- a/packages/client/src/Structures/guilds/Template.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* eslint-disable no-useless-call */ -import type { DiscordTemplate } from '@discordeno/types' -import Base from '../../Base.js' -import type Client from '../../Client.js' -import type { GuildTemplateOptions } from '../../typings.js' -import User from '../users/User.js' -import Guild from './Guild.js' - -export class GuildTemplate { - /** The client class itself. */ - client: Client - /** The template code (unique Id) */ - code: string - /** Template name */ - name: string - /** The description for the template */ - description: string | null - /** Number of times this template has been used */ - usageCount: number - /** The user who created the template */ - creator: User - /** When this template was created */ - createdAt: number - /** When this template was last synced to the source guild */ - updatedAt: number - /** The guild snapshot this template contains */ - serializedSourceGuild: Guild - /** The guild this template is based on. If the guild is not cached, this will be an object with `id` key. No other property is guaranteed */ - sourceGuild: Guild | { id: string } - /** Whether the template has un-synced changes */ - isDirty: boolean | null - - constructor(data: DiscordTemplate, client: Client) { - this.client = client - this.code = data.code - this.createdAt = Date.parse(data.created_at) - this.creator = new User(data.creator, client) - this.client.users.set(this.creator.id, this.creator) - this.description = data.description - this.isDirty = data.is_dirty - this.name = data.name - this.sourceGuild = client.guilds.get(data.source_guild_id) ?? { id: data.source_guild_id } - this.updatedAt = Date.parse(data.updated_at) - this.usageCount = data.usage_count - - data.serialized_source_guild.features = [] - // @ts-expect-error Hacks to get this to not error - this.serializedSourceGuild = new Guild(data.serialized_source_guild, client) - } - - /** - * @deprecated Use .client instead. - */ - get _client(): Client { - return this.client - } - - /** Create a guild based on this template. Only for bots in less than 10 guilds */ - async createGuild(name: string, icon?: string): Promise { - return await this.client.createGuildFromTemplate.call(this.client, this.code, name, icon) - } - - /** Delete this template */ - async delete(): Promise { - return await this.client.deleteGuildTemplate.call(this.client, this.sourceGuild.id, this.code) - } - - /** Edit this template */ - async edit(options: GuildTemplateOptions): Promise { - return await this.client.editGuildTemplate.call(this.client, this.sourceGuild.id, this.code, options) - } - - /** Force this template to sync to the guild's current state */ - async sync(): Promise { - return await this.client.syncGuildTemplate.call(this.client, this.sourceGuild.id, this.code) - } - - toJSON(props: string[] = []): Record { - return Base.prototype.toJSON.call(this, [ - 'code', - 'createdAt', - 'creator', - 'description', - 'isDirty', - 'name', - 'serializedSourceGuild', - 'sourceGuild', - 'updatedAt', - 'usageCount', - ...props, - ]) - } -} - -export default GuildTemplate diff --git a/packages/client/src/Structures/guilds/Unavailable.ts b/packages/client/src/Structures/guilds/Unavailable.ts deleted file mode 100644 index 8109817d2..000000000 --- a/packages/client/src/Structures/guilds/Unavailable.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { DiscordUnavailableGuild } from '@discordeno/types' -import Base from '../../Base.js' -import type Client from '../../Client.js' - -export class UnavailableGuild extends Base { - /** Whether or not the guild is unavailable. */ - unavailable: boolean - - constructor(data: DiscordUnavailableGuild, client: Client) { - super(data.id) - - // TODO: gateway - // this.shard = client.shards.get(client.guildShardMap[this.id]); - this.unavailable = !!data.unavailable - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['unavailable', ...props]) - } -} - -export default UnavailableGuild diff --git a/packages/client/src/Structures/guilds/VoiceState.ts b/packages/client/src/Structures/guilds/VoiceState.ts deleted file mode 100644 index 51d5d8a4c..000000000 --- a/packages/client/src/Structures/guilds/VoiceState.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import type { DiscordVoiceState } from '@discordeno/types' -import Base from '../../Base.js' -import { VoiceStateToggle, VoiceStateToggles } from '../toggles/Voice.js' - -export class VoiceState extends Base { - channelID: string | null = null - requestToSpeakTimestamp: number | null - sessionID!: string | null - - bitfield: VoiceStateToggles - - constructor(data: DiscordVoiceState & { id: string }) { - super(data.id) - - this.requestToSpeakTimestamp = null - this.bitfield = new VoiceStateToggles(data) - - this.update(data) - } - - /** Whether or not the user is deafened by the server. */ - get deaf(): boolean { - return this.bitfield.deaf - } - - /** Set whether or not the user is deafened by the server. */ - set deaf(value: boolean) { - if (value) this.bitfield.add(VoiceStateToggle.deaf) - else this.bitfield.remove(VoiceStateToggle.deaf) - } - - /** Whether or not the user is muted by the server. */ - get mute(): boolean { - return this.bitfield.mute - } - - /** Set whether or not the user is muted by the server. */ - set mute(value: boolean) { - if (value) this.bitfield.add(VoiceStateToggle.mute) - else this.bitfield.remove(VoiceStateToggle.mute) - } - - /** Whether or not the user has deafened themself. */ - get selfDeaf(): boolean { - return this.bitfield.selfDeaf - } - - /** Set whether or not the user has deafened themself. */ - set selfDeaf(value: boolean) { - if (value) this.bitfield.add(VoiceStateToggle.selfDeaf) - else this.bitfield.remove(VoiceStateToggle.selfDeaf) - } - - /** Whether or not the user has muted themself. */ - get selfMute(): boolean { - return this.bitfield.selfMute - } - - /** Set whether or not the user has muted themself. */ - set selfMute(value: boolean) { - if (value) this.bitfield.add(VoiceStateToggle.selfMute) - else this.bitfield.remove(VoiceStateToggle.selfMute) - } - - /** Whether or not the user is streaming. */ - get selfStream(): boolean { - return this.bitfield.selfStream - } - - /** Set whether or not the user is streaming. */ - set selfStream(value: boolean) { - if (value) this.bitfield.add(VoiceStateToggle.selfStream) - else this.bitfield.remove(VoiceStateToggle.selfStream) - } - - /** Whether or not the user is video calling. */ - get selfVideo(): boolean { - return this.bitfield.selfVideo - } - - /** Set whether or not the user is video calling. */ - set selfVideo(value: boolean) { - if (value) this.bitfield.add(VoiceStateToggle.selfVideo) - else this.bitfield.remove(VoiceStateToggle.selfVideo) - } - - /** Whether or not the user is suppressed from speaking. */ - get suppress(): boolean { - return this.bitfield.suppress - } - - /** Set whether or not the user is suppressed from speaking. */ - set suppress(value: boolean) { - if (value) this.bitfield.add(VoiceStateToggle.suppress) - else this.bitfield.remove(VoiceStateToggle.suppress) - } - - update(data: DiscordVoiceState) { - if (data.channel_id !== undefined) { - this.channelID = data.channel_id - this.sessionID = data.channel_id === null ? null : data.session_id - } else if (this.channelID === undefined) { - this.channelID = this.sessionID = null - } - - if (data.mute !== undefined) this.mute = data.mute - if (data.deaf !== undefined) this.deaf = data.deaf - if (data.request_to_speak_timestamp) this.requestToSpeakTimestamp = Date.parse(data.request_to_speak_timestamp) - if (data.self_mute !== undefined) this.selfMute = data.self_mute - if (data.self_deaf !== undefined) this.selfDeaf = data.self_deaf - if (data.self_video !== undefined) this.selfVideo = data.self_video - if (data.self_stream !== undefined) this.selfStream = data.self_stream - if (data.suppress !== undefined) this.suppress = data.suppress - } - - toJSON(props: string[] = []): Record { - return super.toJSON([ - 'channelID', - 'deaf', - 'mute', - 'requestToSpeakTimestamp', - 'selfDeaf', - 'selfMute', - 'selfStream', - 'selfVideo', - 'sessionID', - 'suppress', - ...props, - ]) - } -} diff --git a/packages/client/src/Structures/interactions/Autocomplete.ts b/packages/client/src/Structures/interactions/Autocomplete.ts deleted file mode 100644 index 0618a097e..000000000 --- a/packages/client/src/Structures/interactions/Autocomplete.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* eslint-disable @typescript-eslint/return-await */ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import type { DiscordInteraction, DiscordInteractionData } from '@discordeno/types' -import { InteractionResponseTypes } from '@discordeno/types' -import type Client from '../../Client.js' -import type { ApplicationCommandOptionChoice } from '../../typings.js' -import type NewsChannel from '../channels/News.js' -import type PrivateChannel from '../channels/Private.js' -import type TextChannel from '../channels/Text.js' -import type Guild from '../guilds/Guild.js' -import Member from '../guilds/Member.js' -import Permission from '../Permission.js' -import User from '../users/User.js' -import Interaction from './Interaction.js' - -export class AutocompleteInteraction extends Interaction { - /** The guild id if this interaction occurred in a guild. */ - guildID?: string - /** The permissions the app or bot has within the channel, the interaction was sent from. */ - appPermissions?: Permission - /** The channel id where this interaction was created in. */ - channelID: string - /** The user who triggered the interaction. */ - user: User - /** The data attached to this interaction. */ - data?: DiscordInteractionData - /** The member who triggered the interaction. Sent when used in a guild. */ - member?: Member - - constructor(data: DiscordInteraction, client: Client) { - super(data, client) - - this.channelID = data.channel_id! - this.data = data.data - - if (data.guild_id !== undefined) { - this.guildID = data.guild_id - } - - if (data.member !== undefined && this.guild) { - this.member = new Member(data.member, this.guild, this.client) - this.guild.members.set(this.member.id, this.member) - } - - this.user = new User(data.user ?? data.member!.user, this.client) - this.client.users.set(this.user.id, this.user) - - if (data.app_permissions !== undefined) { - this.appPermissions = new Permission(data.app_permissions) - } - } - - /** The channel the interaction was created in. */ - get channel(): PrivateChannel | TextChannel | NewsChannel { - return this.client.getChannel(this.channelID) as PrivateChannel | TextChannel | NewsChannel - } - - /** The guild the interaction was created in. */ - get guild(): Guild | undefined { - return this.guildID ? this.client.guilds.get(this.guildID) : undefined - } - - async acknowledge(choices: ApplicationCommandOptionChoice[]) { - return await this.result(choices) - } - - async result(choices: ApplicationCommandOptionChoice[]) { - if (this.acknowledged) throw new Error('You have already acknowledged this interaction.') - - return this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.ApplicationCommandAutocompleteResult, - data: { choices }, - }) - .then(() => this.update()) - } -} - -export default AutocompleteInteraction diff --git a/packages/client/src/Structures/interactions/Command.ts b/packages/client/src/Structures/interactions/Command.ts deleted file mode 100644 index bac8120c2..000000000 --- a/packages/client/src/Structures/interactions/Command.ts +++ /dev/null @@ -1,321 +0,0 @@ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/return-await */ -import { ApplicationCommandTypes, InteractionResponseTypes } from '@discordeno/types' - -import Collection from '../../Collection.js' -import Channel from '../channels/Channel.js' -import Member from '../guilds/Member.js' -import Role from '../guilds/Role.js' -import Message from '../Message.js' -import User from '../users/User.js' -import Interaction from './Interaction.js' - -import type { - BigString, - DiscordAttachment, - DiscordInteraction, - DiscordInteractionDataOption, - DiscordMessageComponents, - MessageComponentTypes, -} from '@discordeno/types' -import type Client from '../../Client.js' -import type { AnyChannel, FileContent, InteractionContent, InteractionContentEdit } from '../../typings.js' -export class CommandInteraction extends Interaction { - name: string = ""; - channel: AnyChannel - /** The type of component */ - componentType?: MessageComponentTypes - /** The custom id provided for this component. */ - customId?: string - /** The components if its a Modal Submit interaction. */ - components?: DiscordMessageComponents - /** The values chosen by the user. */ - values?: string[] - /** the type of the invoked command */ - commandType: ApplicationCommandTypes = ApplicationCommandTypes.ChatInput - /** The Ids and Message objects */ - messages = new Collection() - /** The Ids and User objects */ - users = new Collection() - /** The Ids and partial Member objects */ - members = new Collection() - /** The Ids and Role objects */ - roles = new Collection() - /** The Ids and partial Channel objects */ - channels = new Collection() - /** The ids and attachment objects */ - attachments = new Collection() - /** The params + values from the user */ - options?: DiscordInteractionDataOption[] - /** The target id if this is a context menu command. */ - targetID?: string - /** the id of the guild the command is registered to */ - guildID?: string - - member?: Member - user: User - - constructor(info: DiscordInteraction, client: Client) { - super(info, client) - - this.channel = this.client.getChannel(info.channel_id!) ?? { - id: info.channel_id!, - } - - // this.data = info.data!; - const guild = this.client.guilds.get(info.guild_id!) - - if (info.data?.resolved !== undefined) { - // Users - if (info.data.resolved.users !== undefined) { - for (const u of Object.values(info.data.resolved.users)) { - const user = new User(u, this.client) - this.users.set(user.id, user) - } - } - - // Members - if (info.data.resolved.members !== undefined) { - for (const [, m] of Object.entries(info.data.resolved.members)) { - // @ts-expect-error some eris magic at play here - m.id = m - // @ts-expect-error some eris magic at play here - const member = new Member(m, guild, this.client) - this.members.set(member.id, member) - } - } - - // Roles - if (info.data.resolved.roles !== undefined) { - for (const r of Object.values(info.data.resolved.roles)) { - // @ts-expect-error some eris magic at play here - const role = new Role(r, guild) - this.roles.set(role.id, role) - } - } - - // Channels - if (info.data.resolved.channels !== undefined) { - for (const c of Object.values(info.data.resolved.channels)) { - const channel = new Channel(c, this.client) - this.channels.set(channel.id, channel) - } - } - - // Messages - if (info.data.resolved.messages !== undefined) { - for (const m of Object.values(info.data.resolved.messages)) { - const message = new Message(m, this.client) - this.messages.set(message.id, message) - } - } - - // Attachments - if (info.data.resolved.attachments !== undefined) { - for (const attachment of Object.values(info.data.resolved.attachments)) { - this.attachments.set(attachment.id, attachment) - } - } - } - - this.guildID = info.guild_id - - if (info.member !== undefined) { - // @ts-expect-error some eris magic at play here - this.member = new Member(info.member, guild, this.client) - guild?.members.set(this.member.id, this.member) - } - - this.user = new User(info.user ?? info.member!.user, this.client) - this.client.users.set(this.user.id, this.user) - - if (info.data) { - this.name = info.data.name; - this.componentType = info.data.component_type - this.customId = info.data.custom_id - this.components = info.data.components - this.values = info.data.values - this.commandType = info.data.type - this.options = info.data.options - this.targetID = info.data.target_id - } - } - - get data() { - return { - name: this.name, - component_type: this.componentType, - custom_id: this.customId, - components: this.components, - values: this.values, - type: this.commandType, - resolved: { - messages: this.messages.toRecord(), - users: this.users.toRecord(), - members: this.members.toRecord(), - roles: this.roles.toRecord(), - channels: this.channels.toRecord(), - attachments: this.attachments.toRecord(), - }, - options: this.options, - target_id: this.targetID, - guild_id: this.guildID, - } - } - - /** - * Acknowledges the interaction with a defer response - * Note: You can **not** use more than 1 initial interaction response per interaction. - */ - async acknowledge(flags?: number): Promise { - return this.defer(flags) - } - - /** Respond to the interaction with a followup message */ - async createFollowup(content: string | InteractionContent, file?: FileContent | FileContent[]): Promise { - if (!this.acknowledged) { - throw new Error('createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.') - } - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, { - ...content, - wait: true, - file, - }) - } - - /** - * Acknowledges the interaction with a message. If already acknowledged runs createFollowup - * Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response. - */ - async createMessage(content: string | InteractionContent, file?: FileContent | FileContent[]): Promise { - if (this.acknowledged) { - // @ts-expect-error some eris magic at play here - return await this.createFollowup(content, file) - } - - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - - return this.client.createInteractionResponse - .call( - this.client, - this.id, - this.token, - { - type: InteractionResponseTypes.ChannelMessageWithSource, - data: content, - }, - file, - ) - .then(() => this.update()) - } - - /** - * Acknowledges the interaction with a defer response - * Note: You can **not** use more than 1 initial interaction response per interaction. - */ - async defer(flags?: number): Promise { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - return this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.DeferredChannelMessageWithSource, - data: { - flags: flags ?? 0, - }, - }) - .then(() => this.update()) - } - - /** Delete a message */ - async deleteMessage(messageID: BigString): Promise { - if (!this.acknowledged) { - throw new Error('deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.') - } - return this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID) - } - - /** - * Delete the Original message - * Warning: Will error with ephemeral messages. - */ - async deleteOriginalMessage(): Promise { - if (!this.acknowledged) { - throw new Error('deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.') - } - return this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original') - } - - /** Edit a message */ - async editMessage(messageID: BigString, content: string | InteractionContentEdit, file?: FileContent | FileContent[]): Promise { - if (!this.acknowledged) { - throw new Error('editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.') - } - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, { - ...content, - file, - }) - } - - /** Edit the Original response message */ - async editOriginalMessage(content: string | InteractionContentEdit, file?: FileContent | FileContent[]): Promise { - if (!this.acknowledged) { - throw new Error('editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.') - } - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - return this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', { - ...content, - file, - }) - } - - /** - * Get the Original response message - * Warning: Will error with ephemeral messages. - */ - async getOriginalMessage(): Promise { - if (!this.acknowledged) { - throw new Error('getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.') - } - return this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original') - } -} - -export default CommandInteraction diff --git a/packages/client/src/Structures/interactions/Component.ts b/packages/client/src/Structures/interactions/Component.ts deleted file mode 100644 index ece0de745..000000000 --- a/packages/client/src/Structures/interactions/Component.ts +++ /dev/null @@ -1,296 +0,0 @@ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/return-await */ - -import type { BigString, DiscordInteraction, MessageComponentTypes } from '@discordeno/types' -import { InteractionResponseTypes } from '@discordeno/types' -import type Client from '../../Client.js' -import type { AnyChannel, FileContent, InteractionApplicationCommandCallbackData, MessageWebhookContent, WebhookPayload } from '../../typings.js' -import type Guild from '../guilds/Guild.js' -import Member from '../guilds/Member.js' -import Message from '../Message.js' -import Permission from '../Permission.js' -import User from '../users/User.js' -import Interaction from './Interaction.js' - -export class ComponentInteraction extends Interaction { - /** The channel id where this interaction occurred in. */ - channelID: string - /** The guild id where this interaction occurred in. */ - guildID?: string - /** The member object if this interaction occurred in a guild. */ - member?: Member - /** The user object for the user that created this interaction. */ - user: User - /** The permissions the app or bot has within the channel, the interaction was sent from. */ - appPermissions?: Permission - /** The message object, if this interaction occurred on a message. */ - message?: Message - /** The custom id of the component. */ - customID: string - /** The type of component. */ - componentType: MessageComponentTypes - /** The values from a selector component. */ - values?: string[] - - constructor(data: DiscordInteraction, client: Client) { - super(data, client) - - this.channelID = data.channel_id! - // this.data = data.data; - this.guildID = data.guild_id - // Required to make a component - this.customID = data.data!.custom_id! - this.componentType = data.data!.component_type! - this.values = data.data!.values - - if (data.member !== undefined && this.guild) { - this.member = new Member(data.member, this.guild, this.client) - this.guild.members.set(this.member.id, this.member) - } - - if (data.message !== undefined) { - this.message = new Message(data.message, this.client) - } - - this.user = new User(data.user ?? data.member!.user, this.client) - this.client.users.set(this.user.id, this.user) - - if (data.app_permissions !== undefined) { - this.appPermissions = new Permission(data.app_permissions) - } - } - - /** The channel if cached, where this interaction occurred. */ - get channel(): AnyChannel | undefined { - return this.channelID ? this.client.getChannel(this.channelID) : undefined - } - - /** The guild if cached, where this interaction occurred. */ - get guild(): Guild | undefined { - return this.guildID ? this.client.guilds.get(this.guildID) : undefined - } - - /** Acknowledges the interaction with a defer message update response */ - async acknowledge(): Promise { - return await this.deferUpdate() - } - - /** Respond to the interaction with a followup message. */ - async createFollowup(content: WebhookPayload, file?: FileContent | FileContent[]): Promise { - if (!this.acknowledged) { - throw new Error( - 'createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.', - ) - } - - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - if (file) { - content.file = file - } - - return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, { ...content, wait: true }) - } - - /** - * Acknowledges the interaction with a message. If already acknowledged runs createFollowup - * Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response. - */ - async createMessage(content: InteractionApplicationCommandCallbackData, file?: FileContent | FileContent[]): Promise { - if (this.acknowledged) { - return await this.createFollowup(content, file) - } - - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - await this.client.createInteractionResponse - .call( - this.client, - this.id, - this.token, - { - type: InteractionResponseTypes.ChannelMessageWithSource, - data: content, - }, - file, - ) - .then(() => this.update()) - } - - /** - * Acknowledges the interaction with a defer response - * Note: You can **not** use more than 1 initial interaction response per interaction. - */ - async defer(flags: number): Promise { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - - return await this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.DeferredChannelMessageWithSource, - data: { - flags: flags || 0, - }, - }) - .then(() => this.update()) - } - - /** - * Acknowledges the interaction with a defer message update response - * Note: You can **not** use more than 1 initial interaction response per interaction. - */ - async deferUpdate(): Promise { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - - return await this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.DeferredUpdateMessage, - }) - .then(() => this.update()) - } - - /** Delete a message */ - async deleteMessage(messageID: BigString): Promise { - if (!this.acknowledged) { - throw new Error( - 'deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.', - ) - } - - return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID) - } - - /** - * Delete the parent message - * Warning: Will error with ephemeral messages. - */ - async deleteOriginalMessage(): Promise { - if (!this.acknowledged) { - throw new Error( - 'deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.', - ) - } - - return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original') - } - - /** Edit a message */ - async editMessage(messageID: BigString, content: MessageWebhookContent, file?: FileContent | FileContent[]): Promise { - if (!this.acknowledged) { - throw new Error( - 'editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.', - ) - } - - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - if (file) { - content.file = file - } - - return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, content) - } - - /** Edit the parent message */ - async editOriginalMessage(content: MessageWebhookContent, file?: FileContent | FileContent[]): Promise { - if (!this.acknowledged) { - throw new Error( - 'editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.', - ) - } - - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - if (file) { - content.file = file - } - - return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', content) - } - - /** - * Acknowledges the interaction by editing the parent message. If already acknowledged runs editOriginalMessage - * Note: You can **not** use more than 1 initial interaction response per interaction, use edit if you have already responded with a different interaction response. - * Warning: Will error with ephemeral messages. - */ - async editParent(content: InteractionApplicationCommandCallbackData, file?: FileContent | FileContent[]): Promise { - if (this.acknowledged) { - return this.editOriginalMessage(content) - } - - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - await this.client.createInteractionResponse - .call( - this.client, - this.id, - this.token, - { - type: InteractionResponseTypes.UpdateMessage, - data: content, - }, - file, - ) - .then(() => this.update()) - } - - /** - * Get the parent message - * Warning: Will error with ephemeral messages. - */ - async getOriginalMessage(): Promise { - if (!this.acknowledged) { - throw new Error( - 'getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.', - ) - } - - return this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original') - } -} - -export default ComponentInteraction diff --git a/packages/client/src/Structures/interactions/Interaction.ts b/packages/client/src/Structures/interactions/Interaction.ts deleted file mode 100644 index 720e5583f..000000000 --- a/packages/client/src/Structures/interactions/Interaction.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import type { DiscordInteraction, InteractionTypes } from '@discordeno/types' -import Base from '../../Base.js' -import type Client from '../../Client.js' - -export class Interaction extends Base { - client: Client - applicationID: string - token: string - type: InteractionTypes - version: 1 - acknowledged: boolean - - constructor(data: DiscordInteraction, client: Client) { - super(data.id) - this.client = client - - this.applicationID = data.application_id - this.token = data.token - this.type = data.type - this.version = data.version - this.acknowledged = false - } - - /** - * @deprecated Use `.client` - */ - get _client(): Client { - return this.client - } - - update() { - this.acknowledged = true - } - - /** - * @deprecated Use generateInteractionFrom(data, client) instead. - */ - static from(data: DiscordInteraction, client: Client) { - // Remove js hack of circular deps - console.error('Usage of Interaction.from() is deprecated. Use generateInteractionFrom(data, client) instead.') - client.emit('warn', new Error(`Usage of Interaction.from() is deprecated. Use generateInteractionFrom(data, client) instead.`)) - } -} - -export default Interaction diff --git a/packages/client/src/Structures/interactions/Ping.ts b/packages/client/src/Structures/interactions/Ping.ts deleted file mode 100644 index 2dd1a6b90..000000000 --- a/packages/client/src/Structures/interactions/Ping.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint-disable no-useless-call */ -/* eslint-disable @typescript-eslint/return-await */ - -import { InteractionResponseTypes } from '@discordeno/types' -import Interaction from './Interaction.js' - -export class PingInteraction extends Interaction { - /** - * Acknowledges the ping interaction with a pong response. - * Note: You can **not** use more than 1 initial interaction response per interaction. - */ - async acknowledge(): Promise { - return this.pong() - } - - /** - * Acknowledges the ping interaction with a pong response. - * Note: You can **not** use more than 1 initial interaction response per interaction. - */ - async pong(): Promise { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - - return await this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.Pong, - }) - .then(() => this.update()) - } -} - -export default PingInteraction diff --git a/packages/client/src/Structures/interactions/Unknown.ts b/packages/client/src/Structures/interactions/Unknown.ts deleted file mode 100644 index 04a1c7e39..000000000 --- a/packages/client/src/Structures/interactions/Unknown.ts +++ /dev/null @@ -1,493 +0,0 @@ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable no-useless-call */ - -import type { DiscordInteraction } from '@discordeno/types' -import { InteractionResponseTypes } from '@discordeno/types' -import type Client from '../../Client.js' -import type { - ApplicationCommandOptionChoice, - FileContent, - InteractionContent, - InteractionContentEdit, - InteractionResponse, - PossiblyUncachedTextable, - TextableChannel, -} from '../../typings.js' -import Member from '../guilds/Member.js' -import Message from '../Message.js' -import Permission from '../Permission.js' -import User from '../users/User.js' -import Interaction from './Interaction.js' - -export class UnknownInteraction extends Interaction { - channel?: T - data?: unknown - guildID?: string - member?: Member - message?: Message - type: number = 0 - user?: User - appPermissions?: Permission - - constructor(data: DiscordInteraction, client: Client) { - super(data, client) - - if (data.channel_id !== undefined) { - this.channel = (this.client.getChannel(data.channel_id) as unknown as T) || { - id: data.channel_id, - } - } - - if (data.data !== undefined) { - this.data = data.data - } - - if (data.guild_id !== undefined) { - this.guildID = data.guild_id - } - - if (data.member !== undefined) { - const guild = this.client.guilds.get(data.guild_id ?? '')! - this.member = new Member(data.member, guild, this.client) - guild.members.set(this.member.id, this.member) - } - - if (data.message !== undefined) { - this.message = new Message(data.message, this.client) - } - - if (data.user !== undefined) { - this.user = new User(data.user, this.client) - this.client.users.set(this.user.id, this.user) - } - - if (data.app_permissions !== undefined) { - this.appPermissions = new Permission(data.app_permissions) - } - } - - /** - * Acknowledges the autocomplete interaction with a result of choices. - * Note: You can **not** use more than 1 initial interaction response per interaction. - * @arg {Object} data The data object - * @arg {Number} data.type The type of [interaction response](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type) to send - * @arg {Object} data.data The data to return to discord - * @returns {Promise} - */ - async acknowledge(data: InteractionResponse) { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - return await this.client.createInteractionResponse.call(this.client, this.id, this.token, data).then(() => this.update()) - } - - /** - * Respond to the interaction with a followup message - * @arg {String | Object} content A string or object. If an object is passed: - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} [content.content] A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Array} [options.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Number} [content.flags] 64 for Ephemeral - * @arg {Boolean} [content.tts] Set the message TTS flag - * @arg {Object | Array} [file] A file object (or an Array of them) - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - async createFollowup(content: string | InteractionContent, file?: FileContent | FileContent[]) { - if (!this.acknowledged) { - throw new Error( - 'createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.', - ) - } - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - if (file) { - content.file = file - } - return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, Object.assign({ wait: true as true }, content)) - } - - /** - * Acknowledges the interaction with a message. If already acknowledged runs createFollowup - * Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response. - * @arg {String | Object} content A string or object. If an object is passed: - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} [content.content] A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Array} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Boolean} [content.flags] 64 for Ephemeral - * @arg {Boolean} [content.tts] Set the message TTS flag - * @arg {Object | Array} [file] A file object (or an Array of them) - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - async createMessage(content: string | InteractionContent, file?: FileContent | FileContent[]) { - if (this.acknowledged) { - return await this.createFollowup(content, file) - } - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - return await this.client.createInteractionResponse - .call( - this.client, - this.id, - this.token, - { - type: InteractionResponseTypes.ChannelMessageWithSource, - data: content, - }, - file, - ) - .then(() => this.update()) - } - - /** - * Acknowledges the interaction with a defer response - * Note: You can **not** use more than 1 initial interaction response per interaction. - * @arg {Number} [flags] 64 for Ephemeral - * @returns {Promise} - */ - async defer(flags: number) { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - return await this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.DeferredChannelMessageWithSource, - data: { - flags: flags || 0, - }, - }) - .then(() => this.update()) - } - - /** - * Acknowledges the interaction with a defer message update response (Message Component only) - * Note: You can **not** use more than 1 initial interaction response per interaction. - * @returns {Promise} - */ - async deferUpdate() { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - return await this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.DeferredUpdateMessage, - }) - .then(() => this.update()) - } - - /** - * Delete a message - * @arg {String} messageID the id of the message to delete, or "@original" for the original response. - * @returns {Promise} - */ - async deleteMessage(messageID: string) { - if (!this.acknowledged) { - throw new Error( - 'deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.', - ) - } - return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID) - } - - /** - * Delete the Original message (or the parent message for components) - * Warning: Will error with ephemeral messages. - * @returns {Promise} - */ - async deleteOriginalMessage() { - if (!this.acknowledged) { - throw new Error( - 'deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.', - ) - } - return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original') - } - - /** - * Edit a message - * @arg {String} messageID the id of the message to edit, or "@original" for the original response. - * @arg {Object} content Interaction message edit options - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} [content.content] A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Array} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Object | Array} [file] A file object (or an Array of them) - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - async editMessage(messageID: string, content: string | InteractionContentEdit, file?: FileContent | FileContent[]) { - if (!this.acknowledged) { - throw new Error( - 'editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.', - ) - } - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - if (file) { - content.file = file - } - return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, content) - } - - /** - * Edit the Original response message - * @arg {Object} content Interaction message edit options (or the parent message for components) - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} [content.content] A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Array} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Object | Array} [file] A file object (or an Array of them) - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - async editOriginalMessage(content: string | InteractionContentEdit, file?: FileContent | FileContent[]) { - if (!this.acknowledged) { - throw new Error( - 'editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.', - ) - } - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - if (file) { - content.file = file - } - - return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', content) - } - - /** - * Acknowledges the interaction by editing the parent message. If already acknowledged runs editOriginalMessage (Message Component only) - * Note: You can **not** use more than 1 initial interaction response per interaction, use edit if you have already responded with a different interaction response. - * Warning: Will error with ephemeral messages. - * @arg {String | Object} content What to edit the message with - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Array} [content.components] An array of component objects - * @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) - * @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only) - * @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2) - * @arg {String} [content.components[].label] The label to be displayed in the component (type 2) - * @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) - * @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) - * @arg {Array} [content.components[].options] The options for this component (type 3 only) - * @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected - * @arg {String} [content.components[].options[].description] The description for this option - * @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option - * @arg {String} content.components[].options[].label The label for this option - * @arg {Number | String} content.components[].options[].value The value for this option - * @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) - * @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required - * @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu - * @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only) - * @arg {String} [content.content] A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Array} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Boolean} [content.flags] 64 for Ephemeral - * @arg {Boolean} [content.tts] Set the message TTS flag - * @arg {Object | Array} [file] A file object (or an Array of them) - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - async editParent(content: InteractionContentEdit, file?: FileContent | FileContent[]) { - if (this.acknowledged) { - return await this.editOriginalMessage(content) - } - if (content !== undefined) { - if (typeof content !== 'object' || content === null) { - content = { - content: '' + content, - } - } else if (content.content !== undefined && typeof content.content !== 'string') { - content.content = '' + content.content - } - } - - return await this.client.createInteractionResponse - .call( - this.client, - this.id, - this.token, - { - type: InteractionResponseTypes.UpdateMessage, - data: content, - }, - file, - ) - .then(() => this.update()) - } - - /** - * Get the Original response message (or the parent message for components) - * Warning: Will error with ephemeral messages. - * @returns {Promise} - */ - async getOriginalMessage() { - if (!this.acknowledged) { - throw new Error( - 'getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.', - ) - } - return await this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original') - } - - /** - * Acknowledges the ping interaction with a pong response (Ping Only) - * Note: You can **not** use more than 1 initial interaction response per interaction. - * @returns {Promise} - */ - async pong() { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - return await this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.Pong, - }) - .then(() => this.update()) - } - - /** - * Acknowledges the autocomplete interaction with a result of choices. - * Note: You can **not** use more than 1 initial interaction response per interaction. - * @arg {Array} choices The autocomplete choices to return to the user - * @arg {String | Number} choices[].name The choice display name - * @arg {String} choices[].value The choice value to return to the bot - * @returns {Promise} - */ - async result(choices: ApplicationCommandOptionChoice[]) { - if (this.acknowledged) { - throw new Error('You have already acknowledged this interaction.') - } - return await this.client.createInteractionResponse - .call(this.client, this.id, this.token, { - type: InteractionResponseTypes.ApplicationCommandAutocompleteResult, - data: { choices }, - }) - .then(() => this.update()) - } -} - -export default UnknownInteraction diff --git a/packages/client/src/Structures/toggles/Guild.ts b/packages/client/src/Structures/toggles/Guild.ts deleted file mode 100644 index d18b8d5be..000000000 --- a/packages/client/src/Structures/toggles/Guild.ts +++ /dev/null @@ -1,304 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { GuildFeatures, type DiscordGuild } from '@discordeno/types' -import { ToggleBitfieldBigint } from './Toggle.js' - -const featureNames = [ - 'inviteSplash', - 'vipRegions', - 'vanityUrl', - 'verified', - 'partnered', - 'community', - 'developerSupportServer', - 'news', - 'discoverable', - 'featurable', - 'animatedIcon', - 'banner', - 'welcomeScreenEnabled', - 'memberVerificationGateEnabled', - 'previewEnabled', - 'ticketedEventsEnabled', - 'monetizationEnabled', - 'moreStickers', - 'privateThreads', - 'roleIcons', - 'autoModeration', - 'invitesDisabled', - 'animatedBanner', -] - -export const GuildToggle = { - /** Whether the bot is the owner of the guild */ - owner: 1n << 0n, - /** Whether the server widget is enabled */ - widgetEnabled: 1n << 1n, - /** Whether this is considered a large guild */ - large: 1n << 2n, - /** Whether this guild is unavailable due to an outage */ - unavailable: 1n << 3n, - /** Whether the guild has the boost progress bar enabled */ - premiumProgressBarEnabled: 1n << 4n, - - // GUILD FEATURES ARE BELOW THIS - - /** Whether the guild has access to set an invite splash background */ - inviteSplash: 1n << 5n, - /** Whether the guild has access to set 384 kbps bitrate in voice (previously VIP voice servers) */ - vipRegions: 1n << 6n, - /** Whether the guild has access to set a vanity URL */ - vanityUrl: 1n << 7n, - /** Whether the guild is verified */ - verified: 1n << 8n, - /** Whether the guild is partnered */ - partnered: 1n << 9n, - /** Whether the guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates */ - community: 1n << 10n, - /** Whether the guild has access to set an animated guild banner image */ - animatedBanner: 1n << 11n, - /** Whether the guild has access to create news channels */ - news: 1n << 12n, - /** Whether the guild is able to be discovered in the directory */ - discoverable: 1n << 13n, - /** Whether the guild is able to be featured in the directory */ - featurable: 1n << 15n, - /** Whether the guild has access to set an animated guild icon */ - animatedIcon: 1n << 16n, - /** Whether the guild has access to set a guild banner image */ - banner: 1n << 17n, - /** Whether the guild has enabled the welcome screen */ - welcomeScreenEnabled: 1n << 18n, - /** Whether the guild has enabled [Membership Screening](https://discord.com/developers/docs/resources/guild#membership-screening-object) */ - memberVerificationGateEnabled: 1n << 19n, - /** Whether the guild can be previewed before joining via Membership Screening or the directory */ - previewEnabled: 1n << 20n, - /** Whether the guild has enabled ticketed events */ - ticketedEventsEnabled: 1n << 21n, - /** Whether the guild has enabled monetization */ - monetizationEnabled: 1n << 22n, - /** Whether the guild has increased custom sticker slots */ - moreStickers: 1n << 23n, - /** Whether the guild has access to create private threads */ - privateThreads: 1n << 26n, - /** Whether the guild is able to set role icons */ - roleIcons: 1n << 27n, - /** Whether the guild has set up auto moderation rules */ - autoModeration: 1n << 28n, - /** Whether the guild has paused invites, preventing new users from joining */ - invitesDisabled: 1n << 29n, - /** Whether the guild has been set as a support server on the App Directory */ - developerSupportServer: 1n << 30n, -} - -export class GuildToggles extends ToggleBitfieldBigint { - constructor(guildOrTogglesBigint: DiscordGuild | bigint) { - super() - - if (typeof guildOrTogglesBigint === 'bigint') this.bitfield = guildOrTogglesBigint - else { - const guild = guildOrTogglesBigint - - if (guild.owner) this.add(GuildToggle.owner) - if (guild.widget_enabled) this.add(GuildToggle.widgetEnabled) - if (guild.large) this.add(GuildToggle.large) - if (guild.unavailable) this.add(GuildToggle.unavailable) - if (guild.premium_progress_bar_enabled) this.add(GuildToggle.premiumProgressBarEnabled) - - if (guild.features.includes(GuildFeatures.InviteSplash)) this.add(GuildToggle.inviteSplash) - if (guild.features.includes(GuildFeatures.VipRegions)) this.add(GuildToggle.vipRegions) - if (guild.features.includes(GuildFeatures.VanityUrl)) this.add(GuildToggle.vanityUrl) - if (guild.features.includes(GuildFeatures.Verified)) this.add(GuildToggle.verified) - if (guild.features.includes(GuildFeatures.Partnered)) this.add(GuildToggle.partnered) - if (guild.features.includes(GuildFeatures.Community)) this.add(GuildToggle.community) - if (guild.features.includes(GuildFeatures.DeveloperSupportServer)) this.add(GuildToggle.developerSupportServer) - if (guild.features.includes(GuildFeatures.AnimatedBanner)) this.add(GuildToggle.animatedBanner) - if (guild.features.includes(GuildFeatures.News)) this.add(GuildToggle.news) - if (guild.features.includes(GuildFeatures.Discoverable)) this.add(GuildToggle.discoverable) - if (guild.features.includes(GuildFeatures.Featurable)) this.add(GuildToggle.featurable) - if (guild.features.includes(GuildFeatures.AnimatedIcon)) this.add(GuildToggle.animatedIcon) - if (guild.features.includes(GuildFeatures.Banner)) this.add(GuildToggle.banner) - if (guild.features.includes(GuildFeatures.WelcomeScreenEnabled)) this.add(GuildToggle.welcomeScreenEnabled) - if (guild.features.includes(GuildFeatures.MemberVerificationGateEnabled)) { - this.add(GuildToggle.memberVerificationGateEnabled) - } - if (guild.features.includes(GuildFeatures.PreviewEnabled)) this.add(GuildToggle.previewEnabled) - if (guild.features.includes(GuildFeatures.TicketedEventsEnabled)) this.add(GuildToggle.ticketedEventsEnabled) - if (guild.features.includes(GuildFeatures.MoreStickers)) this.add(GuildToggle.moreStickers) - if (guild.features.includes(GuildFeatures.PrivateThreads)) this.add(GuildToggle.privateThreads) - if (guild.features.includes(GuildFeatures.RoleIcons)) this.add(GuildToggle.roleIcons) - if (guild.features.includes(GuildFeatures.AutoModeration)) this.add(GuildToggle.autoModeration) - if (guild.features.includes(GuildFeatures.InvitesDisabled)) this.add(GuildToggle.invitesDisabled) - } - } - - get features() { - const features: GuildToggleKeys[] = [] - for (const key of Object.keys(GuildToggle)) { - if (!featureNames.includes(key)) continue - if (!super.contains(GuildToggle[key as GuildToggleKeys])) continue - - features.push(key as GuildToggleKeys) - } - - return features - } - - /** Whether the bot is the owner of the guild */ - get owner() { - return this.has('owner') - } - - /** Whether the server widget is enabled */ - get widgetEnabled() { - return this.has('widgetEnabled') - } - - /** Whether this is considered a large guild */ - get large() { - return this.has('large') - } - - /** Whether this guild is unavailable due to an outage */ - get unavailable() { - return this.has('unavailable') - } - - /** Whether the guild has the boost progress bar enabled */ - get premiumProgressBarEnabled() { - return this.has('premiumProgressBarEnabled') - } - - /** Whether the guild has access to set an invite splash background */ - get inviteSplash() { - return this.has('inviteSplash') - } - - /** Whether the guild has access to set 384 kbps bitrate in voice (previously VIP voice servers) */ - get vipRegions() { - return this.has('vipRegions') - } - - /** Whether the guild has access to set a vanity URL */ - get vanityUrl() { - return this.has('vanityUrl') - } - - /** Whether the guild is verified */ - get verified() { - return this.has('verified') - } - - /** Whether the guild is partnered */ - get partnered() { - return this.has('partnered') - } - - /** Whether the guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates */ - get community() { - return this.has('community') - } - - /** Whether the Guild has been set as a support server on the App Directory */ - get developerSupportServer() { - return this.has('developerSupportServer') - } - - /** Whether the guild has access to set an animated guild banner image */ - get animatedBanner() { - return this.has('animatedBanner') - } - - /** Whether the guild has access to create news channels */ - get news() { - return this.has('news') - } - - /** Whether the guild is able to be discovered in the directory */ - get discoverable() { - return this.has('discoverable') - } - - /** Whether the guild is able to be featured in the directory */ - get featurable() { - return this.has('featurable') - } - - /** Whether the guild has access to set an animated guild icon */ - get animatedIcon() { - return this.has('animatedIcon') - } - - /** Whether the guild has access to set a guild banner image */ - get banner() { - return this.has('banner') - } - - /** Whether the guild has enabled the welcome screen */ - get welcomeScreenEnabled() { - return this.has('welcomeScreenEnabled') - } - - /** Whether the guild has enabled [Membership Screening](https://discord.com/developers/docs/resources/guild#membership-screening-object) */ - get memberVerificationGateEnabled() { - return this.has('memberVerificationGateEnabled') - } - - /** Whether the guild can be previewed before joining via Membership Screening or the directory */ - get previewEnabled() { - return this.has('previewEnabled') - } - - /** Whether the guild has enabled ticketed events */ - get ticketedEventsEnabled() { - return this.has('ticketedEventsEnabled') - } - - /** Whether the guild has enabled monetization */ - get monetizationEnabled() { - return this.has('monetizationEnabled') - } - - /** Whether the guild has increased custom sticker slots */ - get moreStickers() { - return this.has('moreStickers') - } - - /** Whether the guild has access to create private threads */ - get privateThreads() { - return this.has('privateThreads') - } - - /** Whether the guild is able to set role icons */ - get roleIcons() { - return this.has('roleIcons') - } - - /** Whether the guild has set up auto moderation rules */ - get autoModeration() { - return this.has('autoModeration') - } - - /** Whether the guild has paused invites, preventing new users from joining */ - get invitesDisabled() { - return this.has('invitesDisabled') - } - - /** Checks whether or not the permissions exist in this */ - has(permissions: GuildToggleKeys | GuildToggleKeys[]) { - if (!Array.isArray(permissions)) return super.contains(GuildToggle[permissions]) - - return super.contains(permissions.reduce((a, b) => (a |= GuildToggle[b]), 0n)) - } - - /** Lists all the toggles for the role and whether or not each is true or false. */ - list() { - const json: Record = {} - for (const [key, value] of Object.entries(GuildToggle)) { - json[key] = super.contains(value) - } - - return json as Record - } -} - -export type GuildToggleKeys = keyof typeof GuildToggle diff --git a/packages/client/src/Structures/toggles/Toggle.ts b/packages/client/src/Structures/toggles/Toggle.ts deleted file mode 100644 index e88dda243..000000000 --- a/packages/client/src/Structures/toggles/Toggle.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -export class ToggleBitfield { - bitfield = 0 - - constructor(bitfield?: number) { - if (bitfield) this.bitfield = bitfield - } - - /** Tests whether or not this bitfield has the permission requested. */ - contains(bits: number) { - return Boolean(this.bitfield & bits) - } - - /** Adds some bits to the bitfield. */ - add(bits: number) { - this.bitfield |= bits - return this - } - - /** Removes some bits from the bitfield. */ - remove(bits: number) { - this.bitfield &= ~bits - return this - } -} - -export class ToggleBitfieldBigint { - bitfield = 0n - - constructor(bitfield?: bigint) { - if (bitfield) this.bitfield = bitfield - } - - /** Tests whether or not this bitfield has the permission requested. */ - contains(bits: bigint) { - return Boolean(this.bitfield & bits) - } - - /** Adds some bits to the bitfield. */ - add(bits: bigint) { - this.bitfield |= bits - return this - } - - /** Removes some bits from the bitfield. */ - remove(bits: bigint) { - this.bitfield &= ~bits - return this - } -} diff --git a/packages/client/src/Structures/toggles/Voice.ts b/packages/client/src/Structures/toggles/Voice.ts deleted file mode 100644 index 2e8b5d03a..000000000 --- a/packages/client/src/Structures/toggles/Voice.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import type { DiscordVoiceState } from '@discordeno/types' -import { ToggleBitfield } from './Toggle.js' - -export const VoiceStateToggle = { - /** Whether this user is deafened by the server */ - deaf: 1 << 0, - /** Whether this user is muted by the server */ - mute: 1 << 1, - /** Whether this user is locally deafened */ - selfDeaf: 1 << 2, - /** Whether this user is locally muted */ - selfMute: 1 << 3, - /** Whether this user is streaming using "Go Live" */ - selfStream: 1 << 4, - /** Whether this user's camera is enabled */ - selfVideo: 1 << 5, - /** Whether this user is muted by the current user */ - suppress: 1 << 6, -} - -export class VoiceStateToggles extends ToggleBitfield { - constructor(voiceOrTogglesInt: DiscordVoiceState | number) { - super() - - if (typeof voiceOrTogglesInt === 'number') this.bitfield = voiceOrTogglesInt - else { - const voice = voiceOrTogglesInt - - if (voice.deaf) this.add(VoiceStateToggle.deaf) - if (voice.mute) this.add(VoiceStateToggle.mute) - if (voice.self_deaf) this.add(VoiceStateToggle.selfDeaf) - if (voice.self_mute) this.add(VoiceStateToggle.selfMute) - if (voice.self_stream) this.add(VoiceStateToggle.selfStream) - if (voice.self_video) this.add(VoiceStateToggle.selfVideo) - if (voice.suppress) this.add(VoiceStateToggle.suppress) - } - } - - /** Whether this user is deafened by the server */ - get deaf() { - return this.has('deaf') - } - - /** Whether this user is muted by the server */ - get mute() { - return this.has('mute') - } - - /** Whether this user is locally deafened */ - get selfDeaf() { - return this.has('selfDeaf') - } - - /** Whether this user is locally muted */ - get selfMute() { - return this.has('selfMute') - } - - /** Whether this user is streaming using "Go Live" */ - get selfStream() { - return this.has('selfStream') - } - - /** Whether this user's camera is enabled */ - get selfVideo() { - return this.has('selfVideo') - } - - /** Whether this user is muted by the current user */ - get suppress() { - return this.has('suppress') - } - - /** Checks whether or not the permissions exist in this */ - has(permissions: VoiceStateToggleKeys | VoiceStateToggleKeys[]) { - if (!Array.isArray(permissions)) return super.contains(VoiceStateToggle[permissions]) - - return super.contains(permissions.reduce((a, b) => (a |= VoiceStateToggle[b]), 0)) - } - - /** Lists all the toggles for the role and whether or not each is true or false. */ - list() { - const json: Record = {} - for (const [key, value] of Object.entries(VoiceStateToggle)) { - json[key] = super.contains(value) - } - - return json as Record - } -} - -export type VoiceStateToggleKeys = keyof typeof VoiceStateToggle diff --git a/packages/client/src/Structures/users/Extended.ts b/packages/client/src/Structures/users/Extended.ts deleted file mode 100644 index 4b49e5aa8..000000000 --- a/packages/client/src/Structures/users/Extended.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { PremiumTypes, DiscordUser } from '@discordeno/types' -import type Client from '../../Client.js' -import User from './User.js' - -export class ExtendedUser extends User { - email?: string | null - verified?: boolean - mfaEnabled?: boolean - premiumType?: PremiumTypes - - constructor(data: DiscordUser, client: Client) { - super(data, client) - this.update(data) - } - - update(data: DiscordUser): void { - super.update(data) - if (data.email !== undefined) { - this.email = data.email - } - if (data.verified !== undefined) { - this.verified = data.verified - } - if (data.mfa_enabled !== undefined) { - this.mfaEnabled = data.mfa_enabled - } - if (data.premium_type !== undefined) { - this.premiumType = data.premium_type - } - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['email', 'mfaEnabled', 'premium', 'verified', ...props]) - } -} - -export default ExtendedUser diff --git a/packages/client/src/Structures/users/User.ts b/packages/client/src/Structures/users/User.ts deleted file mode 100644 index 376080353..000000000 --- a/packages/client/src/Structures/users/User.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* eslint-disable no-useless-call */ -import type { BigString, DiscordUser, UserFlags } from '@discordeno/types' -import Base from '../../Base.js' -import type Client from '../../Client.js' -import type { ImageFormat, ImageSize } from '../../Client.js' -import { BANNER, DEFAULT_USER_AVATAR, USER_AVATAR } from '../../Endpoints.js' -import type PrivateChannel from '../channels/Private.js' - -export class User extends Base { - client: Client - bot: boolean - system: boolean - _avatar!: bigint | null - username!: string - discriminator!: string - publicFlags?: UserFlags - _banner!: bigint | null - accentColor?: number - - constructor(data: DiscordUser, client: Client) { - super(data.id) - - this.client = client - this.bot = !!data.bot - this.system = !!data.system - - this.update(data) - } - - /** @deprecated Use User.client Supported for Eris api compatibility. */ - get _client(): Client { - return this.client - } - - get avatar(): string | null { - if (!this._avatar) return null - - return this.client.iconBigintToHash(this._avatar) - } - - set avatar(value: BigString | null) { - this._avatar = typeof value === 'string' ? this.client.iconHashToBigInt(value) : value - } - - get banner(): string | null { - if (!this._banner) return null - - return this.client.iconBigintToHash(this._banner) - } - - set banner(value: BigString | null) { - this._banner = typeof value === 'string' ? this.client.iconHashToBigInt(value) : value - } - - get avatarURL(): string | null { - return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar)) : this.defaultAvatarURL - } - - get bannerURL(): string | null { - if (!this.banner) { - return null - } - - return this.client._formatImage(BANNER(this.id, this.banner)) - } - - get defaultAvatar(): string { - // @ts-expect-error some eris magic at play here - return (this.discriminator % 5).toString() - } - - get defaultAvatarURL(): string { - return `${this.client.CDN_URL}${DEFAULT_USER_AVATAR(this.defaultAvatar)}.png` - } - - get mention(): string { - return `<@${this.id}>` - } - - get staticAvatarURL(): string { - return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar), 'jpg') : this.defaultAvatarURL - } - - update(data: DiscordUser): void { - if (data.avatar !== undefined) { - this.avatar = data.avatar - } - if (data.username !== undefined) { - this.username = data.username - } - if (data.discriminator !== undefined) { - this.discriminator = data.discriminator - } - if (data.public_flags !== undefined) { - this.publicFlags = data.public_flags - } - if (data.banner !== undefined) { - this.banner = data.banner - } - if (data.accent_color !== undefined) { - this.accentColor = data.accent_color - } - } - - /** Get the user's avatar with the given format and size */ - dynamicAvatarURL(format?: ImageFormat, size?: ImageSize): string { - return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar), format, size) : this.defaultAvatarURL - } - - /** Get the user's banner with the given format and size */ - dynamicBannerURL(format?: ImageFormat, size?: ImageSize): string | null { - return this.banner ? this.client._formatImage(BANNER(this.id, this.banner), format, size) : null - } - - /** - * Get a DM channel with the user, or create one if it does not exist - * @returns {Promise} - */ - async getDMChannel(): Promise { - return await this.client.getDMChannel.call(this.client, this.id) - } - - toJSON(props: string[] = []): Record { - return super.toJSON(['accentColor', 'avatar', 'banner', 'bot', 'discriminator', 'publicFlags', 'system', 'username', ...props]) - } -} - -export default User diff --git a/packages/client/src/gateway/Shard.ts b/packages/client/src/gateway/Shard.ts deleted file mode 100644 index 2698eba8c..000000000 --- a/packages/client/src/gateway/Shard.ts +++ /dev/null @@ -1,1680 +0,0 @@ -/* eslint-disable @typescript-eslint/consistent-indexed-object-style */ -/* eslint-disable no-useless-call */ -/* eslint-disable no-prototype-builtins */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-dynamic-delete */ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { DiscordenoShard, ShardState } from '@discordeno/gateway' -import { - ActivityTypes, - ChannelTypes, - GatewayOpcodes, - Intents, - type Camelize, - type DiscordChannel, - type DiscordChannelPinsUpdate, - type DiscordGatewayPayload, - type DiscordGuild, - type DiscordGuildBanAddRemove, - type DiscordGuildEmojisUpdate, - type DiscordGuildMemberAdd, - type DiscordGuildMemberRemove, - type DiscordGuildMembersChunk, - type DiscordGuildMemberUpdate, - type DiscordGuildRoleCreate, - type DiscordGuildRoleDelete, - type DiscordGuildRoleUpdate, - type DiscordGuildStickersUpdate, - type DiscordInteraction, - type DiscordInviteCreate, - type DiscordInviteDelete, - type DiscordMember, - type DiscordMessage, - type DiscordMessageDelete, - type DiscordMessageDeleteBulk, - type DiscordMessageReactionAdd, - type DiscordMessageReactionRemove, - type DiscordMessageReactionRemoveAll, - type DiscordMessageReactionRemoveEmoji, - type DiscordPresenceUpdate, - type DiscordReady, - type DiscordStageInstance, - type DiscordThreadListSync, - type DiscordThreadMembersUpdate, - type DiscordThreadMemberUpdate, - type DiscordTypingStart, - type DiscordUnavailableGuild, - type DiscordUser, - type DiscordVoiceState, - type DiscordWebhookUpdate, -} from '@discordeno/types' -import { snakelize } from '@discordeno/utils' -import EventEmitter from 'node:events' -import type WebSocket from 'ws' -import Base from '../Base.js' -import type Client from '../Client.js' -import GuildChannel from '../Structures/channels/Guild.js' -import PrivateChannel from '../Structures/channels/Private.js' -import type StageChannel from '../Structures/channels/Stage.js' -import type TextChannel from '../Structures/channels/Text.js' -import type TextVoiceChannel from '../Structures/channels/TextVoice.js' -import ThreadMember from '../Structures/channels/threads/Member.js' -import ThreadChannel from '../Structures/channels/threads/Thread.js' -import type VoiceChannel from '../Structures/channels/Voice.js' -import Guild from '../Structures/guilds/Guild.js' -import Member from '../Structures/guilds/Member.js' -import Role from '../Structures/guilds/Role.js' -import StageInstance from '../Structures/guilds/StageInstance.js' -import UnavailableGuild from '../Structures/guilds/Unavailable.js' -import Invite from '../Structures/Invite.js' -import Message from '../Structures/Message.js' -import ExtendedUser from '../Structures/users/Extended.js' -import User from '../Structures/users/User.js' -import type { - ActivityPartial, - BotActivityType, - ClientPresence, - RequestGuildMembersOptions, - RequestMembersPromise, - SelfStatus, - TextableChannel, -} from '../typings.js' -import type Bucket from '../utils/Bucket.js' -import { generateChannelFrom, generateInteractionFrom } from '../utils/generate.js' - -export class Shard extends EventEmitter { - client: Client - connectAttempts: number = 0 - connectTimeout: number | null = null - getAllUsersCount: { [guildID: string]: boolean } = {} - getAllUsersLength: number = 0 - getAllUsersQueue: unknown[] = [] - globalBucket!: Bucket - guildCreateTimeout: NodeJS.Timeout | null = null - guildSyncQueue: string[] = [] - guildSyncQueueLength: number = 0 - id: number - latency: number = 0 - preReady = false - presence: ClientPresence = { activities: [], afk: false, status: 'online', since: 0 } - presenceUpdateBucket!: Bucket - ready = false - reconnectInterval: number = 0 - requestMembersPromise: { [s: string]: RequestMembersPromise } = {} - unsyncedGuilds: number = 0 - // ws: WebSocket | BrowserWebSocket | null = null - - discordeno: DiscordenoShard - - constructor(id: number, client: Client) { - super() - - this.id = id - this.client = client - - this.onPacket = this.onPacket.bind(this) - this._onWSOpen = this._onWSOpen.bind(this) - this._onWSMessage = this._onWSMessage.bind(this) - this._onWSError = this._onWSError.bind(this) - this._onWSClose = this._onWSClose.bind(this) - - this.discordeno = new DiscordenoShard({ - id: this.id, - // TODO: shard events - events: { - message: (_, payload) => { - this.wsEvent(snakelize(payload)) - }, - }, - connection: { - compress: this.client.options.compress, - intents: this.client.options.intents, - properties: { - os: process.platform, - browser: 'Discordeno', - device: 'Discordeno', - }, - token: this.client.token, - totalShards: this.client.options.maxShards as number, - url: this.client.gatewayURL, - version: this.client.apiVersion, - }, - }) - - this.hardReset() - } - - /** - * @deprecated Use .token instead. - */ - get _token(): string { - return this.token - } - - get token(): string { - return this.client.token - } - - get connecting(): boolean { - return this.discordeno.state === ShardState.Connecting - } - - set connecting(connecting: boolean) { - this.discordeno.state = ShardState.Connecting - } - - get heartbeatInterval(): number { - return this.discordeno.heart.interval - } - - set heartbeatInterval(interval: number) { - this.discordeno.heart.interval = interval - } - - get lastHeartbeatAck(): boolean { - return this.discordeno.heart.acknowledged - } - - set lastHeartbeatAck(acknowledged: boolean) { - this.discordeno.heart.acknowledged = acknowledged - } - - get lastHeartbeatReceived(): number | undefined { - return this.discordeno.heart.lastAck - } - - set lastHeartbeatReceived(lastAck: number | undefined) { - this.discordeno.heart.lastAck = lastAck - } - - get lastHeartbeatSent(): number | undefined { - return this.discordeno.heart.lastBeat - } - - set lastHeartbeatSent(lastSent: number | undefined) { - this.discordeno.heart.lastBeat = lastSent - } - - get sessionID(): string | undefined | null { - return this.discordeno.sessionId - } - - set sessionID(id: string | undefined | null) { - this.discordeno.sessionId = id ?? undefined - } - - get seq(): number { - return this.discordeno.previousSequenceNumber ?? 0 - } - - set seq(sequence: number) { - this.discordeno.previousSequenceNumber = sequence - } - - get status() { - switch (this.discordeno.state) { - case ShardState.Disconnected: - return 'disconnected' - case ShardState.Connecting: - return 'connecting' - case ShardState.Resuming: - return 'resuming' - case ShardState.Identifying: - return 'identifying' - default: - return 'disconnected' - } - } - - set status(state: 'connecting' | 'disconnected' | 'handshaking' | 'identifying' | 'ready' | 'resuming' | 'disconnected') { - switch (state) { - case 'connecting': - this.discordeno.state = ShardState.Connecting - break - case 'disconnected': - this.discordeno.state = ShardState.Disconnected - break - case 'identifying': - this.discordeno.state = ShardState.Identifying - break - case 'resuming': - this.discordeno.state = ShardState.Resuming - break - case 'handshaking': - case 'ready': - this.discordeno.state = ShardState.Connected - break - default: - this.discordeno.state = ShardState.Disconnected - break - } - } - - get ws(): WebSocket | undefined { - return this.discordeno.socket - } - - set ws(socket: WebSocket | undefined) { - this.discordeno.socket = socket - } - - checkReady() { - if (!this.ready) { - this.ready = true - super.emit('ready') - } - } - - /** Tells the shard to connect */ - async connect() { - return await this.discordeno.connect() - } - - createGuild(guild: Guild) { - this.client.guildShardMap[guild.id] = this.id - this.client.guilds.set(guild.id, guild) - - return guild - } - - /** Disconnects the shard */ - async disconnect(options: { reconnect?: boolean | 'auto' } = {}, error?: Error) { - return await this.discordeno.shutdown() - } - - /** - * Update the bot's AFK status. - * @deprecated self bot functionality - */ - editAFK(_afk: boolean) {} - - /** - * Updates the bot's status on all guilds the shard is in - */ - async editStatus(status: SelfStatus, activities: Array> | ActivityPartial = []) { - // Selfbots - if (status === 'invisible') return - if (!Array.isArray(activities)) activities = [activities] - - return await this.discordeno.editShardStatus({ status, activities: activities.map((a) => ({ ...a, type: a.type ?? ActivityTypes.Listening })) }) - } - - emit(event: string, ...args: any[]) { - this.client.emit.call(this.client, event, ...args) - if (event !== 'error' || this.listeners('error').length > 0) { - super.emit.call(this, event, ...args) - } - - return false - } - - async getGuildMembers(guildID: string, timeout?: number): Promise> { - return await this.discordeno.requestMembers(guildID) - } - - /** - * @deprecated this is not really necessary for dd gateway functionality - */ - hardReset() { - // this.reset() - // this.seq = 0 - // this.sessionID = null - // this.reconnectInterval = 1000 - // this.connectAttempts = 0 - // this.ws = null - // this.heartbeatInterval = null - // this.guildCreateTimeout = null - // this.globalBucket = new Bucket(120, 60000, { reservedTokens: 5 }) - // this.presenceUpdateBucket = new Bucket(5, 20000) - // this.presence = JSON.parse(JSON.stringify(this.client.presence)) - } - - heartbeat(_normal?: boolean) { - if (!this.discordeno.isOpen()) return - - this.discordeno.heart.lastBeat = Date.now() - // Discord randomly sends this requiring an immediate heartbeat back. - // Using a direct socket.send call here because heartbeat requests are reserved by us. - this.discordeno.socket?.send( - JSON.stringify({ - op: GatewayOpcodes.Heartbeat, - d: this.discordeno.previousSequenceNumber, - }), - ) - this.discordeno.events.heartbeat?.(this.discordeno) - } - - async identify() { - return await this.discordeno.identify() - } - - /** - * @deprecated done in connect() - */ - initializeWS() {} - - async onPacket(packet: DiscordGatewayPayload) { - return await this.discordeno.handleDiscordPacket(packet) - } - - async requestGuildMembers(guildID: string, options?: RequestGuildMembersOptions) { - return await this.discordeno.requestMembers(guildID, { - limit: 0, - userIds: options?.user_ids, - nonce: options?.nonce, - query: options?.query, - presences: options?.presences, - }) - } - - /** - * @deprecated Not necessarily used in dd style - */ - reset() {} - - restartGuildCreateTimeout() { - if (this.guildCreateTimeout) { - clearTimeout(this.guildCreateTimeout) - this.guildCreateTimeout = null - } - if (!this.ready) { - if (this.client.unavailableGuilds.size === 0 && this.unsyncedGuilds === 0) { - return this.checkReady() - } - this.guildCreateTimeout = setTimeout(() => { - this.checkReady() - }, this.client.options.guildCreateTimeout) - } - } - - resume() { - this.discordeno.resume() - } - - sendStatusUpdate() { - if (this.presence.status === 'invisible') this.presence.status = 'online' - - this.discordeno.editBotStatus({ - status: this.presence.status, - // @ts-expect-error eris weird types issues - activities: this.presence.activities ?? [], - }) - } - - sendWS(op: number, _data: Record | number, priority = false) { - this.discordeno.send({ op, d: _data }, priority) - } - - wsEvent(pkt: Required) { - switch ( - pkt.t /* eslint-disable no-redeclare */ // (╯°□°)╯︵ ┻━┻ - ) { - case 'PRESENCE_UPDATE': { - const packet = pkt.d as DiscordPresenceUpdate - - if (packet.user.username !== undefined) { - let user = this.client.users.get(packet.user.id) - let oldUser = null - if ( - user && - (user.username !== packet.user.username || user.discriminator !== packet.user.discriminator || user.avatar !== packet.user.avatar) - ) { - oldUser = { - username: user.username, - discriminator: user.discriminator, - avatar: user.avatar, - } - } - if (!user || oldUser) { - user = this.client.users.update(new User(packet.user, this.client), this.client) - this.emit('userUpdate', user, oldUser) - } - } - break - } - case 'VOICE_STATE_UPDATE': { - const packet = pkt.d as DiscordVoiceState - - // TODO: voice - support voice connections - // if (packet.guild_id && packet.user_id === this.client.id) { - // const voiceConnection = this.client.voiceConnections.get(packet.guild_id) - // if (voiceConnection) { - // if (packet.channel_id === null) { - // this.client.voiceConnections.leave(packet.guild_id) - // } else if (voiceConnection.channelID !== packet.channel_id) { - // voiceConnection.switchChannel(packet.channel_id, true) - // } - // } - // } - if (packet.self_stream === undefined) { - packet.self_stream = false - } - - const guild = this.client.guilds.get(packet.guild_id!) - if (!guild) { - break - } - // TODO: voice - support voice connections - // if (guild.pendingVoiceStates) { - // guild.pendingVoiceStates.push(packet) - // break - // } - let member = guild.members.get(packet.user_id) - if (!member) { - if (!packet.member) { - this.emit( - 'voiceStateUpdate', - { - id: packet.user_id, - voiceState: { - deaf: packet.deaf, - mute: packet.mute, - selfDeaf: packet.self_deaf, - selfMute: packet.self_mute, - selfStream: packet.self_stream, - selfVideo: packet.self_video, - }, - }, - null, - ) - break - } - // Updates the member cache with this member for future events. - member = new Member(packet.member, guild, this.client) - guild.members.set(packet.user_id, member) - - // TODO: voice - support voice connections - // const channel = guild.channels.find( - // (channel) => - // (channel.type === ChannelTypes.GuildVoice || channel.type === ChannelTypes.GuildStageVoice) && channel.voiceMembers.get(packet.user_id), - // ) - // if (channel) { - // channel.voiceMembers.remove(packet) - // this.emit('debug', 'VOICE_STATE_UPDATE member null but in channel: ' + packet.user_id, this.id) - // } - } - const oldState = { - deaf: member.voiceState?.deaf, - mute: member.voiceState?.mute, - selfDeaf: member.voiceState?.selfDeaf, - selfMute: member.voiceState?.selfMute, - selfStream: member.voiceState?.selfStream, - selfVideo: member.voiceState?.selfVideo, - } - const oldChannelID = member.voiceState?.channelID - if (packet.member) member.update(packet.member) - if (oldChannelID !== packet.channel_id) { - let oldChannel: TextVoiceChannel | StageChannel | null, newChannel: TextVoiceChannel | StageChannel | null - if (oldChannelID) { - oldChannel = guild.channels.get(oldChannelID) as TextVoiceChannel | StageChannel - if (oldChannel && oldChannel.type !== ChannelTypes.GuildVoice && oldChannel.type !== ChannelTypes.GuildStageVoice) { - this.emit('warn', 'Old channel not a recognized voice channel: ' + oldChannelID, this.id) - oldChannel = null - } - } - if ( - packet.channel_id && - (newChannel = guild.channels.get(packet.channel_id) as TextVoiceChannel | StageChannel) && - (newChannel.type === ChannelTypes.GuildVoice || newChannel.type === ChannelTypes.GuildStageVoice) - ) { - // Welcome to Discord, where one can "join" text channels - if (oldChannel!) { - oldChannel.voiceMembers.remove(member) - this.emit('voiceChannelSwitch', newChannel.voiceMembers.add(member, guild), newChannel, oldChannel) - } else { - this.emit('voiceChannelJoin', newChannel.voiceMembers.add(member, guild), newChannel) - } - } else if (oldChannel!) { - oldChannel.voiceMembers.remove(member) - this.emit('voiceChannelLeave', member, oldChannel) - } - } - if ( - oldState.mute !== member.voiceState?.mute || - oldState.deaf !== member.voiceState?.deaf || - oldState.selfMute !== member.voiceState?.selfMute || - oldState.selfDeaf !== member.voiceState?.selfDeaf || - oldState.selfStream !== member.voiceState?.selfStream || - oldState.selfVideo !== member.voiceState?.selfVideo - ) { - this.emit('voiceStateUpdate', member, oldState) - } - break - } - case 'TYPING_START': { - const packet = pkt.d as DiscordTypingStart - - let member = null - const guild = this.client.guilds.get(packet.guild_id ?? '') - if (guild) { - member = guild.members.update(new Member({ ...packet.member!, id: packet.user_id }, guild, this.client)) - } - if (this.client.listeners('typingStart').length > 0) { - this.emit( - 'typingStart', - this.client.getChannel(packet.channel_id) ?? { - id: packet.channel_id, - }, - this.client.users.get(packet.user_id) ?? { id: packet.user_id }, - member, - ) - } - break - } - case 'MESSAGE_CREATE': { - const packet = pkt.d as DiscordMessage - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - if (channel) { - // MESSAGE_CREATE just when deleting o.o - channel.lastMessageID = packet.id - - this.emit('messageCreate', channel.messages.add(new Message(packet, this.client))) - } else { - this.emit('messageCreate', new Message(packet, this.client)) - } - break - } - case 'MESSAGE_UPDATE': { - const packet = pkt.d as DiscordMessage - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - if (!channel) { - // @ts-expect-error eris hack - packet.channel = { - id: packet.channel_id, - } - this.emit('messageUpdate', packet, null) - break - } - const message = channel.messages.get(packet.id) - let oldMessage = null - if (message) { - oldMessage = { - attachments: message.attachments, - channelMentions: message.channelMentions, - content: message.content, - editedTimestamp: message.editedTimestamp, - embeds: message.embeds, - flags: message.flags, - // mentionedBy: message.mentionedBy, - mentions: message.mentions, - pinned: message.pinned, - roleMentions: message.roleMentions, - tts: message.tts, - } - } else if (!packet.timestamp) { - // @ts-expect-error eris hack - packet.channel = channel - this.emit('messageUpdate', packet, null) - break - } - - const msg = message ?? new Message(packet, this.client) - if (message) message.update(packet) - else channel.messages.set(msg.id, msg) - - this.emit('messageUpdate', msg, oldMessage) - break - } - case 'MESSAGE_DELETE': { - const packet = pkt.d as DiscordMessageDelete - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - const oldMessage = channel?.messages.get(packet.id) - - this.emit( - 'messageDelete', - oldMessage ?? { - id: packet.id, - channel: channel ?? { - id: packet.channel_id, - guild: packet.guild_id ? { id: packet.guild_id } : undefined, - }, - guildID: packet.guild_id, - }, - ) - break - } - case 'MESSAGE_DELETE_BULK': { - const packet = pkt.d as DiscordMessageDeleteBulk - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - - this.emit( - 'messageDeleteBulk', - packet.ids.map((id) => { - const oldMessage = channel?.messages.get(id) - - return ( - oldMessage ?? { - id, - channel: { - id: packet.channel_id, - guild: packet.guild_id ? { id: packet.guild_id } : undefined, - }, - guildID: packet.guild_id, - } - ) - }), - ) - break - } - case 'MESSAGE_REACTION_ADD': { - const packet = pkt.d as DiscordMessageReactionAdd - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - let message: - | Message - | { - id: string - channel: TextableChannel | { id: string } - guildID?: string - } - | undefined - let member - if (channel) { - message = channel.messages.get(packet.message_id) - if (channel.guild) { - if (packet.member) { - const member = new Member(packet.member, channel.guild, this.client) - channel.guild.members.set(member.user.id, member) - } - } - } - if (message instanceof Message) { - const reaction = packet.emoji.id ? `${packet.emoji.name}:${packet.emoji.id}` : packet.emoji.name! - if (message.reactions[reaction]) { - ++message.reactions[reaction].count - if (packet.user_id === this.client.id) { - message.reactions[reaction].me = true - } - } else { - message.reactions[reaction] = { - count: 1, - me: packet.user_id === this.client.id, - } - } - } else { - message = { - id: packet.message_id, - channel: channel ?? { id: packet.channel_id }, - } - - if (packet.guild_id) { - message.guildID = packet.guild_id - // @ts-expect-error eris hacks - if (!message.channel.guild) { - // @ts-expect-error eris hacks - message.channel.guild = { id: packet.guild_id } - } - } - } - this.emit('messageReactionAdd', message, packet.emoji, member ?? { id: packet.user_id }) - break - } - case 'MESSAGE_REACTION_REMOVE': { - const packet = pkt.d as DiscordMessageReactionRemove - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - let message: - | Message - | { - id: string - channel: TextableChannel | { id: string } - guildID?: string - } - | undefined - if (channel) { - message = channel.messages.get(packet.message_id) - } - if (message instanceof Message) { - const reaction = packet.emoji.id ? `${packet.emoji.name}:${packet.emoji.id}` : packet.emoji.name! - const reactionObj = message.reactions[reaction] - if (reactionObj) { - --reactionObj.count - if (reactionObj.count === 0) { - delete message.reactions[reaction] - } else if (packet.user_id === this.client.id) { - reactionObj.me = false - } - } - } else { - message = { - id: packet.message_id, - channel: channel ?? { id: packet.channel_id }, - } - - if (packet.guild_id) { - message.guildID = packet.guild_id - // @ts-expect-error eris hacks - if (!message.channel.guild) { - // @ts-expect-error eris hacks - message.channel.guild = { id: packet.guild_id } - } - } - } - - this.emit('messageReactionRemove', message, packet.emoji, packet.user_id) - break - } - case 'MESSAGE_REACTION_REMOVE_ALL': { - const packet = pkt.d as DiscordMessageReactionRemoveAll - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - let message - if (channel) { - message = channel.messages.get(packet.message_id) - if (message) { - message.reactions = {} - } - } - if (!message) { - message = { - id: packet.message_id, - channel: channel ?? { id: packet.channel_id }, - } - if (packet.guild_id) { - // @ts-expect-error eris hacks - message.guildID = packet.guild_id - if (!message.channel.guild) { - // @ts-expect-error eris hacks - message.channel.guild = { id: packet.guild_id } - } - } - } - - this.emit('messageReactionRemoveAll', message) - break - } - case 'MESSAGE_REACTION_REMOVE_EMOJI': { - const packet = pkt.d as DiscordMessageReactionRemoveEmoji - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - let message - if (channel) { - message = channel.messages.get(packet.message_id) - if (message) { - const reaction = packet.emoji.id ? `${packet.emoji.name}:${packet.emoji.id}` : packet.emoji.name! - delete message.reactions[reaction] - } - } - if (!message) { - message = { - id: packet.message_id, - channel: channel ?? { id: packet.channel_id }, - } - if (packet.guild_id) { - // @ts-expect-error eris hacks - message.guildID = packet.guild_id - if (!message.channel.guild) { - // @ts-expect-error eris hacks - message.channel.guild = { id: packet.guild_id } - } - } - } - - this.emit('messageReactionRemoveEmoji', message, packet.emoji) - break - } - case 'GUILD_MEMBER_ADD': { - const packet = pkt.d as DiscordGuildMemberAdd - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - // Eventual Consistency™ (╯°□°)╯︵ ┻━┻ - this.emit('debug', `Missing guild ${packet.guild_id} in GUILD_MEMBER_ADD`) - break - } - guild.memberCount = (guild.memberCount ?? 0) + 1 - - const member = new Member(packet, guild, this.client) - guild.members.set(member.id, member) - this.emit('guildMemberAdd', guild, member) - break - } - case 'GUILD_MEMBER_UPDATE': { - const packet = pkt.d as DiscordGuildMemberUpdate - - // Check for member update if GuildPresences intent isn't set, to prevent emitting twice - if (!(this.client.options.intents & Intents.GuildPresences) && packet.user.username !== undefined) { - let user = this.client.users.get(packet.user.id) - let oldUser = null - if ( - user && - (user.username !== packet.user.username || user.discriminator !== packet.user.discriminator || user.avatar !== packet.user.avatar) - ) { - oldUser = { - username: user.username, - discriminator: user.discriminator, - avatar: user.avatar, - } - } - if (!user || oldUser) { - user = this.client.users.update(new User(packet.user, this.client)) - this.emit('userUpdate', user, oldUser) - } - } - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in GUILD_MEMBER_UPDATE`) - break - } - let member = guild.members.get(packet.user.id) - let oldMember = null - if (member) { - oldMember = { - avatar: member.avatar, - communicationDisabledUntil: member.communicationDisabledUntil, - roles: member.roles, - nick: member.nick, - premiumSince: member.premiumSince, - pending: member.pending, - } - } - member = guild.members.update(new Member(packet, guild, this.client)) - - this.emit('guildMemberUpdate', guild, member, oldMember) - break - } - case 'GUILD_MEMBER_REMOVE': { - const packet = pkt.d as DiscordGuildMemberRemove - - if (packet.user.id === this.client.id) { - // The bot is probably leaving - break - } - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - break - } - guild.memberCount = (guild.memberCount ?? 0) - 1 - - this.emit( - 'guildMemberRemove', - guild, - guild.members.get(packet.user.id) ?? { - id: packet.user.id, - user: new User(packet.user, this.client), - }, - ) - break - } - case 'GUILD_CREATE': { - const packet = pkt.d as DiscordGuild - - if (!packet.unavailable) { - const guild = this.createGuild(new Guild(packet, this.client)) - if (this.ready) { - if (this.client.unavailableGuilds.remove(new Guild(packet, this.client))) { - this.emit('guildAvailable', guild) - } else { - this.emit('guildCreate', guild) - } - } else { - this.client.unavailableGuilds.remove(new Guild(packet, this.client)) - this.restartGuildCreateTimeout() - } - } else { - this.client.guilds.remove(new Guild(packet, this.client)) - - this.emit('unavailableGuildCreate', this.client.unavailableGuilds.add(new UnavailableGuild(packet, this.client))) - } - break - } - case 'GUILD_UPDATE': { - const packet = pkt.d as DiscordGuild - - const guild = this.client.guilds.get(packet.id) - if (!guild) { - this.emit('debug', `Guild ${packet.id} undefined in GUILD_UPDATE`) - break - } - const oldGuild = { - afkChannelID: guild.afkChannelID, - afkTimeout: guild.afkTimeout, - banner: guild.banner, - defaultNotifications: guild.defaultNotifications, - description: guild.description, - discoverySplash: guild.discoverySplash, - emojis: guild.emojis, - explicitContentFilter: guild.explicitContentFilter, - features: guild.features, - icon: guild.icon, - large: guild.large, - maxMembers: guild.maxMembers, - maxVideoChannelUsers: guild.maxVideoChannelUsers, - mfaLevel: guild.mfaLevel, - name: guild.name, - nsfw: guild.nsfw, - nsfwLevel: guild.nsfwLevel, - ownerID: guild.ownerID, - preferredLocale: guild.preferredLocale, - premiumSubscriptionCount: guild.premiumSubscriptionCount, - premiumTier: guild.premiumTier, - publicUpdatesChannelID: guild.publicUpdatesChannelID, - rulesChannelID: guild.rulesChannelID, - splash: guild.splash, - stickers: guild.stickers, - systemChannelFlags: guild.systemChannelFlags, - systemChannelID: guild.systemChannelID, - vanityURL: guild.vanityURL, - verificationLevel: guild.verificationLevel, - } - - this.emit('guildUpdate', this.client.guilds.update(new Guild(packet, this.client)), oldGuild) - break - } - case 'GUILD_DELETE': { - const packet = pkt.d as DiscordUnavailableGuild - - // TODO: voice - support voice stuff - // const voiceConnection = this.client.voiceConnections.get(packet.id) - // if (voiceConnection) { - // if (voiceConnection.channelID) { - // this.client.leaveVoiceChannel(voiceConnection.channelID) - // } else { - // this.client.voiceConnections.leave(packet.id) - // } - // } - - delete this.client.guildShardMap[packet.id] - const guild = this.client.guilds.remove(packet) - if (guild) { - // Discord sends GUILD_DELETE for guilds that were previously unavailable in READY - guild.channels.forEach((channel) => { - delete this.client.channelGuildMap[channel.id] - }) - } - if (packet.unavailable) { - this.emit('guildUnavailable', this.client.unavailableGuilds.add(new UnavailableGuild(packet, this.client))) - } else { - this.emit( - 'guildDelete', - guild ?? { - id: packet.id, - }, - ) - } - break - } - case 'GUILD_BAN_ADD': { - const packet = pkt.d as DiscordGuildBanAddRemove - - this.emit('guildBanAdd', this.client.guilds.get(packet.guild_id), this.client.users.update(new User(packet.user, this.client))) - break - } - case 'GUILD_BAN_REMOVE': { - const packet = pkt.d as DiscordGuildBanAddRemove - - this.emit('guildBanRemove', this.client.guilds.get(packet.guild_id), this.client.users.update(new User(packet.user, this.client))) - break - } - case 'GUILD_ROLE_CREATE': { - const packet = pkt.d as DiscordGuildRoleCreate - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in GUILD_ROLE_CREATE`) - break - } - this.emit('guildRoleCreate', guild, guild.roles.add(new Role(packet.role, guild))) - break - } - case 'GUILD_ROLE_UPDATE': { - const packet = pkt.d as DiscordGuildRoleUpdate - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('debug', `Guild ${packet.guild_id} undefined in GUILD_ROLE_UPDATE`) - break - } - const role = new Role(packet.role, guild) - guild.roles.set(role.id, role) - if (!role) { - this.emit('debug', `Role ${packet.role.id} in guild ${packet.guild_id} undefined in GUILD_ROLE_UPDATE`) - break - } - const oldRole = { - color: role.color, - hoist: role.hoist, - icon: role.icon, - managed: role.managed, - mentionable: role.mentionable, - name: role.name, - permissions: role.permissions, - position: role.position, - tags: role.tags, - unicodeEmoji: role.unicodeEmoji, - } - - this.emit('guildRoleUpdate', guild, guild.roles.update(new Role(packet.role, guild)), oldRole) - break - } - case 'GUILD_ROLE_DELETE': { - const packet = pkt.d as DiscordGuildRoleDelete - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in GUILD_ROLE_DELETE`) - break - } - if (!guild.roles.has(packet.role_id)) { - this.emit('debug', `Missing role ${packet.role_id} in GUILD_ROLE_DELETE`) - break - } - this.emit('guildRoleDelete', guild, guild.roles.remove({ id: packet.role_id })) - break - } - case 'INVITE_CREATE': { - const packet = pkt.d as DiscordInviteCreate - - const guild = this.client.guilds.get(packet.guild_id ?? '') - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in INVITE_CREATE`) - break - } - const channel = this.client.getChannel(packet.channel_id) as GuildChannel - if (!channel) { - this.emit('debug', `Missing channel ${packet.channel_id} in INVITE_CREATE`) - break - } - - this.emit( - 'inviteCreate', - guild, - new Invite( - { - ...packet, - guild: guild.toJSON(), - channel, - }, - this.client, - ), - ) - break - } - case 'INVITE_DELETE': { - const packet = pkt.d as DiscordInviteDelete - - const guild = this.client.guilds.get(packet.guild_id ?? '') - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in INVITE_DELETE`) - break - } - const channel = this.client.getChannel(packet.channel_id) as GuildChannel - if (!channel) { - this.emit('debug', `Missing channel ${packet.channel_id} in INVITE_DELETE`) - break - } - - this.emit( - 'inviteDelete', - guild, - new Invite( - { - ...packet, - guild: guild.toJSON(), - channel, - }, - this.client, - ), - ) - break - } - case 'CHANNEL_CREATE': { - const packet = pkt.d as DiscordChannel - - const channel = generateChannelFrom(packet, this.client) - if (packet.guild_id) { - const guildChannel = channel as GuildChannel - if (!guildChannel.guild) { - guildChannel.guild = this.client.guilds.get(packet.guild_id)! - if (!guildChannel.guild) { - this.emit('debug', `Received CHANNEL_CREATE for channel in missing guild ${packet.guild_id}`) - break - } - } - - guildChannel.guild.channels.set(guildChannel.id, guildChannel) - this.client.channelGuildMap[packet.id] = packet.guild_id - - this.emit('channelCreate', channel) - } else { - this.emit('warn', new Error('Unhandled CHANNEL_CREATE type: ' + JSON.stringify(packet, null, 2))) - break - } - break - } - case 'CHANNEL_UPDATE': { - const packet = pkt.d as DiscordChannel - - let channel = this.client.getChannel(packet.id) as GuildChannel - if (!channel) { - break - } - let oldChannel - const oldType = channel.type - - if (channel instanceof GuildChannel) { - oldChannel = { - bitrate: (channel as VoiceChannel).bitrate, - name: channel.name, - nsfw: channel.nsfw, - parentID: channel.parentID, - permissionOverwrites: channel.permissionOverwrites, - position: channel.position, - rateLimitPerUser: (channel as TextChannel).rateLimitPerUser, - rtcRegion: (channel as VoiceChannel).rtcRegion, - topic: (channel as TextChannel).topic, - type: channel.type, - userLimit: (channel as VoiceChannel).userLimit, - videoQualityMode: (channel as VoiceChannel).videoQualityMode, - } - } else { - this.emit('warn', `Unexpected CHANNEL_UPDATE for channel ${packet.id} with type ${oldType}`) - } - if (oldType === packet.type) { - channel.update(packet) - } else { - this.emit('debug', `Channel ${packet.id} changed from type ${oldType} to ${packet.type}`) - const newChannel = generateChannelFrom(packet, this.client) as GuildChannel - if (packet.guild_id) { - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('debug', `Received CHANNEL_UPDATE for channel in missing guild ${packet.guild_id}`) - break - } - guild.channels.remove(channel) - guild.channels.add(newChannel, this.client) - } else if (channel instanceof PrivateChannel) { - this.client.privateChannels.remove(channel) - this.client.privateChannels.add(newChannel as unknown as PrivateChannel, this.client) - } else { - this.emit('warn', new Error('Unhandled CHANNEL_UPDATE type: ' + JSON.stringify(packet, null, 2))) - break - } - channel = newChannel - } - - this.emit('channelUpdate', channel, oldChannel) - break - } - case 'CHANNEL_DELETE': { - const packet = pkt.d as DiscordChannel - - if (packet.type === ChannelTypes.DM || packet.type === undefined) { - if (this.id === 0) { - const channel = this.client.privateChannels.remove(new PrivateChannel(packet, this.client)) - if (channel) { - delete this.client.privateChannelMap[channel.recipient?.id ?? ''] - - this.emit('channelDelete', channel) - } - } - } else if (packet.guild_id) { - delete this.client.channelGuildMap[packet.id] - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in CHANNEL_DELETE`) - break - } - const channel = guild.channels.remove(new GuildChannel(packet, this.client)) - if (!channel) { - break - } - if (channel.type === ChannelTypes.GuildVoice || channel.type === ChannelTypes.GuildStageVoice) { - const voiceChannel = channel as VoiceChannel | StageChannel - voiceChannel.voiceMembers.forEach((member) => { - voiceChannel.voiceMembers.remove(member) - this.emit('voiceChannelLeave', member, channel) - }) - } - this.emit('channelDelete', channel) - } else { - this.emit('warn', new Error('Unhandled CHANNEL_DELETE type: ' + JSON.stringify(packet, null, 2))) - } - break - } - case 'GUILD_MEMBERS_CHUNK': { - const packet = pkt.d as DiscordGuildMembersChunk - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit( - 'debug', - `Received GUILD_MEMBERS_CHUNK, but guild ${packet.guild_id} is ` + - (this.client.unavailableGuilds.has(packet.guild_id) ? 'unavailable' : 'missing'), - this.id, - ) - break - } - - const members = packet.members.map((member) => { - const memb = new Member(member, guild, this.client) - guild.members.set(memb.id, memb) - return memb - }) - - if (packet.presences) { - packet.presences.forEach((presence) => { - const member = guild.members.get(presence.user.id) - if (member) { - // member.update(presence) - } - }) - } - - if (this.requestMembersPromise.hasOwnProperty(packet.nonce ?? '')) { - this.requestMembersPromise[packet.nonce ?? ''].members.push(...members) - } - - if (packet.chunk_index >= packet.chunk_count - 1) { - if (this.requestMembersPromise.hasOwnProperty(packet.nonce ?? '')) { - clearTimeout(this.requestMembersPromise[packet.nonce ?? ''].timeout) - this.requestMembersPromise[packet.nonce ?? ''].res(this.requestMembersPromise[packet.nonce ?? ''].members) - delete this.requestMembersPromise[packet.nonce ?? ''] - } - if (this.getAllUsersCount.hasOwnProperty(guild.id)) { - delete this.getAllUsersCount[guild.id] - this.checkReady() - } - } - - this.emit('guildMemberChunk', guild, members) - - this.lastHeartbeatAck = true - - break - } - case 'RESUMED': - case 'READY': { - const packet = pkt.d as DiscordReady - - this.connectAttempts = 0 - this.reconnectInterval = 1000 - - this.connecting = false - if (this.connectTimeout) { - clearTimeout(this.connectTimeout) - } - this.connectTimeout = null - this.status = 'ready' - this.presence.status = 'online' - this.client.shards._readyPacketCB(this.id) - - if (pkt.t === 'RESUMED') { - // Can only heartbeat after resume succeeds, discord/discord-api-docs#1619 - this.heartbeat() - - this.preReady = true - this.ready = true - - /** - * Fired when a shard finishes resuming - * @event Shard#resume - */ - super.emit('resume') - break - } - - this.client.user = new ExtendedUser(packet.user, this.client) - this.client.users.set(this.client.user.id, this.client.user) - - if (!this.client.token.startsWith('Bot ')) { - this.client.token = 'Bot ' + this.client.token - } - - this.sessionID = packet.session_id - - packet.guilds.forEach((guild) => { - if (guild.unavailable) { - this.client.guilds.delete(guild.id) - this.client.unavailableGuilds.set(guild.id, new UnavailableGuild(guild, this.client)) - } else { - this.client.guildShardMap[guild.id] = this.id - this.client.unavailableGuilds.delete(guild.id) - } - }) - - this.client.options.applicationId = packet.application.id - - this.preReady = true - - this.emit('shardPreReady', this.id) - - if (this.client.unavailableGuilds.size > 0 && packet.guilds.length > 0) { - this.restartGuildCreateTimeout() - } else { - this.checkReady() - } - - break - } - case 'VOICE_SERVER_UPDATE': { - // const packet = pkt.d as DiscordVoiceServerUpdate - // TODO: voice - support voice stuff - // packet.session_id = this.sessionID - // packet.user_id = this.client.id - // packet.shard = this - - // this.client.voiceConnections.voiceServerUpdate(packet) - - break - } - case 'USER_UPDATE': { - const packet = pkt.d as DiscordUser - - let user = this.client.users.get(packet.id) - let oldUser = null - if (user) { - oldUser = { - username: user.username, - discriminator: user.discriminator, - avatar: user.avatar, - } - } - user = this.client.users.update(new User(packet, this.client)) - this.emit('userUpdate', user, oldUser) - break - } - case 'GUILD_EMOJIS_UPDATE': { - const packet = pkt.d as DiscordGuildEmojisUpdate - - const guild = this.client.guilds.get(packet.guild_id) - let oldEmojis = null - const emojis = packet.emojis - if (guild) { - oldEmojis = guild.emojis - guild.emojis = emojis - } - - this.emit('guildEmojisUpdate', guild ?? { id: packet.guild_id }, emojis, oldEmojis) - break - } - case 'GUILD_STICKERS_UPDATE': { - const packet = pkt.d as DiscordGuildStickersUpdate - - const guild = this.client.guilds.get(packet.guild_id) - let oldStickers = null - const stickers = packet.stickers - if (guild) { - oldStickers = guild.stickers - guild.stickers = stickers - } - this.emit('guildStickersUpdate', guild ?? { id: packet.guild_id }, stickers, oldStickers) - break - } - - case 'CHANNEL_PINS_UPDATE': { - const packet = pkt.d as DiscordChannelPinsUpdate - - const channel = this.client.getChannel(packet.channel_id) as TextChannel - if (!channel) { - this.emit('debug', `CHANNEL_PINS_UPDATE target channel ${packet.channel_id} not found`) - break - } - const oldTimestamp = channel.lastPinTimestamp - channel.lastPinTimestamp = Date.parse(packet.last_pin_timestamp ?? '') - - this.emit('channelPinUpdate', channel, channel.lastPinTimestamp, oldTimestamp) - break - } - case 'WEBHOOKS_UPDATE': { - const packet = pkt.d as DiscordWebhookUpdate - - this.emit('webhooksUpdate', { - channelID: packet.channel_id, - guildID: packet.guild_id, - }) - break - } - case 'THREAD_CREATE': { - const packet = pkt.d as DiscordChannel - - const channel = generateChannelFrom(packet, this.client) as ThreadChannel - if (!channel.guild) { - channel.guild = this.client.guilds.get(packet.guild_id ?? '')! - if (!channel.guild) { - this.emit('debug', `Received THREAD_CREATE for channel in missing guild ${packet.guild_id}`) - break - } - } - channel.guild.threads.add(channel, this.client) - this.client.threadGuildMap[packet.id] = packet.guild_id ?? '' - - this.emit('threadCreate', channel) - break - } - case 'THREAD_UPDATE': { - const packet = pkt.d as DiscordChannel - - const channel = this.client.getChannel(packet.id) - if (!channel) { - const thread = generateChannelFrom(packet, this.client) as ThreadChannel - this.emit('threadUpdate', this.client.guilds.get(packet.guild_id ?? '')?.threads.add(thread, this.client), null) - this.client.threadGuildMap[packet.id] = packet.guild_id ?? '' - break - } - if (!(channel instanceof ThreadChannel)) { - // this.emit('warn', `Unexpected THREAD_UPDATE for channel ${packet.id} with type ${channel.type}`) - break - } - const oldChannel = { - name: channel.name, - rateLimitPerUser: channel.rateLimitPerUser, - threadMetadata: channel.threadMetadata, - } - channel.update(packet) - - this.emit('threadUpdate', channel, oldChannel) - break - } - case 'THREAD_DELETE': { - const packet = pkt.d as Pick - - delete this.client.threadGuildMap[packet.id] - const guild = this.client.guilds.get(packet.guild_id ?? '') - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in THREAD_DELETE`) - break - } - const channel = guild.threads.get(packet.id) - guild.threads.delete(packet.id) - if (!channel) { - break - } - - this.emit('threadDelete', channel) - break - } - case 'THREAD_LIST_SYNC': { - const packet = pkt.d as DiscordThreadListSync - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in THREAD_LIST_SYNC`) - break - } - const deletedThreads = (packet.channel_ids ?? guild.threads.map((c) => c.id)) // REVIEW Is this a good name? - .filter((c) => !packet.threads.some((t) => t.id === c)) - .map((id) => guild.threads.remove({ id }) ?? { id }) - const activeThreads = packet.threads.map((t) => { - const thread = generateChannelFrom(t, this.client) as ThreadChannel - guild.threads.set(thread.id, thread) - return thread - }) - // @ts-expect-error js hack - const joinedThreadsMember = packet.members.map((m) => guild.threads.get(m.id)?.members.update(m, this.client)) - - this.emit('threadListSync', guild, deletedThreads, activeThreads, joinedThreadsMember) - break - } - // TODO: Add this when dd has the support for this event - case 'THREAD_MEMBER_UPDATE': { - const packet = pkt.d as DiscordThreadMemberUpdate - const channel = this.client.getChannel(packet.id) as ThreadChannel - if (!channel) { - this.emit('debug', `Missing channel ${packet.id} in THREAD_MEMBER_UPDATE`) - break - } - let oldMember = null - // Thanks Discord - let member = channel.members.get(this.client.id) - if (member) { - oldMember = { - flags: member.flags, - } - } - member = new ThreadMember({ ...packet, user_id: this.client.id.toString(), join_timestamp: new Date().toISOString() }, this.client) - this.emit('threadMemberUpdate', channel, member, oldMember) - break - } - case 'THREAD_MEMBERS_UPDATE': { - const packet = pkt.d as DiscordThreadMembersUpdate - - const channel = this.client.getChannel(packet.id) as ThreadChannel - if (!channel) { - this.emit('debug', `Missing channel ${packet.id} in THREAD_MEMBERS_UPDATE`) - break - } - channel.memberCount = packet.member_count - let addedMembers - let removedMembers - if (packet.added_members) { - addedMembers = packet.added_members.map((m) => { - // if (m.presence) { - // m.presence.id = m.presence.user.id - // this.client.users.update(m.presence.user, this.client) - // } - - // m.thread_id = m.id - // m.id = m.user_id - // m.member.id = m.member.user.id - // const guild = this.client.guilds.get(packet.guild_id) - // if (guild) { - // if (m.presence) { - // guild.members.update(m.presence, guild) - // } - // guild.members.update(m.member, guild) - // } - const member = new ThreadMember(m, this.client) - channel.members.set(member.id, member) - return member - }) - } - if (packet.removed_member_ids) { - removedMembers = packet.removed_member_ids.map((id) => channel.members.remove({ id }) ?? { id }) - } - - this.emit('threadMembersUpdate', channel, addedMembers ?? [], removedMembers ?? []) - break - } - case 'STAGE_INSTANCE_CREATE': { - const packet = pkt.d as DiscordStageInstance - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('debug', `Missing guild ${packet.guild_id} in STAGE_INSTANCE_CREATE`) - break - } - - this.emit('stageInstanceCreate', guild.stageInstances.add(new StageInstance(packet, this.client))) - break - } - case 'STAGE_INSTANCE_UPDATE': { - const packet = pkt.d as DiscordStageInstance - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('stageInstanceUpdate', packet, null) - break - } - const stageInstance = guild.stageInstances.get(packet.id) - let oldStageInstance = null - if (stageInstance) { - oldStageInstance = { - topic: stageInstance.topic, - } - } - - this.emit('stageInstanceUpdate', guild.stageInstances.update(new StageInstance(packet, this.client)), oldStageInstance) - break - } - case 'STAGE_INSTANCE_DELETE': { - const packet = pkt.d as DiscordStageInstance - - const guild = this.client.guilds.get(packet.guild_id) - if (!guild) { - this.emit('stageInstanceDelete', new StageInstance(packet, this.client)) - break - } - - this.emit('stageInstanceDelete', guild.stageInstances.remove(packet) ?? new StageInstance(packet, this.client)) - break - } - case 'GUILD_INTEGRATIONS_UPDATE': { - // Ignore this - break - } - case 'INTERACTION_CREATE': { - const packet = pkt.d as DiscordInteraction - - this.emit('interactionCreate', generateInteractionFrom(packet, this.client)) - break - } - default: { - this.emit('unknown', pkt, this.id) - break - } - } /* eslint-enable no-redeclare */ - } - - _onWSClose(event: { code: number; reason: string }) { - // dd handles this internally - } - - _onWSError(_err: Error) { - // dd handls this internally - } - - _onWSMessage(data: any) { - this.discordeno.handleMessage(data) - } - - _onWSOpen() { - // Handled by dd internally - this.emit('connect', this.id) - } - - toString() { - return Base.prototype.toString.call(this) - } - - toJSON(props: string[] = []) { - return Base.prototype.toJSON.call(this, [ - 'connecting', - 'ready', - 'status', - 'lastHeartbeatReceived', - 'lastHeartbeatSent', - 'latency', - 'preReady', - 'getAllUsersCount', - 'getAllUsersQueue', - 'getAllUsersLength', - 'guildSyncQueue', - 'guildSyncQueueLength', - 'unsyncedGuilds', - 'lastHeartbeatAck', - 'seq', - 'sessionID', - 'reconnectInterval', - 'connectAttempts', - ...props, - ]) - } -} - -export default Shard diff --git a/packages/client/src/gateway/ShardManager.ts b/packages/client/src/gateway/ShardManager.ts deleted file mode 100644 index daa7d31c0..000000000 --- a/packages/client/src/gateway/ShardManager.ts +++ /dev/null @@ -1,151 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import Base from '../Base.js' -import type Client from '../Client.js' -import Collection from '../Collection.js' -import type { ShardManagerOptions } from '../typings.js' -import Shard from './Shard.js' - -export class ShardManager extends Collection { - /** The client manager */ - client: Client - /** The options that were used to configure this manager. */ - options: ShardManagerOptions - /** The buckets that this manager is handling. */ - buckets: Map - /** The queue in which to connect a shard. */ - connectQueue: Shard[] - /** The timeout to use for connecting a shard. */ - connectTimeout: NodeJS.Timeout | null - - constructor(client: Client, options: ShardManagerOptions = {}) { - super() - this.client = client - - this.options = Object.assign({ concurrency: 1 }, options) - this.buckets = new Map() - this.connectQueue = [] - this.connectTimeout = null - } - - /** - * @deprecated Use `.client` instead. - */ - get _client(): Client { - return this.client - } - - connect(shard: Shard) { - this.connectQueue.push(shard) - this.tryConnect() - } - - get concurrency(): number { - return this.options.concurrency as number - } - - setConcurrency(concurrency: number) { - this.options.concurrency = concurrency - } - - spawn(id: number) { - let shard = this.get(id) - - if (!shard) { - shard = new Shard(id, this.client) - this.set(id, shard) - - shard - .on('ready', () => { - this.client.emit('shardReady', shard!.id) - if (this.client.ready) return - - for (const other of this.values()) if (!other.ready) return - - this.client.ready = true - this.client.startTime = Date.now() - - this.client.emit('ready') - }) - .on('resume', () => { - this.client.emit('shardResume', shard!.id) - if (this.client.ready) return - - for (const other of this.values()) if (!other.ready) return - - this.client.ready = true - this.client.startTime = Date.now() - this.client.emit('ready') - }) - .on('disconnect', (error) => { - this.client.emit('shardDisconnect', error, shard!.id) - for (const other of this.values()) if (other.ready) return - - this.client.ready = false - this.client.startTime = 0 - this.client.emit('disconnect') - }) - } - - if (shard.status === 'disconnected') { - return this.connect(shard) - } - } - - tryConnect() { - // nothing in queue - if (this.connectQueue.length === 0) { - return - } - - // loop over the connectQueue - for (const shard of this.connectQueue) { - // find the bucket for our shard - const rateLimitKey = shard.id % this.concurrency || 0 - const lastConnect = this.buckets.get(rateLimitKey) ?? 0 - - // has enough time passed since the last connect for this bucket (5s/bucket)? - // alternatively if we have a sessionID, we can skip this check - if (!shard.sessionID && Date.now() - lastConnect < 5000) { - continue - } - - // Are there any connecting shards in the same bucket we should wait on? - if (this.some((s) => s.connecting && (s.id % this.concurrency || 0) === rateLimitKey)) { - continue - } - - // connect the shard - shard.identify() - this.buckets.set(rateLimitKey, Date.now()) - - // remove the shard from the queue - const index = this.connectQueue.findIndex((s) => s.id === shard.id) - this.connectQueue.splice(index, 1) - } - - // set the next timeout if we have more shards to connect - if (!this.connectTimeout && this.connectQueue.length > 0) { - this.connectTimeout = setTimeout(() => { - this.connectTimeout = null - this.tryConnect() - }, 500) - } - } - - _readyPacketCB(shardID: number) { - const rateLimitKey = shardID % this.concurrency || 0 - this.buckets.set(rateLimitKey, Date.now()) - - this.tryConnect() - } - - toString() { - return `[ShardManager ${this.size}]` - } - - toJSON(props = []) { - return Base.prototype.toJSON.call(this, ['buckets', 'connectQueue', 'connectTimeout', 'options', ...props]) - } -} - -export default ShardManager diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts deleted file mode 100644 index 8268a0c1a..000000000 --- a/packages/client/src/index.ts +++ /dev/null @@ -1,145 +0,0 @@ -import Base from './Base.js' -import type { ClientOptions } from './Client.js' -import Client from './Client.js' -import Collection from './Collection.js' -import * as Constants from './Constants.js' -import Shard from './gateway/Shard.js' -import RequestHandler from './RequestHandler.js' -import { CategoryChannel } from './Structures/channels/Category.js' -import Channel from './Structures/channels/Channel.js' -import Guild, { GuildChannel } from './Structures/channels/Guild.js' -import { NewsChannel } from './Structures/channels/News.js' -import { PrivateChannel } from './Structures/channels/Private.js' -import { StageChannel } from './Structures/channels/Stage.js' -import { TextChannel } from './Structures/channels/Text.js' -import { TextVoiceChannel } from './Structures/channels/TextVoice.js' -import Member, { ThreadMember } from './Structures/channels/threads/Member.js' -import { NewsThreadChannel } from './Structures/channels/threads/NewsThread.js' -import { PrivateThreadChannel } from './Structures/channels/threads/PrivateThread.js' -import { PublicThreadChannel } from './Structures/channels/threads/PublicThread.js' -import { ThreadChannel } from './Structures/channels/threads/Thread.js' -import { VoiceChannel } from './Structures/channels/Voice.js' -import { GuildIntegration } from './Structures/guilds/Integration.js' -import { GuildPreview } from './Structures/guilds/Preview.js' -import Role from './Structures/guilds/Role.js' -import StageInstance from './Structures/guilds/StageInstance.js' -import { GuildTemplate } from './Structures/guilds/Template.js' -import { UnavailableGuild } from './Structures/guilds/Unavailable.js' -import { VoiceState } from './Structures/guilds/VoiceState.js' -import { AutocompleteInteraction } from './Structures/interactions/Autocomplete.js' -import Command, { CommandInteraction } from './Structures/interactions/Command.js' -import { ComponentInteraction } from './Structures/interactions/Component.js' -import Interaction from './Structures/interactions/Interaction.js' -import { PingInteraction } from './Structures/interactions/Ping.js' -import { UnknownInteraction } from './Structures/interactions/Unknown.js' -import Invite from './Structures/Invite.js' -import Message from './Structures/Message.js' -import Permission from './Structures/Permission.js' -import PermissionOverwrite from './Structures/PermissionOverwrite.js' -import { ExtendedUser } from './Structures/users/Extended.js' -import User from './Structures/users/User.js' -import Bucket from './utils/Bucket.js' -import DiscordRESTError from './utils/DiscordRESTError.js' -// TODO: MAKE THIS DYNAMIC FROM PACKAGE.JSON -export const VERSION = "19.0.0"; - -export function DiscordenoClient(token: string, options: ClientOptions): Client { - return new Client(token, options) -} - -DiscordenoClient.AutocompleteInteraction = AutocompleteInteraction -DiscordenoClient.Base = Base -DiscordenoClient.Bucket = Bucket -DiscordenoClient.CategoryChannel = CategoryChannel -DiscordenoClient.Channel = Channel -DiscordenoClient.CommandInteraction = CommandInteraction -DiscordenoClient.ComponentInteraction = ComponentInteraction -DiscordenoClient.Client = Client -DiscordenoClient.Collection = Collection -DiscordenoClient.Command = Command -// DiscordenoClient.CommandClient = CommandClient -DiscordenoClient.Constants = Constants -// DiscordenoClient.DiscordHTTPError = DiscordHTTPError -DiscordenoClient.DiscordRESTError = DiscordRESTError -DiscordenoClient.ExtendedUser = ExtendedUser -DiscordenoClient.Guild = Guild -DiscordenoClient.GuildChannel = GuildChannel -DiscordenoClient.GuildIntegration = GuildIntegration -DiscordenoClient.GuildPreview = GuildPreview -DiscordenoClient.GuildTemplate = GuildTemplate -DiscordenoClient.Interaction = Interaction -DiscordenoClient.Invite = Invite -DiscordenoClient.Member = Member -DiscordenoClient.Message = Message -DiscordenoClient.NewsChannel = NewsChannel -DiscordenoClient.NewsThreadChannel = NewsThreadChannel -DiscordenoClient.Permission = Permission -DiscordenoClient.PermissionOverwrite = PermissionOverwrite -DiscordenoClient.PingInteraction = PingInteraction -DiscordenoClient.PrivateChannel = PrivateChannel -DiscordenoClient.PrivateThreadChannel = PrivateThreadChannel -DiscordenoClient.PublicThreadChannel = PublicThreadChannel -DiscordenoClient.RequestHandler = RequestHandler -DiscordenoClient.Role = Role -DiscordenoClient.Shard = Shard -DiscordenoClient.StageChannel = StageChannel -DiscordenoClient.StageInstance = StageInstance -DiscordenoClient.TextChannel = TextChannel -DiscordenoClient.TextVoiceChannel = TextVoiceChannel -DiscordenoClient.ThreadChannel = ThreadChannel -DiscordenoClient.ThreadMember = ThreadMember -DiscordenoClient.UnavailableGuild = UnavailableGuild -DiscordenoClient.UnknownInteraction = UnknownInteraction -DiscordenoClient.User = User -DiscordenoClient.VERSION = VERSION -DiscordenoClient.VoiceChannel = VoiceChannel -DiscordenoClient.VoiceState = VoiceState - -export * from './Base.js' -export * from './Client.js' -export * from './Collection.js' -export * from './Constants.js' -export * from './Endpoints.js' -export * from './gateway/Shard.js' -export * from './gateway/ShardManager.js' -export * from './Structures/channels/Category.js' -export * from './Structures/channels/Channel.js' -export * from './Structures/channels/Guild.js' -export * from './Structures/channels/News.js' -export * from './Structures/channels/Private.js' -export * from './Structures/channels/Stage.js' -export * from './Structures/channels/Text.js' -export * from './Structures/channels/TextVoice.js' -export * from './Structures/channels/threads/Member.js' -export * from './Structures/channels/threads/NewsThread.js' -export * from './Structures/channels/threads/PrivateThread.js' -export * from './Structures/channels/threads/PublicThread.js' -export * from './Structures/channels/threads/Thread.js' -export * from './Structures/channels/Voice.js' -export * from './Structures/guilds/AuditLogEntry.js' -export * from './Structures/guilds/Guild.js' -export * from './Structures/guilds/Integration.js' -export * from './Structures/guilds/Member.js' -export * from './Structures/guilds/Preview.js' -export * from './Structures/guilds/Role.js' -export * from './Structures/guilds/StageInstance.js' -export * from './Structures/guilds/Template.js' -export * from './Structures/guilds/Unavailable.js' -export * from './Structures/guilds/VoiceState.js' -export * from './Structures/interactions/Autocomplete.js' -export * from './Structures/interactions/Command.js' -export * from './Structures/interactions/Component.js' -export * from './Structures/interactions/Interaction.js' -export * from './Structures/interactions/Ping.js' -export * from './Structures/interactions/Unknown.js' -export * from './Structures/Invite.js' -export * from './Structures/Message.js' -export * from './Structures/Permission.js' -export * from './Structures/PermissionOverwrite.js' -export * from './Structures/users/Extended.js' -export * from './Structures/users/User.js' -export * from './typings.js' -export * from './utils/BrowserWebSocket.js' -export * from './utils/Bucket.js' -export * from './utils/DiscordRESTError.js' -export * from './utils/generate.js' diff --git a/packages/client/src/typings.ts b/packages/client/src/typings.ts deleted file mode 100644 index 6e36184d7..000000000 --- a/packages/client/src/typings.ts +++ /dev/null @@ -1,1016 +0,0 @@ -/* eslint-disable @typescript-eslint/consistent-indexed-object-style */ -import type { - ActivityTypes, - ApplicationCommandOptionTypes, - ApplicationCommandPermissionTypes, - ApplicationCommandTypes, - ButtonStyles, - ChannelTypes, - DefaultMessageNotificationLevels, - ExplicitContentFilterLevels, - FormLayout, - GuildFeatures, - InteractionResponseTypes, - MessageComponentTypes, - OverwriteTypes, - ScheduledEventPrivacyLevel, - StickerFormatTypes, - StickerTypes, - VerificationLevels, - VideoQualityModes, - WebhookTypes -} from '@discordeno/types' -import type { IncomingHttpHeaders } from 'node:http' -import type Collection from './Collection.js' -import type { Guild, Invite } from './index.js' -import type CategoryChannel from './Structures/channels/Category.js' -import type NewsChannel from './Structures/channels/News.js' -import type PrivateChannel from './Structures/channels/Private.js' -import type StageChannel from './Structures/channels/Stage.js' -import type TextChannel from './Structures/channels/Text.js' -import type TextVoiceChannel from './Structures/channels/TextVoice.js' -import type ThreadMember from './Structures/channels/threads/Member.js' -import type NewsThreadChannel from './Structures/channels/threads/NewsThread.js' -import type PrivateThreadChannel from './Structures/channels/threads/PrivateThread.js' -import type PublicThreadChannel from './Structures/channels/threads/PublicThread.js' -import type ThreadChannel from './Structures/channels/threads/Thread.js' -import type GuildAuditLogEntry from './Structures/guilds/AuditLogEntry.js' -import type GuildIntegration from './Structures/guilds/Integration.js' -import type Member from './Structures/guilds/Member.js' -import type Role from './Structures/guilds/Role.js' -import type StageInstance from './Structures/guilds/StageInstance.js' -import type UnavailableGuild from './Structures/guilds/Unavailable.js' -import type AutocompleteInteraction from './Structures/interactions/Autocomplete.js' -import type CommandInteraction from './Structures/interactions/Command.js' -import type ComponentInteraction from './Structures/interactions/Component.js' -import type PingInteraction from './Structures/interactions/Ping.js' -import type UnknownInteraction from './Structures/interactions/Unknown.js' -import type Message from './Structures/Message.js' -import type Permission from './Structures/Permission.js' -import type User from './Structures/users/User.js' - -export type ApplicationCommandStructure = ChatInputApplicationCommandStructure | MessageApplicationCommandStructure | UserApplicationCommandStructure -export type ChatInputApplicationCommand = ApplicationCommand -export type ChatInputApplicationCommandStructure = Omit -export type MessageApplicationCommand = Omit, 'description' | 'options'> -export type MessageApplicationCommandStructure = Omit -export type UserApplicationCommand = Omit, 'description' | 'options'> -export type UserApplicationCommandStructure = Omit -export type ApplicationCommandOptions = - | ApplicationCommandOptionsSubCommand - | ApplicationCommandOptionsSubCommandGroup - | ApplicationCommandOptionsWithValue -export type ApplicationCommandOptionsBoolean = ApplicationCommandOption -export type ApplicationCommandOptionsChannel = ApplicationCommandOption -export type ApplicationCommandOptionsInteger = - | ApplicationCommandOptionsIntegerWithAutocomplete - | ApplicationCommandOptionsIntegerWithoutAutocomplete - | ApplicationCommandOptionsIntegerWithMinMax -export type ApplicationCommandOptionsIntegerWithAutocomplete = Omit< - ApplicationCommandOptionWithChoices, - 'choices' | 'min_value' | 'max_value' -> & - AutocompleteEnabled -export type ApplicationCommandOptionsIntegerWithoutAutocomplete = Omit< - ApplicationCommandOptionWithChoices, - 'autocomplete' | 'min_value' | 'max_value' -> & - AutocompleteDisabledInteger -export type ApplicationCommandOptionsIntegerWithMinMax = Omit< - ApplicationCommandOptionWithChoices, - 'choices' | 'autocomplete' -> & - AutocompleteDisabledIntegerMinMax -export type ApplicationCommandOptionsMentionable = ApplicationCommandOption -export type ApplicationCommandOptionsNumber = - | ApplicationCommandOptionsNumberWithAutocomplete - | ApplicationCommandOptionsNumberWithoutAutocomplete - | ApplicationCommandOptionsNumberWithMinMax -export type ApplicationCommandOptionsNumberWithAutocomplete = Omit< - ApplicationCommandOptionWithChoices, - 'choices' | 'min_value' | 'max_value' -> & - AutocompleteEnabled -export type ApplicationCommandOptionsNumberWithoutAutocomplete = Omit< - ApplicationCommandOptionWithChoices, - 'autocomplete' | 'min_value' | 'max_value' -> & - AutocompleteDisabledInteger -export type ApplicationCommandOptionsNumberWithMinMax = Omit< - ApplicationCommandOptionWithChoices, - 'choices' | 'autocomplete' -> & - AutocompleteDisabledIntegerMinMax -export type ApplicationCommandOptionsRole = ApplicationCommandOption -export type ApplicationCommandOptionsString = ApplicationCommandOptionsStringWithAutocomplete | ApplicationCommandOptionsStringWithoutAutocomplete -export type ApplicationCommandOptionsStringWithAutocomplete = Omit< - ApplicationCommandOptionWithChoices, - 'choices' -> & - AutocompleteEnabled -export type ApplicationCommandOptionsStringWithoutAutocomplete = Omit< - ApplicationCommandOptionWithChoices, - 'autocomplete' -> & - AutocompleteDisabled -export type ApplicationCommandOptionsUser = ApplicationCommandOption -export type ApplicationCommandOptionsWithValue = - | ApplicationCommandOptionsString - | ApplicationCommandOptionsInteger - | ApplicationCommandOptionsBoolean - | ApplicationCommandOptionsUser - | ApplicationCommandOptionsChannel - | ApplicationCommandOptionsRole - | ApplicationCommandOptionsMentionable - | ApplicationCommandOptionsNumber - -export interface Uncached { - id: string -} - -export type AnyChannel = AnyGuildChannel | PrivateChannel | PossiblyUncachedTextable -export type AnyGuildChannel = GuildTextableChannel | AnyVoiceChannel | CategoryChannel | AnyThreadChannel -export type AnyThreadChannel = NewsThreadChannel | PrivateThreadChannel | PublicThreadChannel -export type AnyVoiceChannel = TextVoiceChannel | StageChannel -export type GuildTextableChannel = TextChannel | TextVoiceChannel | NewsChannel -export type PossiblyUncachedTextable = Textable | Uncached -export type TextableChannel = (GuildTextable & GuildTextableChannel) | (ThreadTextable & AnyThreadChannel) | (Textable & PrivateChannel) -export type VideoQualityMode = VideoQualityModes.Auto | VideoQualityModes.Full -export type TextVoiceChannelTypes = ChannelTypes.GuildVoice | ChannelTypes.GuildStageVoice -export type InteractionContent = Pick -export type InteractionContentEdit = Pick -export type ActionRowComponents = Button | SelectMenu -export type Button = InteractionButton | URLButton -export type MessageContent = string | AdvancedMessageContent -export type MessageContentEdit = string | AdvancedMessageContentEdit -export type ActivityType = ActivityTypes -export type BotActivityType = Exclude -export type Status = 'online' | 'idle' | 'dnd' -export type SelfStatus = Status | 'invisible' -export type AutoArchiveDuration = 60 | 1440 | 4320 | 10080 -export type MessageWebhookContent = Pick -export interface JSONCache { - [s: string]: unknown -} -export interface SimpleJSON { - toJSON: (props?: string[]) => JSONCache -} - -export interface ApplicationCommand { - application_id: string - defaultPermission?: boolean - description: T extends ApplicationCommandTypes.ChatInput ? string : never - guild_id?: string - id: string - name: string - options?: ApplicationCommandOptions[] - type: T -} -export interface ApplicationCommandOptionsSubCommand { - description: string - name: string - options?: ApplicationCommandOptionsWithValue[] - type: ApplicationCommandOptionTypes.SubCommand -} -export interface ApplicationCommandOptionsSubCommandGroup { - description: string - name: string - options?: Array - type: ApplicationCommandOptionTypes.SubCommandGroup -} -export interface ApplicationCommandOptionWithChoices< - T extends ApplicationCommandOptionTypes.String | ApplicationCommandOptionTypes.Integer | ApplicationCommandOptionTypes.Number = - | ApplicationCommandOptionTypes.String - | ApplicationCommandOptionTypes.Integer - | ApplicationCommandOptionTypes.Number, -> { - autocomplete?: boolean - choices?: ApplicationCommandOptionChoice[] - description: string - name: string - required?: boolean - type: T -} -export interface ApplicationCommandOption< - T extends Exclude, -> { - channel_types: T extends ApplicationCommandOptionTypes.Channel ? ChannelTypes | undefined : never - description: string - name: string - required?: boolean - type: T -} -export interface ApplicationCommandPermissions { - id: string - permission: boolean - type: ApplicationCommandPermissionTypes -} -export interface AutocompleteEnabled { - autocomplete: true -} -export interface AutocompleteDisabled { - autocomplete?: false -} -export interface AutocompleteDisabledInteger extends AutocompleteDisabled { - min_value?: null - max_value?: null -} -export interface AutocompleteDisabledIntegerMinMax extends AutocompleteDisabled { - choices?: null -} -export interface GuildApplicationCommandPermissions { - application_id: string - guild_id: string - id: string - permissions?: ApplicationCommandPermissions[] -} - -export interface ChannelFollow { - channel_id: string - webhook_id: string -} -export interface ChannelPosition { - id: string - position: number - lockPermissions?: boolean - parentID?: string -} -export interface CreateChannelOptions { - bitrate?: number - nsfw?: boolean - parentID?: string - permissionOverwrites?: Overwrite[] - position?: number - rateLimitPerUser?: number - reason?: string - topic?: string - userLimit?: number -} -export interface EditChannelOptions extends Omit { - archived?: boolean - autoArchiveDuration?: AutoArchiveDuration - defaultAutoArchiveDuration?: AutoArchiveDuration - icon?: string - invitable?: boolean - locked?: boolean - name?: string - ownerID?: string - rtcRegion?: string | null - videoQualityMode?: VideoQualityModes - defaultForumLayout: FormLayout -} -export interface EditChannelPositionOptions { - lockPermissions?: string - parentID?: string -} -export interface GetMessagesOptions { - after?: string - around?: string - before?: string - limit?: number -} -export interface GuildTextable extends Textable { - rateLimitPerUser: number - createWebhook: (options: { name: string; avatar?: string | null }, reason?: string) => Promise - deleteMessages: (messageIDs: string[], reason?: string) => Promise - getWebhooks: () => Promise - purge: (options: PurgeChannelOptions) => Promise - removeMessageReactionEmoji: (messageID: string, reaction: string) => Promise - removeMessageReactions: (messageID: string) => Promise -} -export interface PartialChannel { - bitrate?: number - id: string - name?: string - nsfw?: boolean - parent_id?: number - permission_overwrites?: Overwrite[] - rate_limit_per_user?: number - topic?: string - type: number - user_limit?: number -} -export interface Pinnable { - getPins: () => Promise - pinMessage: (messageID: string) => Promise - unpinMessage: (messageID: string) => Promise -} -export interface PurgeChannelOptions { - after?: string - before?: string - filter?: (m: Message) => boolean - limit: number - reason?: string -} -export interface Textable { - id: string - lastMessageID: string - messages: Collection - addMessageReaction: (messageID: string, reaction: string) => Promise - createMessage: (content: MessageContent, file?: FileContent | FileContent[]) => Promise - deleteMessage: (messageID: string, reason?: string) => Promise - editMessage: (messageID: string, content: MessageContentEdit) => Promise - getMessage: (messageID: string) => Promise - getMessageReaction: (messageID: string, reaction: string, options?: GetMessageReactionOptions) => Promise - getMessages: (options?: GetMessagesOptions) => Promise - removeMessageReaction: (messageID: string, reaction: string, userID?: string) => Promise - sendTyping: () => Promise - unsendMessage: (messageID: string) => Promise -} -export interface ThreadTextable extends Textable, Pinnable { - lastPinTimestamp?: number - deleteMessages: (messageIDs: string[], reason?: string) => Promise - getMembers: () => Promise - join: (userID: string) => Promise - leave: (userID: string) => Promise - purge: (options: PurgeChannelOptions) => Promise - removeMessageReactionEmoji: (messageID: string, reaction: string) => Promise - removeMessageReactions: (messageID: string) => Promise -} -export interface RequestHandlerOptions { - baseURL?: string - decodeReasons?: boolean - disableLatencyCompensation?: boolean - domain?: string - latencyThreshold?: number - ratelimiterOffset?: number - requestTimeout?: number -} - -export interface EmbedAuthorOptions { - icon_url?: string - name: string - url?: string -} -export interface EmbedField { - inline?: boolean - name: string - value: string -} -export interface EmbedFooterOptions { - icon_url?: string - text: string -} -export interface EmbedImageOptions { - url?: string -} -export interface EmbedOptions { - author?: EmbedAuthorOptions - color?: number - description?: string - fields?: EmbedField[] - footer?: EmbedFooterOptions - image?: EmbedImageOptions - thumbnail?: EmbedImageOptions - timestamp?: Date | string - title?: string - url?: string -} -export interface Emoji extends EmojiBase { - animated: boolean - available: boolean - id: string - managed: boolean - require_colons: boolean - roles: string[] - user?: PartialUser -} -export interface EmojiBase { - icon?: string - name: string -} -export interface EmojiOptions extends Exclude { - image: string - roles?: string[] -} -export interface PartialEmoji { - id: string | null - name: string - animated?: boolean -} - -export interface RequestMembersPromise { - members: Member[] - received: number - res: (value: Member[]) => void - timeout: number -} -export interface ShardManagerOptions { - concurrency?: number | 'auto' -} - -export interface CreateGuildOptions { - afkChannelID?: string - afkTimeout?: number - channels?: PartialChannel[] - defaultNotifications?: DefaultMessageNotificationLevels - explicitContentFilter?: ExplicitContentFilterLevels - icon?: string - roles?: PartialRole[] - systemChannelID: string - verificationLevel?: VerificationLevels -} -export interface DiscoveryCategory { - id: number - is_primary: boolean - name: { - default: string - localizations?: { [lang: string]: string } - } -} -export interface DiscoveryMetadata { - category_ids: number[] - emoji_discoverability_enabled: boolean - guild_id: string - keywords: string[] | null - primary_category_id: number -} -export interface DiscoveryOptions { - emojiDiscoverabilityEnabled?: boolean - keywords?: string[] - primaryCategoryID?: string - reason?: string -} -export interface DiscoverySubcategoryResponse { - category_id: number - guild_id: string -} -export interface GetGuildAuditLogOptions { - actionType?: number - before?: string - after?: string - limit?: number - userID?: string -} -export interface GetGuildBansOptions { - after?: string - before?: string - limit?: number -} -export interface GetPruneOptions { - days?: number - includeRoles?: string[] -} -export interface GetRESTGuildMembersOptions { - after?: string - limit?: number -} -export interface GetRESTGuildsOptions { - after?: string - before?: string - limit?: number -} -export interface GuildAuditLog { - entries: GuildAuditLogEntry[] - integrations: GuildIntegration[] - threads: AnyThreadChannel[] - users: User[] - webhooks: Webhook[] -} -export interface GuildBan { - reason?: string - user: User -} -export interface GuildOptions { - afkChannelID?: string - afkTimeout?: number - banner?: string - defaultNotifications?: DefaultMessageNotificationLevels - description?: string - discoverySplash?: string - explicitContentFilter?: ExplicitContentFilterLevels - features?: GuildFeatures[] - icon?: string - name?: string - ownerID?: string - preferredLocale?: string - publicUpdatesChannelID?: string - rulesChannelID?: string - splash?: string - systemChannelFlags?: number - systemChannelID?: string - verificationLevel?: VerificationLevels -} -export interface GuildTemplateOptions { - name?: string - description?: string | null -} -export interface GuildVanity { - code: string | null - uses: number -} -export interface IntegrationOptions { - enableEmoticons?: string - expireBehavior?: string - expireGracePeriod?: string -} -export interface PruneMemberOptions extends GetPruneOptions { - computePruneCount?: boolean - reason?: string -} -export interface VoiceRegion { - custom: boolean - deprecated: boolean - id: string - name: string - optimal: boolean - vip: boolean -} -export interface WelcomeChannel { - channelID: string - description: string - emojiID: string | null - emojiName: string | null -} -export interface WelcomeScreen { - description: string - welcomeChannels: WelcomeChannel[] -} -export interface WelcomeScreenOptions extends WelcomeScreen { - enabled: boolean -} -export interface Widget { - channel_id?: string - enabled: boolean -} -export interface WidgetChannel { - id: string - name: string - position: number -} -export interface WidgetData { - channels: WidgetChannel[] - id: string - instant_invite: string - members: WidgetMember[] - name: string - presence_count: number -} -export interface WidgetMember { - avatar: string | null - avatar_url: string - discriminator: string - id: string - status: string - username: string -} - -/** https://discord.com/developers/docs/interactions/slash-commands#interaction-response */ -export interface InteractionResponse { - type: InteractionResponseTypes - data?: InteractionApplicationCommandCallbackData -} - -export interface InteractionApplicationCommandCallbackData { - content?: string - tts?: boolean - embeds?: EmbedOptions[] - allowedMentions?: AllowedMentions - file?: FileContent | FileContent[] - customId?: string - title?: string - components?: ActionRow[] - flags?: number - choices?: ApplicationCommandOptionChoice[] -} - -export interface ApplicationCommandOptionChoice { - name: string - value: string | number -} - -export interface CreateChannelInviteOptions extends CreateInviteOptions { - targetApplicationID?: string - targetType?: InviteTargetTypes - targetUserID?: string -} -export interface CreateInviteOptions { - maxAge?: number - maxUses?: number - temporary?: boolean - unique?: boolean -} -export interface FetchMembersOptions { - limit?: number - presences?: boolean - query?: string - timeout?: number - userIDs?: string[] -} -export interface MemberOptions { - channelID?: string | null - communicationDisabledUntil?: Date | null - deaf?: boolean - mute?: boolean - nick?: string | null - roles?: string[] -} -export interface PartialUser { - accentColor?: number | null - avatar: string | null - banner?: string | null - discriminator: string - id: string - username: string -} -export interface RequestGuildMembersOptions extends Omit { - nonce: string - user_ids?: string[] -} -export interface ActionRow { - components: ActionRowComponents[] - type: MessageComponentTypes.ActionRow -} -export interface AdvancedMessageContent { - allowedMentions?: AllowedMentions - components?: ActionRow[] - content?: string - embed?: EmbedOptions - embeds?: EmbedOptions[] - flags?: number - messageReference?: MessageReferenceReply - stickerIDs?: string[] - tts?: boolean -} -export interface AdvancedMessageContentEdit extends AdvancedMessageContent { - file?: FileContent | FileContent[] -} -export interface AllowedMentions { - /** Whether or not to allow mentioning @everyone */ - everyone?: boolean - /** Whether or not to allow mentioning the replied user. */ - repliedUser?: boolean - /** The roles to allow mentioning by default or enable all roles to be able to be mentioned by default. */ - roles?: boolean | string[] - /** The users to allow mentioning by default or enable all users to be able to be mentioned by default. */ - users?: boolean | string[] -} -export interface ButtonBase { - disabled?: boolean - emoji?: Partial - label?: string - type: MessageComponentTypes.Button -} -export interface CreateStickerOptions extends Required> { - file: FileContent -} -export interface EditStickerOptions { - description?: string - name?: string - tags?: string -} -export interface SelectMenu { - custom_id: string - disabled?: boolean - max_values?: number - min_values?: number - options: SelectMenuOptions[] - placeholder?: string - type: MessageComponentTypes.SelectMenu -} -export interface SelectMenuOptions { - default?: boolean - description?: string - emoji?: Partial - label: string - value: string -} -export interface GetMessageReactionOptions { - after?: string - limit?: number -} - -export interface InteractionButton extends ButtonBase { - custom_id: string - style: Exclude -} -export interface FileContent { - /** The file data. */ - file: Buffer | string - /** The name of the file, which must include the file suffix. */ - name: string -} -export interface MessageReferenceBase { - channelID?: string - guildID?: string - messageID?: string -} -export interface MessageReferenceReply extends MessageReferenceBase { - messageID: string - failIfNotExists?: boolean -} -export interface Sticker extends StickerItems { - available?: boolean - description: string - guild_id?: string - pack_id?: string - sort_value?: number - tags: string - type: StickerTypes - user?: User -} -export interface StickerItems { - id: string - name: string - format_type: StickerFormatTypes -} -export interface StickerPack { - id: string - stickers: Sticker[] - name: string - sku_id: string - cover_sticker_id?: string - description: string - banner_asset_id: string -} -export interface URLButton extends ButtonBase { - style: ButtonStyles.Link - url: string -} - -export interface Activity extends ActivityPartial { - application_id?: string - assets?: { - large_image?: string - large_text?: string - small_image?: string - small_text?: string - [key: string]: unknown - } - created_at: number - details?: string - emoji?: { animated?: boolean; id?: string; name: string } - flags?: number - instance?: boolean - party?: { id?: string; size?: [number, number] } - secrets?: { join?: string; spectate?: string; match?: string } - state?: string - timestamps?: { end?: number; start: number } - type: T - [key: string]: unknown -} -export interface ActivityPartial { - name: string - type?: T - url?: string -} -export interface ClientPresence { - activities: Activity[] | null - afk: boolean - since: number | null - status: SelfStatus -} -export interface Overwrite { - allow: bigint | number - deny: bigint | number - id: string - type: OverwriteTypes -} -export interface PartialRole { - color?: number - hoist?: boolean - id: string - mentionable?: boolean - name?: string - permissions?: number - position?: number -} -export interface RoleOptions { - color?: number - hoist?: boolean - icon?: string - mentionable?: boolean - name?: string - permissions?: bigint | number | string | Permission - unicodeEmoji?: string -} -export interface CreateThreadOptions { - autoArchiveDuration: AutoArchiveDuration - name: string -} -export interface CreateThreadWithoutMessageOptions { - name: string - autoArchiveDuration: 60 | 1440 | 4320 | 10080 - rateLimitPerUser?: number | null - reason?: string - type: ChannelTypes.AnnouncementThread | ChannelTypes.PublicThread | ChannelTypes.PrivateThread - invitable?: boolean -} -export interface GetArchivedThreadsOptions { - before?: Date - limit?: number -} -export interface ListedChannelThreads extends ListedGuildThreads { - hasMore: boolean -} -export interface ListedGuildThreads { - members: ThreadMember[] - threads: T[] -} -export interface JoinVoiceChannelOptions { - opusOnly?: boolean - selfDeaf?: boolean - selfMute?: boolean - shared?: boolean -} -export interface StageInstanceOptions { - privacyLevel?: ScheduledEventPrivacyLevel - topic?: string -} -export interface VoiceStateOptions { - channelID: string - requestToSpeakTimestamp?: Date | null - suppress?: boolean -} -export interface Webhook { - application_id: string | null - avatar: string | null - channel_id: string | null - guild_id: string | null - id: string - name: string - source_channel?: { id: string; name: string } - source_guild: { icon: string | null; id: string; name: string } - token?: string - type: WebhookTypes - url?: string - user?: PartialUser -} -export interface WebhookOptions { - avatar?: string - channelID?: string - name?: string -} -export interface WebhookPayload { - allowedMentions?: AllowedMentions - auth?: boolean - avatarURL?: string - components?: ActionRow[] - content?: string - embed?: EmbedOptions - embeds?: EmbedOptions[] - file?: FileContent | FileContent[] - flags?: number - threadID?: string - tts?: boolean - username?: string - wait?: boolean -} - -export interface OAuthApplicationInfo { - bot_public: boolean - bot_require_code_grant: boolean - description: string - icon?: string - id: string - name: string - owner: { - avatar?: string - discriminator: string - id: string - username: string - } - team: OAuthTeamInfo | null -} -export interface OAuthTeamInfo { - icon: string | null - id: string - members: OAuthTeamMember[] - owner_user_id: string -} -export interface OAuthTeamMember { - membership_state: number - permissions: string[] - team_id: string - user: PartialUser -} -export enum InviteTargetTypes { - STREAM = 1, - EMBEDDED_APPLICATION, -} - -export interface EventListeners { - channelCreate: [channel: AnyGuildChannel] - channelDelete: [channel: AnyChannel] - channelPinUpdate: [channel: TextableChannel, timestamp: number, oldTimestamp: number] - channelUpdate: [channel: AnyGuildChannel, oldChannel: any] - connect: [id: number] - debug: [message: string, id?: number] - disconnect: [] - error: [err: Error, id?: number] - guildAvailable: [guild: Guild] - guildBanAdd: [guild: Guild, user: User] - guildBanRemove: [guild: Guild, user: User] - guildCreate: [guild: Guild] - guildDelete: [guild: any] - guildEmojisUpdate: [guild: any, emojis: Emoji[], oldEmojis: Emoji[] | null] - guildMemberAdd: [guild: Guild, member: Member] - guildMemberChunk: [guild: Guild, member: Member[]] - guildMemberRemove: [guild: Guild, member: Member | any] - guildMemberUpdate: [guild: Guild, member: Member, oldMember: any | null] - guildRoleCreate: [guild: Guild, role: Role] - guildRoleDelete: [guild: Guild, role: Role] - guildRoleUpdate: [guild: Guild, role: Role, oldRole: any] - guildScheduledEventCreate: [event: any] - guildScheduledEventDelete: [event: any] - guildScheduledEventUpdate: [event: any, oldEvent: any | null] - guildScheduledEventUserAdd: [event: any, user: User | Uncached] - guildScheduledEventUserRemove: [event: any, user: User | Uncached] - guildStickersUpdate: [guild: any, stickers: Sticker[], oldStickers: Sticker[] | null] - guildUnavailable: [guild: UnavailableGuild] - guildUpdate: [guild: Guild, oldGuild: any] - hello: [trace: string[], id: number] - interactionCreate: [interaction: PingInteraction | CommandInteraction | ComponentInteraction | AutocompleteInteraction | UnknownInteraction] - inviteCreate: [guild: Guild, invite: Invite] - inviteDelete: [guild: Guild, invite: Invite] - messageCreate: [message: Message] - messageDelete: [message: any] - messageDeleteBulk: [messages: any[]] - messageReactionAdd: [message: any, emoji: PartialEmoji, reactor: Member | Uncached] - messageReactionRemove: [message: any, emoji: PartialEmoji, userID: string] - messageReactionRemoveAll: [message: any] - messageReactionRemoveEmoji: [message: any, emoji: PartialEmoji] - messageUpdate: [message: Message, oldMessage: any | null] - presenceUpdate: [other: Member, oldPresence: any | null] - rawREST: [request: any] - rawWS: [packet: any, id: number] - ready: [] - shardPreReady: [id: number] - stageInstanceCreate: [stageInstance: StageInstance] - stageInstanceDelete: [stageInstance: StageInstance] - stageInstanceUpdate: [stageInstance: StageInstance, oldStageInstance: any | null] - threadCreate: [channel: AnyThreadChannel] - threadDelete: [channel: AnyThreadChannel] - threadListSync: [ - guild: Guild, - deletedThreads: Array, - activeThreads: AnyThreadChannel[], - joinedThreadsMember: ThreadMember[], - ] - threadMembersUpdate: [channel: AnyThreadChannel, addedMembers: ThreadMember[], removedMembers: Array] - threadMemberUpdate: [channel: AnyThreadChannel, member: ThreadMember, oldMember: any] - threadUpdate: [channel: AnyThreadChannel, oldChannel: any | null] - typingStart: - | [channel: GuildTextableChannel | Uncached, user: User | Uncached, member: Member] - | [channel: PrivateChannel | Uncached, user: User | Uncached, member: null] - unavailableGuildCreate: [guild: UnavailableGuild] - unknown: [packet: any, id?: number] - userUpdate: [user: User, oldUser: PartialUser | null] - voiceChannelJoin: [member: Member, channel: AnyVoiceChannel] - voiceChannelLeave: [member: Member, channel: AnyVoiceChannel] - voiceChannelSwitch: [member: Member, newChannel: AnyVoiceChannel, oldChannel: AnyVoiceChannel] - voiceStateUpdate: [member: Member, oldState: any] - warn: [message: string, id?: number] - webhooksUpdate: [data: any] -} - -export interface ClientEvents extends EventListeners { - shardDisconnect: [err: Error | undefined, id: number] - shardReady: [id: number] - shardResume: [id: number] -} - -export interface HTTPResponse { - code: number - message: string - errors?: HTTPResponse - headers: IncomingHttpHeaders -} - -export type AnyInteraction = PingInteraction | CommandInteraction | ComponentInteraction | AutocompleteInteraction -export type InteractionDataOptions = InteractionDataOptionsSubCommand | InteractionDataOptionsSubCommandGroup | InteractionDataOptionsWithValue -export type InteractionDataOptionsBoolean = InteractionDataOptionWithValue -export type InteractionDataOptionsChannel = InteractionDataOptionWithValue -export type InteractionDataOptionsInteger = InteractionDataOptionWithValue -export type InteractionDataOptionsMentionable = InteractionDataOptionWithValue -export type InteractionDataOptionsNumber = InteractionDataOptionWithValue -export type InteractionDataOptionsRole = InteractionDataOptionWithValue -export type InteractionDataOptionsString = InteractionDataOptionWithValue -export type InteractionDataOptionsUser = InteractionDataOptionWithValue -export type InteractionDataOptionsWithValue = - | InteractionDataOptionsString - | InteractionDataOptionsInteger - | InteractionDataOptionsBoolean - | InteractionDataOptionsUser - | InteractionDataOptionsChannel - | InteractionDataOptionsRole - | InteractionDataOptionsMentionable - | InteractionDataOptionsNumber - -export interface InteractionDataOptionWithValue { - focused?: boolean - name: string - type: T - value: V -} - -export interface InteractionDataOptionsSubCommand { - name: string - options?: InteractionDataOptions[] - type: ApplicationCommandOptionTypes.SubCommand -} -export interface InteractionDataOptionsSubCommandGroup { - name: string - options: InteractionDataOptions[] - type: ApplicationCommandOptionTypes.SubCommandGroup -} diff --git a/packages/client/src/utils/BrowserWebSocket.ts b/packages/client/src/utils/BrowserWebSocket.ts deleted file mode 100644 index 9ab22447d..000000000 --- a/packages/client/src/utils/BrowserWebSocket.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* eslint-disable @typescript-eslint/class-literal-property-style */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/no-var-requires */ -import { EventEmitter } from 'node:events' - -class BrowserWebSocketError extends Error { - static CONNECTING: 0 = 0 - static OPEN: 1 - static CLOSING: 2 - static CLOSED: 3 - - readyState: number = 0 - event: Event - - constructor(message: string | undefined, event: Event) { - super(message) - - this.event = event - } -} - -/** - * Represents a browser's websocket usable by Eris - * @extends EventEmitter - * @prop {String} url The URL to connect to - */ -class BrowserWebSocket extends EventEmitter { - _ws: WebSocket - - constructor(url: string) { - super() - - if (typeof window === 'undefined') { - throw new Error('BrowserWebSocket cannot be used outside of a browser environment') - } - - this._ws = new window.WebSocket(url) - this._ws.onopen = () => this.emit('open') - this._ws.onmessage = this._onMessage.bind(this) - this._ws.onerror = (event) => this.emit('error', new BrowserWebSocketError('Unknown error', event)) - this._ws.onclose = (event) => this.emit('close', event.code, event.reason) - } - - get readyState() { - return this._ws.readyState - } - - static get CONNECTING() { - return 0 - } - - static set CONNECTING(state: number) { - BrowserWebSocket.CONNECTING = state - } - - static get OPEN() { - return 1 - } - - static set OPEN(state: number) { - BrowserWebSocket.OPEN = state - } - - static get CLOSING() { - return 2 - } - - static set CLOSING(state: number) { - BrowserWebSocket.CLOSING = state - } - - static get CLOSED() { - return 3 - } - - static set CLOSED(state: number) { - BrowserWebSocket.CLOSED = state - } - - close(code?: number, reason?: string) { - return this._ws.close(code, reason) - } - - removeEventListener(type: string | symbol, listener: (...args: any[]) => void): this { - return this.removeListener(type, listener) - } - - send(data: string | ArrayBufferLike | Blob | ArrayBufferView) { - return this._ws.send(data) - } - - terminate() { - return this._ws.close() - } - - async _onMessage(event: MessageEvent) { - if (event.data instanceof window.Blob) { - this.emit('message', await event.data.arrayBuffer()) - } else { - this.emit('message', event.data) - } - } -} - -export default BrowserWebSocket diff --git a/packages/client/src/utils/Bucket.ts b/packages/client/src/utils/Bucket.ts deleted file mode 100644 index e3dd20de2..000000000 --- a/packages/client/src/utils/Bucket.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -interface BucketOptions { - latencyRef?: { latency: number } - reservedTokens?: number -} - -/** - * Handle ratelimiting something - * @prop {Number} interval How long (in ms) to wait between clearing used tokens - * @prop {Number} lastReset Timestamp of last token clearing - * @prop {Number} lastSend Timestamp of last token consumption - * @prop {Number} tokenLimit The max number tokens the bucket can consume per interval - * @prop {Number} tokens How many tokens the bucket has consumed in this interval - */ -class Bucket { - interval: number - latencyRef: { latency: number } - lastReset: number - lastSend: number - tokenLimit: number - tokens: number - reservedTokens: number - // eslint-disable-next-line @typescript-eslint/ban-types - _queue: Array<{ func: Function; priority: boolean }> - timeout: NodeJS.Timeout | null = null - - /** - * Construct a Bucket - * @arg {Number} tokenLimit The max number of tokens the bucket can consume per interval - * @arg {Number} interval How long (in ms) to wait between clearing used tokens - * @arg {Object} [options] Optional parameters - * @arg {Object} options.latencyRef A latency reference object - * @arg {Number} options.latencyRef.latency Interval between consuming tokens - * @arg {Number} options.reservedTokens How many tokens to reserve for priority operations - */ - constructor(tokenLimit: number, interval: number, options: BucketOptions = {} as BucketOptions) { - this.tokenLimit = tokenLimit - this.interval = interval - this.latencyRef = options.latencyRef ?? { latency: 0 } - this.lastReset = this.tokens = this.lastSend = 0 - this.reservedTokens = options.reservedTokens ?? 0 - this._queue = [] - } - - check() { - if (this.timeout ?? this._queue.length === 0) { - return - } - if (this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency < Date.now()) { - this.lastReset = Date.now() - this.tokens = Math.max(0, this.tokens - this.tokenLimit) - } - - let val - let tokensAvailable = this.tokens < this.tokenLimit - let unreservedTokensAvailable = this.tokens < this.tokenLimit - this.reservedTokens - while (this._queue.length > 0 && (unreservedTokensAvailable || (tokensAvailable && this._queue[0].priority))) { - this.tokens++ - tokensAvailable = this.tokens < this.tokenLimit - unreservedTokensAvailable = this.tokens < this.tokenLimit - this.reservedTokens - const item = this._queue.shift() - val = this.latencyRef.latency - Date.now() + this.lastSend - if (this.latencyRef.latency === 0 || val <= 0) { - item!.func() - this.lastSend = Date.now() - } else { - setTimeout(() => { - item!.func() - }, val) - this.lastSend = Date.now() + val - } - } - - if (this._queue.length > 0 && !this.timeout) { - this.timeout = setTimeout( - () => { - this.timeout = null - this.check() - }, - this.tokens < this.tokenLimit - ? this.latencyRef.latency - : Math.max(0, this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency - Date.now()), - ) - } - } - - /** - * Queue something in the Bucket - * @arg {Function} func A callback to call when a token can be consumed - * @arg {Boolean} [priority=false] Whether or not the callback should use reserved tokens - */ - queue(func: () => void, priority = false) { - if (priority) { - this._queue.unshift({ func, priority }) - } else { - this._queue.push({ func, priority }) - } - this.check() - } -} - -export default Bucket diff --git a/packages/client/src/utils/DiscordRESTError.ts b/packages/client/src/utils/DiscordRESTError.ts deleted file mode 100644 index c93a5f75d..000000000 --- a/packages/client/src/utils/DiscordRESTError.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { ClientRequest, IncomingHttpHeaders, IncomingMessage } from 'http' -import type { HTTPResponse } from '../typings.js' - -export class DiscordRESTError extends Error { - code: number = -1 - req!: ClientRequest - res!: IncomingMessage - response!: HTTPResponse - - constructor(req: ClientRequest, res: IncomingMessage, response: HTTPResponse, stack: string) { - super() - - Object.defineProperty(this, 'req', { - enumerable: false, - value: req, - }) - Object.defineProperty(this, 'res', { - enumerable: false, - value: res, - }) - Object.defineProperty(this, 'response', { - enumerable: false, - value: response, - }) - - Object.defineProperty(this, 'code', { - enumerable: false, - value: +response.code || -1, - }) - let message = response.message || 'Unknown error' - if (response.errors) { - message += '\n ' + this.flattenErrors(response.errors).join('\n ') - } else { - const errors = this.flattenErrors(response) - if (errors.length > 0) { - message += '\n ' + errors.join('\n ') - } - } - Object.defineProperty(this, 'message', { - enumerable: false, - value: message, - }) - - if (stack) { - this.stack = this.name + ': ' + this.message + '\n' + stack - } else { - Error.captureStackTrace(this, DiscordRESTError) - } - } - - get headers(): IncomingHttpHeaders { - return this.response.headers - } - - get name(): string { - return `${this.constructor.name} [${this.code}]` - } - - flattenErrors(errors: HTTPResponse, keyPrefix?: string): string[] { - let messages: string[] = [] - for (const fieldName of Object.keys(errors)) { - if (fieldName === 'message' || fieldName === 'code') { - continue - } - - const prefix = `${keyPrefix ?? ""}${fieldName}`; - - // @ts-expect-error js hack from eris - if (errors[fieldName]._errors) { - // @ts-expect-error js hack from eris - messages = messages.concat(errors[fieldName]._errors.map((obj: any) => `${prefix}: ${obj.message as string}`)) - // @ts-expect-error js hack from eris - } else if (Array.isArray(errors[fieldName])) { - // @ts-expect-error js hack from eris - messages = messages.concat(errors[fieldName].map((str: string) => `${prefix}: ${str}`)) - // @ts-expect-error js hack from eris - } else if (typeof errors[fieldName] === 'object') { - // @ts-expect-error js hack from eris - messages = messages.concat(this.flattenErrors(errors[fieldName], `${prefix}.`)) - } - } - return messages - } -} - -export default DiscordRESTError diff --git a/packages/client/src/utils/generate.ts b/packages/client/src/utils/generate.ts deleted file mode 100644 index 63db915da..000000000 --- a/packages/client/src/utils/generate.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { DiscordChannel, DiscordInteraction } from '@discordeno/types' -import { ChannelTypes, InteractionTypes } from '@discordeno/types' -import type Client from '../Client.js' -import type { TextableChannel } from '../index.js' -import CategoryChannel from '../Structures/channels/Category.js' -import Channel from '../Structures/channels/Channel.js' -import GuildChannel from '../Structures/channels/Guild.js' -import NewsChannel from '../Structures/channels/News.js' -import PrivateChannel from '../Structures/channels/Private.js' -import StageChannel from '../Structures/channels/Stage.js' -import TextChannel from '../Structures/channels/Text.js' -import TextVoiceChannel from '../Structures/channels/TextVoice.js' -import NewsThreadChannel from '../Structures/channels/threads/NewsThread.js' -import PrivateThreadChannel from '../Structures/channels/threads/PrivateThread.js' -import PublicThreadChannel from '../Structures/channels/threads/PublicThread.js' -import AutocompleteInteraction from '../Structures/interactions/Autocomplete.js' -import CommandInteraction from '../Structures/interactions/Command.js' -import ComponentInteraction from '../Structures/interactions/Component.js' -import PingInteraction from '../Structures/interactions/Ping.js' -import UnknownInteraction from '../Structures/interactions/Unknown.js' - -export function generateChannelFrom(data: DiscordChannel, client: Client): Channel { - switch (data.type) { - case ChannelTypes.GuildText: { - return new TextChannel(data, client) - } - case ChannelTypes.DM: { - return new PrivateChannel(data, client) - } - case ChannelTypes.GuildVoice: { - return new TextVoiceChannel(data, client) - } - case ChannelTypes.GuildCategory: { - return new CategoryChannel(data, client) - } - case ChannelTypes.GuildAnnouncement: { - return new NewsChannel(data, client) - } - case ChannelTypes.AnnouncementThread: { - return new NewsThreadChannel(data, client) - } - case ChannelTypes.PublicThread: { - return new PublicThreadChannel(data, client) - } - case ChannelTypes.PrivateThread: { - return new PrivateThreadChannel(data, client) - } - case ChannelTypes.GuildStageVoice: { - return new StageChannel(data, client) - } - } - if (data.guild_id) { - if (data.last_message_id !== undefined) { - client.emit('warn', new Error(`Unknown guild text channel type: ${data.type}\n${JSON.stringify(data)}`)) - return new TextChannel(data, client) - } - client.emit('warn', new Error(`Unknown guild channel type: ${data.type}\n${JSON.stringify(data)}`)) - return new GuildChannel(data, client) - } - client.emit('warn', new Error(`Unknown channel type: ${data.type}\n${JSON.stringify(data)}`)) - return new Channel(data, client) -} - -export function generateInteractionFrom( - data: DiscordInteraction, - client: Client, -): UnknownInteraction | PingInteraction | CommandInteraction | ComponentInteraction | AutocompleteInteraction { - switch (data.type) { - case InteractionTypes.Ping: { - return new PingInteraction(data, client) - } - case InteractionTypes.ApplicationCommand: { - return new CommandInteraction(data, client) - } - case InteractionTypes.MessageComponent: { - return new ComponentInteraction(data, client) - } - case InteractionTypes.ApplicationCommandAutocomplete: { - return new AutocompleteInteraction(data, client) - } - } - - client.emit('warn', new Error(`Unknown interaction type: ${data.type}\n${JSON.stringify(data)}`)) - return new UnknownInteraction(data, client) -} diff --git a/packages/client/tests/index.spec.ts b/packages/client/tests/index.spec.ts deleted file mode 100644 index 4cc609818..000000000 --- a/packages/client/tests/index.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it } from 'mocha' - -describe('index.ts', () => { - it('will import without error', async () => { - await import('../src/index.js') - }) -}) diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json deleted file mode 100644 index 50e6ef6eb..000000000 --- a/packages/client/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "tsconfig/base.json", - "compilerOptions": { - "outDir": "./dist", - }, - "include": [ - "./src/**/*.ts", - "./src/**/*.tsx" - ], - "exclude": [ - "node_modules", - "dist", - "test", - "tests" - ] -} \ No newline at end of file diff --git a/packages/client/tsconfig.test.json b/packages/client/tsconfig.test.json deleted file mode 100644 index eeb80c521..000000000 --- a/packages/client/tsconfig.test.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "tsconfig/test.json", - "include": [ - "tests", - ], - "exclude": [ - "node_modules", - "dist", - "src" - ] -} \ No newline at end of file diff --git a/packages/utils/tests/bucket.spec.ts b/packages/utils/tests/bucket.spec.ts index bf04afb92..13ee5d7de 100644 --- a/packages/utils/tests/bucket.spec.ts +++ b/packages/utils/tests/bucket.spec.ts @@ -147,9 +147,6 @@ describe('bucket.ts', () => { await bucket.acquire() expect(bucket.remaining).equals(0) expect(bucket.used).equals(1) - // await clock.tickAsync(600) - // expect(bucket.remaining).equals(1) - // expect(bucket.used).equals(0) }) }) }) diff --git a/scripts/finalizeTypedocs.js b/scripts/finalizeTypedocs.js index cc553c26f..eaf59f2fe 100644 --- a/scripts/finalizeTypedocs.js +++ b/scripts/finalizeTypedocs.js @@ -40,13 +40,11 @@ for await (let filepath of walk(typedocOutPath)) { // Converts ugly names to clean names for example discordeno_types.ActionRow becomes ActionRow const cleanForms = [ { ugly: 'discordeno_bot.md', clean: 'Bot.md'}, - { ugly: 'discordeno_client.md', clean: 'Client.md'}, { ugly: 'discordeno_gateway.md', clean: 'Gateway.md'}, { ugly: 'discordeno_rest.md', clean: 'Rest.md'}, { ugly: 'discordeno_types.md', clean: 'Types.md'}, { ugly: 'discordeno_utils.md', clean: 'Utils.md'}, { ugly: 'discordeno_bot.' }, - { ugly: 'discordeno_client.' }, { ugly: 'discordeno_gateway.' }, { ugly: 'discordeno_rest.' }, { ugly: 'discordeno_types.' }, diff --git a/typedoc.json b/typedoc.json index 04efead4e..ee3ae8713 100644 --- a/typedoc.json +++ b/typedoc.json @@ -2,7 +2,6 @@ "entryPointStrategy": "packages", "entryPoints": [ "packages/bot", - "packages/client", "packages/gateway", "packages/rest", "packages/types", diff --git a/website/docs/bigbot/step-2-rest.md b/website/docs/bigbot/step-2-rest.md index 3510702e6..5750b4e38 100644 --- a/website/docs/bigbot/step-2-rest.md +++ b/website/docs/bigbot/step-2-rest.md @@ -236,7 +236,7 @@ One of the last things we should do, is make it possible to run commands on this Let's make a small bot on this process. Make a file called `services/rest/bot.ts`. Then paste the code below. ```ts -import { Client } from '@discordeno/client' +import { createBot } from '@discordeno/bot' import { logger } from '@discordeno/utils' import * as util from 'util' @@ -244,51 +244,53 @@ const inspectOptions = { depth: 1 } -const client = new Client(process.env.TOKEN, { - // client options here -}) +const bot = createBot({ + token: process.env.TOKEN, + events: { + // This is just to keep code short for the guide, there are cleaner ways to write events. + async messageCreate(message) { + // If the message is from a bot simply ignore + if (message.author.bot) return + // If the message is not from bot owner simply ignore + if (message.author.id !== 'YOUR_ID_HERE') return + // If the content of the message is not + if (!message.content.startsWith(`${process.env.PREFIX}eval`)) return -client.on('messageCreate', (message) => { - // If the message is from a bot simply ignore - if (message.author.bot) return - // If the message is not from bot owner simply ignore - if (message.author.id !== 'YOUR_ID_HERE') return - // If the content of the message is not - if (!message.content.startsWith(`${process.env.PREFIX}eval`)) return + const args = message.conten.split(' ') + // remove the .eval part + args.shift() - const args = message.conten.split(' ') - // remove the .eval part - args.shift() + const cleanArgs = args.join(' ').replace(/^\s+/, '').replace(/\s*$/, '') - const cleanArgs = args.join(' ').replace(/^\s+/, '').replace(/\s*$/, '') + // Eval the things and send the results + let result + try { + result = eval(cleanArgs) + } catch (e) { + result = e + } - // Eval the things and send the results - let result - try { - result = eval(cleanArgs) - } catch (e) { - result = e - } + const response = ['```ts'] + const regex = new RegExp(Gamer.token, 'gi') - const response = ['```ts'] - const regex = new RegExp(Gamer.token, 'gi') + if (result && typeof result.then === 'function') { + // We returned a promise? + let value + try { + value = await result + } catch (err) { + value = err + } + response.push(util.inspect(value, inspectOptions).replace(regex, 'YOU WISH!').substring(0, 1985)) + } else { + response.push(String(util.inspect(result)).replace(regex, 'YOU WISH!').substring(0, 1985)) + } - if (result && typeof result.then === 'function') { - // We returned a promise? - let value - try { - value = await result - } catch (err) { - value = err + response.push('```') + + await message.channel.createMessage(response.join('\n')) } - response.push(util.inspect(value, inspectOptions).replace(regex, 'YOU WISH!').substring(0, 1985)) - } else { - response.push(String(util.inspect(result)).replace(regex, 'YOU WISH!').substring(0, 1985)) } - - response.push('```') - - await message.channel.createMessage(response.join('\n')) }) ``` diff --git a/website/docs/generated/modules.md b/website/docs/generated/modules.md index 111af0df6..38ef2e06d 100644 --- a/website/docs/generated/modules.md +++ b/website/docs/generated/modules.md @@ -7,7 +7,6 @@ ### Modules - [@discordeno/bot](modules/Bot.md) -- [@discordeno/client](modules/Client.md) - [@discordeno/gateway](modules/Gateway.md) - [@discordeno/rest](modules/Rest.md) - [@discordeno/types](modules/Types.md) diff --git a/website/docs/intro.md b/website/docs/intro.md index 47a7f2026..c7eefc0da 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -12,9 +12,8 @@ Discordeno is actively maintained to guarantee **excellent performance, latest f - **Simple, Efficient, and Lightweight**: Discordeno is lightweight, simple to use, and adaptable. - By default: No caching. -- **Functional & Class API**: Discordeno is flexible enough to provide both methods. +- **Functional API**: - The functional API eliminates the challenges of extending built-in classes and inheritance while ensuring overall simple but performant code. - - The class based API, client package, provides a similar api as the [Eris](https://github.com/abalabahaha/eris) library to provide the best class based experience. - **Cross Runtime**: Supports the Node.js, Deno, and Bun runtimes. - **Standalone components**: Discordeno offers the option to have practically any component of a bot as a separate piece, including standalone REST, gateways, custom caches, and more. diff --git a/website/docs/migrating/eris.md b/website/docs/migrating/eris.md index d0eaecd4c..244839efc 100644 --- a/website/docs/migrating/eris.md +++ b/website/docs/migrating/eris.md @@ -5,75 +5,5 @@ sidebar_label: Eris To Discordeno # Migrating From Eris To Discordeno Guide -## Understanding The Goals of This Guide - -This guide is a quick-paced walkthrough meant for explaining the migration process for Eris bots to using Discordeno. If you are not sure whether you want to migrate, please read [Should I Migrate?](../intro.md) - -## TypeScript - -I really hope you wrote your bot with TypeScript as it is going to save you a lot of time. If you have not written it in TypeScript, you should probably start now. Using this migrating as a push to switch to TypeScript. - -If your bot is using TypeScript, this migration will be a lot easier. - -First, update typescript to the latest version available. This will be required if your bot is using a much older version. Discordeno uses the leverages the latest and greatest TypeScript features. Please make sure you have the latest version of typescript before going forward. - -## ESM - -If you are using a runtime like Deno that supports ESM from the get go then this step is not required. - -Discordeno uses ESM. More than likely, your bot does not use it and if you do not know what ESM is don't worry. It's just the new standard of using JavaScript. Let's keep it simple, to make Discordeno work add `"type": "module"` to your `package.json` file. - -Next go to your tsconfig.json file: -```json -{ - "module": "ES2022", - "target": "ES2022", -} -``` - -:::tip -You can use any version above ES2022 should you be reading this guide in the future and it is not updated. -- P.S. If it is not updated, blame Yui!!! 🤣 -::: - -## Getting Started - -Open up a terminal and run `tsc --watch --noEmit` to keep TypeScript warning of us of errors that may appear as we migrate. - -Once you are ready, let's go ahead and install Discordeno, while we are at it we can uninstall eris. Open a new terminal and run the following command for the package manager you use: - -```ts -// NPM users -npm uninstall eris && npm install @discordeno/client@19.0.0-next.99fbe1e -// Yarn users -yarn remove eris && yarn add @discordeno/client@19.0.0-next.99fbe1e -``` - -:::caution -Currently, Discordeno v19 is in development. This is why you can not install it as @discordeno/client but have to specify the commit version. Once this version is released, it will be as easy as `@discordeno/client`. -::: - -We are going to use [NayuBot](https://github.com/AwesomeStickz/Nayu-Bot) which is a small bot written in TypeScript with Eris and is open source as the example we are going to migrate to Discordeno. - -At this moment in time, the tsc terminal(from now on we are going to refer to this as TypeScript), is telling us we have 13 errors. This is because when we removed eris, we also need to fix any imports it may have. So let's run a search in VSC to find any `from 'eris';` and `from "eris"`. We need to replace these with `from '@discordeno/client'` and `from "@discordeno/client"`. - -## Intents - -Eris was still using an older version of the api. This meant, that intents like MessageContent was not yet supported. When switching to Discordeno, we use the latest API version possible to provide the best experience possible. This requires that if your bot needs the `MessageContent` intent, that it provide it in the intents. - -```ts -// If your code is a number like this, update the number. -intents: 4086, -// If your code is an array, add Intents.MessageContent -intents: ["guilds", "guildMessages", Intents.MessageContent], -``` - -## Known Issues - -Currently, there are a few issues with the migration. - -- `voice` - Currently **@discordeno/client** does not support voice features. Until we have a large music bot with the desire to migrate to discordeno we don't plan to support this as it is a massive endeavor. It will require a willing developer with good ability to test our implementation and make sure it scales well, like the rest of discordeno. If you are a music bot developer looking to migrate, and are willing to work with us to support this, please do contact me on Discord. -- `selfbots/userbots` - Eris supported a lot of selfbot features that Discordeno does not. All of these features were removed. If you are trying to migrate a self bot, you will not be able to access a lot of features. This will never be supported. Please don't ask. -- `command frameworks` - If you made your bot with a command framework package such as Yuuko, you may have some issues. This is because that package itself depends on Eris. It would need updating to support **@discordeno/client** instead of Eris. In a near future, when both typescript and node.js, you will be able to use *import maps* to replace all instance of eris with discordeno in all your packages. -- `deprecations` - If your bot was written using old code that Eris had marked as deprecated, we removed that behavior. Take this time, to fix those few errors in the new cleaner fashion. The following is a list of reported methods that were effected: - - getMessages +1. Fix code to be better. +2. Enjoy diff --git a/yarn.lock b/yarn.lock index 775fbf9bd..8422df838 100644 --- a/yarn.lock +++ b/yarn.lock @@ -47,32 +47,6 @@ __metadata: languageName: unknown linkType: soft -"@discordeno/client@workspace:packages/client": - version: 0.0.0-use.local - resolution: "@discordeno/client@workspace:packages/client" - dependencies: - "@discordeno/gateway": 19.0.0-alpha.1 - "@discordeno/rest": 19.0.0-alpha.1 - "@discordeno/types": 19.0.0-alpha.1 - "@discordeno/utils": 19.0.0-alpha.1 - "@swc/cli": ^0.1.62 - "@swc/core": ^1.3.40 - "@types/chai": ^4.3.4 - "@types/mocha": ^10.0.1 - "@types/node": ^18.15.3 - "@types/sinon": ^10.0.13 - c8: ^7.13.0 - chai: ^4.3.7 - eslint: ^8.36.0 - eslint-config-discordeno: "*" - mocha: ^10.2.0 - sinon: ^15.0.2 - ts-node: ^10.9.1 - tsconfig: "*" - typescript: ^4.9.5 - languageName: unknown - linkType: soft - "@discordeno/gateway@19.0.0-alpha.1, @discordeno/gateway@workspace:packages/gateway": version: 0.0.0-use.local resolution: "@discordeno/gateway@workspace:packages/gateway"