Compare commits

..

21 Commits

Author SHA1 Message Date
Jiralite
e721e51b03 ci(deprecateVersion): Fix empty message (#11491)
* ci: fix empty `message`

* refactor: move default again

* fix: move others too

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-23 06:55:49 +00:00
Qjuh
0d5a0a69c3 fix: export all used types (#11526)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-23 06:18:45 +00:00
Almeida
07f1d8f5b3 feat(core)!: rename getAllMembers to getMembers with query support (#11484)
BREAKING CHANGE: getAllMembers has been removed. Use getMembers instead, which accepts an optional query parameter.
2026-05-23 02:05:55 +01:00
Almeida
37892ead4c feat(ClientApplication): add fetchActivityInstance method (#11481)
* feat(ClientApplication): add fetchActivityInstance method

* refactor: requested changes

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 22:53:39 +00:00
Qjuh
9f111ed4df docs(Role): fix Role#tags docs (#11524)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 22:51:00 +00:00
Fadi Raad
c313bc58f1 docs: fix typos and duplicated words in comments and guide (#11502)
- fix "wether" -> "whether" and "dependant" -> "dependent" in RedisGateway JSDoc
- fix duplicated "the" in useful-packages guide
- fix duplicated "for" in two internal JSDoc param descriptions

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 22:47:15 +00:00
Almeida
76478c4ba5 feat(ShardingManager): allow setting custom api url (#11471)
* feat(ShardingManager): allow setting custom api url

* Apply suggestion from @Jiralite

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 23:40:09 +01:00
Kshitij Anurag
28dd65d322 feat: add shared client theme support (#11454)
* feat: add shared client theme support

* Apply suggestion from @Qjuh

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* chore: tests

* chore: format

* chore: apply suggestions from code review

---------

Co-authored-by: Almeida <github@almeidx.dev>
Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-22 22:31:00 +00:00
Denis-Adrian Cristea
e490a230a3 fix(stringOption): zod validation (#11532)
fix: zod validation

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-05-21 15:46:49 +00:00
Qjuh
1e88dcdc05 feat(DMChannel)!: allow partial DMChannel without client user (#11443)
BREAKING CHANGE: `DMChannel#recipientId` is now nullable.
2026-05-20 19:05:16 +01:00
Almeida
40ce0791a8 feat(ApplicationsAPI): add getActivityInstance method (#11482)
feat(core): add getActivityInstance to ApplicationsAPI

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-04-24 17:11:18 +00:00
Dramex
8ea7c7c7e4 fix(collection): preserve ReadonlyCollection through tap/each (#11501)
* fix(collection): preserve ReadonlyCollection through tap/each

`each` and `tap` return polymorphic `this`, which TypeScript resolves
against the `Omit<Collection, ...>` portion of `ReadonlyCollection`
rather than the full intersection. That let callers reach `set` and
`delete` on the result of a chain started from a `ReadonlyCollection`:

    const ro: ReadonlyCollection<string, number> = new Collection(...);
    ro.tap(() => {}).set('x', 0); // compiled, mutated the underlying Map

The fix omits `each` and `tap` from the base `Omit` and re-declares
them on the `ReadonlyCollection` side of the intersection so the return
type narrows back to `ReadonlyCollection`.

Closes #10514

* test(collection): gate readonly-chain checks behind if(false)

Previously the `@ts-expect-error` lines still executed the `set` and
`delete` mutations at runtime, and the final `size === 1` passed only
because they happened to cancel out. Wrapping the assertions in
`if (false)` keeps the compile-time guarantee while the backing
collection is truly untouched, and adds a `get('a') === 1` check as
a belt.

* test(collection): move readonly type checks to *.test-d.ts

Addresses review feedback. The type-level assertions around tap() and
each() preserving ReadonlyCollection belong in a *.test-d.ts file so
they run through vitest's typecheck pass instead of runtime.

Replaces the if(false)-gated @ts-expect-error block in collection.test.ts
with expectTypeOf assertions in a new collection.test-d.ts. Covers both
the no-thisArg and with-thisArg overloads of tap and each.
2026-04-20 19:15:29 +00:00
2^1
58c5ebdd08 chore(readme): update related libraries link (#11487)
* fix(readme): related libraries link broke

* docs(readme): update all related-libs links
2026-04-13 16:20:06 +00:00
Almeida
aa1e6be792 fix: github icon size (#11488) 2026-04-09 19:59:41 +00:00
Qjuh
aa2767bd6f fix(MessagePayload): allow AttachmentBuilder in files payload (#11423)
* fix(MessagePayload): allow AttachmentBuilder in files payload

* fix: typo

* chore: apply suggestions from code review

Co-authored-by: Almeida <github@almeidx.dev>

---------

Co-authored-by: Almeida <github@almeidx.dev>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-04-09 09:27:02 +00:00
René
1f9affd979 perf(collection): avoid duplicated population of new collections (#11473) 2026-04-01 09:56:37 +00:00
Almeida
339b8b5655 chore: upgrade dependencies (#11470)
* chore: upgrade dependencies

* chore: upgrade some major dependencies

* chore: bump shiki to v4

* chore: bump actions dependencies

* chore: upgrade meilisearch dependency

* chore: set aria-hidden on GitHubIcon

* fix: use official github icon
2026-03-31 09:57:57 +01:00
Almeida
9b7ea5a1b7 fix: sendSoundboardSound return type (#11452)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-03-29 09:25:00 +00:00
Almeida
8bcf8c94e9 feat(GuildMember): add collectibles (#11468)
feat(GuildMember): add collectibles, fix partial update handling
2026-03-29 09:19:42 +00:00
Muhammad Bin Ali
22b820fbf2 fix(util): detect Cloudflare Workers in shouldUseGlobalFetchAndWebSocket (#11456)
Co-authored-by: Muhammad Ali <muhammadali@cloudflare.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2026-03-26 15:46:57 +00:00
epochzx
9ade8dcc71 docs: fix typo in sharding guide (#11467)
Fix a typo in sharding guide
2026-03-26 10:26:40 +00:00
113 changed files with 7486 additions and 6617 deletions

View File

@@ -78,7 +78,7 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/discord.js
[npm]: https://www.npmjs.com/package/discord.js
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[rpc]: https://www.npmjs.com/package/discord-rpc
[rpc-source]: https://github.com/discordjs/RPC
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -110,7 +110,7 @@ interaction.reply(oneLineCommaListsOr`
`);
```
Check the the documentation to find more useful functions.
Check the documentation to find more useful functions.
## chalk

View File

@@ -9,7 +9,7 @@ Before you dive into this section, please note that sharding may not be necessar
If your bot is in a total of 2,000 or more servers, then please continue with this guide. Otherwise, it may be a good idea to wait until then.
<Callout>
Sharding is only relevant if you app uses gateway events. For webhook callbacks, this is completely irrelevant!
Sharding is only relevant if your app uses gateway events. For webhook callbacks, this is completely irrelevant!
</Callout>
## How does sharding work?

View File

@@ -3,14 +3,14 @@ title: Updating to v15
icon: ArrowDownToLine
---
import { Github } from 'lucide-react';
import { GitHubIcon } from '@/components/GitHubIcon.tsx';
<Callout type="idea">
**Version 15 is in a pre-release** state, but should be usable!
That being said, we do not recommend you update your production instance without careful and thorough testing!
Please report any bugs you experience at our GitHub repository:
<Github className="inline text-red-400"/> https://github.com/discordjs/discord.js/issues
<GitHubIcon className="inline text-red-400"/> https://github.com/discordjs/discord.js/issues
</Callout>

View File

@@ -48,18 +48,18 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@opennextjs/cloudflare": "^1.16.5",
"@vercel/analytics": "^1.6.1",
"fumadocs-core": "^16.6.3",
"fumadocs-mdx": "^14.2.7",
"fumadocs-twoslash": "^3.1.13",
"fumadocs-ui": "^16.6.3",
"@opennextjs/cloudflare": "^1.18.0",
"@vercel/analytics": "^2.0.1",
"fumadocs-core": "^16.7.7",
"fumadocs-mdx": "^14.2.11",
"fumadocs-twoslash": "^3.1.14",
"fumadocs-ui": "^16.7.7",
"geist": "^1.7.0",
"lucide-react": "^0.559.0",
"mermaid": "^11.12.3",
"next": "^16.1.6",
"lucide-react": "^1.7.0",
"mermaid": "^11.13.0",
"next": "^16.2.1",
"next-themes": "^0.4.6",
"p-retry": "^7.1.1",
"p-retry": "^8.0.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"sharp": "^0.34.5",
@@ -68,31 +68,31 @@
"twoslash": "^0.3.6"
},
"devDependencies": {
"@shikijs/rehype": "^3.22.0",
"@tailwindcss/postcss": "^4.2.0",
"@shikijs/rehype": "^4.0.2",
"@tailwindcss/postcss": "^4.2.2",
"@types/mdx": "^2.0.13",
"@types/node": "^24.10.13",
"@types/node": "^24.12.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"autoprefixer": "^10.4.24",
"autoprefixer": "^10.4.27",
"babel-plugin-react-compiler": "^1.0.0",
"cpy-cli": "^6.0.0",
"cpy-cli": "^7.0.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-pretty": "^7.0.0",
"git-describe": "^4.1.1",
"postcss": "^8.5.6",
"postcss": "^8.5.8",
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"remark-gfm": "^4.0.1",
"remark-rehype": "^11.1.2",
"shiki": "^3.22.0",
"tailwindcss": "^4.2.0",
"turbo": "^2.8.10",
"shiki": "^4.0.2",
"tailwindcss": "^4.2.2",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vercel": "^49.2.0",
"wrangler": "^4.67.0"
"vercel": "^50.37.3",
"wrangler": "^4.78.0"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -0,0 +1,27 @@
import type { ComponentProps } from 'react';
export function GitHubIcon(props: ComponentProps<'svg'>) {
return (
<svg
aria-hidden
fill="none"
height="1em"
viewBox="0 0 98 96"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g clipPath="url(#clip0_730_27126)">
<path
d="M41.44 69.385C28.807 67.853 19.906 58.762 19.906 46.99c0-4.785 1.723-9.953 4.594-13.398-1.244-3.158-1.053-9.858.383-12.633 3.828-.479 8.996 1.531 12.058 4.307 3.637-1.149 7.465-1.723 12.155-1.723 4.69 0 8.517.574 11.963 1.627 2.966-2.68 8.23-4.69 12.058-4.211 1.34 2.584 1.531 9.283.287 12.537 3.063 3.637 4.69 8.518 4.69 13.494 0 11.772-8.9 20.672-21.725 22.3 3.254 2.104 5.455 6.698 5.455 11.962v9.953c0 2.871 2.393 4.498 5.264 3.35C84.41 87.95 98 70.629 98 49.19 98 22.107 75.988 0 48.904 0 21.82 0 0 22.107 0 49.191c0 21.246 13.494 38.856 31.678 45.46 2.584.956 5.072-.766 5.072-3.35v-7.657c-1.34.575-3.063.958-4.594.958-6.316 0-10.049-3.446-12.728-9.858-1.053-2.584-2.201-4.115-4.403-4.402-1.148-.096-1.53-.574-1.53-1.149 0-1.148 1.913-2.01 3.827-2.01 2.776 0 5.168 1.723 7.657 5.264 1.914 2.776 3.923 4.02 6.316 4.02 2.392 0 3.924-.861 6.125-3.063 1.627-1.627 2.871-3.062 4.02-4.02z"
fill="#000"
/>
</g>
<defs>
<clipPath id="clip0_730_27126">
<path d="M0 0H98V96H0z" fill="#fff" />
</clipPath>
</defs>
</svg>
);
}

View File

@@ -6,7 +6,7 @@ import { Mermaid } from '@/components/mdx/mermaid';
export function getMDXComponents(components?: MDXComponents): MDXComponents {
return {
...defaultMdxComponents,
...(defaultMdxComponents as MDXComponents),
Popup,
PopupContent,
PopupTrigger,

View File

@@ -80,5 +80,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/apps/proxy-container
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -50,16 +50,16 @@
"tslib": "^2.8.1"
},
"devDependencies": {
"@types/node": "^24.10.13",
"@types/node": "^24.12.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"terser": "^5.46.0",
"terser": "^5.46.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3"
},
"engines": {

View File

@@ -45,5 +45,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/apps/website
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -51,27 +51,27 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@opennextjs/cloudflare": "^1.16.5",
"@opennextjs/cloudflare": "^1.18.0",
"@radix-ui/react-collapsible": "^1.1.12",
"@react-icons/all-files": "^4.1.0",
"@tanstack/react-query": "^5.90.21",
"@vercel/analytics": "^1.6.1",
"@tanstack/react-query": "^5.95.2",
"@vercel/analytics": "^2.0.1",
"@vercel/postgres": "^0.10.0",
"cloudflare": "^5.2.0",
"cmdk": "^1.1.1",
"cva": "1.0.0-beta.3",
"geist": "^1.7.0",
"jotai": "^2.18.0",
"lucide-react": "^0.559.0",
"meilisearch": "^0.54.0",
"next": "^16.1.6",
"jotai": "^2.19.0",
"lucide-react": "^1.7.0",
"meilisearch": "^0.56.0",
"next": "^16.2.1",
"next-themes": "^0.4.6",
"nuqs": "^2.8.8",
"nuqs": "^2.8.9",
"overlayscrollbars": "^2.14.0",
"overlayscrollbars-react": "^0.5.6",
"react": "^19.2.4",
"react-aria": "^3.46.0",
"react-aria-components": "^1.15.1",
"react-aria": "^3.47.0",
"react-aria-components": "^1.16.0",
"react-dom": "^19.2.4",
"safe-mdx": "^1.3.9",
"sharp": "^0.34.5",
@@ -80,32 +80,32 @@
"usehooks-ts": "^3.1.1"
},
"devDependencies": {
"@shikijs/rehype": "^3.22.0",
"@tailwindcss/postcss": "^4.2.0",
"@shikijs/rehype": "^4.0.2",
"@tailwindcss/postcss": "^4.2.2",
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^24.10.13",
"@types/node": "^24.12.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"autoprefixer": "^10.4.24",
"autoprefixer": "^10.4.27",
"babel-plugin-react-compiler": "^1.0.0",
"cpy-cli": "^6.0.0",
"cpy-cli": "^7.0.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-pretty": "^7.0.0",
"git-describe": "^4.1.1",
"postcss": "^8.5.6",
"postcss": "^8.5.8",
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"remark-gfm": "^4.0.1",
"remark-rehype": "^11.1.2",
"shiki": "^3.22.0",
"tailwindcss": "^4.2.0",
"shiki": "^4.0.2",
"tailwindcss": "^4.2.2",
"tailwindcss-react-aria-components": "^2.0.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vercel": "^49.2.0",
"wrangler": "^4.67.0"
"vercel": "^50.37.3",
"wrangler": "^4.78.0"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -3,7 +3,7 @@
import { Command } from 'cmdk';
import { useAtom, useSetAtom } from 'jotai';
import { ArrowRight } from 'lucide-react';
import MeiliSearch from 'meilisearch';
import { MeiliSearch } from 'meilisearch';
import dynamic from 'next/dynamic';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';

View File

@@ -4,18 +4,35 @@ import type {
DynamicImportThemeRegistration,
HighlighterGeneric,
} from 'shiki/types';
import { createSingletonShorthands, createdBundledHighlighter } from 'shiki/core';
import { createBundledHighlighter, createSingletonShorthands } from 'shiki/core';
import { createJavaScriptRegexEngine } from 'shiki/engine-javascript.mjs';
type BundledLanguage = 'typescript' | 'ts' | 'javascript' | 'js' | 'shellscript' | 'bash' | 'sh' | 'shell' | 'zsh';
type BundledLanguage =
| 'typescript'
| 'ts'
| 'cts'
| 'mts'
| 'javascript'
| 'js'
| 'cjs'
| 'mjs'
| 'shellscript'
| 'bash'
| 'sh'
| 'shell'
| 'zsh';
type BundledTheme = 'github-light' | 'github-dark-dimmed';
type Highlighter = HighlighterGeneric<BundledLanguage, BundledTheme>;
const bundledLanguages = {
typescript: () => import('shiki/langs/typescript.mjs'),
ts: () => import('shiki/langs/typescript.mjs'),
cts: () => import('shiki/langs/typescript.mjs'),
mts: () => import('shiki/langs/typescript.mjs'),
javascript: () => import('shiki/langs/javascript.mjs'),
js: () => import('shiki/langs/javascript.mjs'),
cjs: () => import('shiki/langs/javascript.mjs'),
mjs: () => import('shiki/langs/javascript.mjs'),
shellscript: () => import('shiki/langs/shellscript.mjs'),
bash: () => import('shiki/langs/shellscript.mjs'),
sh: () => import('shiki/langs/shellscript.mjs'),
@@ -28,7 +45,7 @@ const bundledThemes = {
'github-dark-dimmed': () => import('shiki/themes/github-dark-dimmed.mjs'),
} as Record<BundledTheme, DynamicImportThemeRegistration>;
const createHighlighter = /* @__PURE__ */ createdBundledHighlighter<BundledLanguage, BundledTheme>({
const createHighlighter = /* @__PURE__ */ createBundledHighlighter<BundledLanguage, BundledTheme>({
langs: bundledLanguages,
themes: bundledThemes,
engine: () => createJavaScriptRegexEngine(),

View File

@@ -51,33 +51,33 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"devDependencies": {
"@commitlint/cli": "^20.4.2",
"@commitlint/config-angular": "^20.4.2",
"@commitlint/cli": "^20.5.0",
"@commitlint/config-angular": "^20.5.0",
"@favware/cliff-jumper": "^6.0.0",
"@favware/npm-deprecate": "^2.0.0",
"@types/lodash.merge": "^4.6.9",
"@unocss/eslint-plugin": "^66.6.0",
"@vitest/coverage-v8": "^4.0.18",
"@unocss/eslint-plugin": "^66.6.7",
"@vitest/coverage-v8": "^4.1.2",
"conventional-changelog-cli": "^5.0.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-react-compiler": "19.1.0-rc.2",
"husky": "^9.1.7",
"is-ci": "^4.1.0",
"lint-staged": "^16.2.7",
"lint-staged": "^16.4.0",
"lodash.merge": "^4.6.2",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"typescript-eslint": "^8.56.0",
"unocss": "^66.6.0",
"vercel": "^49.2.0",
"vitest": "^4.0.18"
"typescript-eslint": "^8.57.2",
"unocss": "^66.6.7",
"vercel": "^50.37.3",
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"
},
"packageManager": "pnpm@10.30.1"
"packageManager": "pnpm@10.33.0"
}

View File

@@ -42,5 +42,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/actions
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -41,35 +41,35 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@actions/core": "^2.0.3",
"@actions/github": "^6.0.1",
"@actions/glob": "^0.5.1",
"@aws-sdk/client-s3": "^3.994.0",
"@actions/core": "^3.0.0",
"@actions/github": "^9.0.0",
"@actions/glob": "^0.6.1",
"@aws-sdk/client-s3": "^3.1019.0",
"@discordjs/scripts": "workspace:^",
"cloudflare": "^5.2.0",
"commander": "^14.0.3",
"meilisearch": "^0.54.0",
"meilisearch": "^0.56.0",
"p-limit": "^7.3.0",
"p-queue": "^9.1.0",
"tslib": "^2.8.1",
"undici": "7.22.0"
"undici": "7.24.6"
},
"devDependencies": {
"@npm/types": "^2.1.0",
"@types/bun": "^1.3.9",
"@types/node": "^24.10.13",
"@vitest/coverage-v8": "^4.0.18",
"@types/bun": "^1.3.11",
"@types/node": "^24.12.0",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"terser": "^5.46.0",
"terser": "^5.46.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -10,7 +10,7 @@ inputs:
message:
description: Deprecation message
required: false
default: This version is deprecated. Please use a newer version.
# Default message lives in the command to handle empty strings.
node-auth-token:
description: npm authentication token
required: true
@@ -19,7 +19,10 @@ runs:
steps:
- name: Deprecate version
shell: bash
run: |
pnpm exec npm-deprecate --name "${{ inputs.version }}" --message "${{ inputs.message }}" --package "${{ inputs.package }}"
env:
VERSION: ${{ inputs.version }}
MESSAGE: ${{ inputs.message || 'This version is deprecated. Please use a newer version.' }}
PACKAGE: ${{ inputs.package }}
NODE_AUTH_TOKEN: ${{ inputs.node-auth-token }}
run: |
pnpm exec npm-deprecate --name "$VERSION" --message "$MESSAGE" --package "$PACKAGE"

View File

@@ -36,16 +36,16 @@
"@rushstack/node-core-library": "5.13.1"
},
"devDependencies": {
"@types/node": "^24.10.13",
"@types/node": "^24.12.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"terser": "^5.46.0",
"terser": "^5.46.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3"
}
}

View File

@@ -42,5 +42,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/api-extractor-utils
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -50,16 +50,16 @@
"@microsoft/tsdoc": "~0.15.1"
},
"devDependencies": {
"@types/node": "^24.10.13",
"@types/node": "^24.12.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"terser": "^5.46.0",
"terser": "^5.46.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3"
},
"engines": {

View File

@@ -63,19 +63,19 @@
"typescript": "~5.5.4"
},
"devDependencies": {
"@types/lodash": "^4.17.23",
"@types/node": "^24.10.13",
"@types/lodash": "^4.17.24",
"@types/node": "^24.12.0",
"@types/resolve": "^1.20.6",
"@types/semver": "^7.7.1",
"cpy-cli": "^6.0.0",
"cpy-cli": "^7.0.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.2.9",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"terser": "^5.46.0",
"terser": "^5.46.1",
"tsup": "^8.5.1",
"turbo": "^2.8.7"
"turbo": "^2.8.21"
}
}

View File

@@ -134,5 +134,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/brokers
[npm]: https://www.npmjs.com/package/@discordjs/brokers
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -71,25 +71,25 @@
"@discordjs/ws": "workspace:^",
"@msgpack/msgpack": "^3.1.3",
"@vladfrangu/async_event_emitter": "^2.4.7",
"ioredis": "^5.9.3"
"ioredis": "^5.10.1"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^22.19.15",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -4,7 +4,7 @@ import type { IRPCBroker } from '../Broker.js';
import type { RedisBrokerOptions } from './BaseRedis.js';
import { BaseRedisBroker, DefaultRedisBrokerOptions } from './BaseRedis.js';
interface InternalPromise {
export interface InternalPromise {
reject(error: any): void;
resolve(data: any): void;
timeout: NodeJS.Timeout;

View File

@@ -9,12 +9,12 @@ export type DiscordEvents = {
};
};
interface BrokerProps<Payload> {
export interface BrokerProps<Payload> {
payload: Payload;
shardId: number;
}
interface Events extends DiscordEvents {
export interface Events extends DiscordEvents {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
[RedisGateway.GatewaySendEvent]: GatewaySendPayload;
}
@@ -35,8 +35,8 @@ export type RedisBrokerDiscordEvents = {
* events as the receiving service expects, and also that you handle GatewaySend events.
* - One drawback to using this directly with `@discordjs/core` is that you lose granular control over when to `ack`
* events. This implementation `ack`s as soon as the event is emitted to listeners. In practice, this means that if your
* service crashes while handling an event, it's pretty arbitrary wether that event gets re-processed on restart or not.
* (Mostly dependant on if your handler is async or not, and also if the `ack` call has time to go through).
* service crashes while handling an event, it's pretty arbitrary whether that event gets re-processed on restart or not.
* (Mostly dependent on if your handler is async or not, and also if the `ack` call has time to go through).
*
* @example
* ```ts

View File

@@ -70,5 +70,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/builders
[npm]: https://www.npmjs.com/package/@discordjs/builders
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -188,10 +188,19 @@ describe('Application Command toJSON() results', () => {
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
// TODO
choices: [],
});
// Starting with zod 4.4.0 (potentially lower), this usecase was broken prior to #11532
// (i.e. choices not present at all with autocomplete: true)
expect(getStringOption().setAutocomplete(true).toJSON()).toEqual<APIApplicationCommandStringOption>({
name: 'owo',
description: 'Testing 123',
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
});
expect(
getStringOption().addChoices({ name: 'uwu', value: '1' }).toJSON(),
).toEqual<APIApplicationCommandStringOption>({

View File

@@ -1,6 +1,6 @@
import { AllowedMentionsTypes, MessageFlags } from 'discord-api-types/v10';
import { AllowedMentionsTypes, BaseThemeType, MessageFlags } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { AllowedMentionsBuilder, EmbedBuilder, MessageBuilder } from '../../src/index.js';
import { AllowedMentionsBuilder, EmbedBuilder, MessageBuilder, SharedClientThemeBuilder } from '../../src/index.js';
const base = {
allowed_mentions: undefined,
@@ -9,6 +9,7 @@ const base = {
embeds: [],
message_reference: undefined,
poll: undefined,
shared_client_theme: undefined,
};
describe('Message', () => {
@@ -103,6 +104,92 @@ describe('Message', () => {
question: { text: 'foo' },
answers: [{ poll_media: { text: 'foo' } }],
},
shared_client_theme: undefined,
});
});
describe('SharedClientTheme', () => {
test('GIVEN a message with a shared client theme THEN return valid toJSON data', () => {
const message = new MessageBuilder().setSharedClientTheme(
new SharedClientThemeBuilder()
.setColors(['5865F2', '7258F2'])
.setGradientAngle(0)
.setBaseMix(58)
.setBaseTheme(BaseThemeType.Dark),
);
expect(message.toJSON()).toStrictEqual({
...base,
shared_client_theme: {
colors: ['5865F2', '7258F2'],
gradient_angle: 0,
base_mix: 58,
base_theme: 1,
},
});
});
test('GIVEN a message with a function to update shared client theme THEN return valid toJSON data', () => {
const message = new MessageBuilder().updateSharedClientTheme((theme) =>
theme.setColors(['5865F2']).setGradientAngle(90).setBaseMix(100),
);
expect(message.toJSON()).toStrictEqual({
...base,
shared_client_theme: {
colors: ['5865F2'],
gradient_angle: 90,
base_mix: 100,
},
});
});
test('GIVEN a message with a shared client theme then cleared THEN shared_client_theme is undefined', () => {
const message = new MessageBuilder()
.setContent('foo')
.setSharedClientTheme(new SharedClientThemeBuilder().setColors(['5865F2']).setGradientAngle(0).setBaseMix(50))
.clearSharedClientTheme();
expect(message.toJSON()).toStrictEqual({
...base,
content: 'foo',
shared_client_theme: undefined,
});
});
test('GIVEN a SharedClientThemeBuilder with too many colors THEN it throws', () => {
const theme = new SharedClientThemeBuilder()
.setColors(['111111', '222222', '333333', '444444', '555555', '666666'])
.setGradientAngle(0)
.setBaseMix(50);
expect(() => theme.toJSON()).toThrow();
});
test('GIVEN a SharedClientThemeBuilder with out of range gradient angle THEN it throws', () => {
const theme = new SharedClientThemeBuilder().setColors(['5865F2']).setGradientAngle(400).setBaseMix(50);
expect(() => theme.toJSON()).toThrow();
});
test('GIVEN a SharedClientThemeBuilder with out of range base mix THEN it throws', () => {
const theme = new SharedClientThemeBuilder().setColors(['5865F2']).setGradientAngle(0).setBaseMix(150);
expect(() => theme.toJSON()).toThrow();
});
test('GIVEN a shared client theme with base_theme set THEN clearBaseTheme works correctly', () => {
const theme = new SharedClientThemeBuilder()
.setColors(['5865F2'])
.setGradientAngle(0)
.setBaseMix(50)
.setBaseTheme(BaseThemeType.Light)
.clearBaseTheme();
expect(theme.toJSON(false)).toStrictEqual({
colors: ['5865F2'],
gradient_angle: 0,
base_mix: 50,
base_theme: undefined,
});
});
});
});

View File

@@ -66,7 +66,7 @@
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@discordjs/util": "workspace:^",
"discord-api-types": "^0.38.41",
"discord-api-types": "^0.38.43",
"ts-mixer": "^6.0.4",
"tslib": "^2.8.1",
"zod": "^4.3.6"
@@ -75,19 +75,19 @@
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^22.19.15",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -88,6 +88,7 @@ export * from './messages/Assertions.js';
export * from './messages/Attachment.js';
export * from './messages/Message.js';
export * from './messages/MessageReference.js';
export * from './messages/SharedClientTheme.js';
export * from './util/normalizeArray.js';
export * from './util/resolveBuilder.js';

View File

@@ -39,7 +39,7 @@ const channelMixinOptionPredicate = z.object({
const autocompleteMixinOptionPredicate = z.object({
autocomplete: z.literal(true),
choices: z.union([z.never(), z.never().array(), z.undefined()]),
choices: z.array(z.any()).length(0).optional(),
});
const choiceValueStringPredicate = z.string().min(1).max(100);

View File

@@ -1,5 +1,11 @@
import { Buffer } from 'node:buffer';
import { AllowedMentionsTypes, ComponentType, MessageFlags, MessageReferenceType } from 'discord-api-types/v10';
import {
AllowedMentionsTypes,
BaseThemeType,
ComponentType,
MessageFlags,
MessageReferenceType,
} from 'discord-api-types/v10';
import { z } from 'zod';
import { snowflakePredicate } from '../Assertions.js';
import { embedPredicate } from './embed/Assertions.js';
@@ -79,18 +85,29 @@ const basicActionRowPredicate = z.object({
.array(),
});
export const sharedClientThemePredicate = z.object({
colors: z
.array(z.string().regex(/^[\da-f]{6}$/i))
.min(1)
.max(5),
gradient_angle: z.int().min(0).max(360),
base_mix: z.int().min(0).max(100),
base_theme: z.enum(BaseThemeType).nullish(),
});
const messageNoComponentsV2Predicate = baseMessagePredicate
.extend({
content: z.string().max(2_000).optional(),
embeds: embedPredicate.array().max(10).optional(),
sticker_ids: z.array(z.string()).max(3).optional(),
poll: pollPredicate.optional(),
shared_client_theme: sharedClientThemePredicate.optional(),
components: basicActionRowPredicate.array().max(5).optional(),
flags: z
.int()
.optional()
.refine((flags) => !flags || (flags & MessageFlags.IsComponentsV2) === 0, {
error: 'Cannot set content, embeds, stickers, or poll with IsComponentsV2 flag set',
error: 'Cannot set content, embeds, stickers, poll, or shared client theme with IsComponentsV2 flag set',
}),
})
.refine(
@@ -100,8 +117,11 @@ const messageNoComponentsV2Predicate = baseMessagePredicate
data.poll !== undefined ||
(data.attachments !== undefined && data.attachments.length > 0) ||
(data.components !== undefined && data.components.length > 0) ||
(data.sticker_ids !== undefined && data.sticker_ids.length > 0),
{ error: 'Messages must have content, embeds, a poll, attachments, components or stickers' },
(data.sticker_ids !== undefined && data.sticker_ids.length > 0) ||
data.shared_client_theme !== undefined,
{
error: 'Messages must have content, embeds, a poll, attachments, components, stickers, or a shared client theme',
},
);
const allTopLevelComponentsPredicate = z
@@ -134,6 +154,7 @@ const messageComponentsV2Predicate = baseMessagePredicate.extend({
embeds: z.array(z.never()).nullish(),
sticker_ids: z.array(z.never()).nullish(),
poll: z.null().optional(),
shared_client_theme: z.null().optional(),
});
export const messagePredicate = z.union([messageNoComponentsV2Predicate, messageComponentsV2Predicate]);

View File

@@ -16,7 +16,7 @@ export class AttachmentBuilder implements JSONEncodable<RESTAPIAttachment> {
/**
* This data is not included in the output of `toJSON()`. For this class specifically, this refers to binary data
* that will wind up being included in the multipart/form-data request, if used with the `MessageBuilder`.
* To retrieve this data, use {@link getRawFile}.
* To retrieve this data, use {@link AttachmentBuilder.getRawFile}.
*
* @remarks This cannot be set via the constructor, primarily because of the behavior described
* {@link https://discord.com/developers/docs/reference#editing-message-attachments | here}.
@@ -107,7 +107,7 @@ export class AttachmentBuilder implements JSONEncodable<RESTAPIAttachment> {
* Sets the file data to upload with this attachment.
*
* @param data - The file data
* @remarks Note that this data is NOT included in the {@link toJSON} output. To retrieve it, use {@link getRawFile}.
* @remarks Note that this data is NOT included in the {@link AttachmentBuilder.toJSON} output. To retrieve it, use {@link AttachmentBuilder.getRawFile}.
*/
public setFileData(data: Buffer | Uint8Array | string): this {
this.fileData.data = data;
@@ -125,7 +125,7 @@ export class AttachmentBuilder implements JSONEncodable<RESTAPIAttachment> {
/**
* Sets the content type of the file data to upload with this attachment.
*
* @remarks Note that this data is NOT included in the {@link toJSON} output. To retrieve it, use {@link getRawFile}.
* @remarks Note that this data is NOT included in the {@link AttachmentBuilder.toJSON} output. To retrieve it, use {@link AttachmentBuilder.getRawFile}.
*/
public setFileContentType(contentType: string): this {
this.fileData.contentType = contentType;
@@ -141,9 +141,9 @@ export class AttachmentBuilder implements JSONEncodable<RESTAPIAttachment> {
}
/**
* Converts this attachment to a {@link RawFile} for uploading.
* Converts this attachment to a {@link @discordjs/util#RawFile} for uploading.
*
* @returns A {@link RawFile} object, or `undefined` if no file data is set
* @returns A {@link @discordjs/util#RawFile} object, or `undefined` if no file data is set
*/
public getRawFile(): Partial<RawFile> | undefined {
if (!this.fileData?.data) {

View File

@@ -17,6 +17,7 @@ import type {
APISeparatorComponent,
APITextDisplayComponent,
APIMessageTopLevelComponent,
APIMessageSharedClientTheme,
} from 'discord-api-types/v10';
import { ActionRowBuilder } from '../components/ActionRow.js';
import { ComponentBuilder } from '../components/Component.js';
@@ -35,13 +36,14 @@ import { AllowedMentionsBuilder } from './AllowedMentions.js';
import { fileBodyMessagePredicate, messagePredicate } from './Assertions.js';
import { AttachmentBuilder } from './Attachment.js';
import { MessageReferenceBuilder } from './MessageReference.js';
import { SharedClientThemeBuilder } from './SharedClientTheme.js';
import { EmbedBuilder } from './embed/Embed.js';
import { PollBuilder } from './poll/Poll.js';
export interface MessageBuilderData extends Partial<
Omit<
RESTPostAPIChannelMessageJSONBody,
'allowed_mentions' | 'attachments' | 'components' | 'embeds' | 'message_reference' | 'poll'
'allowed_mentions' | 'attachments' | 'components' | 'embeds' | 'message_reference' | 'poll' | 'shared_client_theme'
>
> {
allowed_mentions?: AllowedMentionsBuilder;
@@ -50,6 +52,7 @@ export interface MessageBuilderData extends Partial<
embeds: EmbedBuilder[];
message_reference?: MessageReferenceBuilder;
poll?: PollBuilder;
shared_client_theme?: SharedClientThemeBuilder;
}
/**
@@ -90,7 +93,16 @@ export class MessageBuilder
* @param data - The API data to create this message with
*/
public constructor(data: Partial<RESTPostAPIChannelMessageJSONBody> = {}) {
const { attachments = [], embeds = [], components = [], message_reference, poll, allowed_mentions, ...rest } = data;
const {
attachments = [],
embeds = [],
components = [],
message_reference,
poll,
allowed_mentions,
shared_client_theme,
...rest
} = data;
this.data = {
...structuredClone(rest),
@@ -100,6 +112,7 @@ export class MessageBuilder
poll: poll && new PollBuilder(poll),
components: components.map((component) => createComponentBuilder(component)),
message_reference: message_reference && new MessageReferenceBuilder(message_reference),
shared_client_theme: shared_client_theme && new SharedClientThemeBuilder(shared_client_theme),
};
}
@@ -636,6 +649,39 @@ export class MessageBuilder
return this;
}
/**
* Sets the shared client theme for this message.
*
* @param theme - The shared client theme to set
*/
public setSharedClientTheme(
theme:
| APIMessageSharedClientTheme
| SharedClientThemeBuilder
| ((builder: SharedClientThemeBuilder) => SharedClientThemeBuilder),
): this {
this.data.shared_client_theme = resolveBuilder(theme, SharedClientThemeBuilder);
return this;
}
/**
* Updates the shared client theme for this message (and creates it if it doesn't exist).
*
* @param updater - The function to update the shared client theme with
*/
public updateSharedClientTheme(updater: (builder: SharedClientThemeBuilder) => void): this {
updater((this.data.shared_client_theme ??= new SharedClientThemeBuilder()));
return this;
}
/**
* Clears the shared client theme for this message.
*/
public clearSharedClientTheme(): this {
this.data.shared_client_theme = undefined;
return this;
}
/**
* Serializes this builder to API-compatible JSON data.
*
@@ -644,7 +690,8 @@ export class MessageBuilder
* @param validationOverride - Force validation to run/not run regardless of your global preference
*/
public toJSON(validationOverride?: boolean): RESTPostAPIChannelMessageJSONBody {
const { poll, allowed_mentions, attachments, embeds, components, message_reference, ...rest } = this.data;
const { poll, allowed_mentions, attachments, embeds, components, message_reference, shared_client_theme, ...rest } =
this.data;
const data = {
...structuredClone(rest),
@@ -656,6 +703,7 @@ export class MessageBuilder
// Here, the messagePredicate does specific constraints rather than using the componentPredicate
components: components.map((component) => component.toJSON(validationOverride)),
message_reference: message_reference?.toJSON(false),
shared_client_theme: shared_client_theme?.toJSON(false),
};
validate(messagePredicate, data, validationOverride);

View File

@@ -0,0 +1,91 @@
import type { JSONEncodable } from '@discordjs/util';
import type { APIMessageSharedClientTheme, BaseThemeType } from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
import { validate } from '../util/validation.js';
import { sharedClientThemePredicate } from './Assertions.js';
/**
* A builder that creates API-compatible JSON data for shared client themes.
*/
export class SharedClientThemeBuilder implements JSONEncodable<APIMessageSharedClientTheme> {
/**
* The API data associated with this shared client theme.
*/
private readonly data: Partial<APIMessageSharedClientTheme>;
/**
* Creates a new shared client theme builder.
*
* @param data - The API data to create this shared client theme with
*/
public constructor(data: Partial<APIMessageSharedClientTheme> = {}) {
this.data = structuredClone(data);
}
/**
* Sets the colors of this theme.
*
* @remarks
* A maximum of 5 hexadecimal-encoded colors may be provided.
* @param colors - The hexadecimal-encoded colors to set (e.g. `'5865F2'`)
*/
public setColors(...colors: RestOrArray<string>): this {
this.data.colors = normalizeArray(colors);
return this;
}
/**
* Sets the gradient angle of this theme.
*
* @remarks
* The value must be between `0` and `360` (inclusive).
* @param angle - The gradient angle (direction of theme colors)
*/
public setGradientAngle(angle: number): this {
this.data.gradient_angle = angle;
return this;
}
/**
* Sets the base mix (intensity) of this theme.
*
* @remarks
* The value must be between `0` and `100` (inclusive).
* @param baseMix - The base mix intensity
*/
public setBaseMix(baseMix: number): this {
this.data.base_mix = baseMix;
return this;
}
/**
* Sets the base theme (mode) of this theme.
*
* @param baseTheme - The base theme mode
*/
public setBaseTheme(baseTheme: BaseThemeType): this {
this.data.base_theme = baseTheme;
return this;
}
/**
* Clears the base theme of this theme.
*/
public clearBaseTheme(): this {
this.data.base_theme = undefined;
return this;
}
/**
* Serializes this builder to API-compatible JSON data.
*
* Note that by disabling validation, there is no guarantee that the resulting object will be valid.
*
* @param validationOverride - Force validation to run/not run regardless of your global preference
*/
public toJSON(validationOverride?: boolean): APIMessageSharedClientTheme {
const data = structuredClone(this.data);
validate(sharedClientThemePredicate, data, validationOverride);
return data as APIMessageSharedClientTheme;
}
}

View File

@@ -65,5 +65,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/collection
[npm]: https://www.npmjs.com/package/@discordjs/collection
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -0,0 +1,16 @@
import { expectTypeOf, test } from 'vitest';
import { Collection, type ReadonlyCollection } from '../src/index.js';
test('ReadonlyCollection#tap preserves the readonly type', () => {
const readonly: ReadonlyCollection<string, number> = new Collection([['a', 1]]);
expectTypeOf(readonly.tap(() => {})).toEqualTypeOf<ReadonlyCollection<string, number>>();
expectTypeOf(readonly.tap(() => {}, null)).toEqualTypeOf<ReadonlyCollection<string, number>>();
});
test('ReadonlyCollection#each preserves the readonly type', () => {
const readonly: ReadonlyCollection<string, number> = new Collection([['a', 1]]);
expectTypeOf(readonly.each(() => {})).toEqualTypeOf<ReadonlyCollection<string, number>>();
expectTypeOf(readonly.each(() => {}, null)).toEqualTypeOf<ReadonlyCollection<string, number>>();
});

View File

@@ -64,19 +64,19 @@
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^22.19.15",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -5,9 +5,22 @@
*/
export type ReadonlyCollection<Key, Value> = Omit<
Collection<Key, Value>,
keyof Map<Key, Value> | 'ensure' | 'reverse' | 'sort' | 'sweep'
keyof Map<Key, Value> | 'each' | 'ensure' | 'reverse' | 'sort' | 'sweep' | 'tap'
> &
ReadonlyMap<Key, Value>;
ReadonlyMap<Key, Value> & {
each(
fn: (value: Value, key: Key, collection: ReadonlyCollection<Key, Value>) => void,
): ReadonlyCollection<Key, Value>;
each<This>(
fn: (this: This, value: Value, key: Key, collection: ReadonlyCollection<Key, Value>) => void,
thisArg: This,
): ReadonlyCollection<Key, Value>;
tap(fn: (collection: ReadonlyCollection<Key, Value>) => void): ReadonlyCollection<Key, Value>;
tap<This>(
fn: (this: This, collection: ReadonlyCollection<Key, Value>) => void,
thisArg: This,
): ReadonlyCollection<Key, Value>;
};
export interface Collection<Key, Value> {
/**
@@ -1023,7 +1036,10 @@ export class Collection<Key, Value> extends Map<Key, Value> {
* but returns a Collection instead of an Array.
*/
public toReversed() {
return new this.constructor[Symbol.species](this).reverse();
const entries = [...this.entries()];
entries.reverse();
return new this.constructor[Symbol.species](entries);
}
/**
@@ -1039,7 +1055,10 @@ export class Collection<Key, Value> extends Map<Key, Value> {
* ```
*/
public toSorted(compareFunction: Comparator<Key, Value> = Collection.defaultSort): Collection<Key, Value> {
return new this.constructor[Symbol.species](this).sort(compareFunction);
const entries = [...this.entries()];
entries.sort((a, b): number => compareFunction(a[1], b[1], a[0], b[0]));
return new this.constructor[Symbol.species](entries);
}
public toJSON() {

View File

@@ -126,5 +126,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/core
[npm]: https://www.npmjs.com/package/@discordjs/core
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -70,25 +70,25 @@
"@discordjs/ws": "workspace:^",
"@sapphire/snowflake": "^3.5.5",
"@vladfrangu/async_event_emitter": "^2.4.7",
"discord-api-types": "^0.38.41"
"discord-api-types": "^0.38.43"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^22.19.15",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -3,6 +3,7 @@
import type { RequestData, REST } from '@discordjs/rest';
import {
Routes,
type RESTGetAPIApplicationActivityInstanceResult,
type RESTGetAPIApplicationEmojiResult,
type RESTGetAPIApplicationEmojisResult,
type RESTGetCurrentApplicationResult,
@@ -136,4 +137,23 @@ export class ApplicationsAPI {
) {
await this.rest.delete(Routes.applicationEmoji(applicationId, emojiId), { auth, signal });
}
/**
* Fetches an activity instance of an application
*
* @see {@link https://docs.discord.com/developers/resources/application#get-application-activity-instance}
* @param applicationId - The id of the application to fetch the activity instance of
* @param instanceId - The id of the activity instance to fetch
* @param options - The options for fetching the activity instance
*/
public async getActivityInstance(
applicationId: Snowflake,
instanceId: string,
{ auth, signal }: Pick<RequestData, 'auth' | 'signal'> = {},
) {
return this.rest.get(Routes.applicationActivityInstance(applicationId, instanceId), {
auth,
signal,
}) as Promise<RESTGetAPIApplicationActivityInstanceResult>;
}
}

View File

@@ -31,7 +31,6 @@ import {
type RESTPostAPIChannelWebhookJSONBody,
type RESTPostAPIChannelWebhookResult,
type RESTPostAPIGuildForumThreadsJSONBody,
type RESTPostAPISendSoundboardSoundResult,
type RESTPostAPISoundboardSendSoundJSONBody,
type RESTPutAPIChannelPermissionJSONBody,
type RESTPutAPIChannelRecipientJSONBody,
@@ -702,11 +701,11 @@ export class ChannelsAPI {
body: RESTPostAPISoundboardSendSoundJSONBody,
{ auth, signal }: Pick<RequestData, 'auth' | 'signal'> = {},
) {
return this.rest.post(Routes.sendSoundboardSound(channelId), {
await this.rest.post(Routes.sendSoundboardSound(channelId), {
auth,
body,
signal,
}) as Promise<RESTPostAPISendSoundboardSoundResult>;
});
}
/**

View File

@@ -5,6 +5,7 @@ import {
Routes,
type RESTGetAPIChannelThreadMemberQuery,
type RESTGetAPIChannelThreadMemberResult,
type RESTGetAPIChannelThreadMembersQuery,
type RESTGetAPIChannelThreadMembersResult,
type Snowflake,
} from 'discord-api-types/v10';
@@ -112,16 +113,22 @@ export class ThreadsAPI {
}
/**
* Fetches all members of a thread
* Fetches members of a thread
*
* @see {@link https://discord.com/developers/docs/resources/channel#list-thread-members}
* @param threadId - The id of the thread to fetch the members from
* @param query - The query for fetching the members
* @param options - The options for fetching the members
*/
public async getAllMembers(threadId: Snowflake, { auth, signal }: Pick<RequestData, 'auth' | 'signal'> = {}) {
public async getMembers(
threadId: Snowflake,
query: RESTGetAPIChannelThreadMembersQuery = {},
{ auth, signal }: Pick<RequestData, 'auth' | 'signal'> = {},
) {
return this.rest.get(Routes.threadMembers(threadId), {
auth,
signal,
query: makeURLSearchParams(query),
}) as Promise<RESTGetAPIChannelThreadMembersResult>;
}
}

13
packages/core/tsdoc.json Normal file
View File

@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
"extends": ["@discordjs/api-extractor/extends/tsdoc-base.json"],
"tagDefinitions": [
{
"tagName": "@unstable",
"syntaxKind": "modifier"
}
],
"supportForTags": {
"@unstable": true
}
}

View File

@@ -57,16 +57,16 @@
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@types/node": "^22.19.15",
"@types/prompts": "^2.4.9",
"@types/validate-npm-package-name": "^4.0.2",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"terser": "^5.46.0",
"terser": "^5.46.1",
"tsup": "^8.5.1",
"typescript": "~5.9.3"
},

View File

@@ -15,7 +15,7 @@
"discord.js": "^14.25.1"
},
"devDependencies": {
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",

View File

@@ -16,8 +16,8 @@
},
"devDependencies": {
"@sapphire/ts-config": "^5.0.3",
"@types/bun": "^1.3.9",
"eslint": "^9.39.2",
"@types/bun": "^1.3.11",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",

View File

@@ -15,7 +15,7 @@
"discord.js": "^14.25.1"
},
"devDependencies": {
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",

View File

@@ -17,8 +17,8 @@
},
"devDependencies": {
"@sapphire/ts-config": "^5.0.3",
"@types/node": "^22.19.11",
"eslint": "^9.39.2",
"@types/node": "^22.19.15",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",

View File

@@ -142,7 +142,7 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/discord.js
[npm]: https://www.npmjs.com/package/discord.js
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[rpc]: https://www.npmjs.com/package/discord-rpc
[rpc-source]: https://github.com/discordjs/RPC
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -73,12 +73,12 @@
"@discordjs/ws": "workspace:^",
"@sapphire/snowflake": "3.5.5",
"@vladfrangu/async_event_emitter": "^2.4.7",
"discord-api-types": "^0.38.41",
"discord-api-types": "^0.38.43",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
"magic-bytes.js": "^1.13.0",
"tslib": "^2.8.1",
"undici": "7.22.0"
"undici": "7.24.6"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
@@ -86,15 +86,15 @@
"@discordjs/docgen": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@types/node": "^22.19.15",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsd": "^0.33.0",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3"
},
"engines": {

View File

@@ -1,5 +1,6 @@
'use strict';
const { ChannelType } = require('discord-api-types/v10');
const { Poll } = require('../../structures/Poll.js');
const { PollAnswer } = require('../../structures/PollAnswer.js');
const { Partials } = require('../../util/Partials.js');
@@ -33,10 +34,16 @@ class Action {
const payloadData = {};
const id = data.channel_id ?? data.id;
if (!('recipients' in data)) {
// Try to resolve the recipient, but do not add the client user.
if ('recipients' in data) {
// Try to resolve the recipient, but do not add if already existing in recipients.
const recipient = data.author ?? data.user ?? { id: data.user_id };
if (recipient.id !== this.client.user.id) payloadData.recipients = [recipient];
if (!data.recipients.some(existingRecipient => recipient.id === existingRecipient.id)) {
payloadData.recipients = [...data.recipients, recipient];
}
} else if (data.type === ChannelType.DM || data.type === ChannelType.GroupDM) {
// Try to resolve the recipient.
const recipient = data.author ?? data.user ?? { id: data.user_id };
payloadData.recipients = [recipient];
}
if (id !== undefined) payloadData.id = id;

View File

@@ -21,7 +21,9 @@ class InteractionCreateAction extends Action {
const client = this.client;
// Resolve and cache partial channels for Interaction#channel getter
const channel = data.channel && this.getChannel(data.channel);
const channel =
data.channel &&
this.getChannel({ ...data.channel, ...('recipients' in data.channel ? { user: data.user } : undefined) });
// Do not emit this for interactions that cache messages that are non-text-based.
let InteractionClass;

View File

@@ -10,6 +10,7 @@ class MessageCreateAction extends Action {
id: data.channel_id,
author: data.author,
...('guild_id' in data && { guild_id: data.guild_id }),
...('channel_type' in data && { type: data.channel_type }),
});
if (channel) {
if (!channel.isTextBased()) return {};

View File

@@ -103,6 +103,8 @@ exports.VoiceStateManager = require('./managers/VoiceStateManager.js').VoiceStat
// Structures
exports.ActionRow = require('./structures/ActionRow.js').ActionRow;
exports.Activity = require('./structures/Presence.js').Activity;
exports.ActivityInstance = require('./structures/ActivityInstance.js').ActivityInstance;
exports.ActivityLocation = require('./structures/ActivityLocation.js').ActivityLocation;
exports.AnnouncementChannel = require('./structures/AnnouncementChannel.js').AnnouncementChannel;
exports.AnonymousGuild = require('./structures/AnonymousGuild.js').AnonymousGuild;
exports.AuthorizingIntegrationOwners =

View File

@@ -66,7 +66,14 @@ class UserManager extends CachedManager {
}
const data = await this.client.rest.post(Routes.userChannels(), { body: { recipient_id: id } });
return this.client.channels._add(data, null, { cache });
return this.client.channels._add(
{
...data,
...('recipients' in data ? { recipients: [...data.recipients, { id: this.client.user.id }] } : undefined),
},
null,
{ cache },
);
}
/**

View File

@@ -7,6 +7,7 @@ const { setTimeout: sleep } = require('node:timers/promises');
const { Collection } = require('@discordjs/collection');
const { range } = require('@discordjs/util');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { APIVersion, RouteBases } = require('discord-api-types/v10');
const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors/index.js');
const { fetchRecommendedShardCount } = require('../util/Util.js');
const { Shard } = require('./Shard.js');
@@ -43,6 +44,8 @@ class ShardingManager extends AsyncEventEmitter {
* @property {string[]} [shardArgs=[]] Arguments to pass to the shard script when spawning
* @property {string[]} [execArgv=[]] Arguments to pass to the shard script executable when spawning
* @property {string} [token] Token to use for automatic shard count and passing to shards
* @property {string} [api='https://discord.com/api'] The base API URL
* @property {string} [version='10'] The API version to use
*/
/**
@@ -164,6 +167,20 @@ class ShardingManager extends AsyncEventEmitter {
*/
this.token = _options.token?.replace(/^bot\s*/i, '') ?? null;
/**
* The base API URL
*
* @type {string}
*/
this.api = _options.api ?? RouteBases.api;
/**
* The API version to use
*
* @type {string}
*/
this.version = _options.version ?? APIVersion;
/**
* A collection of shards that this manager has spawned
*
@@ -217,7 +234,10 @@ class ShardingManager extends AsyncEventEmitter {
let shardAmount = amount;
if (shardAmount === 'auto') {
// eslint-disable-next-line require-atomic-updates
shardAmount = await fetchRecommendedShardCount(this.token);
shardAmount = await fetchRecommendedShardCount(this.token, {
api: this.api,
version: this.version,
});
} else {
if (typeof shardAmount !== 'number' || Number.isNaN(shardAmount)) {
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'Amount of shards', 'a number.');

View File

@@ -0,0 +1,52 @@
'use strict';
const { ActivityLocation } = require('./ActivityLocation.js');
const { Base } = require('./Base.js');
/**
* Represents an activity instance.
*
* @extends {Base}
*/
class ActivityInstance extends Base {
constructor(client, data) {
super(client);
/**
* The id of the application
*
* @type {Snowflake}
*/
this.applicationId = data.application_id;
/**
* The activity instance id
*
* @type {string}
*/
this.instanceId = data.instance_id;
/**
* The unique identifier for the launch
*
* @type {Snowflake}
*/
this.launchId = data.launch_id;
/**
* The location the instance is running in
*
* @type {ActivityLocation}
*/
this.location = new ActivityLocation(client, data.location);
/**
* The ids of the users connected to the instance
*
* @type {Snowflake[]}
*/
this.users = data.users;
}
}
exports.ActivityInstance = ActivityInstance;

View File

@@ -0,0 +1,65 @@
'use strict';
const { Base } = require('./Base.js');
/**
* Represents the location of an activity instance.
*
* @extends {Base}
*/
class ActivityLocation extends Base {
constructor(client, data) {
super(client);
/**
* The unique identifier for the location
*
* @type {string}
*/
this.id = data.id;
/**
* The kind of location
*
* @type {ActivityLocationKind}
*/
this.kind = data.kind;
/**
* The id of the channel
*
* @type {Snowflake}
*/
this.channelId = data.channel_id;
/**
* The id of the guild
*
* @type {?Snowflake}
*/
this.guildId = data.guild_id ?? null;
}
/**
* The channel of this activity location
*
* @type {?Channel}
* @readonly
*/
get channel() {
return this.client.channels.cache.get(this.channelId) ?? null;
}
/**
* The guild of this activity location
*
* @type {?Guild}
* @readonly
*/
get guild() {
if (!this.guildId) return null;
return this.client.guilds.cache.get(this.guildId) ?? null;
}
}
exports.ActivityLocation = ActivityLocation;

View File

@@ -9,6 +9,7 @@ const { SubscriptionManager } = require('../managers/SubscriptionManager.js');
const { ApplicationFlagsBitField } = require('../util/ApplicationFlagsBitField.js');
const { resolveImage } = require('../util/DataResolver.js');
const { PermissionsBitField } = require('../util/PermissionsBitField.js');
const { ActivityInstance } = require('./ActivityInstance.js');
const { ApplicationRoleConnectionMetadata } = require('./ApplicationRoleConnectionMetadata.js');
const { SKU } = require('./SKU.js');
const { Team } = require('./Team.js');
@@ -446,6 +447,17 @@ class ClientApplication extends Application {
const skus = await this.client.rest.get(Routes.skus(this.id));
return skus.reduce((coll, sku) => coll.set(sku.id, new SKU(this.client, sku)), new Collection());
}
/**
* Fetches an activity instance for this application.
*
* @param {string} instanceId The id of the activity instance
* @returns {Promise<ActivityInstance>}
*/
async fetchActivityInstance(instanceId) {
const data = await this.client.rest.get(Routes.applicationActivityInstance(this.id, instanceId));
return new ActivityInstance(this.client, data);
}
}
exports.ClientApplication = ClientApplication;

View File

@@ -97,7 +97,7 @@ class CommandInteractionOptionResolver {
*
* @param {string} name The name of the option.
* @param {ApplicationCommandOptionType[]} allowedTypes The allowed types of the option.
* @param {string[]} properties The properties to check for for `required`.
* @param {string[]} properties The properties to check for `required`.
* @param {boolean} required Whether to throw an error if the option is not found.
* @returns {?CommandInteractionOption} The option, if found.
* @private

View File

@@ -32,17 +32,19 @@ class DMChannel extends BaseChannel {
super._patch(data);
if (data.recipients) {
const recipient = data.recipients[0];
/**
* The recipient's id
* The recipients' ids
*
* @type {Snowflake}
* @type {Snowflake[]}
*/
this.recipientId = recipient.id;
this.recipientIds = [
...new Set([...(this.recipientIds ?? []), ...data.recipients.map(recipient => recipient.id)]),
];
if ('username' in recipient || this.client.options.partials.includes(Partials.User)) {
this.client.users._add(recipient);
for (const recipient of data.recipients) {
if ('username' in recipient || this.client.options.partials.includes(Partials.User)) {
this.client.users._add(recipient);
}
}
}
@@ -78,7 +80,21 @@ class DMChannel extends BaseChannel {
}
/**
* The recipient on the other end of the DM
* The recipient's id, if this is a DMChannel with the client user.
*
* @type {?Snowflake}
* @readonly
*/
get recipientId() {
if (this.recipientIds.includes(this.client.user.id)) {
return this.recipientIds.find(recipientId => recipientId !== this.client.user.id) ?? null;
}
return null;
}
/**
* The recipient on the other end of the DM, if this is a DMChannel with the client user.
*
* @type {?User}
* @readonly

View File

@@ -5,6 +5,7 @@ const { DiscordjsError, ErrorCodes } = require('../errors/index.js');
const { GuildMemberRoleManager } = require('../managers/GuildMemberRoleManager.js');
const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField.js');
const { PermissionsBitField } = require('../util/PermissionsBitField.js');
const { _transformCollectibles } = require('../util/Transformers.js');
const { Base } = require('./Base.js');
const { VoiceState } = require('./VoiceState.js');
@@ -150,6 +151,17 @@ class GuildMember extends Base {
} else {
this.avatarDecorationData = null;
}
if ('collectibles' in data) {
/**
* The member's collectibles
*
* @type {?Collectibles}
*/
this.collectibles = data.collectibles ? _transformCollectibles(data.collectibles) : null;
} else {
this.collectibles ??= null;
}
}
_clone() {
@@ -599,7 +611,11 @@ class GuildMember extends Base {
(this._roles.length === member._roles.length &&
this._roles.every((role, index) => role === member._roles[index]))) &&
this.avatarDecorationData?.asset === member.avatarDecorationData?.asset &&
this.avatarDecorationData?.skuId === member.avatarDecorationData?.skuId
this.avatarDecorationData?.skuId === member.avatarDecorationData?.skuId &&
this.collectibles?.nameplate?.skuId === member.collectibles?.nameplate?.skuId &&
this.collectibles?.nameplate?.asset === member.collectibles?.nameplate?.asset &&
this.collectibles?.nameplate?.label === member.collectibles?.nameplate?.label &&
this.collectibles?.nameplate?.palette === member.collectibles?.nameplate?.palette
);
}

View File

@@ -499,6 +499,34 @@ class Message extends Base {
} else {
this.call ??= null;
}
/**
* The shared client theme sent with this message
*
* @typedef {Object} SharedClientTheme
* @property {string[]} colors The hexadecimal-encoded colors of the theme (max of 5)
* @property {number} gradientAngle The direction of the theme's colors (0360)
* @property {number} baseMix The intensity of the theme's colors (0100)
* @property {?BaseThemeType} [baseTheme] The mode of the theme
*/
if (data.shared_client_theme) {
/**
* The shared client theme sent with this message
*
* @type {?SharedClientTheme}
*/
this.sharedClientTheme = {
colors: data.shared_client_theme.colors,
gradientAngle: data.shared_client_theme.gradient_angle,
baseMix: data.shared_client_theme.base_mix,
};
if ('base_theme' in data.shared_client_theme) {
this.sharedClientTheme.baseTheme = data.shared_client_theme.base_theme;
}
} else {
this.sharedClientTheme ??= null;
}
}
/**

View File

@@ -1,7 +1,7 @@
'use strict';
const { Buffer } = require('node:buffer');
const { isJSONEncodable, lazy } = require('@discordjs/util');
const { isJSONEncodable, isRawFileEncodable, lazy } = require('@discordjs/util');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const { DiscordjsError, DiscordjsRangeError, ErrorCodes } = require('../errors/index.js');
const { resolveFile } = require('../util/DataResolver.js');
@@ -190,16 +190,24 @@ class MessagePayload {
}
}
const attachments = this.options.files?.map((file, index) => ({
id: index.toString(),
description: file.description,
title: file.title,
waveform: file.waveform,
duration_secs: file.duration,
}));
let attachments = this.options.files?.map((file, index) =>
isRawFileEncodable(file)
? {
id: index.toString(),
...file.toJSON(),
}
: {
id: index.toString(),
description: file.description,
title: file.title,
waveform: file.waveform,
duration_secs: file.duration,
},
);
// Only passable during edits
if (Array.isArray(this.options.attachments)) {
attachments ??= [];
attachments.push(
// Note how we don't check for file body encodable, since we aren't expecting file data here
...this.options.attachments.map(attachment => (isJSONEncodable(attachment) ? attachment.toJSON() : attachment)),
@@ -223,6 +231,18 @@ class MessagePayload {
};
}
let shared_client_theme;
if (this.options.sharedClientTheme) {
shared_client_theme = isJSONEncodable(this.options.sharedClientTheme)
? this.options.sharedClientTheme.toJSON()
: {
colors: this.options.sharedClientTheme.colors,
gradient_angle: this.options.sharedClientTheme.gradientAngle,
base_mix: this.options.sharedClientTheme.baseMix,
base_theme: this.options.sharedClientTheme.baseTheme,
};
}
this.body = {
content,
tts,
@@ -245,6 +265,7 @@ class MessagePayload {
thread_name: threadName,
applied_tags: appliedTags,
poll,
shared_client_theme,
};
return this;
}
@@ -276,6 +297,8 @@ class MessagePayload {
if (ownAttachment) {
attachment = fileLike;
name = findName(attachment);
} else if (isRawFileEncodable(fileLike)) {
return fileLike.getRawFile();
} else {
attachment = fileLike.attachment;
name = fileLike.name ?? findName(attachment);

View File

@@ -79,7 +79,7 @@ class ModalComponentResolver {
*
* @param {string} customId The custom id of the component.
* @param {ComponentType[]} allowedTypes The allowed types of the component.
* @param {string[]} properties The properties to check for for `required`.
* @param {string[]} properties The properties to check for `required`.
* @param {boolean} required Whether to throw an error if the component value(s) are not found.
* @returns {ModalData} The option, if found.
* @private

View File

@@ -141,9 +141,9 @@ class Role extends Base {
}
/**
* The tags this role has
* The tags a role has
*
* @type {?Object}
* @typedef {Object} RoleTagData
* @property {Snowflake} [botId] The id of the bot this role belongs to
* @property {Snowflake|string} [integrationId] The id of the integration this role belongs to
* @property {true} [premiumSubscriberRole] Whether this is the guild's premium subscription role
@@ -151,6 +151,12 @@ class Role extends Base {
* @property {true} [availableForPurchase] Whether this role is available for purchase
* @property {true} [guildConnections] Whether this role is a guild's linked role
*/
/**
* The tags this role has
*
* @type {?RoleTagData}
*/
this.tags = data.tags ? {} : null;
if (data.tags) {
if ('bot_id' in data.tags) {

View File

@@ -170,15 +170,15 @@ class User extends Base {
* @property {?NameplateData} nameplate The user's nameplate data
*/
if (data.collectibles) {
if ('collectibles' in data) {
/**
* The user's collectibles
*
* @type {?Collectibles}
*/
this.collectibles = _transformCollectibles(data.collectibles);
this.collectibles = data.collectibles ? _transformCollectibles(data.collectibles) : null;
} else {
this.collectibles = null;
this.collectibles ??= null;
}
/**

View File

@@ -88,7 +88,7 @@ class TextBasedChannel {
* @property {Array<(EmbedBuilder|Embed|APIEmbed)>} [embeds] The embeds for the message
* @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
* (see {@link https://discord.com/developers/docs/resources/message#allowed-mentions-object here} for more details)
* @property {Array<(Attachment|AttachmentPayload|BufferResolvable|FileBodyEncodable<APIAttachment>|Stream)>} [files]
* @property {Array<(Attachment|AttachmentPayload|BufferResolvable|RawFileEncodable|Stream)>} [files]
* The files to send with the message.
* @property {Array<(ActionRowBuilder|MessageTopLevelComponent|APIMessageTopLevelComponent)>} [components]
* Action rows containing interactive components for the message (buttons, select menus) and other

View File

@@ -5,6 +5,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityFlags}
*/
/**
* @external ActivityLocationKind
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityLocationKind}
*/
/**
* @external ActivityType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityType}
@@ -340,6 +345,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AuditLogEvent}
*/
/**
* @external BaseThemeType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/BaseThemeType}
*/
/**
* @external ButtonStyle
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ButtonStyle}

View File

@@ -3,7 +3,7 @@
const { parse } = require('node:path');
const { Collection } = require('@discordjs/collection');
const { lazy } = require('@discordjs/util');
const { ChannelType, RouteBases, Routes } = require('discord-api-types/v10');
const { APIVersion, ChannelType, Routes, RouteBases } = require('discord-api-types/v10');
const { fetch } = require('undici');
const { Colors } = require('./Colors.js');
// eslint-disable-next-line import-x/order
@@ -67,6 +67,8 @@ function flatten(obj, ...props) {
* @typedef {Object} FetchRecommendedShardCountOptions
* @property {number} [guildsPerShard=1000] Number of guilds assigned per shard
* @property {number} [multipleOf=1] The multiple the shard count should round up to. (16 for large bot sharding)
* @property {string} [api='https://discord.com/api'] The base API URL
* @property {string} [version='10'] The API version to use
*/
/**
@@ -76,9 +78,12 @@ function flatten(obj, ...props) {
* @param {FetchRecommendedShardCountOptions} [options] Options for fetching the recommended shard count
* @returns {Promise<number>} The recommended number of shards
*/
async function fetchRecommendedShardCount(token, { guildsPerShard = 1_000, multipleOf = 1 } = {}) {
async function fetchRecommendedShardCount(
token,
{ guildsPerShard = 1_000, multipleOf = 1, api = RouteBases.api, version = APIVersion } = {},
) {
if (!token) throw new DiscordjsError(ErrorCodes.TokenMissing);
const response = await fetch(RouteBases.api + Routes.gatewayBot(), {
const response = await fetch(`${api}/v${version}${Routes.gatewayBot()}`, {
method: 'GET',
headers: { Authorization: `Bot ${token.replace(/^bot\s*/i, '')}` },
});

View File

@@ -4,11 +4,14 @@ import { Stream } from 'node:stream';
import { MessagePort, Worker } from 'node:worker_threads';
import { Collection, ReadonlyCollection } from '@discordjs/collection';
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions, EmojiURLOptions } from '@discordjs/rest';
import { Awaitable, FileBodyEncodable, JSONEncodable } from '@discordjs/util';
import { Awaitable, FileBodyEncodable, JSONEncodable, RawFileEncodable } from '@discordjs/util';
import { WebSocketManager, WebSocketManagerOptions } from '@discordjs/ws';
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
import {
ActivityFlags,
APIActivityInstance,
APIActivityLocation,
ActivityLocationKind,
ActivityType,
APIActionRowComponent,
APIApplicationCommand,
@@ -61,6 +64,7 @@ import {
APIMessageComponentInteraction,
APIMessageMentionableSelectInteractionData,
APIMessageRoleSelectInteractionData,
APIMessageSharedClientTheme,
APIMessageStringSelectInteractionData,
APIMessageTopLevelComponent,
APIMessageUserSelectInteractionData,
@@ -115,6 +119,7 @@ import {
AutoModerationRuleEventType,
AutoModerationRuleKeywordPresetType,
AutoModerationRuleTriggerType,
BaseThemeType,
ButtonStyle,
ChannelFlags,
ChannelType,
@@ -246,6 +251,25 @@ export class Activity {
public toString(): string;
}
export class ActivityInstance extends Base {
private constructor(client: Client<true>, data: APIActivityInstance);
public applicationId: Snowflake;
public instanceId: string;
public launchId: Snowflake;
public location: ActivityLocation;
public users: Snowflake[];
}
export class ActivityLocation extends Base {
private constructor(client: Client<true>, data: APIActivityLocation);
public id: string;
public kind: ActivityLocationKind;
public channelId: Snowflake;
public guildId: Snowflake | null;
public get channel(): Channel | null;
public get guild(): Guild | null;
}
export type ActivityFlagsString = keyof typeof ActivityFlags;
export interface BaseComponentData {
@@ -1004,6 +1028,7 @@ export class ClientApplication extends Application {
public roleConnectionsVerificationURL: string | null;
public edit(options: ClientApplicationEditOptions): Promise<ClientApplication>;
public fetch(): Promise<ClientApplication>;
public fetchActivityInstance(instanceId: string): Promise<ActivityInstance>;
public fetchRoleConnectionMetadataRecords(): Promise<ApplicationRoleConnectionMetadata[]>;
public fetchSKUs(): Promise<Collection<Snowflake, SKU>>;
public editRoleConnectionMetadataRecords(
@@ -1083,7 +1108,7 @@ export class ContainerComponent extends Component<APIContainerComponent> {
public readonly components: ComponentInContainer[];
}
export { Collection, type ReadonlyCollection } from '@discordjs/collection';
export { Collection, type ReadonlyCollection, type Comparator, type Keep } from '@discordjs/collection';
export interface CollectorEventTypes<Key, Value, Extras extends unknown[] = []> {
collect: [Value, ...Extras];
@@ -1292,7 +1317,8 @@ export interface DMChannel
export class DMChannel extends BaseChannel {
private constructor(client: Client<true>, data?: RawDMChannelData);
public flags: Readonly<ChannelFlagsBitField>;
public recipientId: Snowflake;
public get recipientId(): Snowflake | null;
public recipientIds: Snowflake[];
public get recipient(): User | null;
public type: ChannelType.DM;
public fetch(force?: boolean): Promise<this>;
@@ -1638,6 +1664,7 @@ export class GuildMember extends Base {
public avatarDecorationData: AvatarDecorationData | null;
public banner: string | null;
public get bannable(): boolean;
public collectibles: Collectibles | null;
public get dmChannel(): DMChannel | null;
public get displayColor(): number;
public get displayHexColor(): HexColorString;
@@ -2127,6 +2154,13 @@ export interface MessageCall {
participants: readonly Snowflake[];
}
export interface SharedClientTheme {
baseMix: number;
baseTheme?: BaseThemeType | null;
colors: readonly string[];
gradientAngle: number;
}
export type MessageComponentType =
| ComponentType.Button
| ComponentType.ChannelSelect
@@ -2222,6 +2256,7 @@ export class Message<InGuild extends boolean = boolean> extends Base {
public tts: boolean;
public poll: Poll | null;
public call: MessageCall | null;
public sharedClientTheme: SharedClientTheme | null;
public type: MessageType;
public get url(): string;
public webhookId: Snowflake | null;
@@ -3265,6 +3300,8 @@ export class ShardingManager extends AsyncEventEmitter<ShardingManagerEventTypes
public token: string | null;
public totalShards: number | 'auto';
public shardList: number[] | 'auto';
public api: string;
public version: string;
public broadcast(message: unknown): Promise<Shard[]>;
public broadcastEval<Result>(fn: (client: Client) => Awaitable<Result>): Promise<Serialized<Result>[]>;
public broadcastEval<Result, Context>(
@@ -3287,8 +3324,10 @@ export class ShardingManager extends AsyncEventEmitter<ShardingManagerEventTypes
}
export interface FetchRecommendedShardCountOptions {
api?: string;
guildsPerShard?: number;
multipleOf?: number;
version?: string;
}
export {
@@ -6753,7 +6792,7 @@ export interface BaseMessageOptions {
)[];
content?: string | null;
embeds?: readonly (APIEmbed | JSONEncodable<APIEmbed>)[];
files?: readonly (Attachment | AttachmentPayload | BufferResolvable | FileBodyEncodable<APIAttachment> | Stream)[];
files?: readonly (Attachment | AttachmentPayload | BufferResolvable | RawFileEncodable | Stream)[];
}
export interface BaseMessageSendOptions extends BaseMessageOptions {
@@ -6788,6 +6827,7 @@ export interface BaseMessageCreateOptions
extends BaseMessageSendOptions, MessageOptionsPoll, MessageOptionsFlags, MessageOptionsTTS, MessageOptionsStickers {
enforceNonce?: boolean;
nonce?: number | string;
sharedClientTheme?: JSONEncodable<APIMessageSharedClientTheme> | SharedClientTheme;
}
export interface MessageCreateOptions extends BaseMessageCreateOptions {
@@ -7149,6 +7189,7 @@ export interface SetRolePositionOptions {
export type ShardingManagerMode = 'process' | 'worker';
export interface ShardingManagerOptions {
api?: string;
execArgv?: readonly string[];
mode?: ShardingManagerMode;
respawn?: boolean;
@@ -7157,6 +7198,7 @@ export interface ShardingManagerOptions {
silent?: boolean;
token?: string;
totalShards?: number | 'auto';
version?: string;
}
export interface ShowModalOptions {

View File

@@ -44,5 +44,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/docgen
[npm]: https://www.npmjs.com/package/@discordjs/docgen
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -68,16 +68,16 @@
},
"devDependencies": {
"@types/jsdoc-to-markdown": "^7.0.6",
"@types/node": "^24.10.13",
"@types/node": "^24.12.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"terser": "^5.46.0",
"terser": "^5.46.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3"
},
"engines": {

View File

@@ -82,5 +82,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/formatters
[npm]: https://www.npmjs.com/package/@discordjs/formatters
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -55,25 +55,25 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"discord-api-types": "^0.38.41"
"discord-api-types": "^0.38.43"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^22.19.15",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -58,5 +58,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/next
[npm]: https://www.npmjs.com/package/@discordjs/next
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -72,25 +72,25 @@
"@discordjs/rest": "workspace:^",
"@discordjs/util": "workspace:^",
"@discordjs/ws": "workspace:^",
"discord-api-types": "^0.38.41"
"discord-api-types": "^0.38.43"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^22.19.15",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -66,5 +66,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/proxy
[npm]: https://www.npmjs.com/package/@discordjs/proxy
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -68,27 +68,27 @@
"@discordjs/rest": "workspace:^",
"@discordjs/util": "workspace:^",
"tslib": "^2.8.1",
"undici": "7.22.0"
"undici": "7.24.6"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@types/supertest": "^6.0.3",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^22.19.15",
"@types/supertest": "^7.2.0",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"supertest": "^7.2.2",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -135,5 +135,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/rest
[npm]: https://www.npmjs.com/package/@discordjs/rest
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -88,29 +88,29 @@
"@sapphire/async-queue": "^1.5.5",
"@sapphire/snowflake": "^3.5.5",
"@vladfrangu/async_event_emitter": "^2.4.7",
"discord-api-types": "^0.38.41",
"discord-api-types": "^0.38.43",
"magic-bytes.js": "^1.13.0",
"tslib": "^2.8.1",
"undici": "7.22.0",
"undici": "7.24.6",
"uuid": "^13.0.0"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^22.19.15",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -42,5 +42,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/scripts
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -60,30 +60,30 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@actions/glob": "^0.5.1",
"@actions/glob": "^0.6.1",
"@discordjs/api-extractor-model": "workspace:^",
"@discordjs/api-extractor-utils": "workspace:^",
"@microsoft/tsdoc": "~0.15.1",
"@microsoft/tsdoc-config": "~0.17.1",
"@vercel/blob": "^2.3.0",
"@vercel/blob": "^2.3.2",
"@vercel/postgres": "^0.10.0",
"commander": "^14.0.3",
"tslib": "^2.8.1",
"undici": "7.22.0",
"yaml": "^2.8.2"
"undici": "7.24.6",
"yaml": "^2.8.3"
},
"devDependencies": {
"@turbo/gen": "^2.8.10",
"@types/node": "^24.10.13",
"@turbo/gen": "^2.8.21",
"@types/node": "^24.12.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"terser": "^5.46.0",
"terser": "^5.46.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3"
},
"engines": {

View File

@@ -59,19 +59,19 @@
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^24.10.13",
"@vitest/coverage-v8": "^4.0.18",
"@types/node": "^24.12.0",
"@vitest/coverage-v8": "^4.1.2",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -66,5 +66,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/structures
[npm]: https://www.npmjs.com/package/@discordjs/structures
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -16,6 +16,7 @@ import type {
APIUserSelectComponent,
} from 'discord-api-types/v10';
import {
BaseThemeType,
MessageReferenceType,
MessageType,
MessageFlags,
@@ -28,6 +29,7 @@ import {
import { describe, expect, test } from 'vitest';
import { Attachment } from '../src/messages/Attachment.js';
import { Message } from '../src/messages/Message.js';
import { SharedClientTheme } from '../src/messages/SharedClientTheme.js';
import { ContainerComponent } from '../src/messages/components/ContainerComponent.js';
import { Embed } from '../src/messages/embeds/Embed.js';
import { User } from '../src/users/User.js';
@@ -476,3 +478,32 @@ describe('message with components', () => {
expect(containerInstance.spoiler).toBe(container.spoiler);
});
});
describe('SharedClientTheme structure', () => {
const rawTheme = {
colors: ['5865F2', '7258F2', '9858F2'],
gradient_angle: 45,
base_mix: 58,
base_theme: BaseThemeType.Dark,
};
test('GIVEN a shared client theme THEN exposes all getters correctly', () => {
const instance = new SharedClientTheme(rawTheme);
expect(instance.colors).toStrictEqual(rawTheme.colors);
expect(instance.gradientAngle).toBe(rawTheme.gradient_angle);
expect(instance.baseMix).toBe(rawTheme.base_mix);
expect(instance.baseTheme).toBe(BaseThemeType.Dark);
expect(instance.toJSON()).toEqual(rawTheme);
});
test('GIVEN a shared client theme without base_theme THEN baseTheme is undefined', () => {
const { base_theme: _, ...withoutTheme } = rawTheme;
const instance = new SharedClientTheme(withoutTheme);
expect(instance.baseTheme).toBeUndefined();
});
test('GIVEN a shared client theme with null base_theme THEN baseTheme is null', () => {
const instance = new SharedClientTheme({ ...rawTheme, base_theme: null });
expect(instance.baseTheme).toBeNull();
});
});

View File

@@ -63,26 +63,26 @@
"dependencies": {
"@discordjs/formatters": "workspace:^",
"@sapphire/snowflake": "^3.5.5",
"discord-api-types": "^0.38.41"
"discord-api-types": "^0.38.43"
},
"devDependencies": {
"@discordjs/api-extractor": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^6.0.0",
"@types/node": "^22.19.11",
"@vitest/coverage-v8": "^4.0.18",
"cpy-cli": "^6.0.0",
"@types/node": "^22.19.15",
"@vitest/coverage-v8": "^4.1.2",
"cpy-cli": "^7.0.0",
"cross-env": "^10.1.0",
"esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"turbo": "^2.8.10",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"vitest": "^4.0.18"
"vitest": "^4.1.2"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -1,4 +1,4 @@
import type { APIAutoModerationRuleTriggerMetadata, AutoModerationRuleTriggerType } from 'discord-api-types/v10';
import type { APIAutoModerationRuleTriggerMetadata } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
@@ -32,7 +32,7 @@ export class AutoModerationRuleTriggerMetadata<
*
* @see {@link https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-matching-strategies | Keyword matching strategies}
*
* Associated trigger types: {@link AutoModerationRuleTriggerType.Keyword}, {@link AutoModerationRuleTriggerType.MemberProfile}
* Associated trigger types: {@link discord-api-types/v10#AutoModerationRuleTriggerType.Keyword}, {@link discord-api-types/v10#AutoModerationRuleTriggerType.MemberProfile}
*/
public get keywordFilter() {
return this[kData].keyword_filter;
@@ -45,7 +45,7 @@ export class AutoModerationRuleTriggerMetadata<
*
* Each regex pattern must be 260 characters or less.
*
* Associated trigger types: {@link AutoModerationRuleTriggerType.Keyword}, {@link AutoModerationRuleTriggerType.MemberProfile}
* Associated trigger types: {@link discord-api-types/v10#AutoModerationRuleTriggerType.Keyword}, {@link discord-api-types/v10#AutoModerationRuleTriggerType.MemberProfile}
*/
public get regexPatterns() {
return this[kData].regex_patterns;
@@ -56,7 +56,7 @@ export class AutoModerationRuleTriggerMetadata<
*
* @see {@link https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-preset-types | Keyword preset types}
*
* Associated trigger types: {@link AutoModerationRuleTriggerType.KeywordPreset}
* Associated trigger types: {@link discord-api-types/v10#AutoModerationRuleTriggerType.KeywordPreset}
*/
public get presets() {
return this[kData].presets;
@@ -76,7 +76,7 @@ export class AutoModerationRuleTriggerMetadata<
* @see {@link https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-types | triggerType}
* @see {@link https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-matching-strategies | Keyword matching strategies}
*
* Associated trigger types: {@link AutoModerationRuleTriggerType.Keyword}, {@link AutoModerationRuleTriggerType.KeywordPreset}, {@link AutoModerationRuleTriggerType.MemberProfile}
* Associated trigger types: {@link discord-api-types/v10#AutoModerationRuleTriggerType.Keyword}, {@link discord-api-types/v10#AutoModerationRuleTriggerType.KeywordPreset}, {@link discord-api-types/v10#AutoModerationRuleTriggerType.MemberProfile}
*/
public get allowList() {
return this[kData].allow_list;
@@ -85,7 +85,7 @@ export class AutoModerationRuleTriggerMetadata<
/**
* Total number of unique role and user mentions allowed per message (Maximum of 50)
*
* Associated trigger types: {@link AutoModerationRuleTriggerType.MentionSpam}
* Associated trigger types: {@link discord-api-types/v10#AutoModerationRuleTriggerType.MentionSpam}
*/
public get mentionTotalLimit() {
return this[kData].mention_total_limit;
@@ -94,7 +94,7 @@ export class AutoModerationRuleTriggerMetadata<
/**
* Whether to automatically detect mention raids
*
* Associated trigger types: {@link AutoModerationRuleTriggerType.MentionSpam}
* Associated trigger types: {@link discord-api-types/v10#AutoModerationRuleTriggerType.MentionSpam}
*/
public get mentionRaidProtectionEnabled() {
return this[kData].mention_raid_protection_enabled;

View File

@@ -1,8 +1,4 @@
import type {
APIAutoModerationActionMetadata,
AutoModerationActionType,
AutoModerationRuleTriggerType,
} from 'discord-api-types/v10';
import type { APIAutoModerationActionMetadata } from 'discord-api-types/v10';
import { Structure } from '../../Structure.js';
import { kData } from '../../utils/symbols.js';
import type { Partialize } from '../../utils/types.js';
@@ -30,7 +26,7 @@ export class AutoModerationActionMetadata<
/**
* Channel to which user content should be logged. This must be an existing channel
*
* Associated action types: {@link AutoModerationActionType.SendAlertMessage}
* Associated action types: {@link discord-api-types/v10#AutoModerationActionType.SendAlertMessage}
*/
public get channelId() {
return this[kData].channel_id;
@@ -39,11 +35,11 @@ export class AutoModerationActionMetadata<
/**
* Timeout duration in seconds. Maximum of 2419200 seconds (4 weeks).
*
* A `TIMEOUT` action can only be set up for {@link AutoModerationRuleTriggerType.Keyword} and {@link AutoModerationRuleTriggerType.MentionSpam}.
* A `TIMEOUT` action can only be set up for {@link discord-api-types/v10#AutoModerationRuleTriggerType.Keyword} and {@link discord-api-types/v10#AutoModerationRuleTriggerType.MentionSpam}.
*
* The `MODERATE_MEMBERS` permission is required to use {@link AutoModerationActionType.Timeout} actions.
* The `MODERATE_MEMBERS` permission is required to use {@link discord-api-types/v10#AutoModerationActionType.Timeout} actions.
*
* Associated action types: {@link AutoModerationActionType.Timeout}
* Associated action types: {@link discord-api-types/v10#AutoModerationActionType.Timeout}
*/
public get durationSeconds() {
return this[kData].duration_seconds;
@@ -52,7 +48,7 @@ export class AutoModerationActionMetadata<
/**
* Additional explanation that will be shown to members whenever their message is blocked. Maximum of 150 characters
*
* Associated action types: {@link AutoModerationActionType.BlockMessage}
* Associated action types: {@link discord-api-types/v10#AutoModerationActionType.BlockMessage}
*/
public get customMessage() {
return this[kData].custom_message;

View File

@@ -2,7 +2,7 @@ import { SKUFlags } from 'discord-api-types/v10';
import { BitField } from './BitField.js';
/**
* Data structure that makes it easy to interact with an {@link SKUFlags} bitfield.
* Data structure that makes it easy to interact with an {@link discord-api-types/v10#SKUFlags} bitfield.
*/
export class SKUFlagsBitField extends BitField<keyof typeof SKUFlags> {
/**

View File

@@ -13,7 +13,7 @@ import type { Partialize } from '../utils/types.js';
* Represents a message on Discord.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @remarks has substructures `Message`, `Channel`, `MessageActivity`, `MessageCall`, `MessageReference`, `Attachment`, `Application`, `ChannelMention`, `Reaction`, `Poll`, `ResolvedInteractionData`, `RoleSubscriptionData`, `Sticker`, all the different `Component`s, ... which need to be instantiated and stored by an extending class using it
* @remarks has substructures `Message`, `Channel`, `MessageActivity`, `MessageCall`, `MessageReference`, `SharedClientTheme`, `Attachment`, `Application`, `ChannelMention`, `Reaction`, `Poll`, `ResolvedInteractionData`, `RoleSubscriptionData`, `Sticker`, all the different `Component`s, ... which need to be instantiated and stored by an extending class using it
*/
export class Message<Omitted extends keyof APIMessage | '' = 'edited_timestamp' | 'timestamp'> extends Structure<
APIMessage,

View File

@@ -0,0 +1,57 @@
import type { APIMessageSharedClientTheme } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { kData } from '../utils/symbols.js';
import type { Partialize } from '../utils/types.js';
/**
* Represents the shared client theme sent with a Discord message.
*
* @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
* @see {@link https://docs.discord.com/developers/resources/message#shared-client-theme-object}
*/
export class SharedClientTheme<Omitted extends keyof APIMessageSharedClientTheme | '' = ''> extends Structure<
APIMessageSharedClientTheme,
Omitted
> {
/**
* The template used for removing data from the raw data stored for each SharedClientTheme.
*/
public static override DataTemplate: Partial<APIMessageSharedClientTheme> = {};
/**
* @param data - The raw data received from the API for the shared client theme
*/
public constructor(data: Partialize<APIMessageSharedClientTheme, Omitted>) {
super(data);
}
/**
* The hexadecimal-encoded colors of this theme (max of 5)
*/
public get colors() {
return this[kData].colors;
}
/**
* The gradient angle (direction) of this theme's colors (0360)
*/
public get gradientAngle() {
return this[kData].gradient_angle;
}
/**
* The base mix (intensity) of this theme's colors (0100)
*/
public get baseMix() {
return this[kData].base_mix;
}
/**
* The base theme mode
*
* @see {@link https://docs.discord.com/developers/resources/message#base-theme-types}
*/
public get baseTheme() {
return this[kData].base_theme;
}
}

View File

@@ -14,3 +14,4 @@ export * from './ModalSubmitInteractionMetadata.js';
export * from './Reaction.js';
export * from './ReactionCountDetails.js';
export * from './RoleSubscriptionData.js';
export * from './SharedClientTheme.js';

View File

@@ -1,5 +1,5 @@
import { DiscordSnowflake } from '@sapphire/snowflake';
import type { APISubscription, SubscriptionStatus } from 'discord-api-types/v10';
import type { APISubscription } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { dateToDiscordISOTimestamp } from '../utils/optimization.js';
import {
@@ -131,7 +131,7 @@ export class Subscription<
}
/**
* The {@link SubscriptionStatus} of the current subscription
* The {@link discord-api-types/v10#SubscriptionStatus} of the current subscription
*/
public get status() {
return this[kData].status;
@@ -147,7 +147,7 @@ export class Subscription<
/**
* The time when the subscription was canceled
*
* @remarks This is populated when the {@link Subscription#status} transitions to {@link SubscriptionStatus.Ending}.
* @remarks This is populated when the {@link Subscription.status} transitions to {@link discord-api-types/v10#SubscriptionStatus.Ending}.
*/
public get canceledAt() {
const canceledTimestamp = this.canceledTimestamp;

View File

@@ -13,8 +13,7 @@ export function extendTemplate<SuperTemplate extends Record<string, unknown>>(
* Turns a JavaScript Date object into the timestamp format used by Discord in payloads.
* E.g. `2025-11-16T14:09:25.239000+00:00`
*
* @private
* @param date a Date instance
* @param date - a Date instance
* @returns an ISO8601 timestamp with microseconds precision and explicit +00:00 timezone
*/
export function dateToDiscordISOTimestamp(date: Date) {

View File

@@ -42,5 +42,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord]: https://discord.gg/djs
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/ui
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

View File

@@ -52,7 +52,7 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@ariakit/react": "^0.4.21",
"@ariakit/react": "^0.4.24",
"@react-icons/all-files": "^4.1.0",
"react": "^19.2.4",
"react-dom": "^19.2.4"
@@ -60,30 +60,30 @@
"devDependencies": {
"@favware/cliff-jumper": "^6.0.0",
"@react-icons/all-files": "^4.1.0",
"@storybook/addon-docs": "^10.2.10",
"@storybook/addon-links": "^10.2.10",
"@storybook/addon-themes": "^10.2.10",
"@storybook/builder-vite": "^10.2.10",
"@storybook/react": "^10.2.10",
"@storybook/react-vite": "^10.2.10",
"@types/node": "^24.10.13",
"@storybook/addon-docs": "^10.3.3",
"@storybook/addon-links": "^10.3.3",
"@storybook/addon-themes": "^10.3.3",
"@storybook/builder-vite": "^10.3.3",
"@storybook/react": "^10.3.3",
"@storybook/react-vite": "^10.3.3",
"@types/node": "^24.12.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@unocss/eslint-plugin": "^66.6.0",
"@unocss/reset": "^66.6.0",
"@vitejs/plugin-react": "^5.1.4",
"chromatic": "^13.3.5",
"@unocss/eslint-plugin": "^66.6.7",
"@unocss/reset": "^66.6.7",
"@vitejs/plugin-react": "^5.2.0",
"chromatic": "^16.0.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint": "^9.39.4",
"eslint-config-neon": "^0.3.2",
"eslint-formatter-compact": "^9.0.1",
"eslint-formatter-pretty": "^7.0.0",
"prettier": "^3.8.1",
"prop-types": "^15.8.1",
"storybook": "^10.2.10",
"turbo": "^2.8.10",
"storybook": "^10.3.3",
"turbo": "^2.8.21",
"typescript": "~5.9.3",
"unocss": "^66.6.0",
"unocss": "^66.6.7",
"vite": "^7.3.1",
"vite-plugin-dts": "^4.5.4"
},

View File

@@ -63,5 +63,5 @@ If you don't understand something in the documentation, you are experiencing pro
[discord-developers]: https://discord.gg/discord-developers
[source]: https://github.com/discordjs/discord.js/tree/main/packages/util
[npm]: https://www.npmjs.com/package/@discordjs/util
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
[related-libs]: https://docs.discord.com/developers/developer-tools/community-resources#libraries
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md

Some files were not shown because too many files have changed in this diff Show More