diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
index 31c7875b7886..29eadb2e05bd 100644
--- a/.github/workflows/documentation.yml
+++ b/.github/workflows/documentation.yml
@@ -38,10 +38,9 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
- cache: 'yarn'
- name: Install dependencies
- run: yarn --immutable
+ uses: ./packages/actions/src/yarnCache
- name: Build dependencies
run: yarn build
@@ -81,10 +80,9 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
- cache: 'yarn'
- name: Install dependencies
- run: yarn --immutable
+ uses: ./packages/actions/src/yarnCache
- name: Build actions
run: yarn workspace @discordjs/actions build
diff --git a/.github/workflows/npm-auto-deprecate.yml b/.github/workflows/npm-auto-deprecate.yml
index 9774b2f259a4..c57e71e670b8 100644
--- a/.github/workflows/npm-auto-deprecate.yml
+++ b/.github/workflows/npm-auto-deprecate.yml
@@ -16,10 +16,9 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
- cache: 'yarn'
- name: Install dependencies
- run: yarn --immutable
+ uses: ./packages/actions/src/yarnCache
- name: Deprecate versions
run: 'yarn npm-deprecate --name "*dev*" --package @discordjs/brokers @discordjs/builders @discordjs/collection discord.js @discordjs/proxy @discordjs/rest @discordjs/util @discordjs/voice @discordjs/ws'
diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml
index ea51cee9baad..2630fe615d40 100644
--- a/.github/workflows/publish-dev.yml
+++ b/.github/workflows/publish-dev.yml
@@ -42,10 +42,9 @@ jobs:
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- cache: 'yarn'
- name: Install dependencies
- run: yarn --immutable
+ uses: ./packages/actions/src/yarnCache
- name: Build dependencies
run: yarn build
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 901c900453cd..371758b99e0b 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -20,10 +20,9 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
- cache: 'yarn'
- name: Install dependencies
- run: yarn install --immutable
+ uses: ./packages/actions/src/yarnCache
- name: Build dependencies
run: yarn build
diff --git a/apps/guide/package.json b/apps/guide/package.json
index 6f16f5669706..454d51ed9b7a 100644
--- a/apps/guide/package.json
+++ b/apps/guide/package.json
@@ -34,9 +34,9 @@
},
"homepage": "https://discord.js.org",
"dependencies": {
- "@astrojs/image": "^0.9.3",
+ "@astrojs/image": "^0.10.0",
"@astrojs/mdx": "^0.11.4",
- "@astrojs/react": "^1.2.0",
+ "@astrojs/react": "^1.2.1",
"@code-hike/mdx": "^0.7.4",
"@discordjs/ui": "workspace:^",
"ariakit": "^2.0.0-next.41",
@@ -45,43 +45,43 @@
"react": "^17.0.2",
"react-custom-scrollbars-2": "^4.5.0",
"react-dom": "^17.0.2",
- "react-icons": "^4.4.0",
+ "react-icons": "^4.6.0",
"react-use": "^17.4.0",
"sharp": "^0.31.1",
"shiki": "^0.11.1"
},
"devDependencies": {
- "@astrojs/prefetch": "^0.0.8",
+ "@astrojs/prefetch": "^0.1.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
- "@types/node": "16.11.64",
+ "@types/node": "16.11.68",
"@types/react": "^17.0.50",
"@types/react-dom": "^17.0.17",
"@types/react-syntax-highlighter": "^15.5.5",
- "@unocss/cli": "^0.45.29",
- "@unocss/reset": "^0.45.29",
+ "@unocss/cli": "^0.45.30",
+ "@unocss/reset": "^0.45.30",
"@vitejs/plugin-react": "^2.1.0",
- "@vitest/coverage-c8": "^0.24.1",
- "astro": "^1.4.7",
- "astro-compress": "^1.0.11",
+ "@vitest/coverage-c8": "^0.24.3",
+ "astro": "^1.5.2",
+ "astro-compress": "^1.0.12",
"astro-critters": "^1.0.5",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
- "happy-dom": "^7.5.6",
+ "happy-dom": "^7.6.0",
"hast-util-to-string": "^2.0.0",
"hastscript": "^7.1.0",
"html-escaper": "^3.0.3",
"prettier": "^2.7.1",
- "prettier-plugin-astro": "^0.5.5",
+ "prettier-plugin-astro": "^0.6.0",
"prettier-plugin-tailwindcss": "^0.1.13",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.0.1",
"typescript": "^4.8.4",
- "unocss": "^0.45.29",
- "vercel": "^28.4.9",
- "vitest": "^0.24.1"
+ "unocss": "^0.45.30",
+ "vercel": "^28.4.12",
+ "vitest": "^0.24.3"
},
"engines": {
"node": ">=16.9.0"
diff --git a/apps/website/package.json b/apps/website/package.json
index d81744306745..bbec98fe9503 100644
--- a/apps/website/package.json
+++ b/apps/website/package.json
@@ -45,7 +45,7 @@
"@discordjs/ui": "workspace:^",
"@microsoft/api-extractor-model": "7.24.0",
"@microsoft/tsdoc": "0.14.1",
- "@vercel/og": "^0.0.15",
+ "@vercel/og": "^0.0.19",
"@vscode/codicons": "^0.0.32",
"ariakit": "^2.0.0-next.41",
"cmdk": "^0.1.20",
@@ -57,11 +57,11 @@
"react": "^18.2.0",
"react-custom-scrollbars-2": "^4.5.0",
"react-dom": "^18.2.0",
- "react-icons": "^4.4.0",
+ "react-icons": "^4.6.0",
"react-syntax-highlighter": "^15.5.0",
"react-use": "^17.4.0",
"rehype-ignore": "^1.0.1",
- "rehype-pretty-code": "^0.3.2",
+ "rehype-pretty-code": "^0.4.0",
"rehype-raw": "^6.1.1",
"rehype-slug": "^5.0.1",
"remark-gfm": "^3.0.1",
@@ -72,26 +72,26 @@
"devDependencies": {
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
- "@types/node": "16.11.64",
+ "@types/node": "16.11.68",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@types/react-syntax-highlighter": "^15.5.5",
- "@unocss/cli": "^0.45.29",
- "@unocss/reset": "^0.45.29",
+ "@unocss/cli": "^0.45.30",
+ "@unocss/reset": "^0.45.30",
"@vitejs/plugin-react": "^2.1.0",
- "@vitest/coverage-c8": "^0.24.1",
+ "@vitest/coverage-c8": "^0.24.3",
"concurrently": "^7.4.0",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
- "happy-dom": "^7.5.6",
+ "happy-dom": "^7.6.0",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"typescript": "^4.8.4",
- "unocss": "^0.45.29",
- "vercel": "^28.4.9",
- "vitest": "^0.24.1"
+ "unocss": "^0.45.30",
+ "vercel": "^28.4.12",
+ "vitest": "^0.24.3"
},
"engines": {
"node": ">=16.9.0"
diff --git a/apps/website/src/components/CodeListing.tsx b/apps/website/src/components/CodeListing.tsx
index b20affd00d04..7ddf5275327b 100644
--- a/apps/website/src/components/CodeListing.tsx
+++ b/apps/website/src/components/CodeListing.tsx
@@ -34,7 +34,7 @@ export function CodeListing({
}>) {
return (
-
+
{summary || inheritanceData ? (
-
+
{deprecation ?
: null}
{summary ?
: null}
{comment ?
: null}
diff --git a/apps/website/src/components/model/Enum.tsx b/apps/website/src/components/model/Enum.tsx
index 5ff1fdd16ad2..56bc4700b338 100644
--- a/apps/website/src/components/model/Enum.tsx
+++ b/apps/website/src/components/model/Enum.tsx
@@ -1,5 +1,6 @@
import type { ApiEnumJSON } from '@discordjs/api-extractor-utils';
import { Section } from '@discordjs/ui';
+import { Fragment } from 'react';
import { VscSymbolEnumMember } from 'react-icons/vsc';
import { useMedia } from 'react-use';
import { CodeListing, CodeListingSeparatorType } from '../CodeListing';
@@ -13,13 +14,15 @@ export function Enum({ data }: { data: ApiEnumJSON }) {
} padded title="Members">
{data.members.map((member) => (
-
+
+
+
+
))}
diff --git a/package.json b/package.json
index 86037216ee7e..a1fba1e57176 100644
--- a/package.json
+++ b/package.json
@@ -46,11 +46,11 @@
"husky": "^8.0.1",
"is-ci": "^3.0.1",
"lint-staged": "^13.0.3",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"turbo": "^1.5.6",
"typescript": "^4.8.4",
- "unocss": "^0.45.29",
- "vitest": "^0.24.1"
+ "unocss": "^0.45.30",
+ "vitest": "^0.24.3"
},
"resolutions": {
"@microsoft/tsdoc-config": "patch:@microsoft/tsdoc-config@npm:0.16.1#.yarn/patches/@microsoft-tsdoc-config-npm-0.16.1-81031b1bbf.patch"
diff --git a/packages/actions/package.json b/packages/actions/package.json
index b4dd94df96e4..d95dfbbf91b8 100644
--- a/packages/actions/package.json
+++ b/packages/actions/package.json
@@ -43,16 +43,16 @@
"tslib": "^2.4.0"
},
"devDependencies": {
- "@types/node": "16.11.64",
- "@vitest/coverage-c8": "^0.24.1",
+ "@types/node": "16.11.68",
+ "@vitest/coverage-c8": "^0.24.3",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4",
- "vitest": "^0.24.1"
+ "vitest": "^0.24.3"
},
"engines": {
"node": ">=16.9.0"
diff --git a/packages/actions/src/yarnCache/action.yml b/packages/actions/src/yarnCache/action.yml
new file mode 100644
index 000000000000..dd0a4235d584
--- /dev/null
+++ b/packages/actions/src/yarnCache/action.yml
@@ -0,0 +1,35 @@
+name: 'yarn install'
+description: 'Run yarn install with node_modules linker and cache enabled'
+runs:
+ using: 'composite'
+ steps:
+ - name: Expose yarn config as "$GITHUB_OUTPUT"
+ id: yarn-config
+ shell: bash
+ run: |
+ echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
+
+ - name: Restore yarn cache
+ uses: actions/cache@v3
+ id: yarn-download-cache
+ with:
+ path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
+ key: yarn-download-cache-${{ hashFiles('yarn.lock') }}
+ restore-keys: |
+ yarn-download-cache-
+
+ - name: Restore yarn install state
+ id: yarn-install-state-cache
+ uses: actions/cache@v3
+ with:
+ path: .yarn/ci-cache/
+ key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
+
+ - name: Install dependencies
+ shell: bash
+ run: |
+ yarn install --immutable --inline-builds
+ env:
+ YARN_ENABLE_GLOBAL_CACHE: 'false'
+ YARN_NM_MODE: 'hardlinks-local'
+ YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz
diff --git a/packages/api-extractor-utils/package.json b/packages/api-extractor-utils/package.json
index c173b55b415e..7b4e4fae615e 100644
--- a/packages/api-extractor-utils/package.json
+++ b/packages/api-extractor-utils/package.json
@@ -35,13 +35,13 @@
"@microsoft/tsdoc": "0.14.1"
},
"devDependencies": {
- "@types/node": "16.11.64",
+ "@types/node": "16.11.68",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4"
},
"engines": {
diff --git a/packages/brokers/__tests__/index.test.ts b/packages/brokers/__tests__/index.test.ts
index e133c58d9487..898be4e7b61f 100644
--- a/packages/brokers/__tests__/index.test.ts
+++ b/packages/brokers/__tests__/index.test.ts
@@ -2,6 +2,12 @@ import type Redis from 'ioredis';
import { test, expect, vi } from 'vitest';
import { PubSubRedisBroker } from '../src/index.js';
+vi.mock('node:fs', () => {
+ return {
+ readFileSync: vi.fn(),
+ };
+});
+
const mockRedisClient = {
defineCommand: vi.fn(),
xadd: vi.fn(),
diff --git a/packages/brokers/package.json b/packages/brokers/package.json
index c6c98860054d..dcd626c2e41d 100644
--- a/packages/brokers/package.json
+++ b/packages/brokers/package.json
@@ -26,7 +26,8 @@
"test": "__tests__"
},
"files": [
- "dist"
+ "dist",
+ "scripts"
],
"contributors": [
"Crawl
",
@@ -61,17 +62,17 @@
},
"devDependencies": {
"@favware/cliff-jumper": "^1.8.8",
- "@microsoft/api-extractor": "^7.32.1",
- "@types/node": "^16.11.52",
- "@vitest/coverage-c8": "^0.22.1",
+ "@microsoft/api-extractor": "^7.33.4",
+ "@types/node": "^16.11.68",
+ "@vitest/coverage-c8": "^0.24.3",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4",
- "vitest": "^0.22.1"
+ "vitest": "^0.24.3"
},
"engines": {
"node": ">=16.9.0"
diff --git a/packages/brokers/src/brokers/redis/BaseRedis.ts b/packages/brokers/src/brokers/redis/BaseRedis.ts
index a075c0a47e68..1e95913b4c05 100644
--- a/packages/brokers/src/brokers/redis/BaseRedis.ts
+++ b/packages/brokers/src/brokers/redis/BaseRedis.ts
@@ -61,7 +61,7 @@ export abstract class BaseRedisBroker>
this.options = { ...DefaultBrokerOptions, ...options };
options.redisClient.defineCommand('xcleangroup', {
numberOfKeys: 1,
- lua: readFileSync(resolve(__dirname, '..', '..', '..', 'scripts', 'xcleangroup.lua'), 'utf8'),
+ lua: readFileSync(resolve(__dirname, '..', 'scripts', 'xcleangroup.lua'), 'utf8'),
});
this.streamReadClient = options.redisClient.duplicate();
}
diff --git a/packages/builders/__tests__/components/actionRow.test.ts b/packages/builders/__tests__/components/actionRow.test.ts
index d0bb9f7b584f..b9f63b501529 100644
--- a/packages/builders/__tests__/components/actionRow.test.ts
+++ b/packages/builders/__tests__/components/actionRow.test.ts
@@ -9,8 +9,8 @@ import {
ActionRowBuilder,
ButtonBuilder,
createComponentBuilder,
- SelectMenuBuilder,
- SelectMenuOptionBuilder,
+ StringSelectMenuBuilder,
+ StringSelectMenuOptionBuilder,
} from '../../src/index.js';
const rowWithButtonData: APIActionRowComponent = {
@@ -29,7 +29,7 @@ const rowWithSelectMenuData: APIActionRowComponent
type: ComponentType.ActionRow,
components: [
{
- type: ComponentType.SelectMenu,
+ type: ComponentType.StringSelect,
custom_id: '1234',
options: [
{
@@ -73,7 +73,7 @@ describe('Action Row Components', () => {
url: 'https://google.com',
},
{
- type: ComponentType.SelectMenu,
+ type: ComponentType.StringSelect,
placeholder: 'test',
custom_id: 'test',
options: [
@@ -108,7 +108,7 @@ describe('Action Row Components', () => {
type: ComponentType.ActionRow,
components: [
{
- type: ComponentType.SelectMenu,
+ type: ComponentType.StringSelect,
custom_id: '1234',
options: [
{
@@ -134,17 +134,17 @@ describe('Action Row Components', () => {
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
- const selectMenu = new SelectMenuBuilder()
+ const selectMenu = new StringSelectMenuBuilder()
.setCustomId('1234')
.setMaxValues(10)
.setMinValues(12)
.setOptions(
- new SelectMenuOptionBuilder().setLabel('one').setValue('one'),
- new SelectMenuOptionBuilder().setLabel('two').setValue('two'),
+ new StringSelectMenuOptionBuilder().setLabel('one').setValue('one'),
+ new StringSelectMenuOptionBuilder().setLabel('two').setValue('two'),
)
.setOptions([
- new SelectMenuOptionBuilder().setLabel('one').setValue('one'),
- new SelectMenuOptionBuilder().setLabel('two').setValue('two'),
+ new StringSelectMenuOptionBuilder().setLabel('one').setValue('one'),
+ new StringSelectMenuOptionBuilder().setLabel('two').setValue('two'),
]);
expect(new ActionRowBuilder().addComponents(button).toJSON()).toEqual(rowWithButtonData);
diff --git a/packages/builders/__tests__/components/components.test.ts b/packages/builders/__tests__/components/components.test.ts
index 520244d438eb..fa0bd4607f65 100644
--- a/packages/builders/__tests__/components/components.test.ts
+++ b/packages/builders/__tests__/components/components.test.ts
@@ -13,12 +13,12 @@ import {
ActionRowBuilder,
ButtonBuilder,
createComponentBuilder,
- SelectMenuBuilder,
+ StringSelectMenuBuilder,
TextInputBuilder,
} from '../../src/index.js';
describe('createComponentBuilder', () => {
- test.each([ButtonBuilder, SelectMenuBuilder, TextInputBuilder])(
+ test.each([ButtonBuilder, StringSelectMenuBuilder, TextInputBuilder])(
'passing an instance of %j should return itself',
(Builder) => {
const builder = new Builder();
@@ -45,14 +45,14 @@ describe('createComponentBuilder', () => {
expect(createComponentBuilder(button)).toBeInstanceOf(ButtonBuilder);
});
- test('GIVEN a select menu component THEN returns a SelectMenuBuilder', () => {
+ test('GIVEN a select menu component THEN returns a StringSelectMenuBuilder', () => {
const selectMenu: APISelectMenuComponent = {
custom_id: 'abc',
options: [],
- type: ComponentType.SelectMenu,
+ type: ComponentType.StringSelect,
};
- expect(createComponentBuilder(selectMenu)).toBeInstanceOf(SelectMenuBuilder);
+ expect(createComponentBuilder(selectMenu)).toBeInstanceOf(StringSelectMenuBuilder);
});
test('GIVEN a text input component THEN returns a TextInputBuilder', () => {
diff --git a/packages/builders/__tests__/components/selectMenu.test.ts b/packages/builders/__tests__/components/selectMenu.test.ts
index c5e10c53ccde..f7ab28144918 100644
--- a/packages/builders/__tests__/components/selectMenu.test.ts
+++ b/packages/builders/__tests__/components/selectMenu.test.ts
@@ -1,9 +1,9 @@
import { ComponentType, type APISelectMenuComponent, type APISelectMenuOption } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
-import { SelectMenuBuilder, SelectMenuOptionBuilder } from '../../src/index.js';
+import { StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from '../../src/index.js';
-const selectMenu = () => new SelectMenuBuilder();
-const selectMenuOption = () => new SelectMenuOptionBuilder();
+const selectMenu = () => new StringSelectMenuBuilder();
+const selectMenuOption = () => new StringSelectMenuOptionBuilder();
const longStr = 'a'.repeat(256);
@@ -165,16 +165,16 @@ describe('Select Menu Components', () => {
test('GIVEN valid JSON input THEN valid JSON history is correct', () => {
expect(
- new SelectMenuBuilder(selectMenuDataWithoutOptions)
- .addOptions(new SelectMenuOptionBuilder(selectMenuOptionData))
+ new StringSelectMenuBuilder(selectMenuDataWithoutOptions)
+ .addOptions(new StringSelectMenuOptionBuilder(selectMenuOptionData))
.toJSON(),
).toEqual(selectMenuData);
expect(
- new SelectMenuBuilder(selectMenuDataWithoutOptions)
- .addOptions([new SelectMenuOptionBuilder(selectMenuOptionData)])
+ new StringSelectMenuBuilder(selectMenuDataWithoutOptions)
+ .addOptions([new StringSelectMenuOptionBuilder(selectMenuOptionData)])
.toJSON(),
).toEqual(selectMenuData);
- expect(new SelectMenuOptionBuilder(selectMenuOptionData).toJSON()).toEqual(selectMenuOptionData);
+ expect(new StringSelectMenuOptionBuilder(selectMenuOptionData).toJSON()).toEqual(selectMenuOptionData);
});
});
});
diff --git a/packages/builders/package.json b/packages/builders/package.json
index d8693c686898..fd481908da84 100644
--- a/packages/builders/package.json
+++ b/packages/builders/package.json
@@ -56,25 +56,25 @@
"dependencies": {
"@discordjs/util": "workspace:^",
"@sapphire/shapeshift": "^3.7.0",
- "discord-api-types": "^0.37.13",
+ "discord-api-types": "^0.37.15",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.1",
"tslib": "^2.4.0"
},
"devDependencies": {
"@favware/cliff-jumper": "^1.8.8",
- "@microsoft/api-extractor": "^7.32.0",
- "@types/node": "16.11.64",
- "@vitest/coverage-c8": "^0.24.1",
+ "@microsoft/api-extractor": "^7.33.4",
+ "@types/node": "16.11.68",
+ "@vitest/coverage-c8": "^0.24.3",
"cross-env": "^7.0.3",
"esbuild-plugin-version-injector": "^1.0.0",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4",
- "vitest": "^0.24.1"
+ "vitest": "^0.24.3"
},
"engines": {
"node": ">=16.9.0"
diff --git a/packages/builders/src/components/ActionRow.ts b/packages/builders/src/components/ActionRow.ts
index 1f10ddc85e8c..90dda30cae9e 100644
--- a/packages/builders/src/components/ActionRow.ts
+++ b/packages/builders/src/components/ActionRow.ts
@@ -11,14 +11,24 @@ import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
import { ComponentBuilder } from './Component.js';
import { createComponentBuilder } from './Components.js';
import type { ButtonBuilder } from './button/Button.js';
-import type { SelectMenuBuilder } from './selectMenu/SelectMenu.js';
+import type { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js';
+import type { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js';
+import type { RoleSelectMenuBuilder } from './selectMenu/RoleSelectMenu.js';
+import type { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
+import type { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
import type { TextInputBuilder } from './textInput/TextInput.js';
export type MessageComponentBuilder =
| ActionRowBuilder
| MessageActionRowComponentBuilder;
export type ModalComponentBuilder = ActionRowBuilder | ModalActionRowComponentBuilder;
-export type MessageActionRowComponentBuilder = ButtonBuilder | SelectMenuBuilder;
+export type MessageActionRowComponentBuilder =
+ | ButtonBuilder
+ | ChannelSelectMenuBuilder
+ | MentionableSelectMenuBuilder
+ | RoleSelectMenuBuilder
+ | StringSelectMenuBuilder
+ | UserSelectMenuBuilder;
export type ModalActionRowComponentBuilder = TextInputBuilder;
export type AnyComponentBuilder = MessageActionRowComponentBuilder | ModalActionRowComponentBuilder;
diff --git a/packages/builders/src/components/Assertions.ts b/packages/builders/src/components/Assertions.ts
index faae51dd16b7..960efd706c7c 100644
--- a/packages/builders/src/components/Assertions.ts
+++ b/packages/builders/src/components/Assertions.ts
@@ -1,7 +1,7 @@
import { s } from '@sapphire/shapeshift';
-import { ButtonStyle, type APIMessageComponentEmoji } from 'discord-api-types/v10';
+import { ButtonStyle, ChannelType, type APIMessageComponentEmoji } from 'discord-api-types/v10';
import { isValidationEnabled } from '../util/validation.js';
-import { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption.js';
+import { StringSelectMenuOptionBuilder } from './selectMenu/StringSelectMenuOption.js';
export const customIdValidator = s.string
.lengthGreaterThanOrEqual(1)
@@ -46,7 +46,7 @@ export const jsonOptionValidator = s
})
.setValidationEnabled(isValidationEnabled);
-export const optionValidator = s.instance(SelectMenuOptionBuilder).setValidationEnabled(isValidationEnabled);
+export const optionValidator = s.instance(StringSelectMenuOptionBuilder).setValidationEnabled(isValidationEnabled);
export const optionsValidator = optionValidator.array
.lengthGreaterThanOrEqual(0)
@@ -56,7 +56,7 @@ export const optionsLengthValidator = s.number.int
.lessThanOrEqual(25)
.setValidationEnabled(isValidationEnabled);
-export function validateRequiredSelectMenuParameters(options: SelectMenuOptionBuilder[], customId?: string) {
+export function validateRequiredSelectMenuParameters(options: StringSelectMenuOptionBuilder[], customId?: string) {
customIdValidator.parse(customId);
optionsValidator.parse(options);
}
@@ -68,6 +68,8 @@ export function validateRequiredSelectMenuOptionParameters(label?: string, value
labelValueDescriptionValidator.parse(value);
}
+export const channelTypesValidator = s.nativeEnum(ChannelType).array.setValidationEnabled(isValidationEnabled);
+
export const urlValidator = s.string
.url({
allowedProtocols: ['http:', 'https:', 'discord:'],
diff --git a/packages/builders/src/components/Components.ts b/packages/builders/src/components/Components.ts
index ea13e013de12..d3e635ece957 100644
--- a/packages/builders/src/components/Components.ts
+++ b/packages/builders/src/components/Components.ts
@@ -7,14 +7,22 @@ import {
} from './ActionRow.js';
import { ComponentBuilder } from './Component.js';
import { ButtonBuilder } from './button/Button.js';
-import { SelectMenuBuilder } from './selectMenu/SelectMenu.js';
+import { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js';
+import { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js';
+import { RoleSelectMenuBuilder } from './selectMenu/RoleSelectMenu.js';
+import { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
+import { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
import { TextInputBuilder } from './textInput/TextInput.js';
export interface MappedComponentTypes {
[ComponentType.ActionRow]: ActionRowBuilder;
[ComponentType.Button]: ButtonBuilder;
- [ComponentType.SelectMenu]: SelectMenuBuilder;
+ [ComponentType.StringSelect]: StringSelectMenuBuilder;
[ComponentType.TextInput]: TextInputBuilder;
+ [ComponentType.UserSelect]: UserSelectMenuBuilder;
+ [ComponentType.RoleSelect]: RoleSelectMenuBuilder;
+ [ComponentType.MentionableSelect]: MentionableSelectMenuBuilder;
+ [ComponentType.ChannelSelect]: ChannelSelectMenuBuilder;
}
/**
@@ -39,10 +47,18 @@ export function createComponentBuilder(
return new ActionRowBuilder(data);
case ComponentType.Button:
return new ButtonBuilder(data);
- case ComponentType.SelectMenu:
- return new SelectMenuBuilder(data);
+ case ComponentType.StringSelect:
+ return new StringSelectMenuBuilder(data);
case ComponentType.TextInput:
return new TextInputBuilder(data);
+ case ComponentType.UserSelect:
+ return new UserSelectMenuBuilder(data);
+ case ComponentType.RoleSelect:
+ return new RoleSelectMenuBuilder(data);
+ case ComponentType.MentionableSelect:
+ return new MentionableSelectMenuBuilder(data);
+ case ComponentType.ChannelSelect:
+ return new ChannelSelectMenuBuilder(data);
default:
// @ts-expect-error: This case can still occur if we get a newer unsupported component type
throw new Error(`Cannot properly serialize component type: ${data.type}`);
diff --git a/packages/builders/src/components/selectMenu/BaseSelectMenu.ts b/packages/builders/src/components/selectMenu/BaseSelectMenu.ts
new file mode 100644
index 000000000000..cd1a306ac8f8
--- /dev/null
+++ b/packages/builders/src/components/selectMenu/BaseSelectMenu.ts
@@ -0,0 +1,64 @@
+import type { APISelectMenuComponent } from 'discord-api-types/v10';
+import { customIdValidator, disabledValidator, minMaxValidator, placeholderValidator } from '../Assertions.js';
+import { ComponentBuilder } from '../Component.js';
+
+export class BaseSelectMenuBuilder<
+ SelectMenuType extends APISelectMenuComponent,
+> extends ComponentBuilder {
+ /**
+ * Sets the placeholder for this select menu
+ *
+ * @param placeholder - The placeholder to use for this select menu
+ */
+ public setPlaceholder(placeholder: string) {
+ this.data.placeholder = placeholderValidator.parse(placeholder);
+ return this;
+ }
+
+ /**
+ * Sets the minimum values that must be selected in the select menu
+ *
+ * @param minValues - The minimum values that must be selected
+ */
+ public setMinValues(minValues: number) {
+ this.data.min_values = minMaxValidator.parse(minValues);
+ return this;
+ }
+
+ /**
+ * Sets the maximum values that must be selected in the select menu
+ *
+ * @param maxValues - The maximum values that must be selected
+ */
+ public setMaxValues(maxValues: number) {
+ this.data.max_values = minMaxValidator.parse(maxValues);
+ return this;
+ }
+
+ /**
+ * Sets the custom id for this select menu
+ *
+ * @param customId - The custom id to use for this select menu
+ */
+ public setCustomId(customId: string) {
+ this.data.custom_id = customIdValidator.parse(customId);
+ return this;
+ }
+
+ /**
+ * Sets whether this select menu is disabled
+ *
+ * @param disabled - Whether this select menu is disabled
+ */
+ public setDisabled(disabled = true) {
+ this.data.disabled = disabledValidator.parse(disabled);
+ return this;
+ }
+
+ public toJSON(): SelectMenuType {
+ customIdValidator.parse(this.data.custom_id);
+ return {
+ ...this.data,
+ } as SelectMenuType;
+ }
+}
diff --git a/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts
new file mode 100644
index 000000000000..a2d46f35e65c
--- /dev/null
+++ b/packages/builders/src/components/selectMenu/ChannelSelectMenu.ts
@@ -0,0 +1,63 @@
+import type { APIChannelSelectComponent, ChannelType } from 'discord-api-types/v10';
+import { ComponentType } from 'discord-api-types/v10';
+import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
+import { channelTypesValidator, customIdValidator } from '../Assertions.js';
+import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
+
+export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder {
+ /**
+ * Creates a new select menu from API data
+ *
+ * @param data - The API data to create this select menu with
+ * @example
+ * Creating a select menu from an API data object
+ * ```ts
+ * const selectMenu = new ChannelSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * placeholder: 'select an option',
+ * max_values: 2,
+ * });
+ * ```
+ * @example
+ * Creating a select menu using setters and API data
+ * ```ts
+ * const selectMenu = new ChannelSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * })
+ * .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
+ * .setMinValues(2)
+ * ```
+ */
+ public constructor(data?: Partial) {
+ super({ ...data, type: ComponentType.ChannelSelect });
+ }
+
+ public addChannelTypes(...types: RestOrArray) {
+ // eslint-disable-next-line no-param-reassign
+ types = normalizeArray(types);
+
+ this.data.channel_types ??= [];
+ this.data.channel_types.push(...channelTypesValidator.parse(types));
+ return this;
+ }
+
+ public setChannelTypes(...types: RestOrArray) {
+ // eslint-disable-next-line no-param-reassign
+ types = normalizeArray(types);
+
+ this.data.channel_types ??= [];
+ this.data.channel_types.splice(0, this.data.channel_types.length, ...channelTypesValidator.parse(types));
+ return this;
+ }
+
+ /**
+ * {@inheritDoc ComponentBuilder.toJSON}
+ */
+ public override toJSON(): APIChannelSelectComponent {
+ customIdValidator.parse(this.data.custom_id);
+
+ return {
+ ...this.data,
+ } as APIChannelSelectComponent;
+ }
+}
diff --git a/packages/builders/src/components/selectMenu/MentionableSelectMenu.ts b/packages/builders/src/components/selectMenu/MentionableSelectMenu.ts
new file mode 100644
index 000000000000..c996e2b4776d
--- /dev/null
+++ b/packages/builders/src/components/selectMenu/MentionableSelectMenu.ts
@@ -0,0 +1,31 @@
+import type { APIMentionableSelectComponent } from 'discord-api-types/v10';
+import { ComponentType } from 'discord-api-types/v10';
+import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
+
+export class MentionableSelectMenuBuilder extends BaseSelectMenuBuilder {
+ /**
+ * Creates a new select menu from API data
+ *
+ * @param data - The API data to create this select menu with
+ * @example
+ * Creating a select menu from an API data object
+ * ```ts
+ * const selectMenu = new MentionableSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * placeholder: 'select an option',
+ * max_values: 2,
+ * });
+ * ```
+ * @example
+ * Creating a select menu using setters and API data
+ * ```ts
+ * const selectMenu = new MentionableSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * })
+ * .setMinValues(1)
+ * ```
+ */
+ public constructor(data?: Partial) {
+ super({ ...data, type: ComponentType.MentionableSelect });
+ }
+}
diff --git a/packages/builders/src/components/selectMenu/RoleSelectMenu.ts b/packages/builders/src/components/selectMenu/RoleSelectMenu.ts
new file mode 100644
index 000000000000..818ef5b7763f
--- /dev/null
+++ b/packages/builders/src/components/selectMenu/RoleSelectMenu.ts
@@ -0,0 +1,31 @@
+import type { APIRoleSelectComponent } from 'discord-api-types/v10';
+import { ComponentType } from 'discord-api-types/v10';
+import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
+
+export class RoleSelectMenuBuilder extends BaseSelectMenuBuilder {
+ /**
+ * Creates a new select menu from API data
+ *
+ * @param data - The API data to create this select menu with
+ * @example
+ * Creating a select menu from an API data object
+ * ```ts
+ * const selectMenu = new RoleSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * placeholder: 'select an option',
+ * max_values: 2,
+ * });
+ * ```
+ * @example
+ * Creating a select menu using setters and API data
+ * ```ts
+ * const selectMenu = new RoleSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * })
+ * .setMinValues(1)
+ * ```
+ */
+ public constructor(data?: Partial) {
+ super({ ...data, type: ComponentType.RoleSelect });
+ }
+}
diff --git a/packages/builders/src/components/selectMenu/SelectMenu.ts b/packages/builders/src/components/selectMenu/SelectMenu.ts
deleted file mode 100644
index 496138a020ac..000000000000
--- a/packages/builders/src/components/selectMenu/SelectMenu.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import { ComponentType, type APISelectMenuComponent, type APISelectMenuOption } from 'discord-api-types/v10';
-import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
-import {
- customIdValidator,
- disabledValidator,
- jsonOptionValidator,
- minMaxValidator,
- optionsLengthValidator,
- placeholderValidator,
- validateRequiredSelectMenuParameters,
-} from '../Assertions.js';
-import { ComponentBuilder } from '../Component.js';
-import { SelectMenuOptionBuilder } from './SelectMenuOption.js';
-
-/**
- * Represents a select menu component
- */
-export class SelectMenuBuilder extends ComponentBuilder {
- /**
- * The options within this select menu
- */
- public readonly options: SelectMenuOptionBuilder[];
-
- /**
- * Creates a new select menu from API data
- *
- * @param data - The API data to create this select menu with
- * @example
- * Creating a select menu from an API data object
- * ```ts
- * const selectMenu = new SelectMenuBuilder({
- * custom_id: 'a cool select menu',
- * placeholder: 'select an option',
- * max_values: 2,
- * options: [
- * { label: 'option 1', value: '1' },
- * { label: 'option 2', value: '2' },
- * { label: 'option 3', value: '3' },
- * ],
- * });
- * ```
- * @example
- * Creating a select menu using setters and API data
- * ```ts
- * const selectMenu = new SelectMenuBuilder({
- * custom_id: 'a cool select menu',
- * })
- * .setMinValues(1)
- * .addOptions({
- * label: 'Catchy',
- * value: 'catch',
- * });
- * ```
- */
- public constructor(data?: Partial) {
- const { options, ...initData } = data ?? {};
- super({ type: ComponentType.SelectMenu, ...initData });
- this.options = options?.map((option) => new SelectMenuOptionBuilder(option)) ?? [];
- }
-
- /**
- * Sets the placeholder for this select menu
- *
- * @param placeholder - The placeholder to use for this select menu
- */
- public setPlaceholder(placeholder: string) {
- this.data.placeholder = placeholderValidator.parse(placeholder);
- return this;
- }
-
- /**
- * Sets the minimum values that must be selected in the select menu
- *
- * @param minValues - The minimum values that must be selected
- */
- public setMinValues(minValues: number) {
- this.data.min_values = minMaxValidator.parse(minValues);
- return this;
- }
-
- /**
- * Sets the maximum values that must be selected in the select menu
- *
- * @param maxValues - The maximum values that must be selected
- */
- public setMaxValues(maxValues: number) {
- this.data.max_values = minMaxValidator.parse(maxValues);
- return this;
- }
-
- /**
- * Sets the custom id for this select menu
- *
- * @param customId - The custom id to use for this select menu
- */
- public setCustomId(customId: string) {
- this.data.custom_id = customIdValidator.parse(customId);
- return this;
- }
-
- /**
- * Sets whether this select menu is disabled
- *
- * @param disabled - Whether this select menu is disabled
- */
- public setDisabled(disabled = true) {
- this.data.disabled = disabledValidator.parse(disabled);
- return this;
- }
-
- /**
- * Adds options to this select menu
- *
- * @param options - The options to add to this select menu
- * @returns
- */
- public addOptions(...options: RestOrArray) {
- // eslint-disable-next-line no-param-reassign
- options = normalizeArray(options);
- optionsLengthValidator.parse(this.options.length + options.length);
- this.options.push(
- ...options.map((option) =>
- option instanceof SelectMenuOptionBuilder
- ? option
- : new SelectMenuOptionBuilder(jsonOptionValidator.parse(option)),
- ),
- );
- return this;
- }
-
- /**
- * Sets the options on this select menu
- *
- * @param options - The options to set on this select menu
- */
- public setOptions(...options: RestOrArray) {
- // eslint-disable-next-line no-param-reassign
- options = normalizeArray(options);
- optionsLengthValidator.parse(options.length);
- this.options.splice(
- 0,
- this.options.length,
- ...options.map((option) =>
- option instanceof SelectMenuOptionBuilder
- ? option
- : new SelectMenuOptionBuilder(jsonOptionValidator.parse(option)),
- ),
- );
- return this;
- }
-
- /**
- * {@inheritDoc ComponentBuilder.toJSON}
- */
- public toJSON(): APISelectMenuComponent {
- validateRequiredSelectMenuParameters(this.options, this.data.custom_id);
-
- return {
- ...this.data,
- options: this.options.map((option) => option.toJSON()),
- } as APISelectMenuComponent;
- }
-}
diff --git a/packages/builders/src/components/selectMenu/StringSelectMenu.ts b/packages/builders/src/components/selectMenu/StringSelectMenu.ts
new file mode 100644
index 000000000000..ed5f928c144b
--- /dev/null
+++ b/packages/builders/src/components/selectMenu/StringSelectMenu.ts
@@ -0,0 +1,106 @@
+import type { APIStringSelectComponent } from 'discord-api-types/v10';
+import { ComponentType, type APISelectMenuOption } from 'discord-api-types/v10';
+import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
+import { jsonOptionValidator, optionsLengthValidator, validateRequiredSelectMenuParameters } from '../Assertions.js';
+import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
+import { StringSelectMenuOptionBuilder } from './StringSelectMenuOption.js';
+
+/**
+ * Represents a string select menu component
+ */
+export class StringSelectMenuBuilder extends BaseSelectMenuBuilder {
+ /**
+ * The options within this select menu
+ */
+ public readonly options: StringSelectMenuOptionBuilder[];
+
+ /**
+ * Creates a new select menu from API data
+ *
+ * @param data - The API data to create this select menu with
+ * @example
+ * Creating a select menu from an API data object
+ * ```ts
+ * const selectMenu = new StringSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * placeholder: 'select an option',
+ * max_values: 2,
+ * options: [
+ * { label: 'option 1', value: '1' },
+ * { label: 'option 2', value: '2' },
+ * { label: 'option 3', value: '3' },
+ * ],
+ * });
+ * ```
+ * @example
+ * Creating a select menu using setters and API data
+ * ```ts
+ * const selectMenu = new StringSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * })
+ * .setMinValues(1)
+ * .addOptions({
+ * label: 'Catchy',
+ * value: 'catch',
+ * });
+ * ```
+ */
+ public constructor(data?: Partial) {
+ const { options, ...initData } = data ?? {};
+ super({ ...initData, type: ComponentType.StringSelect });
+ this.options = options?.map((option: APISelectMenuOption) => new StringSelectMenuOptionBuilder(option)) ?? [];
+ }
+
+ /**
+ * Adds options to this select menu
+ *
+ * @param options - The options to add to this select menu
+ * @returns
+ */
+ public addOptions(...options: RestOrArray) {
+ // eslint-disable-next-line no-param-reassign
+ options = normalizeArray(options);
+ optionsLengthValidator.parse(this.options.length + options.length);
+ this.options.push(
+ ...options.map((option) =>
+ option instanceof StringSelectMenuOptionBuilder
+ ? option
+ : new StringSelectMenuOptionBuilder(jsonOptionValidator.parse(option)),
+ ),
+ );
+ return this;
+ }
+
+ /**
+ * Sets the options on this select menu
+ *
+ * @param options - The options to set on this select menu
+ */
+ public setOptions(...options: RestOrArray) {
+ // eslint-disable-next-line no-param-reassign
+ options = normalizeArray(options);
+ optionsLengthValidator.parse(options.length);
+ this.options.splice(
+ 0,
+ this.options.length,
+ ...options.map((option) =>
+ option instanceof StringSelectMenuOptionBuilder
+ ? option
+ : new StringSelectMenuOptionBuilder(jsonOptionValidator.parse(option)),
+ ),
+ );
+ return this;
+ }
+
+ /**
+ * {@inheritDoc ComponentBuilder.toJSON}
+ */
+ public override toJSON(): APIStringSelectComponent {
+ validateRequiredSelectMenuParameters(this.options, this.data.custom_id);
+
+ return {
+ ...this.data,
+ options: this.options.map((option) => option.toJSON()),
+ } as APIStringSelectComponent;
+ }
+}
diff --git a/packages/builders/src/components/selectMenu/SelectMenuOption.ts b/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts
similarity index 83%
rename from packages/builders/src/components/selectMenu/SelectMenuOption.ts
rename to packages/builders/src/components/selectMenu/StringSelectMenuOption.ts
index 6654e7dba0dc..c43145463947 100644
--- a/packages/builders/src/components/selectMenu/SelectMenuOption.ts
+++ b/packages/builders/src/components/selectMenu/StringSelectMenuOption.ts
@@ -8,15 +8,15 @@ import {
} from '../Assertions.js';
/**
- * Represents a option within a select menu component
+ * Represents an option within a string select menu component
*/
-export class SelectMenuOptionBuilder implements JSONEncodable {
+export class StringSelectMenuOptionBuilder implements JSONEncodable {
/**
- * Creates a new select menu option from API data
+ * Creates a new string select menu option from API data
*
- * @param data - The API data to create this select menu option with
+ * @param data - The API data to create this string select menu option with
* @example
- * Creating a select menu option from an API data object
+ * Creating a string select menu option from an API data object
* ```ts
* const selectMenuOption = new SelectMenuOptionBuilder({
* label: 'catchy label',
@@ -24,7 +24,7 @@ export class SelectMenuOptionBuilder implements JSONEncodable {
+ /**
+ * Creates a new select menu from API data
+ *
+ * @param data - The API data to create this select menu with
+ * @example
+ * Creating a select menu from an API data object
+ * ```ts
+ * const selectMenu = new UserSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * placeholder: 'select an option',
+ * max_values: 2,
+ * });
+ * ```
+ * @example
+ * Creating a select menu using setters and API data
+ * ```ts
+ * const selectMenu = new UserSelectMenuBuilder({
+ * custom_id: 'a cool select menu',
+ * })
+ * .setMinValues(1)
+ * ```
+ */
+ public constructor(data?: Partial) {
+ super({ ...data, type: ComponentType.UserSelect });
+ }
+}
diff --git a/packages/builders/src/index.ts b/packages/builders/src/index.ts
index 0db122bc6062..9f644d5b064c 100644
--- a/packages/builders/src/index.ts
+++ b/packages/builders/src/index.ts
@@ -11,8 +11,27 @@ export * from './components/textInput/TextInput.js';
export * as TextInputAssertions from './components/textInput/Assertions.js';
export * from './interactions/modals/Modal.js';
export * as ModalAssertions from './interactions/modals/Assertions.js';
-export * from './components/selectMenu/SelectMenu.js';
-export * from './components/selectMenu/SelectMenuOption.js';
+
+export * from './components/selectMenu/BaseSelectMenu.js';
+export * from './components/selectMenu/ChannelSelectMenu.js';
+export * from './components/selectMenu/MentionableSelectMenu.js';
+export * from './components/selectMenu/RoleSelectMenu.js';
+export * from './components/selectMenu/StringSelectMenu.js';
+// TODO: Remove those aliases in v2
+export {
+ /**
+ * @deprecated Will be removed in the next major version, use {@link StringSelectMenuBuilder} instead.
+ */
+ StringSelectMenuBuilder as SelectMenuBuilder,
+} from './components/selectMenu/StringSelectMenu.js';
+export {
+ /**
+ * @deprecated Will be removed in the next major version, use {@link StringSelectMenuOptionBuilder} instead.
+ */
+ StringSelectMenuOptionBuilder as SelectMenuOptionBuilder,
+} from './components/selectMenu/StringSelectMenuOption.js';
+export * from './components/selectMenu/StringSelectMenuOption.js';
+export * from './components/selectMenu/UserSelectMenu.js';
export * as SlashCommandAssertions from './interactions/slashCommands/Assertions.js';
export * from './interactions/slashCommands/SlashCommandBuilder.js';
diff --git a/packages/builders/src/interactions/contextMenuCommands/Assertions.ts b/packages/builders/src/interactions/contextMenuCommands/Assertions.ts
index 8a7b64b8609d..593da48212b9 100644
--- a/packages/builders/src/interactions/contextMenuCommands/Assertions.ts
+++ b/packages/builders/src/interactions/contextMenuCommands/Assertions.ts
@@ -7,7 +7,7 @@ const namePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(32)
// eslint-disable-next-line prefer-named-capture-group, unicorn/no-unsafe-regex
- .regex(/^( *[\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+ *)+$/u)
+ .regex(/^( *[\p{P}\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}]+ *)+$/u)
.setValidationEnabled(isValidationEnabled);
const typePredicate = s
.union(s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message))
diff --git a/packages/collection/package.json b/packages/collection/package.json
index 53e96e8ef6cb..68cb5ba4e5d3 100644
--- a/packages/collection/package.json
+++ b/packages/collection/package.json
@@ -51,18 +51,18 @@
"homepage": "https://discord.js.org",
"devDependencies": {
"@favware/cliff-jumper": "^1.8.8",
- "@microsoft/api-extractor": "^7.32.0",
- "@types/node": "16.11.64",
- "@vitest/coverage-c8": "^0.24.1",
+ "@microsoft/api-extractor": "^7.33.4",
+ "@types/node": "16.11.68",
+ "@vitest/coverage-c8": "^0.24.3",
"cross-env": "^7.0.3",
"esbuild-plugin-version-injector": "^1.0.0",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4",
- "vitest": "^0.24.1"
+ "vitest": "^0.24.3"
},
"engines": {
"node": ">=16.9.0"
diff --git a/packages/discord.js/package.json b/packages/discord.js/package.json
index 07dece73e724..48f3461df95c 100644
--- a/packages/discord.js/package.json
+++ b/packages/discord.js/package.json
@@ -55,7 +55,7 @@
"@discordjs/util": "workspace:^",
"@sapphire/snowflake": "^3.2.2",
"@types/ws": "^8.5.3",
- "discord-api-types": "^0.37.13",
+ "discord-api-types": "^0.37.15",
"fast-deep-equal": "^3.1.3",
"lodash.snakecase": "^4.1.1",
"tslib": "^2.4.0",
@@ -65,11 +65,11 @@
"devDependencies": {
"@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.8",
- "@types/node": "16.11.64",
+ "@types/node": "16.11.68",
"dtslint": "^4.2.1",
"eslint": "^8.25.0",
"eslint-formatter-pretty": "^4.1.0",
- "jest": "^29.1.2",
+ "jest": "^29.2.1",
"prettier": "^2.7.1",
"tsd": "^0.24.1",
"tslint": "^6.1.3",
diff --git a/packages/discord.js/src/client/Client.js b/packages/discord.js/src/client/Client.js
index 0cf4c2543fd6..f9a91f6dd46e 100644
--- a/packages/discord.js/src/client/Client.js
+++ b/packages/discord.js/src/client/Client.js
@@ -214,13 +214,7 @@ class Client extends BaseClient {
if (!token || typeof token !== 'string') throw new DiscordjsError(ErrorCodes.TokenInvalid);
this.token = token = token.replace(/^(Bot|Bearer)\s*/i, '');
this.rest.setToken(token);
- this.emit(
- Events.Debug,
- `Provided token: ${token
- .split('.')
- .map((val, i) => (i > 1 ? val.replace(/./g, '*') : val))
- .join('.')}`,
- );
+ this.emit(Events.Debug, `Provided token: ${this._censoredToken}`);
if (this.options.presence) {
this.options.ws.presence = this.presence._parse(this.options.presence);
@@ -459,6 +453,21 @@ class Client extends BaseClient {
});
}
+ /**
+ * Partially censored client token for debug logging purposes.
+ * @type {?string}
+ * @readonly
+ * @private
+ */
+ get _censoredToken() {
+ if (!this.token) return null;
+
+ return this.token
+ .split('.')
+ .map((val, i) => (i > 1 ? val.replace(/./g, '*') : val))
+ .join('.');
+ }
+
/**
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
* with the client as `this`.
diff --git a/packages/discord.js/src/client/actions/InteractionCreate.js b/packages/discord.js/src/client/actions/InteractionCreate.js
index 9d2b12dceb8f..c079121c7f6f 100644
--- a/packages/discord.js/src/client/actions/InteractionCreate.js
+++ b/packages/discord.js/src/client/actions/InteractionCreate.js
@@ -4,11 +4,15 @@ const { InteractionType, ComponentType, ApplicationCommandType } = require('disc
const Action = require('./Action');
const AutocompleteInteraction = require('../../structures/AutocompleteInteraction');
const ButtonInteraction = require('../../structures/ButtonInteraction');
+const ChannelSelectMenuInteraction = require('../../structures/ChannelSelectMenuInteraction');
const ChatInputCommandInteraction = require('../../structures/ChatInputCommandInteraction');
+const MentionableSelectMenuInteraction = require('../../structures/MentionableSelectMenuInteraction');
const MessageContextMenuCommandInteraction = require('../../structures/MessageContextMenuCommandInteraction');
const ModalSubmitInteraction = require('../../structures/ModalSubmitInteraction');
-const SelectMenuInteraction = require('../../structures/SelectMenuInteraction');
+const RoleSelectMenuInteraction = require('../../structures/RoleSelectMenuInteraction');
+const StringSelectMenuInteraction = require('../../structures/StringSelectMenuInteraction');
const UserContextMenuCommandInteraction = require('../../structures/UserContextMenuCommandInteraction');
+const UserSelectMenuInteraction = require('../../structures/UserSelectMenuInteraction');
const Events = require('../../util/Events');
class InteractionCreateAction extends Action {
@@ -49,8 +53,20 @@ class InteractionCreateAction extends Action {
case ComponentType.Button:
InteractionClass = ButtonInteraction;
break;
- case ComponentType.SelectMenu:
- InteractionClass = SelectMenuInteraction;
+ case ComponentType.StringSelect:
+ InteractionClass = StringSelectMenuInteraction;
+ break;
+ case ComponentType.UserSelect:
+ InteractionClass = UserSelectMenuInteraction;
+ break;
+ case ComponentType.RoleSelect:
+ InteractionClass = RoleSelectMenuInteraction;
+ break;
+ case ComponentType.MentionableSelect:
+ InteractionClass = MentionableSelectMenuInteraction;
+ break;
+ case ComponentType.ChannelSelect:
+ InteractionClass = ChannelSelectMenuInteraction;
break;
default:
client.emit(
diff --git a/packages/discord.js/src/client/websocket/WebSocketShard.js b/packages/discord.js/src/client/websocket/WebSocketShard.js
index 5683938f939d..1f7a9da86127 100644
--- a/packages/discord.js/src/client/websocket/WebSocketShard.js
+++ b/packages/discord.js/src/client/websocket/WebSocketShard.js
@@ -64,6 +64,13 @@ class WebSocketShard extends EventEmitter {
*/
this.sessionId = null;
+ /**
+ * The resume url for this shard
+ * @type {?string}
+ * @private
+ */
+ this.resumeURL = null;
+
/**
* The previous heartbeat ping of the shard
* @type {number}
@@ -73,7 +80,6 @@ class WebSocketShard extends EventEmitter {
/**
* The last time a ping was sent (a timestamp)
* @type {number}
- * @private
*/
this.lastPingTimestamp = -1;
@@ -193,12 +199,14 @@ class WebSocketShard extends EventEmitter {
* or reject if we couldn't connect
*/
connect() {
- const { gateway, client } = this.manager;
+ const { client } = this.manager;
if (this.connection?.readyState === WebSocket.OPEN && this.status === Status.Ready) {
return Promise.resolve();
}
+ const gateway = this.resumeURL ?? this.manager.gateway;
+
return new Promise((resolve, reject) => {
const cleanup = () => {
this.removeListener(WebSocketShardEvents.Close, onClose);
@@ -416,9 +424,10 @@ class WebSocketShard extends EventEmitter {
this.emit(WebSocketShardEvents.Ready);
this.sessionId = packet.d.session_id;
+ this.resumeURL = packet.d.resume_gateway_url;
this.expectedGuilds = new Set(packet.d.guilds.map(d => d.id));
this.status = Status.WaitingForGuilds;
- this.debug(`[READY] Session ${this.sessionId}.`);
+ this.debug(`[READY] Session ${this.sessionId} | Resume url ${this.resumeURL}.`);
this.lastHeartbeatAcked = true;
this.sendHeartbeat('ReadyHeartbeat');
break;
@@ -740,7 +749,12 @@ class WebSocketShard extends EventEmitter {
*/
_send(data) {
if (this.connection?.readyState !== WebSocket.OPEN) {
- this.debug(`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`);
+ this.debug(
+ `Tried to send packet '${JSON.stringify(data).replaceAll(
+ this.manager.client.token,
+ this.manager.client._censoredToken,
+ )}' but no WebSocket is available!`,
+ );
this.destroy({ closeCode: 4_000 });
return;
}
@@ -842,10 +856,11 @@ class WebSocketShard extends EventEmitter {
// Step 4: Cache the old sequence (use to attempt a resume)
if (this.sequence !== -1) this.closeSequence = this.sequence;
- // Step 5: Reset the sequence and session id if requested
+ // Step 5: Reset the sequence, resume url and session id if requested
if (reset) {
this.sequence = -1;
this.sessionId = null;
+ this.resumeURL = null;
}
// Step 6: reset the rate limit data
diff --git a/packages/discord.js/src/errors/ErrorCodes.js b/packages/discord.js/src/errors/ErrorCodes.js
index f0566a0433a8..b5faf0c1cefa 100644
--- a/packages/discord.js/src/errors/ErrorCodes.js
+++ b/packages/discord.js/src/errors/ErrorCodes.js
@@ -128,6 +128,7 @@
* @property {'InteractionAlreadyReplied'} InteractionAlreadyReplied
* @property {'InteractionNotReplied'} InteractionNotReplied
* @property {'InteractionEphemeralReplied'} InteractionEphemeralReplied
+ * This property is deprecated.
* @property {'CommandInteractionOptionNotFound'} CommandInteractionOptionNotFound
* @property {'CommandInteractionOptionType'} CommandInteractionOptionType
diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js
index 1be0d3f7c0eb..61927e2b964c 100644
--- a/packages/discord.js/src/index.js
+++ b/packages/discord.js/src/index.js
@@ -154,9 +154,27 @@ exports.ReactionEmoji = require('./structures/ReactionEmoji');
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
exports.Role = require('./structures/Role').Role;
exports.SelectMenuBuilder = require('./structures/SelectMenuBuilder');
+exports.ChannelSelectMenuBuilder = require('./structures/ChannelSelectMenuBuilder');
+exports.MentionableSelectMenuBuilder = require('./structures/MentionableSelectMenuBuilder');
+exports.RoleSelectMenuBuilder = require('./structures/RoleSelectMenuBuilder');
+exports.StringSelectMenuBuilder = require('./structures/StringSelectMenuBuilder');
+exports.UserSelectMenuBuilder = require('./structures/UserSelectMenuBuilder');
+exports.BaseSelectMenuComponent = require('./structures/BaseSelectMenuComponent');
exports.SelectMenuComponent = require('./structures/SelectMenuComponent');
+exports.ChannelSelectMenuComponent = require('./structures/ChannelSelectMenuComponent');
+exports.MentionableSelectMenuComponent = require('./structures/MentionableSelectMenuComponent');
+exports.RoleSelectMenuComponent = require('./structures/RoleSelectMenuComponent');
+exports.StringSelectMenuComponent = require('./structures/StringSelectMenuComponent');
+exports.UserSelectMenuComponent = require('./structures/UserSelectMenuComponent');
exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction');
+exports.ChannelSelectMenuInteraction = require('./structures/ChannelSelectMenuInteraction');
+exports.MentionableSelectMenuInteraction = require('./structures/MentionableSelectMenuInteraction');
+exports.MentionableSelectMenuInteraction = require('./structures/MentionableSelectMenuInteraction');
+exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteraction');
+exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction');
+exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction');
exports.SelectMenuOptionBuilder = require('./structures/SelectMenuOptionBuilder');
+exports.StringSelectMenuOptionBuilder = require('./structures/StringSelectMenuOptionBuilder');
exports.StageChannel = require('./structures/StageChannel');
exports.StageInstance = require('./structures/StageInstance').StageInstance;
exports.Sticker = require('./structures/Sticker').Sticker;
diff --git a/packages/discord.js/src/managers/GuildChannelManager.js b/packages/discord.js/src/managers/GuildChannelManager.js
index 22358885ee22..81974c01a201 100644
--- a/packages/discord.js/src/managers/GuildChannelManager.js
+++ b/packages/discord.js/src/managers/GuildChannelManager.js
@@ -98,6 +98,24 @@ class GuildChannelManager extends CachedManager {
return super.resolveId(channel);
}
+ /**
+ * Adds the target channel to a channel's followers.
+ * @param {NewsChannel|Snowflake} channel The channel to follow
+ * @param {TextChannelResolvable} targetChannel The channel where published announcements will be posted at
+ * @param {string} [reason] Reason for creating the webhook
+ * @returns {Promise} Returns created target webhook id.
+ */
+ async addFollower(channel, targetChannel, reason) {
+ const channelId = this.resolveId(channel);
+ const targetChannelId = this.resolveId(targetChannel);
+ if (!channelId || !targetChannelId) throw new Error(ErrorCodes.GuildChannelResolve);
+ const { webhook_id } = await this.client.rest.post(Routes.channelFollowers(channelId), {
+ body: { webhook_channel_id: targetChannelId },
+ reason,
+ });
+ return webhook_id;
+ }
+
/**
* Options used to create a new channel in a guild.
* @typedef {CategoryCreateChannelOptions} GuildChannelCreateOptions
diff --git a/packages/discord.js/src/structures/BaseInteraction.js b/packages/discord.js/src/structures/BaseInteraction.js
index 5278c6781381..df98cf2e1c85 100644
--- a/packages/discord.js/src/structures/BaseInteraction.js
+++ b/packages/discord.js/src/structures/BaseInteraction.js
@@ -1,8 +1,10 @@
'use strict';
+const { deprecate } = require('node:util');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10');
const Base = require('./Base');
+const { SelectMenuTypes } = require('../util/Constants');
const PermissionsBitField = require('../util/PermissionsBitField');
/**
@@ -268,12 +270,63 @@ class BaseInteraction extends Base {
return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.Button;
}
+ // TODO: Get rid of this in the next major
/**
- * Indicates whether this interaction is a {@link SelectMenuInteraction}.
+ * Indicates whether this interaction is a {@link StringSelectMenuInteraction}.
* @returns {boolean}
+ *
+ * @deprecated Use {@link Interaction#isStringSelectMenu} instead
*/
isSelectMenu() {
- return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.SelectMenu;
+ return this.isStringSelectMenu();
+ }
+
+ /**
+ * Indicates whether this interaction is a select menu of any known type.
+ * @returns {boolean}
+ */
+ isAnySelectMenu() {
+ return this.type === InteractionType.MessageComponent && SelectMenuTypes.includes(this.componentType);
+ }
+
+ /**
+ * Indicates whether this interaction is a {@link StringSelectMenuInteraction}.
+ * @returns {boolean}
+ */
+ isStringSelectMenu() {
+ return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.StringSelect;
+ }
+
+ /**
+ * Indicates whether this interaction is a {@link UserSelectMenuInteraction}
+ * @returns {boolean}
+ */
+ isUserSelectMenu() {
+ return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.UserSelect;
+ }
+
+ /**
+ * Indicates whether this interaction is a {@link RoleSelectMenuInteraction}
+ * @returns {boolean}
+ */
+ isRoleSelectMenu() {
+ return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.RoleSelect;
+ }
+
+ /**
+ * Indicates whether this interaction is a {@link ChannelSelectMenuInteraction}
+ * @returns {boolean}
+ */
+ isChannelSelectMenu() {
+ return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.ChannelSelect;
+ }
+
+ /**
+ * Indicates whether this interaction is a {@link MenionableSelectMenuInteraction}
+ * @returns {boolean}
+ */
+ isMentionableSelectMenu() {
+ return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.MentionableSelect;
}
/**
@@ -285,4 +338,9 @@ class BaseInteraction extends Base {
}
}
+BaseInteraction.prototype.isSelectMenu = deprecate(
+ BaseInteraction.prototype.isSelectMenu,
+ 'BaseInteraction#isSelectMenu() is deprecated. Use BaseInteraction#isStringSelectMenu() instead.',
+);
+
module.exports = BaseInteraction;
diff --git a/packages/discord.js/src/structures/BaseSelectMenuComponent.js b/packages/discord.js/src/structures/BaseSelectMenuComponent.js
new file mode 100644
index 000000000000..bb08087630a6
--- /dev/null
+++ b/packages/discord.js/src/structures/BaseSelectMenuComponent.js
@@ -0,0 +1,56 @@
+'use strict';
+
+const Component = require('./Component');
+
+/**
+ * Represents a select menu component
+ * @extends {Component}
+ */
+class BaseSelectMenuComponent extends Component {
+ /**
+ * The placeholder for this select menu
+ * @type {?string}
+ * @readonly
+ */
+ get placeholder() {
+ return this.data.placeholder ?? null;
+ }
+
+ /**
+ * The maximum amount of options that can be selected
+ * @type {?number}
+ * @readonly
+ */
+ get maxValues() {
+ return this.data.max_values ?? null;
+ }
+
+ /**
+ * The minimum amount of options that must be selected
+ * @type {?number}
+ * @readonly
+ */
+ get minValues() {
+ return this.data.min_values ?? null;
+ }
+
+ /**
+ * The custom id of this select menu
+ * @type {string}
+ * @readonly
+ */
+ get customId() {
+ return this.data.custom_id;
+ }
+
+ /**
+ * Whether this select menu is disabled
+ * @type {?boolean}
+ * @readonly
+ */
+ get disabled() {
+ return this.data.disabled ?? null;
+ }
+}
+
+module.exports = BaseSelectMenuComponent;
diff --git a/packages/discord.js/src/structures/ChannelSelectMenuBuilder.js b/packages/discord.js/src/structures/ChannelSelectMenuBuilder.js
new file mode 100644
index 000000000000..324f70b3e27f
--- /dev/null
+++ b/packages/discord.js/src/structures/ChannelSelectMenuBuilder.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const { ChannelSelectMenuBuilder: BuildersChannelSelectMenu, isJSONEncodable } = require('@discordjs/builders');
+const { toSnakeCase } = require('../util/Transformers');
+
+/**
+ * Class used to build select menu components to be sent through the API
+ * @extends {BuildersChannelSelectMenu}
+ */
+class ChannelSelectMenuBuilder extends BuildersChannelSelectMenu {
+ constructor(data = {}) {
+ super(toSnakeCase(data));
+ }
+
+ /**
+ * Creates a new select menu builder from json data
+ * @param {JSONEncodable | APISelectMenuComponent} other The other data
+ * @returns {ChannelSelectMenuBuilder}
+ */
+ static from(other) {
+ if (isJSONEncodable(other)) {
+ return new this(other.toJSON());
+ }
+ return new this(other);
+ }
+}
+
+module.exports = ChannelSelectMenuBuilder;
+
+/**
+ * @external BuildersChannelSelectMenu
+ * @see {@link https://discord.js.org/#/docs/builders/main/class/ChannelSelectMenuBuilder}
+ */
diff --git a/packages/discord.js/src/structures/ChannelSelectMenuComponent.js b/packages/discord.js/src/structures/ChannelSelectMenuComponent.js
new file mode 100644
index 000000000000..90a706315fb0
--- /dev/null
+++ b/packages/discord.js/src/structures/ChannelSelectMenuComponent.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const BaseSelectMenuComponent = require('./BaseSelectMenuComponent');
+
+/**
+ * Represents a channel select menu component
+ * @extends {BaseSelectMenuComponent}
+ */
+class ChannelSelectMenuComponent extends BaseSelectMenuComponent {
+ /**
+ * The options in this select menu
+ * @type {?(ChannelType[])}
+ * @readonly
+ */
+ get channelTypes() {
+ return this.data.channel_types ?? null;
+ }
+}
+
+module.exports = ChannelSelectMenuComponent;
diff --git a/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js b/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js
new file mode 100644
index 000000000000..04d076e599f0
--- /dev/null
+++ b/packages/discord.js/src/structures/ChannelSelectMenuInteraction.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const { Collection } = require('@discordjs/collection');
+const MessageComponentInteraction = require('./MessageComponentInteraction');
+
+/**
+ * Represents a {@link ComponentType.ChannelSelect} select menu interaction.
+ * @extends {MessageComponentInteraction}
+ */
+class ChannelSelectMenuInteraction extends MessageComponentInteraction {
+ constructor(client, data) {
+ super(client, data);
+
+ /**
+ * Collection of the selected channels
+ * @type {Collection}
+ */
+ this.channels = new Collection();
+ for (const channel of Object.values(data.data.resolved.channels)) {
+ this.channels.set(channel.id, this.client.channels._add(channel, this.guild) ?? channel);
+ }
+ }
+}
+
+module.exports = ChannelSelectMenuInteraction;
diff --git a/packages/discord.js/src/structures/InteractionCollector.js b/packages/discord.js/src/structures/InteractionCollector.js
index 92f4d63a9ee4..0e63c7d1c20b 100644
--- a/packages/discord.js/src/structures/InteractionCollector.js
+++ b/packages/discord.js/src/structures/InteractionCollector.js
@@ -147,10 +147,17 @@ class InteractionCollector extends Collector {
* @event InteractionCollector#collect
* @param {BaseInteraction} interaction The interaction that was collected
*/
+
if (this.interactionType && interaction.type !== this.interactionType) return null;
if (this.componentType && interaction.componentType !== this.componentType) return null;
if (this.messageId && interaction.message?.id !== this.messageId) return null;
- if (this.messageInteractionId && interaction.message?.interaction?.id !== this.messageInteractionId) return null;
+ if (
+ this.messageInteractionId &&
+ interaction.message?.interaction?.id &&
+ interaction.message.interaction.id !== this.messageInteractionId
+ ) {
+ return null;
+ }
if (this.channelId && interaction.channelId !== this.channelId) return null;
if (this.guildId && interaction.guildId !== this.guildId) return null;
diff --git a/packages/discord.js/src/structures/MentionableSelectMenuBuilder.js b/packages/discord.js/src/structures/MentionableSelectMenuBuilder.js
new file mode 100644
index 000000000000..d5673db6865e
--- /dev/null
+++ b/packages/discord.js/src/structures/MentionableSelectMenuBuilder.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const { MentionableSelectMenuBuilder: BuildersMentionableSelectMenu, isJSONEncodable } = require('@discordjs/builders');
+const { toSnakeCase } = require('../util/Transformers');
+
+/**
+ * Class used to build select menu components to be sent through the API
+ * @extends {BuildersMentionableSelectMenu}
+ */
+class MentionableSelectMenuBuilder extends BuildersMentionableSelectMenu {
+ constructor(data = {}) {
+ super(toSnakeCase(data));
+ }
+
+ /**
+ * Creates a new select menu builder from json data
+ * @param {JSONEncodable | APISelectMenuComponent} other The other data
+ * @returns {MentionableSelectMenuBuilder}
+ */
+ static from(other) {
+ if (isJSONEncodable(other)) {
+ return new this(other.toJSON());
+ }
+ return new this(other);
+ }
+}
+
+module.exports = MentionableSelectMenuBuilder;
+
+/**
+ * @external BuildersMentionableSelectMenu
+ * @see {@link https://discord.js.org/#/docs/builders/main/class/MentionableSelectMenuBuilder}
+ */
diff --git a/packages/discord.js/src/structures/MentionableSelectMenuComponent.js b/packages/discord.js/src/structures/MentionableSelectMenuComponent.js
new file mode 100644
index 000000000000..d0f75c356e0c
--- /dev/null
+++ b/packages/discord.js/src/structures/MentionableSelectMenuComponent.js
@@ -0,0 +1,11 @@
+'use strict';
+
+const BaseSelectMenuComponent = require('./BaseSelectMenuComponent');
+
+/**
+ * Represents a mentionable select menu component
+ * @extends {BaseSelectMenuComponent}
+ */
+class MentionableSelectMenuComponent extends BaseSelectMenuComponent {}
+
+module.exports = MentionableSelectMenuComponent;
diff --git a/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js
new file mode 100644
index 000000000000..bd294a04a74a
--- /dev/null
+++ b/packages/discord.js/src/structures/MentionableSelectMenuInteraction.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const { Collection } = require('@discordjs/collection');
+const MessageComponentInteraction = require('./MessageComponentInteraction');
+const Events = require('../util/Events');
+
+/**
+ * Represents a {@link ComponentType.MentionableSelect} select menu interaction.
+ * @extends {MessageComponentInteraction}
+ */
+class MentionableSelectMenuInteraction extends MessageComponentInteraction {
+ constructor(client, data) {
+ super(client, data);
+
+ const { members, users, roles } = data.data.resolved ?? {};
+
+ /**
+ * Collection of the selected users
+ * @type {Collection}
+ */
+ this.users = new Collection();
+
+ /**
+ * Collection of the selected users
+ * @type {Collection}
+ */
+ this.members = new Collection();
+
+ /**
+ * Collection of the selected roles
+ * @type {Collection}
+ */
+ this.roles = new Collection();
+
+ if (members) {
+ for (const [id, member] of Object.entries(members)) {
+ const user = users[id];
+ if (!user) {
+ this.client.emit(
+ Events.Debug,
+ `[MentionableSelectMenuInteraction] Received a member without a user, skipping ${id}`,
+ );
+
+ continue;
+ }
+
+ this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member });
+ }
+ }
+
+ if (users) {
+ for (const user of Object.values(users)) {
+ this.users.set(user.id, this.client.users._add(user));
+ }
+ }
+
+ if (roles) {
+ for (const role of Object.values(roles)) {
+ this.roles.set(role.id, this.guild?.roles._add(role) ?? role);
+ }
+ }
+ }
+}
+
+module.exports = MentionableSelectMenuInteraction;
diff --git a/packages/discord.js/src/structures/Message.js b/packages/discord.js/src/structures/Message.js
index 6d5bc84d3d4d..dd5a4b6ff12e 100644
--- a/packages/discord.js/src/structures/Message.js
+++ b/packages/discord.js/src/structures/Message.js
@@ -788,7 +788,6 @@ class Message extends Base {
/**
* Options provided when sending a message as an inline reply.
* @typedef {BaseMessageCreateOptions} MessageReplyOptions
- * @property {StickerResolvable[]} [stickers=[]] The stickers to send in the message
* @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the referenced
* message does not exist (creates a standard message in this case when false)
* @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
diff --git a/packages/discord.js/src/structures/RoleSelectMenuBuilder.js b/packages/discord.js/src/structures/RoleSelectMenuBuilder.js
new file mode 100644
index 000000000000..a42b436fa2c8
--- /dev/null
+++ b/packages/discord.js/src/structures/RoleSelectMenuBuilder.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const { RoleSelectMenuBuilder: BuildersRoleSelectMenu, isJSONEncodable } = require('@discordjs/builders');
+const { toSnakeCase } = require('../util/Transformers');
+
+/**
+ * Class used to build select menu components to be sent through the API
+ * @extends {BuildersRoleSelectMenu}
+ */
+class RoleSelectMenuBuilder extends BuildersRoleSelectMenu {
+ constructor(data = {}) {
+ super(toSnakeCase(data));
+ }
+
+ /**
+ * Creates a new select menu builder from json data
+ * @param {JSONEncodable | APISelectMenuComponent} other The other data
+ * @returns {RoleSelectMenuBuilder}
+ */
+ static from(other) {
+ if (isJSONEncodable(other)) {
+ return new this(other.toJSON());
+ }
+ return new this(other);
+ }
+}
+
+module.exports = RoleSelectMenuBuilder;
+
+/**
+ * @external BuildersRoleSelectMenu
+ * @see {@link https://discord.js.org/#/docs/builders/main/class/RoleSelectMenuBuilder}
+ */
diff --git a/packages/discord.js/src/structures/RoleSelectMenuComponent.js b/packages/discord.js/src/structures/RoleSelectMenuComponent.js
new file mode 100644
index 000000000000..1b279428d57c
--- /dev/null
+++ b/packages/discord.js/src/structures/RoleSelectMenuComponent.js
@@ -0,0 +1,11 @@
+'use strict';
+
+const BaseSelectMenuComponent = require('./BaseSelectMenuComponent');
+
+/**
+ * Represents a role select menu component
+ * @extends {BaseSelectMenuComponent}
+ */
+class RoleSelectMenuComponent extends BaseSelectMenuComponent {}
+
+module.exports = RoleSelectMenuComponent;
diff --git a/packages/discord.js/src/structures/RoleSelectMenuInteraction.js b/packages/discord.js/src/structures/RoleSelectMenuInteraction.js
new file mode 100644
index 000000000000..b45d356a2828
--- /dev/null
+++ b/packages/discord.js/src/structures/RoleSelectMenuInteraction.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const { Collection } = require('@discordjs/collection');
+const MessageComponentInteraction = require('./MessageComponentInteraction');
+
+/**
+ * Represents a {@link ComponentType.RoleSelect} select menu interaction.
+ * @extends {MessageComponentInteraction}
+ */
+class RoleSelectMenuInteraction extends MessageComponentInteraction {
+ constructor(client, data) {
+ super(client, data);
+
+ /**
+ * Collection of the selected roles
+ * @type {Collection}
+ */
+ this.roles = new Collection();
+ for (const role of Object.values(data.data.resolved.roles)) {
+ this.roles.set(role.id, this.guild?.roles._add(role) ?? role);
+ }
+ }
+}
+
+module.exports = RoleSelectMenuInteraction;
diff --git a/packages/discord.js/src/structures/SelectMenuBuilder.js b/packages/discord.js/src/structures/SelectMenuBuilder.js
index 738a18fe2950..caa971b468e9 100644
--- a/packages/discord.js/src/structures/SelectMenuBuilder.js
+++ b/packages/discord.js/src/structures/SelectMenuBuilder.js
@@ -1,78 +1,25 @@
'use strict';
-const { SelectMenuBuilder: BuildersSelectMenu, isJSONEncodable, normalizeArray } = require('@discordjs/builders');
-const { toSnakeCase } = require('../util/Transformers');
-const { resolvePartialEmoji } = require('../util/Util');
+const process = require('node:process');
+const StringSelectMenuBuilder = require('./StringSelectMenuBuilder');
+
+let deprecationEmitted = false;
/**
- * Class used to build select menu components to be sent through the API
- * @extends {BuildersSelectMenu}
+ * @deprecated Use {@link StringSelectMenuBuilder} instead.
*/
-class SelectMenuBuilder extends BuildersSelectMenu {
- constructor({ options, ...data } = {}) {
- super(
- toSnakeCase({
- ...data,
- options: options?.map(({ emoji, ...option }) => ({
- ...option,
- emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji,
- })),
- }),
- );
- }
-
- /**
- * Normalizes a select menu option emoji
- * @param {SelectMenuOptionData|JSONEncodable} selectMenuOption The option to normalize
- * @returns {Array}
- * @private
- */
- static normalizeEmoji(selectMenuOption) {
- if (isJSONEncodable(selectMenuOption)) {
- return selectMenuOption;
- }
-
- const { emoji, ...option } = selectMenuOption;
- return {
- ...option,
- emoji: typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji,
- };
- }
-
- /**
- * Adds options to this select menu
- * @param {RestOrArray} options The options to add to this select menu
- * @returns {SelectMenuBuilder}
- */
- addOptions(...options) {
- return super.addOptions(normalizeArray(options).map(option => SelectMenuBuilder.normalizeEmoji(option)));
- }
-
- /**
- * Sets the options on this select menu
- * @param {RestOrArray} options The options to set on this select menu
- * @returns {SelectMenuBuilder}
- */
- setOptions(...options) {
- return super.setOptions(normalizeArray(options).map(option => SelectMenuBuilder.normalizeEmoji(option)));
- }
-
- /**
- * Creates a new select menu builder from json data
- * @param {JSONEncodable | APISelectMenuComponent} other The other data
- * @returns {SelectMenuBuilder}
- */
- static from(other) {
- if (isJSONEncodable(other)) {
- return new this(other.toJSON());
+class SelectMenuBuilder extends StringSelectMenuBuilder {
+ constructor(...params) {
+ super(...params);
+
+ if (!deprecationEmitted) {
+ process.emitWarning(
+ 'The SelectMenuBuilder class is deprecated, use StringSelectMenuBuilder instead.',
+ 'DeprecationWarning',
+ );
+ deprecationEmitted = true;
}
- return new this(other);
}
}
module.exports = SelectMenuBuilder;
-
-/**
- * @external BuildersSelectMenu
- * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuBuilder}
- */
diff --git a/packages/discord.js/src/structures/SelectMenuComponent.js b/packages/discord.js/src/structures/SelectMenuComponent.js
index 1d3e80bc77d0..850c985275c2 100644
--- a/packages/discord.js/src/structures/SelectMenuComponent.js
+++ b/packages/discord.js/src/structures/SelectMenuComponent.js
@@ -1,64 +1,24 @@
'use strict';
-const Component = require('./Component');
+const process = require('node:process');
+const StringSelectMenuComponent = require('./StringSelectMenuComponent');
+
+let deprecationEmitted = false;
/**
- * Represents a select menu component
- * @extends {Component}
+ * @deprecated Use {@link StringSelectMenuComponent} instead.
*/
-class SelectMenuComponent extends Component {
- /**
- * The placeholder for this select menu
- * @type {?string}
- * @readonly
- */
- get placeholder() {
- return this.data.placeholder ?? null;
- }
-
- /**
- * The maximum amount of options that can be selected
- * @type {?number}
- * @readonly
- */
- get maxValues() {
- return this.data.max_values ?? null;
- }
-
- /**
- * The minimum amount of options that must be selected
- * @type {?number}
- * @readonly
- */
- get minValues() {
- return this.data.min_values ?? null;
- }
-
- /**
- * The custom id of this select menu
- * @type {string}
- * @readonly
- */
- get customId() {
- return this.data.custom_id;
- }
-
- /**
- * Whether this select menu is disabled
- * @type {?boolean}
- * @readonly
- */
- get disabled() {
- return this.data.disabled ?? null;
- }
-
- /**
- * The options in this select menu
- * @type {APISelectMenuOption[]}
- * @readonly
- */
- get options() {
- return this.data.options;
+class SelectMenuComponent extends StringSelectMenuComponent {
+ constructor(...params) {
+ super(...params);
+
+ if (!deprecationEmitted) {
+ process.emitWarning(
+ 'The SelectMenuComponent class is deprecated, use StringSelectMenuComponent instead.',
+ 'DeprecationWarning',
+ );
+ deprecationEmitted = true;
+ }
}
}
diff --git a/packages/discord.js/src/structures/SelectMenuInteraction.js b/packages/discord.js/src/structures/SelectMenuInteraction.js
index 42ef0c1069ff..9ee092d008b1 100644
--- a/packages/discord.js/src/structures/SelectMenuInteraction.js
+++ b/packages/discord.js/src/structures/SelectMenuInteraction.js
@@ -1,20 +1,24 @@
'use strict';
-const MessageComponentInteraction = require('./MessageComponentInteraction');
+const process = require('node:process');
+const StringSelectMenuInteraction = require('./StringSelectMenuInteraction');
+
+let deprecationEmitted = false;
/**
- * Represents a select menu interaction.
- * @extends {MessageComponentInteraction}
+ * @deprecated Use {@link StringSelectMenuInteraction} instead.
*/
-class SelectMenuInteraction extends MessageComponentInteraction {
- constructor(client, data) {
- super(client, data);
+class SelectMenuInteraction extends StringSelectMenuInteraction {
+ constructor(...params) {
+ super(...params);
- /**
- * The values selected, if the component which was interacted with was a select menu
- * @type {string[]}
- */
- this.values = data.data.values ?? [];
+ if (!deprecationEmitted) {
+ process.emitWarning(
+ 'The SelectMenuInteraction class is deprecated, use StringSelectMenuInteraction instead.',
+ 'DeprecationWarning',
+ );
+ deprecationEmitted = true;
+ }
}
}
diff --git a/packages/discord.js/src/structures/SelectMenuOptionBuilder.js b/packages/discord.js/src/structures/SelectMenuOptionBuilder.js
index 8c9d87a22779..ac4d265379d7 100644
--- a/packages/discord.js/src/structures/SelectMenuOptionBuilder.js
+++ b/packages/discord.js/src/structures/SelectMenuOptionBuilder.js
@@ -1,50 +1,25 @@
'use strict';
-const { SelectMenuOptionBuilder: BuildersSelectMenuOption, isJSONEncodable } = require('@discordjs/builders');
-const { toSnakeCase } = require('../util/Transformers');
-const { resolvePartialEmoji } = require('../util/Util');
+const process = require('node:process');
+const StringSelectMenuOptionBuilder = require('./StringSelectMenuOptionBuilder');
+
+let deprecationEmitted = false;
/**
- * Represents a select menu option builder.
- * @extends {BuildersSelectMenuOption}
+ * @deprecated Use {@link StringSelectMenuOptionBuilder} instead.
*/
-class SelectMenuOptionBuilder extends BuildersSelectMenuOption {
- constructor({ emoji, ...data } = {}) {
- super(
- toSnakeCase({
- ...data,
- emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji,
- }),
- );
- }
- /**
- * Sets the emoji to display on this option
- * @param {ComponentEmojiResolvable} emoji The emoji to display on this option
- * @returns {SelectMenuOptionBuilder}
- */
- setEmoji(emoji) {
- if (typeof emoji === 'string') {
- return super.setEmoji(resolvePartialEmoji(emoji));
- }
- return super.setEmoji(emoji);
- }
+class SelectMenuOptionBuilder extends StringSelectMenuOptionBuilder {
+ constructor(...params) {
+ super(...params);
- /**
- * Creates a new select menu option builder from JSON data
- * @param {JSONEncodable|APISelectMenuOption} other The other data
- * @returns {SelectMenuOptionBuilder}
- */
- static from(other) {
- if (isJSONEncodable(other)) {
- return new this(other.toJSON());
+ if (!deprecationEmitted) {
+ process.emitWarning(
+ 'The SelectMenuOptionBuilder class is deprecated, use StringSelectMenuOptionBuilder instead.',
+ 'DeprecationWarning',
+ );
+ deprecationEmitted = true;
}
- return new this(other);
}
}
module.exports = SelectMenuOptionBuilder;
-
-/**
- * @external BuildersSelectMenuOption
- * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuOptionBuilder}
- */
diff --git a/packages/discord.js/src/structures/StringSelectMenuBuilder.js b/packages/discord.js/src/structures/StringSelectMenuBuilder.js
new file mode 100644
index 000000000000..3dd645e3cb53
--- /dev/null
+++ b/packages/discord.js/src/structures/StringSelectMenuBuilder.js
@@ -0,0 +1,78 @@
+'use strict';
+
+const { SelectMenuBuilder: BuildersSelectMenu, isJSONEncodable, normalizeArray } = require('@discordjs/builders');
+const { toSnakeCase } = require('../util/Transformers');
+const { resolvePartialEmoji } = require('../util/Util');
+
+/**
+ * Class used to build select menu components to be sent through the API
+ * @extends {BuildersSelectMenu}
+ */
+class StringSelectMenuBuilder extends BuildersSelectMenu {
+ constructor({ options, ...data } = {}) {
+ super(
+ toSnakeCase({
+ ...data,
+ options: options?.map(({ emoji, ...option }) => ({
+ ...option,
+ emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji,
+ })),
+ }),
+ );
+ }
+
+ /**
+ * Normalizes a select menu option emoji
+ * @param {SelectMenuOptionData|JSONEncodable} selectMenuOption The option to normalize
+ * @returns {SelectMenuOptionBuilder|APISelectMenuOption}
+ * @private
+ */
+ static normalizeEmoji(selectMenuOption) {
+ if (isJSONEncodable(selectMenuOption)) {
+ return selectMenuOption;
+ }
+
+ const { emoji, ...option } = selectMenuOption;
+ return {
+ ...option,
+ emoji: typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji,
+ };
+ }
+
+ /**
+ * Adds options to this select menu
+ * @param {RestOrArray} options The options to add to this select menu
+ * @returns {StringSelectMenuBuilder}
+ */
+ addOptions(...options) {
+ return super.addOptions(normalizeArray(options).map(option => StringSelectMenuBuilder.normalizeEmoji(option)));
+ }
+
+ /**
+ * Sets the options on this select menu
+ * @param {RestOrArray} options The options to set on this select menu
+ * @returns {StringSelectMenuBuilder}
+ */
+ setOptions(...options) {
+ return super.setOptions(normalizeArray(options).map(option => StringSelectMenuBuilder.normalizeEmoji(option)));
+ }
+
+ /**
+ * Creates a new select menu builder from json data
+ * @param {JSONEncodable | APISelectMenuComponent} other The other data
+ * @returns {StringSelectMenuBuilder}
+ */
+ static from(other) {
+ if (isJSONEncodable(other)) {
+ return new this(other.toJSON());
+ }
+ return new this(other);
+ }
+}
+
+module.exports = StringSelectMenuBuilder;
+
+/**
+ * @external BuildersSelectMenu
+ * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuBuilder}
+ */
diff --git a/packages/discord.js/src/structures/StringSelectMenuComponent.js b/packages/discord.js/src/structures/StringSelectMenuComponent.js
new file mode 100644
index 000000000000..e008ae5f2b08
--- /dev/null
+++ b/packages/discord.js/src/structures/StringSelectMenuComponent.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const BaseSelectMenuComponent = require('./BaseSelectMenuComponent');
+
+/**
+ * Represents a string select menu component
+ * @extends {BaseSelectMenuComponent}
+ */
+class StringSelectMenuComponent extends BaseSelectMenuComponent {
+ /**
+ * The options in this select menu
+ * @type {APISelectMenuOption[]}
+ * @readonly
+ */
+ get options() {
+ return this.data.options;
+ }
+}
+
+module.exports = StringSelectMenuComponent;
diff --git a/packages/discord.js/src/structures/StringSelectMenuInteraction.js b/packages/discord.js/src/structures/StringSelectMenuInteraction.js
new file mode 100644
index 000000000000..1db8c28f6711
--- /dev/null
+++ b/packages/discord.js/src/structures/StringSelectMenuInteraction.js
@@ -0,0 +1,21 @@
+'use strict';
+
+const MessageComponentInteraction = require('./MessageComponentInteraction');
+
+/**
+ * Represents a {@link ComponentType.StringSelect} select menu interaction.
+ * @extends {MessageComponentInteraction}
+ */
+class StringSelectMenuInteraction extends MessageComponentInteraction {
+ constructor(client, data) {
+ super(client, data);
+
+ /**
+ * The values selected
+ * @type {string[]}
+ */
+ this.values = data.data.values ?? [];
+ }
+}
+
+module.exports = StringSelectMenuInteraction;
diff --git a/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js b/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js
new file mode 100644
index 000000000000..f5fa6d9e6884
--- /dev/null
+++ b/packages/discord.js/src/structures/StringSelectMenuOptionBuilder.js
@@ -0,0 +1,51 @@
+'use strict';
+
+const { SelectMenuOptionBuilder: BuildersSelectMenuOption, isJSONEncodable } = require('@discordjs/builders');
+const { toSnakeCase } = require('../util/Transformers');
+const { resolvePartialEmoji } = require('../util/Util');
+
+/**
+ * Represents a select menu option builder.
+ * @extends {BuildersSelectMenuOption}
+ */
+class StringSelectMenuOptionBuilder extends BuildersSelectMenuOption {
+ constructor({ emoji, ...data } = {}) {
+ super(
+ toSnakeCase({
+ ...data,
+ emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji,
+ }),
+ );
+ }
+
+ /**
+ * Sets the emoji to display on this option
+ * @param {ComponentEmojiResolvable} emoji The emoji to display on this option
+ * @returns {StringSelectMenuOptionBuilder}
+ */
+ setEmoji(emoji) {
+ if (typeof emoji === 'string') {
+ return super.setEmoji(resolvePartialEmoji(emoji));
+ }
+ return super.setEmoji(emoji);
+ }
+
+ /**
+ * Creates a new select menu option builder from JSON data
+ * @param {JSONEncodable|APISelectMenuOption} other The other data
+ * @returns {StringSelectMenuOptionBuilder}
+ */
+ static from(other) {
+ if (isJSONEncodable(other)) {
+ return new this(other.toJSON());
+ }
+ return new this(other);
+ }
+}
+
+module.exports = StringSelectMenuOptionBuilder;
+
+/**
+ * @external BuildersSelectMenuOption
+ * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuOptionBuilder}
+ */
diff --git a/packages/discord.js/src/structures/UserSelectMenuBuilder.js b/packages/discord.js/src/structures/UserSelectMenuBuilder.js
new file mode 100644
index 000000000000..39db60fff326
--- /dev/null
+++ b/packages/discord.js/src/structures/UserSelectMenuBuilder.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const { UserSelectMenuBuilder: BuildersUserSelectMenu, isJSONEncodable } = require('@discordjs/builders');
+const { toSnakeCase } = require('../util/Transformers');
+
+/**
+ * Class used to build select menu components to be sent through the API
+ * @extends {BuildersUserSelectMenu}
+ */
+class UserSelectMenuBuilder extends BuildersUserSelectMenu {
+ constructor(data = {}) {
+ super(toSnakeCase(data));
+ }
+
+ /**
+ * Creates a new select menu builder from json data
+ * @param {JSONEncodable | APISelectMenuComponent} other The other data
+ * @returns {UserSelectMenuBuilder}
+ */
+ static from(other) {
+ if (isJSONEncodable(other)) {
+ return new this(other.toJSON());
+ }
+ return new this(other);
+ }
+}
+
+module.exports = UserSelectMenuBuilder;
+
+/**
+ * @external BuildersUserSelectMenu
+ * @see {@link https://discord.js.org/#/docs/builders/main/class/UserSelectMenuBuilder}
+ */
diff --git a/packages/discord.js/src/structures/UserSelectMenuComponent.js b/packages/discord.js/src/structures/UserSelectMenuComponent.js
new file mode 100644
index 000000000000..0acacdfab2e5
--- /dev/null
+++ b/packages/discord.js/src/structures/UserSelectMenuComponent.js
@@ -0,0 +1,11 @@
+'use strict';
+
+const BaseSelectMenuComponent = require('./BaseSelectMenuComponent');
+
+/**
+ * Represents a user select menu component
+ * @extends {BaseSelectMenuComponent}
+ */
+class UserSelectMenuComponent extends BaseSelectMenuComponent {}
+
+module.exports = UserSelectMenuComponent;
diff --git a/packages/discord.js/src/structures/UserSelectMenuInteraction.js b/packages/discord.js/src/structures/UserSelectMenuInteraction.js
new file mode 100644
index 000000000000..d2af4176a5e7
--- /dev/null
+++ b/packages/discord.js/src/structures/UserSelectMenuInteraction.js
@@ -0,0 +1,49 @@
+'use strict';
+
+const { Collection } = require('@discordjs/collection');
+const MessageComponentInteraction = require('./MessageComponentInteraction');
+const Events = require('../util/Events');
+
+/**
+ * Represents a {@link ComponentType.UserSelect} select menu interaction.
+ * @extends {MessageComponentInteraction}
+ */
+class UserSelectMenuInteraction extends MessageComponentInteraction {
+ constructor(client, data) {
+ super(client, data);
+
+ /**
+ * Collection of the selected users
+ * @type {Collection}
+ */
+ this.users = new Collection();
+
+ /**
+ * Collection of the selected members
+ * @type {Collection}
+ */
+ this.members = new Collection();
+
+ for (const user of Object.values(data.data.resolved.users)) {
+ this.users.set(user.id, this.client.users._add(user));
+ }
+
+ if (data.data.resolved.members) {
+ for (const [id, member] of Object.entries(data.data.resolved.members)) {
+ const user = data.data.resolved.users[id];
+ if (!user) {
+ this.client.emit(
+ Events.Debug,
+ `[UserSelectMenuInteraction] Received a member without a user, skipping ${id}`,
+ );
+
+ continue;
+ }
+
+ this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member });
+ }
+ }
+ }
+}
+
+module.exports = UserSelectMenuInteraction;
diff --git a/packages/discord.js/src/structures/interfaces/InteractionResponses.js b/packages/discord.js/src/structures/interfaces/InteractionResponses.js
index f8f32ebe607d..5d07325756b0 100644
--- a/packages/discord.js/src/structures/interfaces/InteractionResponses.js
+++ b/packages/discord.js/src/structures/interfaces/InteractionResponses.js
@@ -122,50 +122,56 @@ class InteractionResponses {
}
/**
- * Fetches the initial reply to this interaction.
+ * Fetches a reply to this interaction.
* @see Webhook#fetchMessage
+ * @param {Snowflake|'@original'} [message='@original'] The response to fetch
* @returns {Promise}
* @example
- * // Fetch the reply to this interaction
+ * // Fetch the initial reply to this interaction
* interaction.fetchReply()
* .then(reply => console.log(`Replied with ${reply.content}`))
* .catch(console.error);
*/
- fetchReply() {
- return this.webhook.fetchMessage('@original');
+ fetchReply(message = '@original') {
+ return this.webhook.fetchMessage(message);
}
/**
- * Edits the initial reply to this interaction.
+ * @typedef {WebhookEditMessageOptions} InteractionEditReplyOptions
+ * @property {MessageResolvable|'@original'} [message='@original'] The response to edit
+ */
+
+ /**
+ * Edits a reply to this interaction.
* @see Webhook#editMessage
- * @param {string|MessagePayload|WebhookEditMessageOptions} options The new options for the message
+ * @param {string|MessagePayload|InteractionEditReplyOptions} options The new options for the message
* @returns {Promise}
* @example
- * // Edit the reply to this interaction
+ * // Edit the initial reply to this interaction
* interaction.editReply('New content')
* .then(console.log)
* .catch(console.error);
*/
async editReply(options) {
if (!this.deferred && !this.replied) throw new DiscordjsError(ErrorCodes.InteractionNotReplied);
- const message = await this.webhook.editMessage('@original', options);
+ const msg = await this.webhook.editMessage(options.message ?? '@original', options);
this.replied = true;
- return message;
+ return msg;
}
/**
- * Deletes the initial reply to this interaction.
+ * Deletes a reply to this interaction.
* @see Webhook#deleteMessage
+ * @param {MessageResolvable|'@original'} [message='@original'] The response to delete
* @returns {Promise}
* @example
- * // Delete the reply to this interaction
+ * // Delete the initial reply to this interaction
* interaction.deleteReply()
* .then(console.log)
* .catch(console.error);
*/
- async deleteReply() {
- if (this.ephemeral) throw new DiscordjsError(ErrorCodes.InteractionEphemeralReplied);
- await this.webhook.deleteMessage('@original');
+ async deleteReply(message = '@original') {
+ await this.webhook.deleteMessage(message);
}
/**
diff --git a/packages/discord.js/src/util/Components.js b/packages/discord.js/src/util/Components.js
index 02411f42b11c..42bee7f2afa0 100644
--- a/packages/discord.js/src/util/Components.js
+++ b/packages/discord.js/src/util/Components.js
@@ -82,10 +82,18 @@ function createComponent(data) {
return new ActionRow(data);
case ComponentType.Button:
return new ButtonComponent(data);
- case ComponentType.SelectMenu:
- return new SelectMenuComponent(data);
+ case ComponentType.StringSelect:
+ return new StringSelectMenuComponent(data);
case ComponentType.TextInput:
return new TextInputComponent(data);
+ case ComponentType.UserSelect:
+ return new UserSelectMenuComponent(data);
+ case ComponentType.RoleSelect:
+ return new RoleSelectMenuComponent(data);
+ case ComponentType.MentionableSelect:
+ return new MentionableSelectMenuComponent(data);
+ case ComponentType.ChannelSelect:
+ return new ChannelSelectMenuComponent(data);
default:
return new Component(data);
}
@@ -106,10 +114,18 @@ function createComponentBuilder(data) {
return new ActionRowBuilder(data);
case ComponentType.Button:
return new ButtonBuilder(data);
- case ComponentType.SelectMenu:
- return new SelectMenuBuilder(data);
+ case ComponentType.StringSelect:
+ return new StringSelectMenuBuilder(data);
case ComponentType.TextInput:
return new TextInputBuilder(data);
+ case ComponentType.UserSelect:
+ return new UserSelectMenuBuilder(data);
+ case ComponentType.RoleSelect:
+ return new RoleSelectMenuBuilder(data);
+ case ComponentType.MentionableSelect:
+ return new MentionableSelectMenuBuilder(data);
+ case ComponentType.ChannelSelect:
+ return new ChannelSelectMenuBuilder(data);
default:
return new ComponentBuilder(data);
}
@@ -121,11 +137,19 @@ const ActionRow = require('../structures/ActionRow');
const ActionRowBuilder = require('../structures/ActionRowBuilder');
const ButtonBuilder = require('../structures/ButtonBuilder');
const ButtonComponent = require('../structures/ButtonComponent');
+const ChannelSelectMenuBuilder = require('../structures/ChannelSelectMenuBuilder');
+const ChannelSelectMenuComponent = require('../structures/ChannelSelectMenuComponent');
const Component = require('../structures/Component');
-const SelectMenuBuilder = require('../structures/SelectMenuBuilder');
-const SelectMenuComponent = require('../structures/SelectMenuComponent');
+const MentionableSelectMenuBuilder = require('../structures/MentionableSelectMenuBuilder');
+const MentionableSelectMenuComponent = require('../structures/MentionableSelectMenuComponent');
+const RoleSelectMenuBuilder = require('../structures/RoleSelectMenuBuilder');
+const RoleSelectMenuComponent = require('../structures/RoleSelectMenuComponent');
+const StringSelectMenuBuilder = require('../structures/StringSelectMenuBuilder');
+const StringSelectMenuComponent = require('../structures/StringSelectMenuComponent');
const TextInputBuilder = require('../structures/TextInputBuilder');
const TextInputComponent = require('../structures/TextInputComponent');
+const UserSelectMenuBuilder = require('../structures/UserSelectMenuBuilder');
+const UserSelectMenuComponent = require('../structures/UserSelectMenuComponent');
/**
* @external JSONEncodable
diff --git a/packages/discord.js/src/util/Constants.js b/packages/discord.js/src/util/Constants.js
index ac012e5f97b0..3f06c3fd50d1 100644
--- a/packages/discord.js/src/util/Constants.js
+++ b/packages/discord.js/src/util/Constants.js
@@ -1,6 +1,6 @@
'use strict';
-const { ChannelType, MessageType } = require('discord-api-types/v10');
+const { ChannelType, MessageType, ComponentType } = require('discord-api-types/v10');
/**
* Max bulk deletable message age
@@ -119,6 +119,23 @@ exports.ThreadChannelTypes = [ChannelType.AnnouncementThread, ChannelType.Public
*/
exports.VoiceBasedChannelTypes = [ChannelType.GuildVoice, ChannelType.GuildStageVoice];
+/**
+ * The types of select menus. The available types are:
+ * * {@link ComponentType.StringSelect}
+ * * {@link ComponentType.UserSelect}
+ * * {@link ComponentType.RoleSelect}
+ * * {@link ComponentType.MentionableSelect}
+ * * {@link ComponentType.ChannelSelect}
+ * @typedef {ComponentType[]} SelectMenuTypes
+ */
+exports.SelectMenuTypes = [
+ ComponentType.StringSelect,
+ ComponentType.UserSelect,
+ ComponentType.RoleSelect,
+ ComponentType.MentionableSelect,
+ ComponentType.ChannelSelect,
+];
+
/**
* @typedef {Object} Constants Constants that can be used in an enum or object-like way.
* @property {number} MaxBulkDeletableMessageAge Max bulk deletable message age
@@ -127,4 +144,5 @@ exports.VoiceBasedChannelTypes = [ChannelType.GuildVoice, ChannelType.GuildStage
* @property {TextBasedChannelTypes} TextBasedChannelTypes The types of channels that are text-based
* @property {ThreadChannelTypes} ThreadChannelTypes The types of channels that are threads
* @property {VoiceBasedChannelTypes} VoiceBasedChannelTypes The types of channels that are voice-based
+ * @property {SelectMenuTypes} SelectMenuTypes The types of components that are select menus.
*/
diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts
index 6b7eeb531c4a..437db959f4d5 100644
--- a/packages/discord.js/typings/index.d.ts
+++ b/packages/discord.js/typings/index.d.ts
@@ -14,14 +14,17 @@ import {
italic,
quote,
roleMention,
- SelectMenuBuilder as BuilderSelectMenuComponent,
+ ChannelSelectMenuBuilder as BuilderChannelSelectMenuComponent,
+ MentionableSelectMenuBuilder as BuilderMentionableSelectMenuComponent,
+ RoleSelectMenuBuilder as BuilderRoleSelectMenuComponent,
+ StringSelectMenuBuilder as BuilderStringSelectMenuComponent,
+ UserSelectMenuBuilder as BuilderUserSelectMenuComponent,
TextInputBuilder as BuilderTextInputComponent,
SelectMenuOptionBuilder as BuildersSelectMenuOption,
spoiler,
strikethrough,
time,
TimestampStyles,
- TimestampStylesString,
underscore,
userMention,
ModalActionRowComponentBuilder,
@@ -35,7 +38,6 @@ import { Collection } from '@discordjs/collection';
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
import {
APIActionRowComponent,
- APIApplicationCommand,
APIApplicationCommandInteractionData,
APIApplicationCommandOption,
APIAuditLogChange,
@@ -126,6 +128,17 @@ import {
TextChannelType,
ChannelFlags,
SortOrderType,
+ APIMessageStringSelectInteractionData,
+ APIMessageUserSelectInteractionData,
+ APIStringSelectComponent,
+ APIUserSelectComponent,
+ APIRoleSelectComponent,
+ APIMentionableSelectComponent,
+ APIChannelSelectComponent,
+ APIGuildMember,
+ APIMessageRoleSelectInteractionData,
+ APIMessageMentionableSelectInteractionData,
+ APIMessageChannelSelectInteractionData,
} from 'discord-api-types/v10';
import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
@@ -158,13 +171,11 @@ import {
RawInviteData,
RawInviteGuildData,
RawInviteStageInstance,
- RawAttachmentData,
RawMessageButtonInteractionData,
RawMessageComponentInteractionData,
RawMessageData,
RawMessagePayloadData,
RawMessageReactionData,
- RawMessageSelectMenuInteractionData,
RawOAuth2GuildData,
RawPartialGroupDMChannelData,
RawPartialMessageData,
@@ -242,7 +253,11 @@ export interface BaseComponentData {
export type MessageActionRowComponentData =
| JSONEncodable
| ButtonComponentData
- | SelectMenuComponentData;
+ | StringSelectMenuComponentData
+ | UserSelectMenuComponentData
+ | RoleSelectMenuComponentData
+ | MentionableSelectMenuComponentData
+ | ChannelSelectMenuComponentData;
export type ModalActionRowComponentData = JSONEncodable | TextInputComponentData;
@@ -269,7 +284,13 @@ export class ActionRowBuilder extends Component<
@@ -439,11 +460,11 @@ export abstract class CommandInteraction e
options: InteractionDeferReplyOptions & { fetchReply: true },
): Promise>>;
public deferReply(options?: InteractionDeferReplyOptions): Promise>>;
- public deleteReply(): Promise;
+ public deleteReply(message?: MessageResolvable | '@original'): Promise;
public editReply(
- options: string | MessagePayload | WebhookEditMessageOptions,
+ options: string | MessagePayload | InteractionEditReplyOptions,
): Promise>>;
- public fetchReply(): Promise>>;
+ public fetchReply(message?: Snowflake | '@original'): Promise>>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise>>;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>>;
public reply(
@@ -604,8 +625,8 @@ export class ButtonBuilder extends BuilderButtonComponent {
public override setEmoji(emoji: ComponentEmojiResolvable): this;
}
-export class SelectMenuBuilder extends BuilderSelectMenuComponent {
- public constructor(data?: Partial);
+export class StringSelectMenuBuilder extends BuilderStringSelectMenuComponent {
+ public constructor(data?: Partial);
private static normalizeEmoji(
selectMenuOption: JSONEncodable | SelectMenuComponentOptionData,
): (APISelectMenuOption | SelectMenuOptionBuilder)[];
@@ -615,7 +636,34 @@ export class SelectMenuBuilder extends BuilderSelectMenuComponent {
public override setOptions(
...options: RestOrArray
): this;
- public static from(other: JSONEncodable | APISelectMenuComponent): SelectMenuBuilder;
+ public static from(other: JSONEncodable | APISelectMenuComponent): StringSelectMenuBuilder;
+}
+
+export {
+ /** @deprecated Use {@link StringSelectMenuBuilder} instead */
+ StringSelectMenuBuilder as SelectMenuBuilder,
+};
+
+export class UserSelectMenuBuilder extends BuilderUserSelectMenuComponent {
+ public constructor(data?: Partial);
+ public static from(other: JSONEncodable | APISelectMenuComponent): UserSelectMenuBuilder;
+}
+
+export class RoleSelectMenuBuilder extends BuilderRoleSelectMenuComponent {
+ public constructor(data?: Partial);
+ public static from(other: JSONEncodable | APISelectMenuComponent): RoleSelectMenuBuilder;
+}
+
+export class MentionableSelectMenuBuilder extends BuilderMentionableSelectMenuComponent {
+ public constructor(data?: Partial);
+ public static from(
+ other: JSONEncodable | APISelectMenuComponent,
+ ): MentionableSelectMenuBuilder;
+}
+
+export class ChannelSelectMenuBuilder extends BuilderChannelSelectMenuComponent {
+ public constructor(data?: Partial);
+ public static from(other: JSONEncodable | APISelectMenuComponent): ChannelSelectMenuBuilder;
}
export class SelectMenuOptionBuilder extends BuildersSelectMenuOption {
@@ -639,16 +687,34 @@ export class TextInputComponent extends Component {
public get value(): string;
}
-export class SelectMenuComponent extends Component {
- private constructor(data: APISelectMenuComponent);
+export class BaseSelectMenuComponent extends Component {
+ protected constructor(data: Data);
public get placeholder(): string | null;
public get maxValues(): number | null;
public get minValues(): number | null;
public get customId(): string;
public get disabled(): boolean | null;
+}
+
+export class StringSelectMenuComponent extends BaseSelectMenuComponent {
public get options(): APISelectMenuOption[];
}
+export {
+ /** @deprecated Use {@link StringSelectMenuComponent} instead */
+ StringSelectMenuComponent as SelectMenuComponent,
+};
+
+export class UserSelectMenuComponent extends BaseSelectMenuComponent {}
+
+export class RoleSelectMenuComponent extends BaseSelectMenuComponent {}
+
+export class MentionableSelectMenuComponent extends BaseSelectMenuComponent {}
+
+export class ChannelSelectMenuComponent extends BaseSelectMenuComponent {
+ public getChannelTypes(): ChannelType[] | null;
+}
+
export interface EmbedData {
title?: string;
type?: EmbedType;
@@ -766,6 +832,7 @@ export class Client extends BaseClient {
private presence: ClientPresence;
private _eval(script: string): unknown;
private _validateOptions(options: ClientOptions): void;
+ private get _censoredToken(): string | null;
public application: If;
public channels: ChannelManager;
@@ -1500,7 +1567,7 @@ export type Interaction =
| ChatInputCommandInteraction
| MessageContextMenuCommandInteraction
| UserContextMenuCommandInteraction
- | SelectMenuInteraction
+ | AnySelectMenuInteraction
| ButtonInteraction
| AutocompleteInteraction
| ModalSubmitInteraction;
@@ -1549,7 +1616,14 @@ export class BaseInteraction extends Base
public isMessageContextMenuCommand(): this is MessageContextMenuCommandInteraction;
public isModalSubmit(): this is ModalSubmitInteraction;
public isUserContextMenuCommand(): this is UserContextMenuCommandInteraction;
- public isSelectMenu(): this is SelectMenuInteraction;
+ /** @deprecated Use {@link BaseInteraction#isStringSelectMenu} instead */
+ public isSelectMenu(): this is StringSelectMenuInteraction;
+ public isAnySelectMenu(): this is AnySelectMenuInteraction;
+ public isStringSelectMenu(): this is StringSelectMenuInteraction;
+ public isUserSelectMenu(): this is UserSelectMenuInteraction;
+ public isRoleSelectMenu(): this is RoleSelectMenuInteraction;
+ public isMentionableSelectMenu(): this is MentionableSelectMenuInteraction;
+ public isChannelSelectMenu(): this is ChannelSelectMenuInteraction;
public isRepliable(): this is RepliableInteraction;
}
@@ -1672,7 +1746,11 @@ export type AwaitMessageCollectorOptionsParams {
Button: ButtonInteraction;
- SelectMenu: SelectMenuInteraction;
+ StringSelectMenu: StringSelectMenuInteraction;
+ UserSelectMenu: UserSelectMenuInteraction;
+ RoleSelectMenu: RoleSelectMenuInteraction;
+ MentionableSelectMenu: MentionableSelectMenuInteraction;
+ ChannelSelectMenu: ChannelSelectMenuInteraction;
ActionRow: MessageComponentInteraction;
}
@@ -1680,7 +1758,11 @@ export type WrapBooleanCache = If;
export interface MappedInteractionTypes {
[ComponentType.Button]: ButtonInteraction>;
- [ComponentType.SelectMenu]: SelectMenuInteraction>;
+ [ComponentType.StringSelect]: StringSelectMenuInteraction>;
+ [ComponentType.UserSelect]: UserSelectMenuInteraction>;
+ [ComponentType.RoleSelect]: RoleSelectMenuInteraction>;
+ [ComponentType.MentionableSelect]: MentionableSelectMenuInteraction>;
+ [ComponentType.ChannelSelect]: ChannelSelectMenuInteraction>;
}
export class Message extends Base {
@@ -1830,11 +1912,11 @@ export class MessageComponentInteraction e
options: InteractionDeferUpdateOptions & { fetchReply: true },
): Promise>>;
public deferUpdate(options?: InteractionDeferUpdateOptions): Promise>>;
- public deleteReply(): Promise;
+ public deleteReply(message?: MessageResolvable | '@original'): Promise;
public editReply(
- options: string | MessagePayload | WebhookEditMessageOptions,
+ options: string | MessagePayload | InteractionEditReplyOptions,
): Promise>>;
- public fetchReply(): Promise>>;
+ public fetchReply(message?: Snowflake | '@original'): Promise>>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise>>;
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>>;
public reply(
@@ -2020,15 +2102,15 @@ export class ModalSubmitInteraction extend
public reply(
options: string | MessagePayload | InteractionReplyOptions,
): Promise>>;
- public deleteReply(): Promise;
+ public deleteReply(message?: MessageResolvable | '@original'): Promise;
public editReply(
- options: string | MessagePayload | WebhookEditMessageOptions,
+ options: string | MessagePayload | InteractionEditReplyOptions,
): Promise>>;
public deferReply(
options: InteractionDeferReplyOptions & { fetchReply: true },
): Promise>>;
public deferReply(options?: InteractionDeferReplyOptions): Promise>>;
- public fetchReply(): Promise>>;
+ public fetchReply(message?: Snowflake | '@original'): Promise>>;
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise>>;
public deferUpdate(
options: InteractionDeferUpdateOptions & { fetchReply: true },
@@ -2254,22 +2336,116 @@ export class Role extends Base {
public toString(): RoleMention;
}
-export class SelectMenuInteraction extends MessageComponentInteraction {
- public constructor(client: Client, data: RawMessageSelectMenuInteractionData);
+export class StringSelectMenuInteraction<
+ Cached extends CacheType = CacheType,
+> extends MessageComponentInteraction {
+ public constructor(client: Client, data: APIMessageStringSelectInteractionData);
public get component(): CacheTypeReducer<
Cached,
- SelectMenuComponent,
- APISelectMenuComponent,
- SelectMenuComponent | APISelectMenuComponent,
- SelectMenuComponent | APISelectMenuComponent
+ StringSelectMenuComponent,
+ APIStringSelectComponent,
+ StringSelectMenuComponent | APIStringSelectComponent,
+ StringSelectMenuComponent | APIStringSelectComponent
>;
- public componentType: ComponentType.SelectMenu;
+ public componentType: ComponentType.StringSelect;
public values: string[];
- public inGuild(): this is SelectMenuInteraction<'raw' | 'cached'>;
- public inCachedGuild(): this is SelectMenuInteraction<'cached'>;
- public inRawGuild(): this is SelectMenuInteraction<'raw'>;
+ public inGuild(): this is StringSelectMenuInteraction<'raw' | 'cached'>;
+ public inCachedGuild(): this is StringSelectMenuInteraction<'cached'>;
+ public inRawGuild(): this is StringSelectMenuInteraction<'raw'>;
+}
+
+export {
+ /** @deprecated Use {@link StringSelectMenuInteraction} instead */
+ StringSelectMenuInteraction as SelectMenuInteraction,
+};
+
+export class UserSelectMenuInteraction<
+ Cached extends CacheType = CacheType,
+> extends MessageComponentInteraction {
+ public constructor(client: Client, data: APIMessageUserSelectInteractionData);
+ public get component(): CacheTypeReducer<
+ Cached,
+ UserSelectMenuComponent,
+ APIUserSelectComponent,
+ UserSelectMenuComponent | APIUserSelectComponent,
+ UserSelectMenuComponent | APIUserSelectComponent
+ >;
+ public componentType: ComponentType.UserSelect;
+ public users: Collection;
+ public members: Collection>;
+ public inGuild(): this is UserSelectMenuInteraction<'raw' | 'cached'>;
+ public inCachedGuild(): this is UserSelectMenuInteraction<'cached'>;
+ public inRawGuild(): this is UserSelectMenuInteraction<'raw'>;
+}
+
+export class RoleSelectMenuInteraction<
+ Cached extends CacheType = CacheType,
+> extends MessageComponentInteraction {
+ public constructor(client: Client, data: APIMessageRoleSelectInteractionData);
+ public get component(): CacheTypeReducer<
+ Cached,
+ RoleSelectMenuComponent,
+ APIRoleSelectComponent,
+ RoleSelectMenuComponent | APIRoleSelectComponent,
+ RoleSelectMenuComponent | APIRoleSelectComponent
+ >;
+ public componentType: ComponentType.RoleSelect;
+ public roles: Collection>;
+ public inGuild(): this is RoleSelectMenuInteraction<'raw' | 'cached'>;
+ public inCachedGuild(): this is RoleSelectMenuInteraction<'cached'>;
+ public inRawGuild(): this is RoleSelectMenuInteraction<'raw'>;
}
+export class MentionableSelectMenuInteraction<
+ Cached extends CacheType = CacheType,
+> extends MessageComponentInteraction {
+ public constructor(client: Client, data: APIMessageMentionableSelectInteractionData);
+ public get component(): CacheTypeReducer<
+ Cached,
+ MentionableSelectMenuComponent,
+ APIMentionableSelectComponent,
+ MentionableSelectMenuComponent | APIMentionableSelectComponent,
+ MentionableSelectMenuComponent | APIMentionableSelectComponent
+ >;
+ public componentType: ComponentType.MentionableSelect;
+ public users: Collection;
+ public members: Collection>;
+ public roles: Collection>;
+ public inGuild(): this is MentionableSelectMenuInteraction<'raw' | 'cached'>;
+ public inCachedGuild(): this is MentionableSelectMenuInteraction<'cached'>;
+ public inRawGuild(): this is MentionableSelectMenuInteraction<'raw'>;
+}
+
+export class ChannelSelectMenuInteraction<
+ Cached extends CacheType = CacheType,
+> extends MessageComponentInteraction {
+ public constructor(client: Client, data: APIMessageChannelSelectInteractionData);
+ public get component(): CacheTypeReducer<
+ Cached,
+ ChannelSelectMenuComponent,
+ APIChannelSelectComponent,
+ ChannelSelectMenuComponent | APIChannelSelectComponent,
+ ChannelSelectMenuComponent | APIChannelSelectComponent
+ >;
+ public componentType: ComponentType.ChannelSelect;
+ public channels: Collection>;
+ public inGuild(): this is ChannelSelectMenuInteraction<'raw' | 'cached'>;
+ public inCachedGuild(): this is ChannelSelectMenuInteraction<'cached'>;
+ public inRawGuild(): this is ChannelSelectMenuInteraction<'raw'>;
+}
+
+// Ideally this should be named SelectMenuInteraction, but that's the name of the "old" StringSelectMenuInteraction, meaning
+// the type name is reserved as a re-export to prevent a breaking change from being made, as such:
+// TODO: Rename this to SelectMenuInteraction in the next major
+export type AnySelectMenuInteraction =
+ | StringSelectMenuInteraction
+ | UserSelectMenuInteraction
+ | RoleSelectMenuInteraction
+ | MentionableSelectMenuInteraction
+ | ChannelSelectMenuInteraction;
+
+export type SelectMenuType = APISelectMenuComponent['type'];
+
export interface ShardEventTypes {
death: [process: ChildProcess | Worker];
disconnect: [];
@@ -2770,14 +2946,22 @@ export function parseWebhookURL(url: string): WebhookClientDataIdWithToken | nul
export interface MappedComponentBuilderTypes {
[ComponentType.Button]: ButtonBuilder;
- [ComponentType.SelectMenu]: SelectMenuBuilder;
+ [ComponentType.StringSelect]: StringSelectMenuBuilder;
+ [ComponentType.UserSelect]: UserSelectMenuBuilder;
+ [ComponentType.RoleSelect]: RoleSelectMenuBuilder;
+ [ComponentType.MentionableSelect]: MentionableSelectMenuBuilder;
+ [ComponentType.ChannelSelect]: ChannelSelectMenuBuilder;
[ComponentType.ActionRow]: ActionRowBuilder;
[ComponentType.TextInput]: TextInputBuilder;
}
export interface MappedComponentTypes {
[ComponentType.Button]: ButtonComponent;
- [ComponentType.SelectMenu]: SelectMenuComponent;
+ [ComponentType.StringSelect]: StringSelectMenuComponent;
+ [ComponentType.UserSelect]: UserSelectMenuComponent;
+ [ComponentType.RoleSelect]: RoleSelectMenuComponent;
+ [ComponentType.MentionableSelect]: MentionableSelectMenuComponent;
+ [ComponentType.ChannelSelect]: ChannelSelectMenuComponent;
[ComponentType.ActionRow]: ActionRowComponent;
[ComponentType.TextInput]: TextInputComponent;
}
@@ -2995,7 +3179,8 @@ export class WebSocketShard extends EventEmitter {
private sequence: number;
private closeSequence: number;
private sessionId: string | null;
- private lastPingTimestamp: number;
+ private resumeURL: string | null;
+ public lastPingTimestamp: number;
private lastHeartbeatAcked: boolean;
private readonly ratelimit: {
queue: unknown[];
@@ -3116,6 +3301,7 @@ export const Constants: {
TextBasedChannelTypes: TextBasedChannelTypes[];
ThreadChannelTypes: ThreadChannelType[];
VoiceBasedChannelTypes: VoiceBasedChannelTypes[];
+ SelectMenuTypes: SelectMenuType[];
};
export const version: string;
@@ -3248,6 +3434,7 @@ export enum DiscordjsErrorCodes {
InteractionAlreadyReplied = 'InteractionAlreadyReplied',
InteractionNotReplied = 'InteractionNotReplied',
+ /** @deprecated */
InteractionEphemeralReplied = 'InteractionEphemeralReplied',
CommandInteractionOptionNotFound = 'CommandInteractionOptionNotFound',
@@ -3459,6 +3646,11 @@ export class GuildChannelManager extends CachedManager;
public create(
options: GuildChannelCreateOptions & { type: T },
): Promise;
@@ -5138,7 +5330,11 @@ export interface IntegrationAccount {
export type IntegrationType = 'twitch' | 'youtube' | 'discord';
export type CollectedInteraction =
- | SelectMenuInteraction
+ | StringSelectMenuInteraction
+ | UserSelectMenuInteraction
+ | RoleSelectMenuInteraction
+ | MentionableSelectMenuInteraction
+ | ChannelSelectMenuInteraction
| ButtonInteraction
| ModalSubmitInteraction;
@@ -5210,7 +5406,13 @@ export interface MakeErrorOptions {
stack: string;
}
-export type ActionRowComponentOptions = ButtonComponentData | SelectMenuComponentData;
+export type ActionRowComponentOptions =
+ | ButtonComponentData
+ | StringSelectMenuComponentData
+ | UserSelectMenuComponentData
+ | RoleSelectMenuComponentData
+ | MentionableSelectMenuComponentData
+ | ChannelSelectMenuComponentData;
export type MessageActionRowComponentResolvable = MessageActionRowComponent | ActionRowComponentOptions;
@@ -5247,7 +5449,11 @@ export type MessageComponent =
| Component
| ActionRowBuilder
| ButtonComponent
- | SelectMenuComponent;
+ | StringSelectMenuComponent
+ | UserSelectMenuComponent
+ | RoleSelectMenuComponent
+ | MentionableSelectMenuComponent
+ | ChannelSelectMenuComponent;
export type CollectedMessageInteraction = Exclude<
CollectedInteraction,
@@ -5345,16 +5551,36 @@ export interface MessageReference {
export type MessageResolvable = Message | Snowflake;
-export interface SelectMenuComponentData extends BaseComponentData {
- type: ComponentType.SelectMenu;
+export interface BaseSelectMenuComponentData extends BaseComponentData {
customId: string;
disabled?: boolean;
maxValues?: number;
minValues?: number;
- options?: SelectMenuComponentOptionData[];
placeholder?: string;
}
+export interface StringSelectMenuComponentData extends BaseSelectMenuComponentData {
+ type: ComponentType.StringSelect;
+ options?: SelectMenuComponentOptionData[];
+}
+
+export interface UserSelectMenuComponentData extends BaseSelectMenuComponentData {
+ type: ComponentType.UserSelect;
+}
+
+export interface RoleSelectMenuComponentData extends BaseSelectMenuComponentData {
+ type: ComponentType.RoleSelect;
+}
+
+export interface MentionableSelectMenuComponentData extends BaseSelectMenuComponentData {
+ type: ComponentType.MentionableSelect;
+}
+
+export interface ChannelSelectMenuComponentData extends BaseSelectMenuComponentData {
+ type: ComponentType.ChannelSelect;
+ channelTypes?: ChannelType[];
+}
+
export interface MessageSelectOption {
default: boolean;
description: string | null;
@@ -5752,6 +5978,10 @@ export interface WebhookEditMessageOptions extends Omit {
expectAssignable>(buttonCollector);
// Verify that select menus interaction are inferred.
- const selectMenuCollector = message.createMessageComponentCollector({ componentType: ComponentType.SelectMenu });
- expectAssignable>(
- message.awaitMessageComponent({ componentType: ComponentType.SelectMenu }),
+ const selectMenuCollector = message.createMessageComponentCollector({ componentType: ComponentType.StringSelect });
+ expectAssignable>(
+ message.awaitMessageComponent({ componentType: ComponentType.StringSelect }),
);
- expectAssignable>(
- channel.awaitMessageComponent({ componentType: ComponentType.SelectMenu }),
+ expectAssignable>(
+ channel.awaitMessageComponent({ componentType: ComponentType.StringSelect }),
);
- expectAssignable>(selectMenuCollector);
+ expectAssignable>(selectMenuCollector);
// Verify that message component interactions are default collected types.
const defaultCollector = message.createMessageComponentCollector();
@@ -405,9 +412,9 @@ client.on('messageCreate', async message => {
});
message.createMessageComponentCollector({
- componentType: ComponentType.SelectMenu,
+ componentType: ComponentType.StringSelect,
filter: i => {
- expectType(i);
+ expectType(i);
return true;
},
});
@@ -428,9 +435,9 @@ client.on('messageCreate', async message => {
});
message.awaitMessageComponent({
- componentType: ComponentType.SelectMenu,
+ componentType: ComponentType.StringSelect,
filter: i => {
- expectType(i);
+ expectType(i);
return true;
},
});
@@ -464,9 +471,9 @@ client.on('messageCreate', async message => {
});
channel.awaitMessageComponent({
- componentType: ComponentType.SelectMenu,
+ componentType: ComponentType.StringSelect,
filter: i => {
- expectType>(i);
+ expectType>(i);
return true;
},
});
@@ -489,9 +496,9 @@ client.on('messageCreate', async message => {
const selectsRow: ActionRowData = {
type: ComponentType.ActionRow,
components: [
- new SelectMenuBuilder(),
+ new StringSelectMenuBuilder(),
{
- type: ComponentType.SelectMenu,
+ type: ComponentType.StringSelect,
label: 'select menu',
options: [{ label: 'test', value: 'test' }],
customId: 'test',
@@ -1122,8 +1129,8 @@ client.on('guildCreate', async g => {
new ButtonBuilder(),
{ type: ComponentType.Button, style: ButtonStyle.Primary, label: 'string', customId: 'foo' },
{ type: ComponentType.Button, style: ButtonStyle.Link, label: 'test', url: 'test' },
- { type: ComponentType.SelectMenu, customId: 'foo' },
- new SelectMenuBuilder(),
+ { type: ComponentType.StringSelect, customId: 'foo' },
+ new StringSelectMenuBuilder(),
// @ts-expect-error
{ type: ComponentType.TextInput, style: TextInputStyle.Paragraph, customId: 'foo', label: 'test' },
// @ts-expect-error
@@ -1136,7 +1143,7 @@ client.on('guildCreate', async g => {
components: [
{ type: ComponentType.Button, style: ButtonStyle.Primary, label: 'string', customId: 'foo' },
{ type: ComponentType.Button, style: ButtonStyle.Link, label: 'test', url: 'test' },
- { type: ComponentType.SelectMenu, customId: 'foo' },
+ { type: ComponentType.StringSelect, customId: 'foo' },
],
});
@@ -1508,7 +1515,7 @@ if (interaction.inGuild()) {
client.on('interactionCreate', async interaction => {
if (interaction.type === InteractionType.MessageComponent) {
- expectType(interaction);
+ expectType(interaction);
expectType(interaction.component);
expectType(interaction.message);
if (interaction.inCachedGuild()) {
@@ -1640,25 +1647,28 @@ client.on('interactionCreate', async interaction => {
}
}
- if (interaction.type === InteractionType.MessageComponent && interaction.componentType === ComponentType.SelectMenu) {
- expectType(interaction);
- expectType(interaction.component);
+ if (
+ interaction.type === InteractionType.MessageComponent &&
+ interaction.componentType === ComponentType.StringSelect
+ ) {
+ expectType(interaction);
+ expectType(interaction.component);
expectType(interaction.message);
if (interaction.inCachedGuild()) {
- expectAssignable(interaction);
+ expectAssignable(interaction);
expectType(interaction.component);
expectType>(interaction.message);
expectType(interaction.guild);
expectType>>(interaction.reply({ fetchReply: true }));
} else if (interaction.inRawGuild()) {
- expectAssignable(interaction);
- expectType(interaction.component);
+ expectAssignable(interaction);
+ expectType(interaction.component);
expectType>(interaction.message);
expectType(interaction.guild);
expectType>>(interaction.reply({ fetchReply: true }));
} else if (interaction.inGuild()) {
- expectAssignable(interaction);
- expectType(interaction.component);
+ expectAssignable(interaction);
+ expectType(interaction.component);
expectType(interaction.message);
expectType(interaction.guild);
expectType>(interaction.reply({ fetchReply: true }));
@@ -1882,7 +1892,7 @@ const button = new ButtonBuilder({
customId: 'test',
});
-const selectMenu = new SelectMenuBuilder({
+const selectMenu = new StringSelectMenuBuilder({
maxValues: 10,
minValues: 2,
customId: 'test',
@@ -1892,7 +1902,7 @@ new ActionRowBuilder({
components: [selectMenu.toJSON(), button.toJSON()],
});
-new SelectMenuBuilder({
+new StringSelectMenuBuilder({
customId: 'foo',
});
@@ -1951,10 +1961,10 @@ chatInputInteraction.showModal({
});
declare const selectMenuData: APISelectMenuComponent;
-SelectMenuBuilder.from(selectMenuData);
+StringSelectMenuBuilder.from(selectMenuData);
declare const selectMenuComp: SelectMenuComponent;
-SelectMenuBuilder.from(selectMenuComp);
+StringSelectMenuBuilder.from(selectMenuComp);
declare const buttonData: APIButtonComponent;
ButtonBuilder.from(buttonData);
@@ -2025,3 +2035,22 @@ expectType>(categoryChannel.flags);
expectType>(threadChannel.flags);
expectType(partialGroupDMChannel.flags);
+
+// Select menu type narrowing
+if (interaction.isAnySelectMenu()) {
+ expectType(interaction);
+}
+
+declare const anySelectMenu: AnySelectMenuInteraction;
+
+if (anySelectMenu.isStringSelectMenu()) {
+ expectType(anySelectMenu);
+} else if (anySelectMenu.isUserSelectMenu()) {
+ expectType(anySelectMenu);
+} else if (anySelectMenu.isRoleSelectMenu()) {
+ expectType(anySelectMenu);
+} else if (anySelectMenu.isChannelSelectMenu()) {
+ expectType(anySelectMenu);
+} else if (anySelectMenu.isMentionableSelectMenu()) {
+ expectType(anySelectMenu);
+}
diff --git a/packages/docgen/package.json b/packages/docgen/package.json
index 4366fd36ffa1..8ed9a8b67565 100644
--- a/packages/docgen/package.json
+++ b/packages/docgen/package.json
@@ -43,18 +43,18 @@
"commander": "^9.4.1",
"jsdoc-to-markdown": "^7.1.1",
"tslib": "^2.4.0",
- "typedoc": "^0.23.16"
+ "typedoc": "^0.23.17"
},
"devDependencies": {
"@favware/cliff-jumper": "^1.8.8",
"@types/jsdoc-to-markdown": "^7.0.3",
- "@types/node": "16.11.64",
+ "@types/node": "16.11.68",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4"
},
"engines": {
diff --git a/packages/proxy-container/package.json b/packages/proxy-container/package.json
index a6aaf4000e27..762696582c3f 100644
--- a/packages/proxy-container/package.json
+++ b/packages/proxy-container/package.json
@@ -48,13 +48,13 @@
"tslib": "^2.4.0"
},
"devDependencies": {
- "@types/node": "16.11.64",
+ "@types/node": "16.11.68",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4"
},
"engines": {
diff --git a/packages/proxy/package.json b/packages/proxy/package.json
index 5108e66dca15..d2e0b1ced365 100644
--- a/packages/proxy/package.json
+++ b/packages/proxy/package.json
@@ -61,19 +61,19 @@
},
"devDependencies": {
"@favware/cliff-jumper": "^1.8.8",
- "@microsoft/api-extractor": "^7.32.0",
- "@types/node": "16.11.64",
+ "@microsoft/api-extractor": "^7.33.4",
+ "@types/node": "16.11.68",
"@types/supertest": "^2.0.12",
- "@vitest/coverage-c8": "^0.24.1",
+ "@vitest/coverage-c8": "^0.24.3",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
"supertest": "^6.3.0",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4",
- "vitest": "^0.24.1"
+ "vitest": "^0.24.3"
},
"engines": {
"node": ">=16.9.0"
diff --git a/packages/rest/package.json b/packages/rest/package.json
index 9008e96744ec..cb40dd28a283 100644
--- a/packages/rest/package.json
+++ b/packages/rest/package.json
@@ -56,25 +56,25 @@
"@discordjs/util": "workspace:^",
"@sapphire/async-queue": "^1.5.0",
"@sapphire/snowflake": "^3.2.2",
- "discord-api-types": "^0.37.13",
+ "discord-api-types": "^0.37.15",
"file-type": "^18.0.0",
"tslib": "^2.4.0",
"undici": "^5.11.0"
},
"devDependencies": {
"@favware/cliff-jumper": "^1.8.8",
- "@microsoft/api-extractor": "^7.32.0",
- "@types/node": "16.11.64",
- "@vitest/coverage-c8": "^0.24.1",
+ "@microsoft/api-extractor": "^7.33.4",
+ "@types/node": "16.11.68",
+ "@vitest/coverage-c8": "^0.24.3",
"cross-env": "^7.0.3",
"esbuild-plugin-version-injector": "^1.0.0",
"eslint": "^8.25.0",
- "eslint-config-neon": "^0.1.38",
+ "eslint-config-neon": "^0.1.39",
"eslint-formatter-pretty": "^4.1.0",
"prettier": "^2.7.1",
- "tsup": "^6.2.3",
+ "tsup": "^6.3.0",
"typescript": "^4.8.4",
- "vitest": "^0.24.1"
+ "vitest": "^0.24.3"
},
"engines": {
"node": ">=16.9.0"
diff --git a/packages/rest/src/lib/handlers/SequentialHandler.ts b/packages/rest/src/lib/handlers/SequentialHandler.ts
index 60ca20851f91..4f5e5ccde7b8 100644
--- a/packages/rest/src/lib/handlers/SequentialHandler.ts
+++ b/packages/rest/src/lib/handlers/SequentialHandler.ts
@@ -8,7 +8,7 @@ import { DiscordAPIError, type DiscordErrorData, type OAuthErrorData } from '../
import { HTTPError } from '../errors/HTTPError.js';
import { RateLimitError } from '../errors/RateLimitError.js';
import { RESTEvents } from '../utils/constants.js';
-import { hasSublimit, parseHeader, parseResponse } from '../utils/utils.js';
+import { hasSublimit, parseHeader, parseResponse, shouldRetry } from '../utils/utils.js';
import type { IHandler } from './IHandler.js';
/**
@@ -137,7 +137,7 @@ export class SequentialHandler implements IHandler {
* @param time - The amount of time to delay all requests for
*/
private async globalDelayFor(time: number): Promise {
- await sleep(time, undefined, { ref: false });
+ await sleep(time);
this.manager.globalDelay = null;
}
@@ -307,8 +307,9 @@ export class SequentialHandler implements IHandler {
try {
res = await request(url, { ...options, signal: controller.signal });
} catch (error: unknown) {
- // Retry the specified number of times for possible timed out requests
- if (error instanceof Error && error.name === 'AbortError' && retries !== this.manager.options.retries) {
+ if (!(error instanceof Error)) throw error;
+ // Retry the specified number of times if needed
+ if (shouldRetry(error) && retries !== this.manager.options.retries) {
// eslint-disable-next-line no-param-reassign
return await this.runRequest(routeId, url, options, requestData, ++retries);
}
@@ -460,7 +461,7 @@ export class SequentialHandler implements IHandler {
this.#sublimitPromise?.resolve();
this.#sublimitPromise = null;
- await sleep(sublimitTimeout, undefined, { ref: false });
+ await sleep(sublimitTimeout);
let resolve: () => void;
// eslint-disable-next-line promise/param-names, no-promise-executor-return
const promise = new Promise