Compare commits

...

71 Commits

Author SHA1 Message Date
iCrawl
e92d937bcc chore: release packages 2022-06-04 16:59:00 +02:00
iCrawl
091f1bd170 chore: deps 2022-06-04 16:49:47 +02:00
iCrawl
bd2cf20ecb chore: re-generate changelogs 2022-06-04 16:42:00 +02:00
iCrawl
8af916dba0 chore: remove accidental changelog for voice 2022-06-04 16:22:31 +02:00
iCrawl
781a6e013c chore: fix changelogs 2022-06-04 16:20:33 +02:00
iCrawl
2172a00c50 chore: remove unneeded actions dep 2022-06-04 15:45:18 +02:00
iCrawl
271b1c8e5d feat: actions for workflows 2022-06-04 15:34:40 +02:00
iCrawl
6b8ef20cb3 style: cleanup tests and tsup configs 2022-06-04 15:21:57 +02:00
iCrawl
a45bef4cad fix(proxy): add docs script 2022-06-04 13:34:57 +02:00
DD
1ba2d2a898 feat: @discordjs/proxy (#7925)
Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
2022-06-04 13:26:25 +02:00
iCrawl
e518c8a137 chore: update yarn 2022-06-04 13:13:52 +02:00
iCrawl
80cd4a4a43 ci: run assigning reviewers also on drafts 2022-06-04 12:59:54 +02:00
iCrawl
8a7cd10554 fix(builders): leftover invalid null type 2022-06-04 12:59:26 +02:00
iCrawl
251862ea57 chore: deps 2022-06-04 12:58:09 +02:00
Jiralite
2f03f9ad3f fix(GuildAuditLogs): Cache guild scheduled events (#7951) 2022-06-04 09:04:08 +02:00
Suneet Tipirneni
db81127886 chore: add external links for dapi-types (#7966) 2022-06-04 09:03:05 +02:00
Suneet Tipirneni
cdd2ba036a feat(guildChannelManager): add videoQualityMode option for create() (#7980)
Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
2022-06-04 09:02:21 +02:00
Suneet Tipirneni
97eaab35d7 feat(rest): add guild member banner cdn url (#7973) 2022-06-04 09:00:39 +02:00
Suneet Tipirneni
d60c464e61 types: add types to EventEmitter static methods (#7986) 2022-06-04 08:58:15 +02:00
Suneet Tipirneni
643dab3b1b refactor: clean up modal submissions (#7994)
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
2022-06-04 08:57:19 +02:00
Azarias B
68d5169f66 feat: Export types from interactions/slashCommands/mixins (#7942) 2022-05-25 10:04:20 +02:00
Suneet Tipirneni
7f4d411e73 chore: reexport all dapi-types (#7909) 2022-05-25 10:03:51 +02:00
Jeroen Claassens
636d4f263b chore: fix git cliff configs (#7948)
Co-authored-by: Vlad <kingdgrizzle@gmail.com>
2022-05-25 10:02:17 +02:00
Jiralite
fdeac9d9fb types: nullify guildScheduledEventUpdate's old parameter (#7955) 2022-05-21 02:02:57 +02:00
Suneet Tipirneni
adf461baf4 fix: make sure action row builders create djs builders (#7945) 2022-05-18 21:56:56 +02:00
DD
191510b7f8 fix(TextBasedChannel#bulkDelete): return deleted message (#7943) 2022-05-18 19:58:39 +02:00
DD
e2f5a4a494 chore: enable noUncheckedIndexAccess (#7931) 2022-05-18 19:56:42 +02:00
A. Román
993eb74475 chore(deps): update discord-api-types and /voice (#7934) 2022-05-18 19:54:35 +02:00
DD
dfe449c253 feat: REST#raw (#7929) 2022-05-17 16:31:19 +02:00
Jiralite
5e9b757a37 fix: Remove trailing invites on channel deletion (#7932) 2022-05-16 12:13:02 +02:00
Sam
28172ca7b5 fix(DataResolver): fix check for readable streams (#7928) 2022-05-16 12:12:34 +02:00
Vlad Frangu
7ce641d33a fix(SlashCommandBuilder): import Permissions correctly (#7921) 2022-05-14 22:47:26 +02:00
Khafra
e92b17d855 fix(REST): remove dom types (#7922) 2022-05-14 18:39:12 +02:00
Khafra
d1504f2ae1 fix: ok statusCode can be 200..299 (#7919) 2022-05-14 18:38:05 +02:00
Jiralite
b1a3aa97ea chore: bump discord-api-types (#7923) 2022-05-14 18:37:01 +02:00
iCrawl
d522320aa4 chore: update v13 changelog 2022-05-13 12:11:36 +02:00
Khafra
76694c1497 feat(EnumResolvers): remove Enumresolvers (#7876) 2022-05-13 11:35:08 +02:00
Vlad Frangu
de3f1573f0 feat(builders): add new command permissions v2 (#7861) 2022-05-13 11:33:40 +02:00
Synbulat Biishev
aed687b09f feat: move me to GuildMemberManager manager (#7669)
Co-authored-by: muchnameless <12682826+muchnameless@users.noreply.github.com>
Co-authored-by: Almeida <almeidx@pm.me>
2022-05-12 22:51:28 +02:00
n1ck_pro
e07b910e68 types: make CacheType generic more accurate for return values (#7868) 2022-05-12 22:50:31 +02:00
Khafra
d1ec8c37ff feat(rest): use undici (#7747)
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
Co-authored-by: ckohen <chaikohen@gmail.com>
2022-05-12 22:49:15 +02:00
Parbez
4515a1ea80 chore: bump shapeshift for faster object validation (#7914) 2022-05-12 12:09:50 +02:00
Vlad Frangu
c1b5e731da fix: add localizations for subcommand builders and option choices (#7862) 2022-05-12 10:32:27 +02:00
Suneet Tipirneni
64bdf53116 types(guildScheduledEvent): Mark entityMetadata as nullable (#7908) 2022-05-12 10:30:26 +02:00
Ben
d308c66eec docs(InteractionResponses): replace outdated Embed example for reply (#7875) 2022-05-12 10:29:44 +02:00
Jiralite
dfd9eb20b2 refactor(ThreadChannel): Remove MAX helper from threads (#7846) 2022-05-12 10:29:21 +02:00
Ryan Munro
19eaed6390 fix(AuditLog): default changes to empty array (#7880) 2022-05-12 10:26:07 +02:00
SpaceEEC
4ba0f56b6a feat(VoiceChannel): add support for text in voice (#6921) 2022-05-02 09:39:14 +02:00
advaith
5eeef3f708 types(discord.js): export missing enums (#7864) 2022-05-02 09:37:46 +02:00
with-heart
679dcda970 feat(REST): enable setting default authPrefix (#7853) 2022-04-28 13:08:02 +02:00
newracket
df64d3ea38 fix(Util): flatten ignoring certain fields (#7773) 2022-04-26 00:45:52 +02:00
Jiralite
6239d83c4d fix: possibly missing (#7829) 2022-04-26 00:31:21 +02:00
Khafra
0c18dab128 fix: endReason not being properly set in base Collector (#7833)
* fix: `endReason` not being properly set in base Collector

* types: add _endReason type
2022-04-26 00:31:12 +02:00
Almeida
ece628986c types: fix return type of toString() on channels (#7836)
* types(DMChannel): `toString()` returns a `UserMention`

* test: add more tests
2022-04-26 00:31:05 +02:00
Jiralite
f4ccc6772c docs: require parameter (#7838) 2022-04-26 00:30:58 +02:00
Almeida
5ba7740fcf refactor(Activity): remove undocumented properties (#7844) 2022-04-26 00:30:45 +02:00
Almeida
bfeaf856f7 types(Message#activity): make partyId optional and use enum for type (#7845) 2022-04-26 00:30:13 +02:00
FIRE
361709332b fix(SelectMenuBuilder): options array (#7826) 2022-04-22 20:50:36 +02:00
Rodry
61a44c509c docs(ApplicationCommand): fix and improve localization docs (#7804) 2022-04-21 19:07:49 +02:00
Hyro
4ac91c61d0 fix(Activity): platform type (#7805) 2022-04-21 19:07:18 +02:00
Hyro
4972bd87c1 fix(ApplicationCommand): equal nameLocalizations and descriptionLocalizations (#7802) 2022-04-21 19:06:42 +02:00
Euan Caskie
a1b145e0ce makeContent should accept empty strings (#7764) 2022-04-21 18:57:00 +02:00
Suneet Tipirneni
29293d7bbb refactor: use arrays instead of rest parameters for builders (#7759) 2022-04-21 18:56:10 +02:00
Rodry
f22245e9d0 feat(SelectMenu): allow emojis in options and option constructors (#7797) 2022-04-21 18:55:51 +02:00
Suneet Tipirneni
585169f2f0 types: cleanup *Data type definitions (#7716) 2022-04-19 16:00:55 +02:00
Ryan Munro
ec8d87f932 feat(builders): improve embed errors and predicates (#7795)
* feat(builders): improve embed errors

* fix: use imageURLPredicates

Co-authored-by: Synbulat Biishev <arziksin@gmail.com>

Co-authored-by: Synbulat Biishev <arziksin@gmail.com>
2022-04-19 15:58:47 +02:00
n1ck_pro
440ac243ca types: fix BooleanCache never resolving to true (#7809)
* types: fix BooleanCache never resolving to true

* fix: broke other types by accident
2022-04-19 15:57:44 +02:00
Ryan Munro
c5fb548529 fix(InteractionResponses): use optional chaining on nullable property (#7812)
* fix(InteractionResponses): use optional chaining on nullable property

* fix: one more spot
2022-04-19 15:56:45 +02:00
Almeida
a6d9ce57c6 types(CommandInteraction): add awaitModalSubmit (#7811) 2022-04-19 15:56:22 +02:00
Rodry
08574763eb types(ThreadChannel): fix autoArchiveDuration types (#7816) 2022-04-19 15:54:54 +02:00
iCrawl
9ef7ffdc6d ci: use proper regex escapes 2022-04-17 20:07:19 +02:00
204 changed files with 7001 additions and 5673 deletions

View File

@@ -15,6 +15,7 @@ body:
- builders
- collection
- rest
- proxy
- voice
validations:
required: true

View File

@@ -17,6 +17,7 @@ body:
- builders
- collection
- rest
- proxy
- voice
validations:
required: true

View File

@@ -5,3 +5,4 @@ reviewers:
- kyranet
- vladfrangu
numberOfReviewers: 0
runOnDraft: true

4
.github/labeler.yml vendored
View File

@@ -14,6 +14,10 @@ chore:
- packages/discord.js/*
- packages/discord.js/**/*
'packages:proxy':
- packages/proxy/*
- packages/proxy/**/*
'packages:rest':
- packages/rest/*
- packages/rest/**/*

2
.github/labels.yml vendored
View File

@@ -50,6 +50,8 @@
color: 'fbca04'
- name: 'packages:discord.js'
color: 'fbca04'
- name: 'packages:proxy'
color: 'fbca04'
- name: 'packages:rest'
color: 'fbca04'
- name: 'packages:voice'

View File

@@ -62,13 +62,38 @@ jobs:
max-parallel: 1
fail-fast: false
matrix:
package: ['builders', 'collection', 'discord.js', 'rest', 'voice']
package: ['builders', 'collection', 'discord.js', 'proxy', 'rest', 'voice']
runs-on: ubuntu-latest
env:
BRANCH_NAME: ${{ needs.build.outputs.BRANCH_NAME }}
BRANCH_OR_TAG: ${{ needs.build.outputs.BRANCH_OR_TAG }}
SHA: ${{ needs.build.outputs.SHA }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install node.js v16
uses: actions/setup-node@v2
with:
node-version: 16
cache: 'yarn'
cache-dependency-path: yarn.lock
- name: Turbo cache
id: turbo-cache
uses: actions/cache@v2
with:
path: .turbo
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
turbo-${{ github.job }}-${{ github.ref_name }}-
- name: Install dependencies
run: yarn --immutable
- name: Build actions
run: yarn build --cache-dir=".turbo"
- name: Download artifacts
uses: actions/download-artifact@v2
with:
@@ -82,29 +107,18 @@ jobs:
token: ${{ secrets.DJS_DOCS }}
path: 'out'
- name: 'Extract package from tag'
- name: Extract package and semver from tag
if: env.BRANCH_OR_TAG == 'tag'
id: package-name
uses: frabert/replace-string-action@v2.0
id: extract-tag
uses: ./packages/actions/src/formatTag
with:
pattern: '(^@.*\\/(?<package>.*)@v?)?(?<semver>\d+.\d+.\d+)-?.*'
string: ${{ env.BRANCH_NAME }}
replace-with: '$<package>'
- name: 'Extract semver from tag'
if: env.BRANCH_OR_TAG == 'tag'
id: semver
uses: frabert/replace-string-action@v2.0
with:
pattern: '(^@.*\\/(?<package>.*)@v?)?(?<semver>\d+.\d+.\d+)-?.*'
string: ${{ env.BRANCH_NAME }}
replace-with: '$<semver>'
tag: ${{ env.BRANCH_NAME }}
- name: Move docs to correct directory
if: env.BRANCH_OR_TAG == 'tag'
env:
PACKAGE: ${{ steps.package-name.outputs.replaced }}
SEMVER: ${{ steps.semver.outputs.replaced }}
PACKAGE: ${{ steps.extract-tag.outputs.package }}
SEMVER: ${{ steps.extract-tag.outputs.semver }}
run: |
mkdir -p out/${PACKAGE}
mv docs/${PACKAGE}/docs/docs.json out/${PACKAGE}/${SEMVER}.json

View File

@@ -12,5 +12,5 @@ jobs:
sync-labels: true
- name: Automatically assign reviewers
if: ${{ github.event.action == 'opened' }}
uses: kentaro-m/auto-assign-action@v1.1.2
if: github.event.action == 'opened'
uses: kentaro-m/auto-assign-action@v1.2.1

View File

@@ -16,6 +16,8 @@ jobs:
folder: 'collection'
- package: 'discord.js'
folder: 'discord.js'
- package: '@discordjs/proxy'
folder: 'proxy'
- package: '@discordjs/rest'
folder: 'rest'
- package: '@discordjs/voice'

View File

@@ -5,5 +5,8 @@
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": false
},
"files.exclude": {
"**/node_modules": true
}
}

File diff suppressed because one or more lines are too long

786
.yarn/releases/yarn-3.2.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: "@yarnpkg/plugin-version"
yarnPath: .yarn/releases/yarn-3.1.1.cjs
yarnPath: .yarn/releases/yarn-3.2.1.cjs

View File

@@ -38,13 +38,13 @@
},
"homepage": "https://discord.js.org",
"devDependencies": {
"@commitlint/cli": "^16.2.3",
"@commitlint/config-angular": "^16.2.3",
"@commitlint/cli": "^17.0.2",
"@commitlint/config-angular": "^17.0.0",
"@favware/npm-deprecate": "^1.0.4",
"conventional-changelog-cli": "^2.2.2",
"husky": "^7.0.4",
"husky": "^8.0.1",
"prettier": "^2.6.2",
"turbo": "^1.2.4"
"turbo": "^1.2.16"
},
"engines": {
"node": ">=16.9.0"
@@ -52,5 +52,5 @@
"workspaces": [
"packages/*"
],
"packageManager": "yarn@3.1.1"
"packageManager": "yarn@3.2.1"
}

View File

@@ -0,0 +1,12 @@
{
"root": true,
"extends": "marine/prettier/node",
"parserOptions": {
"project": "./tsconfig.eslint.json",
"extraFileExtensions": [".mjs"]
},
"ignorePatterns": ["**/dist/*"],
"env": {
"jest": true
}
}

29
packages/actions/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# Packages
node_modules/
# Log files
logs/
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Env
.env
# Dist
dist/
typings/
docs/**/*
!docs/index.yml
!docs/README.md
!docs/examples/
!docs/examples/*.md
# Miscellaneous
.tmp/
coverage/

View File

@@ -0,0 +1,8 @@
# Autogenerated
CHANGELOG.md
.turbo
dist/
docs/**/*
!docs/index.yml
!docs/README.md
coverage/

View File

@@ -0,0 +1,8 @@
{
"printWidth": 120,
"useTabs": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"endOfLine": "lf"
}

191
packages/actions/LICENSE Normal file
View File

@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2021 Noel Buechler
Copyright 2021 Vlad Frangu
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,53 @@
<div align="center">
<br />
<p>
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
</p>
<br />
<p>
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/v/@discordjs/builders.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/dt/@discordjs/builders.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
<a href="https://codecov.io/gh/discordjs/builders"><img src="https://codecov.io/gh/discordjs/builders/branch/main/graph/badge.svg" alt="Code coverage" /></a>
</p>
</div>
## Installation
**Node.js 16.9.0 or newer is required.**
```sh-session
npm install @discordjs/builders
yarn add @discordjs/builders
pnpm add @discordjs/builders
```
## Examples
Here are some examples for the builders and utilities you can find in this package:
- [Slash Command Builders](https://github.com/discordjs/discord.js/blob/main/packages/builders/docs/examples/Slash%20Command%20Builders.md)
## Links
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
- [Documentation](https://discord.js.org/#/docs/builders)
- [Guide](https://discordjs.guide/) ([source](https://github.com/discordjs/guide))
See also the [Update Guide](https://discordjs.guide/additional-info/changes-in-v13.html), including updated and removed items in the library.
- [discord.js Discord server](https://discord.gg/djs)
- [Discord API Discord server](https://discord.gg/discord-api)
- [GitHub](https://github.com/discordjs/discord.js/tree/main/packages/builders)
- [npm](https://www.npmjs.com/package/@discordjs/builders)
- [Related libraries](https://discord.com/developers/docs/topics/community-resources#libraries)
## Contributing
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discord.js.org/#/docs/builders).
See [the contribution guide](https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md) if you'd like to submit a PR.
## Help
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
nudge in the right direction, please don't hesitate to join our official [discord.js Server](https://discord.gg/djs).

View File

@@ -0,0 +1,21 @@
import { formatTag } from '../src';
describe('Format Tag', () => {
test('GIVEN tag with a prefix THEN format tag to not contain the prefix', () => {
expect(formatTag('@discordjs/rest@0.4.0')).toEqual({ package: 'rest', semver: '0.4.0' });
expect(formatTag('@discordjs/collection@0.6.0')).toEqual({ package: 'collection', semver: '0.6.0' });
expect(formatTag('@discordjs/proxy@0.1.0')).toEqual({ package: 'proxy', semver: '0.1.0' });
expect(formatTag('@discordjs/builders@0.13.0')).toEqual({ package: 'builders', semver: '0.13.0' });
expect(formatTag('@discordjs/voice@0.9.0')).toEqual({ package: 'voice', semver: '0.9.0' });
});
test('GIVEN tag with no prefix THEN return tag', () => {
expect(formatTag('13.5.1')).toEqual({ semver: '13.5.1' });
expect(formatTag('13.7.0')).toEqual({ package: undefined, semver: '13.7.0' });
});
test('GIVEN no or invalid tag THEN return null', () => {
expect(formatTag('')).toEqual(null);
expect(formatTag('abc')).toEqual(null);
});
});

View File

@@ -0,0 +1,18 @@
/**
* @type {import('@babel/core').TransformOptions}
*/
module.exports = {
parserOpts: { strictMode: true },
sourceMaps: 'inline',
presets: [
[
'@babel/preset-env',
{
targets: { node: 'current' },
modules: 'commonjs',
},
],
'@babel/preset-typescript',
],
plugins: ['babel-plugin-transform-typescript-metadata', ['@babel/plugin-proposal-decorators', { legacy: true }]],
};

View File

@@ -0,0 +1,10 @@
coverage:
status:
project:
default:
target: 70%
threshold: 5%
patch:
default:
target: 70%
threshold: 5%

View File

@@ -0,0 +1,19 @@
/**
* @type {import('@jest/types').Config.InitialOptions}
*/
module.exports = {
testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
testEnvironment: 'node',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts'],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'clover'],
coverageThreshold: {
global: {
branches: 70,
lines: 70,
statements: 70,
},
},
coveragePathIgnorePatterns: ['src/index.ts', 'src/formatTag/index.ts'],
};

View File

@@ -0,0 +1,73 @@
{
"name": "@discordjs/actions",
"version": "0.1.0-dev",
"description": "A set of actions that we use for our workflows",
"private": true,
"scripts": {
"build": "tsup",
"test": "jest --pass-with-no-tests",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"directories": {
"lib": "src",
"test": "__tests__"
},
"files": [
"dist"
],
"contributors": [
"Crawl <icrawltogo@gmail.com>"
],
"license": "Apache-2.0",
"keywords": [
"api",
"bot",
"client",
"node",
"discordjs"
],
"repository": {
"type": "git",
"url": "https://github.com/discordjs/discord.js.git"
},
"bugs": {
"url": "https://github.com/discordjs/discord.js/issues"
},
"homepage": "https://discord.js.org",
"dependencies": {
"@actions/core": "^1.8.2",
"tslib": "^2.4.0"
},
"devDependencies": {
"@babel/core": "^7.18.2",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/preset-env": "^7.18.2",
"@babel/preset-typescript": "^7.17.12",
"@types/jest": "^28.1.0",
"@types/node": "^16.11.38",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"babel-plugin-transform-typescript-metadata": "^0.3.2",
"eslint": "^8.17.0",
"eslint-config-marine": "^9.4.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"jest": "^28.1.0",
"prettier": "^2.6.2",
"tsup": "^6.0.1",
"typedoc": "^0.22.17",
"typescript": "^4.7.3"
},
"engines": {
"node": ">=16.9.0"
}
}

View File

@@ -0,0 +1,14 @@
name: 'Format Tag'
description: 'Formats a git tag to remove potentially prefixes'
inputs:
tag:
description: 'The input tag'
required: true
outputs:
package:
description: 'The package string that was extracted from this tag'
semver:
description: 'The semver string that was extracted from this tag'
runs:
using: node16
main: ../../dist/formatTag/index.mjs

View File

@@ -0,0 +1,9 @@
export function formatTag(tag: string) {
const parsed = /(^@.*\/(?<package>.*)@v?)?(?<semver>\d+.\d+.\d+)-?.*/.exec(tag);
if (parsed?.groups) {
return parsed.groups;
}
return null;
}

View File

@@ -0,0 +1,10 @@
import { getInput, setOutput } from '@actions/core';
import { formatTag } from './formatTag';
const tag = getInput('tag', { required: true });
const parsed = formatTag(tag);
if (parsed?.groups) {
setOutput('package', parsed.package);
setOutput('semver', parsed.semver);
}

View File

@@ -0,0 +1 @@
export * from './formatTag/formatTag';

View File

@@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowJs": true
},
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.mjs",
"**/*.jsx",
"**/*.test.ts",
"**/*.test.js",
"**/*.test.mjs",
"**/*.spec.ts",
"**/*.spec.js",
"**/*.spec.mjs"
],
"exclude": []
}

View File

@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,13 @@
import { defineConfig } from 'tsup';
export default defineConfig({
clean: true,
dts: true,
entryPoints: ['src/index.ts', 'src/formatTag/index.ts'],
format: ['esm'],
minify: true,
skipNodeModulesBundle: false,
noExternal: ['@actions/core'],
sourcemap: true,
target: 'es2021',
});

View File

@@ -2,7 +2,29 @@
All notable changes to this project will be documented in this file.
# [0.13.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.12.0...@discordjs/builders@0.13.0) (2022-04-17)
# [@discordjs/builders@0.14.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.13.0...@discordjs/builders@0.14.0) - (2022-06-04)
## Bug Fixes
- **builders:** Leftover invalid null type ([8a7cd10](https://github.com/discordjs/discord.js/commit/8a7cd10554a2a71cd2fe7f6a177b5f4f43464348))
- **SlashCommandBuilder:** Import `Permissions` correctly (#7921) ([7ce641d](https://github.com/discordjs/discord.js/commit/7ce641d33a4af6586d5e7beffbe7d38619dcf1a2))
- Add localizations for subcommand builders and option choices (#7862) ([c1b5e73](https://github.com/discordjs/discord.js/commit/c1b5e731daa9cbbfca03a046e47cb1221ee1ed7c))
## Features
- Export types from `interactions/slashCommands/mixins` (#7942) ([68d5169](https://github.com/discordjs/discord.js/commit/68d5169f66c96f8fe5be17a1c01cdd5155607ab2))
- **builders:** Add new command permissions v2 (#7861) ([de3f157](https://github.com/discordjs/discord.js/commit/de3f1573f07dda294cc0fbb1ca4b659eb2388a12))
- **builders:** Improve embed errors and predicates (#7795) ([ec8d87f](https://github.com/discordjs/discord.js/commit/ec8d87f93272cc9987f9613735c0361680c4ed1e))
## Refactor
- Use arrays instead of rest parameters for builders (#7759) ([29293d7](https://github.com/discordjs/discord.js/commit/29293d7bbb5ed463e52e5a5853817e5a09cf265b))
## Styling
- Cleanup tests and tsup configs ([6b8ef20](https://github.com/discordjs/discord.js/commit/6b8ef20cb3af5b5cfd176dd0aa0a1a1e98551629))
# [@discordjs/builders@0.13.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.12.0...@discordjs/builders@0.13.0) - (2022-04-17)
## Bug Fixes
@@ -15,13 +37,11 @@ All notable changes to this project will be documented in this file.
- Fix some typos (#7393) ([92a04f4](https://github.com/discordjs/discord.js/commit/92a04f4d98f6c6760214034cc8f5a1eaa78893c7))
- **builders:** Make type optional in constructor (#7391) ([4abb28c](https://github.com/discordjs/discord.js/commit/4abb28c0a1256c57a60369a6b8ec9e98c265b489))
- Don't create new instances of builders classes (#7343) ([d6b56d0](https://github.com/discordjs/discord.js/commit/d6b56d0080c4c5f8ace731f1e8bcae0c9d3fb5a5))
- **builders:** Dont export `Button` component stuff twice (#7289) ([86d9d06](https://github.com/discordjs/discord.js/commit/86d9d0674347c08d056cd054cb4ce4253195bf94))
## Documentation
- Completely fix builders example link (#7543) ([1a14c0c](https://github.com/discordjs/discord.js/commit/1a14c0ca562ea173d363a770a0437209f461fd23))
- Add slash command builders example, fixes #7338 (#7339) ([3ae6f3c](https://github.com/discordjs/discord.js/commit/3ae6f3c313091151245d6e6b52337b459ecfc765))
- **SlashCommandSubcommands:** Updating old links from Discord developer portal (#7224) ([bd7a6f2](https://github.com/discordjs/discord.js/commit/bd7a6f265212624199fb0b2ddc8ece39759c63de))
## Features
@@ -33,7 +53,6 @@ All notable changes to this project will be documented in this file.
- **builders:** Add attachment command option type (#7203) ([ae0f35f](https://github.com/discordjs/discord.js/commit/ae0f35f51d68dfa5a7dc43d161ef9365171debdb))
- **components:** Add unsafe message component builders (#7387) ([6b6222b](https://github.com/discordjs/discord.js/commit/6b6222bf513d1ee8cd98fba0ad313def560b864f))
- **embed:** Add setFields (#7322) ([bcc5cda](https://github.com/discordjs/discord.js/commit/bcc5cda8a902ddb28c7e3578e0f29b4272832624))
- Add components to /builders (#7195) ([2bb40fd](https://github.com/discordjs/discord.js/commit/2bb40fd767cf5918e3ba422ff73082734bfa05b0))
## Refactor
@@ -55,9 +74,8 @@ All notable changes to this project will be documented in this file.
## Typings
- Fix regressions (#7649) ([5748dbe](https://github.com/discordjs/discord.js/commit/5748dbe08783beb80c526de38ccd105eb0e82664))
- Make `required` a boolean (#7307) ([c10afea](https://github.com/discordjs/discord.js/commit/c10afeadc702ab98bec5e077b3b92494a9596f9c))
# [0.12.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.11.0...@discordjs/builders@0.12.0) (2021-12-08)
# [@discordjs/builders@0.12.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.11.0...@discordjs/builders@0.12.0) - (2022-01-24)
## Bug Fixes

View File

@@ -44,8 +44,8 @@ const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent>
describe('Action Row Components', () => {
describe('Assertion Tests', () => {
test('GIVEN valid components THEN do not throw', () => {
expect(() => new ActionRowBuilder().addComponents(new ButtonBuilder())).not.toThrowError();
expect(() => new ActionRowBuilder().setComponents(new ButtonBuilder())).not.toThrowError();
expect(() => new ActionRowBuilder().addComponents([new ButtonBuilder()])).not.toThrowError();
expect(() => new ActionRowBuilder().setComponents([new ButtonBuilder()])).not.toThrowError();
});
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
@@ -128,13 +128,13 @@ describe('Action Row Components', () => {
.setCustomId('1234')
.setMaxValues(10)
.setMinValues(12)
.setOptions(
.setOptions([
new SelectMenuOptionBuilder().setLabel('one').setValue('one'),
new SelectMenuOptionBuilder().setLabel('two').setValue('two'),
);
]);
expect(new ActionRowBuilder().addComponents(button).toJSON()).toEqual(rowWithButtonData);
expect(new ActionRowBuilder().addComponents(selectMenu).toJSON()).toEqual(rowWithSelectMenuData);
expect(new ActionRowBuilder().addComponents([button]).toJSON()).toEqual(rowWithButtonData);
expect(new ActionRowBuilder().addComponents([selectMenu]).toJSON()).toEqual(rowWithSelectMenuData);
});
});
});

View File

@@ -43,29 +43,31 @@ describe('Select Menu Components', () => {
.setDefault(true)
.setEmoji({ name: 'test' })
.setDescription('description');
expect(() => selectMenu().addOptions(option)).not.toThrowError();
expect(() => selectMenu().setOptions(option)).not.toThrowError();
expect(() => selectMenu().setOptions({ label: 'test', value: 'test' })).not.toThrowError();
expect(() => selectMenu().addOptions([option])).not.toThrowError();
expect(() => selectMenu().setOptions([option])).not.toThrowError();
expect(() => selectMenu().setOptions([{ label: 'test', value: 'test' }])).not.toThrowError();
expect(() =>
selectMenu().addOptions({
label: 'test',
value: 'test',
emoji: {
id: '123',
name: 'test',
animated: true,
selectMenu().addOptions([
{
label: 'test',
value: 'test',
emoji: {
id: '123',
name: 'test',
animated: true,
},
},
}),
]),
).not.toThrowError();
const options = new Array<APISelectMenuOption>(25).fill({ label: 'test', value: 'test' });
expect(() => selectMenu().addOptions(...options)).not.toThrowError();
expect(() => selectMenu().setOptions(...options)).not.toThrowError();
expect(() => selectMenu().addOptions(options)).not.toThrowError();
expect(() => selectMenu().setOptions(options)).not.toThrowError();
expect(() =>
selectMenu()
.addOptions({ label: 'test', value: 'test' })
.addOptions(...new Array<APISelectMenuOption>(24).fill({ label: 'test', value: 'test' })),
.addOptions([{ label: 'test', value: 'test' }])
.addOptions(new Array<APISelectMenuOption>(24).fill({ label: 'test', value: 'test' })),
).not.toThrowError();
});
@@ -77,24 +79,24 @@ describe('Select Menu Components', () => {
expect(() => selectMenu().setDisabled(0)).toThrowError();
expect(() => selectMenu().setPlaceholder(longStr)).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions({ label: 'test' })).toThrowError();
expect(() => selectMenu().addOptions({ label: longStr, value: 'test' })).toThrowError();
expect(() => selectMenu().addOptions({ value: longStr, label: 'test' })).toThrowError();
expect(() => selectMenu().addOptions({ label: 'test', value: 'test', description: longStr })).toThrowError();
expect(() => selectMenu().addOptions([{ label: 'test' }])).toThrowError();
expect(() => selectMenu().addOptions([{ label: longStr, value: 'test' }])).toThrowError();
expect(() => selectMenu().addOptions([{ value: longStr, label: 'test' }])).toThrowError();
expect(() => selectMenu().addOptions([{ label: 'test', value: 'test', description: longStr }])).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions({ label: 'test', value: 'test', default: 100 })).toThrowError();
expect(() => selectMenu().addOptions([{ label: 'test', value: 'test', default: 100 }])).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions({ value: 'test' })).toThrowError();
expect(() => selectMenu().addOptions([{ value: 'test' }])).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions({ default: true })).toThrowError();
expect(() => selectMenu().addOptions([{ default: true }])).toThrowError();
const tooManyOptions = new Array<APISelectMenuOption>(26).fill({ label: 'test', value: 'test' });
expect(() => selectMenu().setOptions(...tooManyOptions)).toThrowError();
expect(() => selectMenu().setOptions(tooManyOptions)).toThrowError();
expect(() =>
selectMenu()
.addOptions({ label: 'test', value: 'test' })
.addOptions(...tooManyOptions),
.addOptions([{ label: 'test', value: 'test' }])
.addOptions(tooManyOptions),
).toThrowError();
expect(() => {
@@ -112,7 +114,7 @@ describe('Select Menu Components', () => {
test('GIVEN valid JSON input THEN valid JSON history is correct', () => {
expect(
new SelectMenuBuilder(selectMenuDataWithoutOptions)
.addOptions(new SelectMenuOptionBuilder(selectMenuOptionData))
.addOptions([new SelectMenuOptionBuilder(selectMenuOptionData)])
.toJSON(),
).toEqual(selectMenuData);
expect(new SelectMenuOptionBuilder(selectMenuOptionData).toJSON()).toEqual(selectMenuOptionData);

View File

@@ -1,3 +1,4 @@
import { PermissionFlagsBits } from 'discord-api-types/v10';
import { ContextMenuCommandAssertions, ContextMenuCommandBuilder } from '../../src/index';
const getBuilder = () => new ContextMenuCommandBuilder();
@@ -85,5 +86,58 @@ describe('Context Menu Commands', () => {
expect(() => getBuilder().setName('foo').setDefaultPermission(false)).not.toThrowError();
});
});
describe('Context menu command localizations', () => {
const expectedSingleLocale = { 'en-US': 'foobar' };
const expectedMultipleLocales = {
...expectedSingleLocale,
bg: 'test',
};
test('GIVEN valid name localizations THEN does not throw error', () => {
expect(() => getBuilder().setNameLocalization('en-US', 'foobar')).not.toThrowError();
expect(() => getBuilder().setNameLocalizations({ 'en-US': 'foobar' })).not.toThrowError();
});
test('GIVEN invalid name localizations THEN does throw error', () => {
// @ts-expect-error
expect(() => getBuilder().setNameLocalization('en-U', 'foobar')).toThrowError();
// @ts-expect-error
expect(() => getBuilder().setNameLocalizations({ 'en-U': 'foobar' })).toThrowError();
});
test('GIVEN valid name localizations THEN valid data is stored', () => {
expect(getBuilder().setNameLocalization('en-US', 'foobar').name_localizations).toEqual(expectedSingleLocale);
expect(getBuilder().setNameLocalizations({ 'en-US': 'foobar', bg: 'test' }).name_localizations).toEqual(
expectedMultipleLocales,
);
expect(getBuilder().setNameLocalizations(null).name_localizations).toBeNull();
expect(getBuilder().setNameLocalization('en-US', null).name_localizations).toEqual({
'en-US': null,
});
});
});
describe('permissions', () => {
test('GIVEN valid permission string THEN does not throw error', () => {
expect(() => getBuilder().setDefaultMemberPermissions('1')).not.toThrowError();
});
test('GIVEN valid permission bitfield THEN does not throw error', () => {
expect(() =>
getBuilder().setDefaultMemberPermissions(PermissionFlagsBits.AddReactions | PermissionFlagsBits.AttachFiles),
).not.toThrowError();
});
test('GIVEN null permissions THEN does not throw error', () => {
expect(() => getBuilder().setDefaultMemberPermissions(null)).not.toThrowError();
});
test('GIVEN invalid inputs THEN does throw error', () => {
expect(() => getBuilder().setDefaultMemberPermissions('1.1')).toThrowError();
expect(() => getBuilder().setDefaultMemberPermissions(1.1)).toThrowError();
});
});
});
});

View File

@@ -1,4 +1,4 @@
import { APIApplicationCommandOptionChoice, ChannelType } from 'discord-api-types/v10';
import { APIApplicationCommandOptionChoice, ChannelType, PermissionFlagsBits } from 'discord-api-types/v10';
import {
SlashCommandAssertions,
SlashCommandBooleanOption,
@@ -433,7 +433,7 @@ describe('Slash Commands', () => {
expect(() => getBuilder().setNameLocalizations({ 'en-US': 'foobar' })).not.toThrowError();
});
test('GIVEN valid name localizations THEN does not throw error', () => {
test('GIVEN invalid name localizations THEN does throw error', () => {
// @ts-expect-error
expect(() => getBuilder().setNameLocalization('en-U', 'foobar')).toThrowError();
// @ts-expect-error
@@ -456,7 +456,7 @@ describe('Slash Commands', () => {
expect(() => getBuilder().setDescriptionLocalizations({ 'en-US': 'foobar' })).not.toThrowError();
});
test('GIVEN valid description localizations THEN does not throw error', () => {
test('GIVEN invalid description localizations THEN does throw error', () => {
// @ts-expect-error
expect(() => getBuilder().setDescriptionLocalization('en-U', 'foobar')).toThrowError();
// @ts-expect-error
@@ -476,5 +476,27 @@ describe('Slash Commands', () => {
});
});
});
describe('permissions', () => {
test('GIVEN valid permission string THEN does not throw error', () => {
expect(() => getBuilder().setDefaultMemberPermissions('1')).not.toThrowError();
});
test('GIVEN valid permission bitfield THEN does not throw error', () => {
expect(() =>
getBuilder().setDefaultMemberPermissions(PermissionFlagsBits.AddReactions | PermissionFlagsBits.AttachFiles),
).not.toThrowError();
});
test('GIVEN null permissions THEN does not throw error', () => {
expect(() => getBuilder().setDefaultMemberPermissions(null)).not.toThrowError();
});
test('GIVEN invalid inputs THEN does throw error', () => {
expect(() => getBuilder().setDefaultMemberPermissions('1.1')).toThrowError();
expect(() => getBuilder().setDefaultMemberPermissions(1.1)).toThrowError();
});
});
});
});

View File

@@ -48,15 +48,11 @@ describe('Modals', () => {
test('GIVEN valid fields THEN builder does not throw', () => {
expect(() =>
modal().setTitle('test').setCustomId('foobar').setComponents(new ActionRowBuilder()),
modal().setTitle('test').setCustomId('foobar').setComponents([new ActionRowBuilder()]),
).not.toThrowError();
});
test('GIVEN invalid fields THEN builder does throw', () => {
expect(() =>
// @ts-expect-error
modal().setTitle('test').setCustomId('foobar').setComponents([new ActionRowBuilder()]).toJSON(),
).toThrowError();
expect(() => modal().setTitle('test').setCustomId('foobar').toJSON()).toThrowError();
// @ts-expect-error
expect(() => modal().setTitle('test').setCustomId(42).toJSON()).toThrowError();
@@ -87,11 +83,11 @@ describe('Modals', () => {
modal()
.setTitle(modalData.title)
.setCustomId('custom id')
.setComponents(
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
.setComponents([
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents([
new TextInputBuilder().setCustomId('custom id').setLabel('label').setStyle(TextInputStyle.Paragraph),
),
)
]),
])
.toJSON(),
).toEqual(modalData);
});

View File

@@ -13,13 +13,13 @@ describe('Embed', () => {
footer: { text: alpha },
});
expect(embedLength(embed.data)).toBe(alpha.length * 6);
expect(embedLength(embed.data)).toEqual(alpha.length * 6);
});
test('GIVEN an embed with zero characters THEN returns amount of characters', () => {
const embed = new EmbedBuilder();
expect(embedLength(embed.data)).toBe(0);
expect(embedLength(embed.data)).toEqual(0);
});
});
@@ -322,25 +322,28 @@ describe('Embed', () => {
test('GIVEN an embed using Embed#addFields THEN returns valid toJSON data', () => {
const embed = new EmbedBuilder();
embed.addFields({ name: 'foo', value: 'bar' });
embed.addFields([{ name: 'foo', value: 'bar' }]);
expect(embed.toJSON()).toStrictEqual({
fields: [{ name: 'foo', value: 'bar', inline: undefined }],
fields: [{ name: 'foo', value: 'bar' }],
});
});
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data', () => {
const embed = new EmbedBuilder();
embed.addFields({ name: 'foo', value: 'bar' }, { name: 'foo', value: 'baz' });
embed.addFields([
{ name: 'foo', value: 'bar' },
{ name: 'foo', value: 'baz' },
]);
expect(embed.spliceFields(0, 1).toJSON()).toStrictEqual({
fields: [{ name: 'foo', value: 'baz', inline: undefined }],
fields: [{ name: 'foo', value: 'baz' }],
});
});
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data', () => {
const embed = new EmbedBuilder();
embed.addFields(...Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
embed.addFields(Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
expect(() =>
embed.spliceFields(0, 3, ...Array.from({ length: 5 }, () => ({ name: 'foo', value: 'bar' }))),
@@ -349,7 +352,7 @@ describe('Embed', () => {
test('GIVEN an embed using Embed#spliceFields that adds additional fields resulting in fields > 25 THEN throws error', () => {
const embed = new EmbedBuilder();
embed.addFields(...Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
embed.addFields(Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
expect(() =>
embed.spliceFields(0, 3, ...Array.from({ length: 8 }, () => ({ name: 'foo', value: 'bar' }))),
@@ -360,25 +363,21 @@ describe('Embed', () => {
const embed = new EmbedBuilder();
expect(() =>
embed.setFields(...Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' }))),
embed.setFields(Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' }))),
).not.toThrowError();
});
test('GIVEN an embed using Embed#setFields that sets more than 25 fields THEN throws error', () => {
const embed = new EmbedBuilder();
expect(() =>
embed.setFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
).toThrowError();
expect(() => embed.setFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
});
describe('GIVEN invalid field amount THEN throws error', () => {
test('', () => {
const embed = new EmbedBuilder();
expect(() =>
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
).toThrowError();
expect(() => embed.addFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
});
});
@@ -386,7 +385,7 @@ describe('Embed', () => {
test('', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields({ name: '', value: 'bar' })).toThrowError();
expect(() => embed.addFields([{ name: '', value: 'bar' }])).toThrowError();
});
});
@@ -394,7 +393,7 @@ describe('Embed', () => {
test('', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
expect(() => embed.addFields([{ name: 'a'.repeat(257), value: 'bar' }])).toThrowError();
});
});
@@ -402,7 +401,7 @@ describe('Embed', () => {
test('', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1025) })).toThrowError();
expect(() => embed.addFields([{ name: '', value: 'a'.repeat(1025) }])).toThrowError();
});
});
});

View File

@@ -22,75 +22,75 @@ import {
describe('Message formatters', () => {
describe('codeBlock', () => {
test('GIVEN "discord.js" with no language THEN returns "```\\ndiscord.js```"', () => {
expect<'```\ndiscord.js```'>(codeBlock('discord.js')).toBe('```\ndiscord.js```');
expect<'```\ndiscord.js```'>(codeBlock('discord.js')).toEqual('```\ndiscord.js```');
});
test('GIVEN "discord.js" with "js" as language THEN returns "```js\\ndiscord.js```"', () => {
expect<'```js\ndiscord.js```'>(codeBlock('js', 'discord.js')).toBe('```js\ndiscord.js```');
expect<'```js\ndiscord.js```'>(codeBlock('js', 'discord.js')).toEqual('```js\ndiscord.js```');
});
});
describe('inlineCode', () => {
test('GIVEN "discord.js" THEN returns "`discord.js`"', () => {
expect<'`discord.js`'>(inlineCode('discord.js')).toBe('`discord.js`');
expect<'`discord.js`'>(inlineCode('discord.js')).toEqual('`discord.js`');
});
});
describe('italic', () => {
test('GIVEN "discord.js" THEN returns "_discord.js_"', () => {
expect<'_discord.js_'>(italic('discord.js')).toBe('_discord.js_');
expect<'_discord.js_'>(italic('discord.js')).toEqual('_discord.js_');
});
});
describe('bold', () => {
test('GIVEN "discord.js" THEN returns "**discord.js**"', () => {
expect<'**discord.js**'>(bold('discord.js')).toBe('**discord.js**');
expect<'**discord.js**'>(bold('discord.js')).toEqual('**discord.js**');
});
});
describe('underscore', () => {
test('GIVEN "discord.js" THEN returns "__discord.js__"', () => {
expect<'__discord.js__'>(underscore('discord.js')).toBe('__discord.js__');
expect<'__discord.js__'>(underscore('discord.js')).toEqual('__discord.js__');
});
});
describe('strikethrough', () => {
test('GIVEN "discord.js" THEN returns "~~discord.js~~"', () => {
expect<'~~discord.js~~'>(strikethrough('discord.js')).toBe('~~discord.js~~');
expect<'~~discord.js~~'>(strikethrough('discord.js')).toEqual('~~discord.js~~');
});
});
describe('quote', () => {
test('GIVEN "discord.js" THEN returns "> discord.js"', () => {
expect<'> discord.js'>(quote('discord.js')).toBe('> discord.js');
expect<'> discord.js'>(quote('discord.js')).toEqual('> discord.js');
});
});
describe('blockQuote', () => {
test('GIVEN "discord.js" THEN returns ">>> discord.js"', () => {
expect<'>>> discord.js'>(blockQuote('discord.js')).toBe('>>> discord.js');
expect<'>>> discord.js'>(blockQuote('discord.js')).toEqual('>>> discord.js');
});
});
describe('hideLinkEmbed', () => {
test('GIVEN "https://discord.js.org" THEN returns "<https://discord.js.org>"', () => {
expect<'<https://discord.js.org>'>(hideLinkEmbed('https://discord.js.org')).toBe('<https://discord.js.org>');
expect<'<https://discord.js.org>'>(hideLinkEmbed('https://discord.js.org')).toEqual('<https://discord.js.org>');
});
test('GIVEN new URL("https://discord.js.org") THEN returns "<https://discord.js.org>"', () => {
expect<`<${string}>`>(hideLinkEmbed(new URL('https://discord.js.org/'))).toBe('<https://discord.js.org/>');
expect<`<${string}>`>(hideLinkEmbed(new URL('https://discord.js.org/'))).toEqual('<https://discord.js.org/>');
});
});
describe('hyperlink', () => {
test('GIVEN content and string URL THEN returns "[content](url)"', () => {
expect<'[discord.js](https://discord.js.org)'>(hyperlink('discord.js', 'https://discord.js.org')).toBe(
expect<'[discord.js](https://discord.js.org)'>(hyperlink('discord.js', 'https://discord.js.org')).toEqual(
'[discord.js](https://discord.js.org)',
);
});
test('GIVEN content and URL THEN returns "[content](url)"', () => {
expect<`[discord.js](${string})`>(hyperlink('discord.js', new URL('https://discord.js.org'))).toBe(
expect<`[discord.js](${string})`>(hyperlink('discord.js', new URL('https://discord.js.org'))).toEqual(
'[discord.js](https://discord.js.org/)',
);
});
@@ -98,53 +98,53 @@ describe('Message formatters', () => {
test('GIVEN content, string URL, and title THEN returns "[content](url "title")"', () => {
expect<'[discord.js](https://discord.js.org "Official Documentation")'>(
hyperlink('discord.js', 'https://discord.js.org', 'Official Documentation'),
).toBe('[discord.js](https://discord.js.org "Official Documentation")');
).toEqual('[discord.js](https://discord.js.org "Official Documentation")');
});
test('GIVEN content, URL, and title THEN returns "[content](url "title")"', () => {
expect<`[discord.js](${string} "Official Documentation")`>(
hyperlink('discord.js', new URL('https://discord.js.org'), 'Official Documentation'),
).toBe('[discord.js](https://discord.js.org/ "Official Documentation")');
).toEqual('[discord.js](https://discord.js.org/ "Official Documentation")');
});
});
describe('spoiler', () => {
test('GIVEN "discord.js" THEN returns "||discord.js||"', () => {
expect<'||discord.js||'>(spoiler('discord.js')).toBe('||discord.js||');
expect<'||discord.js||'>(spoiler('discord.js')).toEqual('||discord.js||');
});
});
describe('Mentions', () => {
describe('userMention', () => {
test('GIVEN userId THEN returns "<@[userId]>"', () => {
expect(userMention('139836912335716352')).toBe('<@139836912335716352>');
expect(userMention('139836912335716352')).toEqual('<@139836912335716352>');
});
});
describe('channelMention', () => {
test('GIVEN channelId THEN returns "<#[channelId]>"', () => {
expect(channelMention('829924760309334087')).toBe('<#829924760309334087>');
expect(channelMention('829924760309334087')).toEqual('<#829924760309334087>');
});
});
describe('roleMention', () => {
test('GIVEN roleId THEN returns "<&[roleId]>"', () => {
expect(roleMention('815434166602170409')).toBe('<@&815434166602170409>');
expect(roleMention('815434166602170409')).toEqual('<@&815434166602170409>');
});
});
});
describe('formatEmoji', () => {
test('GIVEN static emojiId THEN returns "<:_:${emojiId}>"', () => {
expect<`<:_:851461487498493952>`>(formatEmoji('851461487498493952')).toBe('<:_:851461487498493952>');
expect<`<:_:851461487498493952>`>(formatEmoji('851461487498493952')).toEqual('<:_:851461487498493952>');
});
test('GIVEN static emojiId WITH animated explicitly false THEN returns "<:_:[emojiId]>"', () => {
expect<`<:_:851461487498493952>`>(formatEmoji('851461487498493952', false)).toBe('<:_:851461487498493952>');
expect<`<:_:851461487498493952>`>(formatEmoji('851461487498493952', false)).toEqual('<:_:851461487498493952>');
});
test('GIVEN animated emojiId THEN returns "<a:_:${emojiId}>"', () => {
expect<`<a:_:827220205352255549>`>(formatEmoji('827220205352255549', true)).toBe('<a:_:827220205352255549>');
expect<`<a:_:827220205352255549>`>(formatEmoji('827220205352255549', true)).toEqual('<a:_:827220205352255549>');
});
});
@@ -153,47 +153,49 @@ describe('Message formatters', () => {
jest.useFakeTimers('modern');
jest.setSystemTime(1566424897579);
expect<`<t:${bigint}>`>(time()).toBe('<t:1566424897>');
expect<`<t:${bigint}>`>(time()).toEqual('<t:1566424897>');
jest.useRealTimers();
});
test('GIVEN a date THEN returns "<t:${bigint}>"', () => {
expect<`<t:${bigint}>`>(time(new Date(1867424897579))).toBe('<t:1867424897>');
expect<`<t:${bigint}>`>(time(new Date(1867424897579))).toEqual('<t:1867424897>');
});
test('GIVEN a date and a style from string THEN returns "<t:${bigint}:${style}>"', () => {
expect<`<t:${bigint}:d>`>(time(new Date(1867424897579), 'd')).toBe('<t:1867424897:d>');
expect<`<t:${bigint}:d>`>(time(new Date(1867424897579), 'd')).toEqual('<t:1867424897:d>');
});
test('GIVEN a date and a format from enum THEN returns "<t:${bigint}:${style}>"', () => {
expect<`<t:${bigint}:R>`>(time(new Date(1867424897579), TimestampStyles.RelativeTime)).toBe('<t:1867424897:R>');
expect<`<t:${bigint}:R>`>(time(new Date(1867424897579), TimestampStyles.RelativeTime)).toEqual(
'<t:1867424897:R>',
);
});
test('GIVEN a date THEN returns "<t:${time}>"', () => {
expect<'<t:1867424897>'>(time(1867424897)).toBe('<t:1867424897>');
expect<'<t:1867424897>'>(time(1867424897)).toEqual('<t:1867424897>');
});
test('GIVEN a date and a style from string THEN returns "<t:${time}:${style}>"', () => {
expect<'<t:1867424897:d>'>(time(1867424897, 'd')).toBe('<t:1867424897:d>');
expect<'<t:1867424897:d>'>(time(1867424897, 'd')).toEqual('<t:1867424897:d>');
});
test('GIVEN a date and a format from enum THEN returns "<t:${time}:${style}>"', () => {
expect<'<t:1867424897:R>'>(time(1867424897, TimestampStyles.RelativeTime)).toBe('<t:1867424897:R>');
expect<'<t:1867424897:R>'>(time(1867424897, TimestampStyles.RelativeTime)).toEqual('<t:1867424897:R>');
});
});
describe('Faces', () => {
test('GIVEN Faces.Shrug THEN returns "¯\\_(ツ)\\_/¯"', () => {
expect<'¯\\_(ツ)\\_/¯'>(Faces.Shrug).toBe('¯\\_(ツ)\\_/¯');
expect<'¯\\_(ツ)\\_/¯'>(Faces.Shrug).toEqual('¯\\_(ツ)\\_/¯');
});
test('GIVEN Faces.Tableflip THEN returns "(╯°□°)╯︵ ┻━┻"', () => {
expect<'(╯°□°)╯︵ ┻━┻'>(Faces.Tableflip).toBe('(╯°□°)╯︵ ┻━┻');
expect<'(╯°□°)╯︵ ┻━┻'>(Faces.Tableflip).toEqual('(╯°□°)╯︵ ┻━┻');
});
test('GIVEN Faces.Unflip THEN returns "┬─┬ ( ゜-゜ノ)"', () => {
expect<'┬─┬ ( ゜-゜ノ)'>(Faces.Unflip).toBe('┬─┬ ( ゜-゜ノ)');
expect<'┬─┬ ( ゜-゜ノ)'>(Faces.Unflip).toEqual('┬─┬ ( ゜-゜ノ)');
});
});
});

View File

@@ -6,29 +6,32 @@ All notable changes to this project will be documented in this file.\n
"""
body = """
{% if version %}\
# [{{ version | trim_start_matches(pat="v") }}]\
{% if previous %}\
{% if previous.version %}\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
# [{{ version | trim_start_matches(pat="v") }}]\
{% if previous %}\
{% if previous.version %}\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
# [unreleased]
# [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
## {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}\
[**breaking**] \
{% endif %}\
{% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% endfor %}
## {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% if commit.breaking %}\
\n\n {% raw %} {% endraw %} ### Breaking Changes:\n \
{% for breakingChange in commit.footers %}\
{% raw %} {% endraw %} - {{ breakingChange }}\n\
{% endfor %}\
{% endif %}\
{% endfor %}
{% endfor %}\n
"""
trim = true
@@ -38,25 +41,24 @@ footer = ""
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^docs", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^typings", group = "Typings"},
{ message = "^types", group = "Typings"},
{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
{ message = "^revert", skip = true},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore", skip = true},
{ message = "^ci", skip = true},
{ message = "^build", skip = true},
{ body = ".*security", group = "Security"},
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^docs", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^typings", group = "Typings"},
{ message = "^types", group = "Typings"},
{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
{ message = "^revert", skip = true},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore", skip = true},
{ message = "^ci", skip = true},
{ message = "^build", skip = true},
{ body = ".*security", group = "Security"},
]
filter_commits = true
tag_pattern = "@discordjs\\/builders@.*"
skip_tags = "v[0-9]*|11|12"
tag_pattern = "@discordjs/builders@[0-9]*"
ignore_tags = ""
topo_order = false
sort_commits = "newest"

View File

@@ -1,6 +1,6 @@
{
"name": "@discordjs/builders",
"version": "0.14.0-dev",
"version": "0.14.0",
"description": "A set of builders that you can use when creating your bot",
"scripts": {
"build": "tsup",
@@ -9,7 +9,7 @@
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"docs": "typedoc --json docs/typedoc-out.json src/index.ts && node scripts/docs.mjs",
"prepublishOnly": "yarn build && yarn lint && yarn test",
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/builders/*'"
"changelog": "git cliff --prepend ./CHANGELOG.md -l -c ./cliff.toml -r ../../ --include-path 'packages/builders/*'"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -52,33 +52,33 @@
},
"homepage": "https://discord.js.org",
"dependencies": {
"@sapphire/shapeshift": "^2.0.0",
"@sapphire/shapeshift": "^3.1.0",
"@sindresorhus/is": "^4.6.0",
"discord-api-types": "^0.31.1",
"discord-api-types": "^0.33.3",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.1",
"tslib": "^2.3.1"
"tslib": "^2.4.0"
},
"devDependencies": {
"@babel/core": "^7.17.9",
"@babel/plugin-proposal-decorators": "^7.17.9",
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
"@babel/core": "^7.18.2",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/preset-env": "^7.18.2",
"@babel/preset-typescript": "^7.17.12",
"@discordjs/ts-docgen": "^0.4.1",
"@types/jest": "^27.4.1",
"@types/node": "^16.11.27",
"@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0",
"@types/jest": "^28.1.0",
"@types/node": "^16.11.38",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"babel-plugin-transform-typescript-metadata": "^0.3.2",
"eslint": "^8.13.0",
"eslint": "^8.17.0",
"eslint-config-marine": "^9.4.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"jest": "^27.5.1",
"jest": "^28.1.0",
"prettier": "^2.6.2",
"tsup": "^5.12.5",
"typedoc": "^0.22.15",
"typescript": "^4.6.3"
"tsup": "^6.0.1",
"typedoc": "^0.22.17",
"typescript": "^4.7.3"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -38,7 +38,7 @@ export class ActionRowBuilder<T extends AnyComponentBuilder> extends ComponentBu
* @param components The components to add to this action row.
* @returns
*/
public addComponents(...components: T[]) {
public addComponents(components: T[]) {
this.components.push(...components);
return this;
}
@@ -47,7 +47,7 @@ export class ActionRowBuilder<T extends AnyComponentBuilder> extends ComponentBu
* Sets the components in this action row
* @param components The components to set this row to
*/
public setComponents(...components: T[]) {
public setComponents(components: T[]) {
this.components.splice(0, this.components.length, ...components);
return this;
}

View File

@@ -3,7 +3,7 @@ import { APIMessageComponentEmoji, ButtonStyle } from 'discord-api-types/v10';
import type { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption';
import { UnsafeSelectMenuOptionBuilder } from './selectMenu/UnsafeSelectMenuOption';
export const customIdValidator = s.string.lengthGe(1).lengthLe(100);
export const customIdValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
export const emojiValidator = s.object({
id: s.string,
@@ -13,14 +13,14 @@ export const emojiValidator = s.object({
export const disabledValidator = s.boolean;
export const buttonLabelValidator = s.string.lengthGe(1).lengthLe(80);
export const buttonLabelValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(80);
export const buttonStyleValidator = s.nativeEnum(ButtonStyle);
export const placeholderValidator = s.string.lengthLe(150);
export const minMaxValidator = s.number.int.ge(0).le(25);
export const placeholderValidator = s.string.lengthLessThanOrEqual(150);
export const minMaxValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(25);
export const labelValueDescriptionValidator = s.string.lengthGe(1).lengthLe(100);
export const labelValueDescriptionValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
export const optionValidator = s.union(
s.object({
label: labelValueDescriptionValidator,
@@ -31,15 +31,15 @@ export const optionValidator = s.union(
}),
s.instance(UnsafeSelectMenuOptionBuilder),
);
export const optionsValidator = optionValidator.array.lengthGe(0);
export const optionsLengthValidator = s.number.int.ge(0).le(25);
export const optionsValidator = optionValidator.array.lengthGreaterThanOrEqual(0);
export const optionsLengthValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(25);
export function validateRequiredSelectMenuParameters(options: SelectMenuOptionBuilder[], customId?: string) {
customIdValidator.parse(customId);
optionsValidator.parse(options);
}
export const labelValueValidator = s.string.lengthGe(1).lengthLe(100);
export const labelValueValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
export const defaultValidator = s.boolean;
export function validateRequiredSelectMenuOptionParameters(label?: string, value?: string) {

View File

@@ -35,7 +35,7 @@ export class SelectMenuBuilder extends UnsafeSelectMenuBuilder {
return super.setDisabled(disabledValidator.parse(disabled));
}
public override addOptions(...options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
public override addOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
optionsLengthValidator.parse(this.options.length + options.length);
this.options.push(
...options.map((option) =>
@@ -47,7 +47,7 @@ export class SelectMenuBuilder extends UnsafeSelectMenuBuilder {
return this;
}
public override setOptions(...options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
public override setOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
optionsLengthValidator.parse(options.length);
this.options.splice(
0,

View File

@@ -67,7 +67,7 @@ export class UnsafeSelectMenuBuilder extends ComponentBuilder<APISelectMenuCompo
* @param options The options to add to this select menu
* @returns
*/
public addOptions(...options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
public addOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
this.options.push(
...options.map((option) =>
option instanceof UnsafeSelectMenuOptionBuilder ? option : new UnsafeSelectMenuOptionBuilder(option),
@@ -80,7 +80,7 @@ export class UnsafeSelectMenuBuilder extends ComponentBuilder<APISelectMenuCompo
* Sets the options on this select menu
* @param options The options to set on this select menu
*/
public setOptions(...options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
public setOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
this.options.splice(
0,
this.options.length,

View File

@@ -3,12 +3,12 @@ import { TextInputStyle } from 'discord-api-types/v10';
import { customIdValidator } from '../Assertions';
export const textInputStyleValidator = s.nativeEnum(TextInputStyle);
export const minLengthValidator = s.number.int.ge(0).le(4000);
export const maxLengthValidator = s.number.int.ge(1).le(4000);
export const minLengthValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(4000);
export const maxLengthValidator = s.number.int.greaterThanOrEqual(1).lessThanOrEqual(4000);
export const requiredValidator = s.boolean;
export const valueValidator = s.string.lengthLe(4000);
export const placeholderValidator = s.string.lengthLe(100);
export const labelValidator = s.string.lengthGe(1).lengthLe(45);
export const valueValidator = s.string.lengthLessThanOrEqual(4000);
export const placeholderValidator = s.string.lengthLessThanOrEqual(100);
export const labelValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(45);
export function validateRequiredParameters(customId?: string, style?: TextInputStyle, label?: string) {
customIdValidator.parse(customId);

View File

@@ -9,7 +9,7 @@ export class UnsafeTextInputBuilder extends ComponentBuilder<APITextInputCompone
/**
* Sets the custom id for this text input
* @param customId The custom id of this text inputå
* @param customId The custom id of this text input
*/
public setCustomId(customId: string) {
this.data.custom_id = customId;

View File

@@ -32,6 +32,12 @@ export * from './interactions/slashCommands/options/role';
export * from './interactions/slashCommands/options/attachment';
export * from './interactions/slashCommands/options/string';
export * from './interactions/slashCommands/options/user';
export * from './interactions/slashCommands/mixins/ApplicationCommandNumericOptionMinMaxValueMixin';
export * from './interactions/slashCommands/mixins/ApplicationCommandOptionBase';
export * from './interactions/slashCommands/mixins/ApplicationCommandOptionChannelTypesMixin';
export * from './interactions/slashCommands/mixins/ApplicationCommandOptionWithChoicesAndAutocompleteMixin';
export * from './interactions/slashCommands/mixins/NameAndDescription';
export * from './interactions/slashCommands/mixins/SharedSlashCommandOptions';
export * as ContextMenuCommandAssertions from './interactions/contextMenuCommands/Assertions';
export * from './interactions/contextMenuCommands/ContextMenuCommandBuilder';

View File

@@ -3,8 +3,8 @@ import { ApplicationCommandType } from 'discord-api-types/v10';
import type { ContextMenuCommandType } from './ContextMenuCommandBuilder';
const namePredicate = s.string
.lengthGe(1)
.lengthLe(32)
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(32)
.regex(/^( *[\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+ *)+$/u);
const typePredicate = s.union(s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message));
@@ -30,3 +30,19 @@ export function validateRequiredParameters(name: string, type: number) {
// Assert type is valid
validateType(type);
}
const dmPermissionPredicate = s.boolean.nullish;
export function validateDMPermission(value: unknown): asserts value is boolean | null | undefined {
dmPermissionPredicate.parse(value);
}
const memberPermissionPredicate = s.union(
s.bigint.transform((value) => value.toString()),
s.number.safeInt.transform((value) => value.toString()),
s.string.regex(/^\d+$/),
).nullish;
export function validateDefaultMemberPermissions(permissions: unknown) {
return memberPermissionPredicate.parse(permissions);
}

View File

@@ -1,5 +1,19 @@
import type { ApplicationCommandType, RESTPostAPIApplicationCommandsJSONBody } from 'discord-api-types/v10';
import { validateRequiredParameters, validateName, validateType, validateDefaultPermission } from './Assertions';
import type {
ApplicationCommandType,
LocaleString,
LocalizationMap,
Permissions,
RESTPostAPIApplicationCommandsJSONBody,
} from 'discord-api-types/v10';
import {
validateRequiredParameters,
validateName,
validateType,
validateDefaultPermission,
validateDefaultMemberPermissions,
validateDMPermission,
} from './Assertions';
import { validateLocale, validateLocalizationMap } from '../slashCommands/Assertions';
export class ContextMenuCommandBuilder {
/**
@@ -7,6 +21,11 @@ export class ContextMenuCommandBuilder {
*/
public readonly name: string = undefined!;
/**
* The localized names for this command
*/
public readonly name_localizations?: LocalizationMap;
/**
* The type of this context menu command
*/
@@ -15,9 +34,21 @@ export class ContextMenuCommandBuilder {
/**
* Whether the command is enabled by default when the app is added to a guild
*
* @default true
* @deprecated This property is deprecated and will be removed in the future.
* You should use `setDefaultMemberPermissions` or `setDMPermission` instead.
*/
public readonly defaultPermission: boolean | undefined = undefined;
public readonly default_permission: boolean | undefined = undefined;
/**
* Set of permissions represented as a bit set for the command
*/
public readonly default_member_permissions: Permissions | null | undefined = undefined;
/**
* Indicates whether the command is available in DMs with the application, only for globally-scoped commands.
* By default, commands are visible.
*/
public readonly dm_permission: boolean | undefined = undefined;
/**
* Sets the name
@@ -55,16 +86,95 @@ export class ContextMenuCommandBuilder {
* @param value Whether or not to enable this command by default
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
* @deprecated Use `setDefaultMemberPermissions` or `setDMPermission` instead.
*/
public setDefaultPermission(value: boolean) {
// Assert the value matches the conditions
validateDefaultPermission(value);
Reflect.set(this, 'defaultPermission', value);
Reflect.set(this, 'default_permission', value);
return this;
}
/**
* Sets the default permissions a member should have in order to run the command.
*
* **Note:** You can set this to `'0'` to disable the command by default.
*
* @param permissions The permissions bit field to set
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
*/
public setDefaultMemberPermissions(permissions: Permissions | bigint | number | null | undefined) {
// Assert the value and parse it
const permissionValue = validateDefaultMemberPermissions(permissions);
Reflect.set(this, 'default_member_permissions', permissionValue);
return this;
}
/**
* Sets if the command is available in DMs with the application, only for globally-scoped commands.
* By default, commands are visible.
*
* @param enabled If the command should be enabled in DMs
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
*/
public setDMPermission(enabled: boolean | null | undefined) {
// Assert the value matches the conditions
validateDMPermission(enabled);
Reflect.set(this, 'dm_permission', enabled);
return this;
}
/**
* Sets a name localization
*
* @param locale The locale to set a description for
* @param localizedName The localized description for the given locale
*/
public setNameLocalization(locale: LocaleString, localizedName: string | null) {
if (!this.name_localizations) {
Reflect.set(this, 'name_localizations', {});
}
const parsedLocale = validateLocale(locale);
if (localizedName === null) {
this.name_localizations![parsedLocale] = null;
return this;
}
validateName(localizedName);
this.name_localizations![parsedLocale] = localizedName;
return this;
}
/**
* Sets the name localizations
*
* @param localizedNames The dictionary of localized descriptions to set
*/
public setNameLocalizations(localizedNames: LocalizationMap | null) {
if (localizedNames === null) {
Reflect.set(this, 'name_localizations', null);
return this;
}
Reflect.set(this, 'name_localizations', {});
Object.entries(localizedNames).forEach((args) =>
this.setNameLocalization(...(args as [LocaleString, string | null])),
);
return this;
}
/**
* Returns the final data that should be sent to Discord.
*
@@ -72,11 +182,10 @@ export class ContextMenuCommandBuilder {
*/
public toJSON(): RESTPostAPIApplicationCommandsJSONBody {
validateRequiredParameters(this.name, this.type);
return {
name: this.name,
type: this.type,
default_permission: this.defaultPermission,
};
validateLocalizationMap(this.name_localizations);
return { ...this };
}
}

View File

@@ -2,8 +2,8 @@ import { s } from '@sapphire/shapeshift';
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../..';
import { customIdValidator } from '../../components/Assertions';
export const titleValidator = s.string.lengthGe(1).lengthLe(45);
export const componentsValidator = s.instance(ActionRowBuilder).array.lengthGe(1);
export const titleValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(45);
export const componentsValidator = s.instance(ActionRowBuilder).array.lengthGreaterThanOrEqual(1);
export function validateRequiredParameters(
customId?: string,

View File

@@ -38,10 +38,10 @@ export class UnsafeModalBuilder implements JSONEncodable<APIModalInteractionResp
* @param components The components to add to this modal
*/
public addComponents(
...components: (
components: (
| ActionRowBuilder<ModalActionRowComponentBuilder>
| APIActionRowComponent<APIModalActionRowComponent>
)[]
)[],
) {
this.components.push(
...components.map((component) =>
@@ -57,7 +57,7 @@ export class UnsafeModalBuilder implements JSONEncodable<APIModalInteractionResp
* Sets the components in this modal
* @param components The components to set this modal to
*/
public setComponents(...components: ActionRowBuilder<ModalActionRowComponentBuilder>[]) {
public setComponents(components: ActionRowBuilder<ModalActionRowComponentBuilder>[]) {
this.components.splice(0, this.components.length, ...components);
return this;
}

View File

@@ -1,27 +1,27 @@
import { s } from '@sapphire/shapeshift';
import is from '@sindresorhus/is';
import { type APIApplicationCommandOptionChoice, Locale } from 'discord-api-types/v10';
import { type APIApplicationCommandOptionChoice, Locale, LocalizationMap } from 'discord-api-types/v10';
import type { ToAPIApplicationCommandOptions } from './SlashCommandBuilder';
import type { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from './SlashCommandSubcommands';
import type { ApplicationCommandOptionBase } from './mixins/ApplicationCommandOptionBase';
const namePredicate = s.string
.lengthGe(1)
.lengthLe(32)
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(32)
.regex(/^[\P{Lu}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u);
export function validateName(name: unknown): asserts name is string {
namePredicate.parse(name);
}
const descriptionPredicate = s.string.lengthGe(1).lengthLe(100);
const descriptionPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
const localePredicate = s.nativeEnum(Locale);
export function validateDescription(description: unknown): asserts description is string {
descriptionPredicate.parse(description);
}
const maxArrayLengthPredicate = s.unknown.array.lengthLe(25);
const maxArrayLengthPredicate = s.unknown.array.lengthLessThanOrEqual(25);
export function validateLocale(locale: unknown) {
return localePredicate.parse(locale);
}
@@ -55,7 +55,7 @@ export function validateRequired(required: unknown): asserts required is boolean
booleanPredicate.parse(required);
}
const choicesLengthPredicate = s.number.le(25);
const choicesLengthPredicate = s.number.lessThanOrEqual(25);
export function validateChoicesLength(amountAdding: number, choices?: APIApplicationCommandOptionChoice[]): void {
choicesLengthPredicate.parse((choices?.length ?? 0) + amountAdding);
@@ -87,3 +87,27 @@ export function assertReturnOfBuilder<
throw new TypeError(`Expected to receive a ${instanceName} builder, got ${fullResultName} instead.`);
}
}
export const localizationMapPredicate = s.object<LocalizationMap>(
Object.fromEntries(Object.values(Locale).map((locale) => [locale, s.string.nullish])),
).strict.nullish;
export function validateLocalizationMap(value: unknown): asserts value is LocalizationMap {
localizationMapPredicate.parse(value);
}
const dmPermissionPredicate = s.boolean.nullish;
export function validateDMPermission(value: unknown): asserts value is boolean | null | undefined {
dmPermissionPredicate.parse(value);
}
const memberPermissionPredicate = s.union(
s.bigint.transform((value) => value.toString()),
s.number.safeInt.transform((value) => value.toString()),
s.string.regex(/^\d+$/),
).nullish;
export function validateDefaultMemberPermissions(permissions: unknown) {
return memberPermissionPredicate.parse(permissions);
}

View File

@@ -1,12 +1,16 @@
import type {
APIApplicationCommandOption,
LocalizationMap,
Permissions,
RESTPostAPIApplicationCommandsJSONBody,
} from 'discord-api-types/v10';
import { mix } from 'ts-mixer';
import {
assertReturnOfBuilder,
validateDefaultMemberPermissions,
validateDefaultPermission,
validateLocalizationMap,
validateDMPermission,
validateMaxOptionsLength,
validateRequiredParameters,
} from './Assertions';
@@ -44,9 +48,21 @@ export class SlashCommandBuilder {
/**
* Whether the command is enabled by default when the app is added to a guild
*
* @default true
* @deprecated This property is deprecated and will be removed in the future.
* You should use `setDefaultMemberPermissions` or `setDMPermission` instead.
*/
public readonly defaultPermission: boolean | undefined = undefined;
public readonly default_permission: boolean | undefined = undefined;
/**
* Set of permissions represented as a bit set for the command
*/
public readonly default_member_permissions: Permissions | null | undefined = undefined;
/**
* Indicates whether the command is available in DMs with the application, only for globally-scoped commands.
* By default, commands are visible.
*/
public readonly dm_permission: boolean | undefined = undefined;
/**
* Returns the final data that should be sent to Discord.
@@ -56,13 +72,12 @@ export class SlashCommandBuilder {
public toJSON(): RESTPostAPIApplicationCommandsJSONBody {
validateRequiredParameters(this.name, this.description, this.options);
validateLocalizationMap(this.name_localizations);
validateLocalizationMap(this.description_localizations);
return {
name: this.name,
name_localizations: this.name_localizations,
description: this.description,
description_localizations: this.description_localizations,
...this,
options: this.options.map((option) => option.toJSON()),
default_permission: this.defaultPermission,
};
}
@@ -74,12 +89,48 @@ export class SlashCommandBuilder {
* @param value Whether or not to enable this command by default
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
* @deprecated Use `setDefaultMemberPermissions` or `setDMPermission` instead.
*/
public setDefaultPermission(value: boolean) {
// Assert the value matches the conditions
validateDefaultPermission(value);
Reflect.set(this, 'defaultPermission', value);
Reflect.set(this, 'default_permission', value);
return this;
}
/**
* Sets the default permissions a member should have in order to run the command.
*
* **Note:** You can set this to `'0'` to disable the command by default.
*
* @param permissions The permissions bit field to set
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
*/
public setDefaultMemberPermissions(permissions: Permissions | bigint | number | null | undefined) {
// Assert the value and parse it
const permissionValue = validateDefaultMemberPermissions(permissions);
Reflect.set(this, 'default_member_permissions', permissionValue);
return this;
}
/**
* Sets if the command is available in DMs with the application, only for globally-scoped commands.
* By default, commands are visible.
*
* @param enabled If the command should be enabled in DMs
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
*/
public setDMPermission(enabled: boolean | null | undefined) {
// Assert the value matches the conditions
validateDMPermission(enabled);
Reflect.set(this, 'dm_permission', enabled);
return this;
}

View File

@@ -66,7 +66,9 @@ export class SlashCommandSubcommandGroupBuilder implements ToAPIApplicationComma
return {
type: ApplicationCommandOptionType.SubcommandGroup,
name: this.name,
name_localizations: this.name_localizations,
description: this.description,
description_localizations: this.description_localizations,
options: this.options.map((option) => option.toJSON()),
};
}
@@ -102,7 +104,9 @@ export class SlashCommandSubcommandBuilder implements ToAPIApplicationCommandOpt
return {
type: ApplicationCommandOptionType.Subcommand,
name: this.name,
name_localizations: this.name_localizations,
description: this.description,
description_localizations: this.description_localizations,
options: this.options.map((option) => option.toJSON()),
};
}

View File

@@ -1,6 +1,6 @@
import type { APIApplicationCommandBasicOption, ApplicationCommandOptionType } from 'discord-api-types/v10';
import { SharedNameAndDescription } from './NameAndDescription';
import { validateRequiredParameters, validateRequired } from '../Assertions';
import { validateRequiredParameters, validateRequired, validateLocalizationMap } from '../Assertions';
export abstract class ApplicationCommandOptionBase extends SharedNameAndDescription {
public abstract readonly type: ApplicationCommandOptionType;
@@ -26,6 +26,10 @@ export abstract class ApplicationCommandOptionBase extends SharedNameAndDescript
protected runRequiredValidations() {
validateRequiredParameters(this.name, this.description, []);
// Validate localizations
validateLocalizationMap(this.name_localizations);
validateLocalizationMap(this.description_localizations);
// Assert that you actually passed a boolean
validateRequired(this.required);
}

View File

@@ -1,10 +1,14 @@
import { s } from '@sapphire/shapeshift';
import { APIApplicationCommandOptionChoice, ApplicationCommandOptionType } from 'discord-api-types/v10';
import { validateChoicesLength } from '../Assertions';
import { localizationMapPredicate, validateChoicesLength } from '../Assertions';
const stringPredicate = s.string.lengthGe(1).lengthLe(100);
const numberPredicate = s.number.gt(-Infinity).lt(Infinity);
const choicesPredicate = s.object({ name: stringPredicate, value: s.union(stringPredicate, numberPredicate) }).array;
const stringPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
const numberPredicate = s.number.greaterThan(-Infinity).lessThan(Infinity);
const choicesPredicate = s.object({
name: stringPredicate,
name_localizations: localizationMapPredicate,
value: s.union(stringPredicate, numberPredicate),
}).array;
const booleanPredicate = s.boolean;
export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends string | number> {
@@ -32,7 +36,7 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
validateChoicesLength(choices.length, this.choices);
for (const { name, value } of choices) {
for (const { name, name_localizations, value } of choices) {
// Validate the value
if (this.type === ApplicationCommandOptionType.String) {
stringPredicate.parse(value);
@@ -40,7 +44,7 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
numberPredicate.parse(value);
}
this.choices!.push({ name, value });
this.choices!.push({ name, name_localizations, value });
}
return this;

View File

@@ -46,14 +46,16 @@ export class SharedNameAndDescription {
Reflect.set(this, 'name_localizations', {});
}
const parsedLocale = validateLocale(locale);
if (localizedName === null) {
this.name_localizations![locale] = null;
this.name_localizations![parsedLocale] = null;
return this;
}
validateName(localizedName);
this.name_localizations![validateLocale(locale)] = localizedName;
this.name_localizations![parsedLocale] = localizedName;
return this;
}
@@ -87,14 +89,16 @@ export class SharedNameAndDescription {
Reflect.set(this, 'description_localizations', {});
}
const parsedLocale = validateLocale(locale);
if (localizedDescription === null) {
this.description_localizations![locale] = null;
this.description_localizations![parsedLocale] = null;
return this;
}
validateDescription(localizedDescription);
this.description_localizations![validateLocale(locale)] = localizedDescription;
this.description_localizations![parsedLocale] = localizedDescription;
return this;
}

View File

@@ -1,9 +1,9 @@
import { s } from '@sapphire/shapeshift';
import type { APIEmbedField } from 'discord-api-types/v10';
export const fieldNamePredicate = s.string.lengthGe(1).lengthLe(256);
export const fieldNamePredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(256);
export const fieldValuePredicate = s.string.lengthGe(1).lengthLe(1024);
export const fieldValuePredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(1024);
export const fieldInlinePredicate = s.boolean.optional;
@@ -15,7 +15,7 @@ export const embedFieldPredicate = s.object({
export const embedFieldsArrayPredicate = embedFieldPredicate.array;
export const fieldLengthPredicate = s.number.le(25);
export const fieldLengthPredicate = s.number.lessThanOrEqual(25);
export function validateFieldLength(amountAdding: number, fields?: APIEmbedField[]): void {
fieldLengthPredicate.parse((fields?.length ?? 0) + amountAdding);
@@ -31,15 +31,26 @@ export const urlPredicate = s.string.url({
allowedProtocols: ['http:', 'https:'],
}).nullish;
export const RGBPredicate = s.number.int.ge(0).le(255);
export const embedAuthorPredicate = s.object({
name: authorNamePredicate,
iconURL: imageURLPredicate,
url: urlPredicate,
});
export const RGBPredicate = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(255);
export const colorPredicate = s.number.int
.ge(0)
.le(0xffffff)
.greaterThanOrEqual(0)
.lessThanOrEqual(0xffffff)
.or(s.tuple([RGBPredicate, RGBPredicate, RGBPredicate])).nullable;
export const descriptionPredicate = s.string.lengthGe(1).lengthLe(4096).nullable;
export const descriptionPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(4096).nullable;
export const footerTextPredicate = s.string.lengthGe(1).lengthLe(2048).nullable;
export const footerTextPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(2048).nullable;
export const embedFooterPredicate = s.object({
text: footerTextPredicate,
iconURL: imageURLPredicate,
});
export const timestampPredicate = s.union(s.number, s.date).nullable;

View File

@@ -1,10 +1,10 @@
import type { APIEmbedField } from 'discord-api-types/v10';
import {
authorNamePredicate,
colorPredicate,
descriptionPredicate,
embedAuthorPredicate,
embedFieldsArrayPredicate,
footerTextPredicate,
embedFooterPredicate,
imageURLPredicate,
timestampPredicate,
titlePredicate,
@@ -17,12 +17,12 @@ import { EmbedAuthorOptions, EmbedFooterOptions, RGBTuple, UnsafeEmbedBuilder }
* Represents a validated embed in a message (image/video preview, rich embed, etc.)
*/
export class EmbedBuilder extends UnsafeEmbedBuilder {
public override addFields(...fields: APIEmbedField[]): this {
public override addFields(fields: APIEmbedField[]): this {
// Ensure adding these fields won't exceed the 25 field limit
validateFieldLength(fields.length, this.data.fields);
// Data assertions
return super.addFields(...embedFieldsArrayPredicate.parse(fields));
return super.addFields(embedFieldsArrayPredicate.parse(fields));
}
public override spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
@@ -39,9 +39,7 @@ export class EmbedBuilder extends UnsafeEmbedBuilder {
}
// Data assertions
authorNamePredicate.parse(options.name);
urlPredicate.parse(options.iconURL);
urlPredicate.parse(options.url);
embedAuthorPredicate.parse(options);
return super.setAuthor(options);
}
@@ -62,8 +60,7 @@ export class EmbedBuilder extends UnsafeEmbedBuilder {
}
// Data assertions
footerTextPredicate.parse(options.text);
urlPredicate.parse(options.iconURL);
embedFooterPredicate.parse(options);
return super.setFooter(options);
}

View File

@@ -44,7 +44,7 @@ export class UnsafeEmbedBuilder {
*
* @param fields The fields to add
*/
public addFields(...fields: APIEmbedField[]): this {
public addFields(fields: APIEmbedField[]): this {
if (this.data.fields) this.data.fields.push(...fields);
else this.data.fields = fields;
return this;
@@ -67,7 +67,7 @@ export class UnsafeEmbedBuilder {
* Sets the embed's fields (max 25).
* @param fields The fields to set
*/
public setFields(...fields: APIEmbedField[]) {
public setFields(fields: APIEmbedField[]) {
this.spliceFields(0, this.data.fields?.length ?? 0, ...fields);
return this;
}

View File

@@ -1,6 +1,6 @@
import type { Options } from 'tsup';
import { defineConfig } from 'tsup';
export const tsup: Options = {
export default defineConfig({
clean: true,
dts: true,
entryPoints: ['src/index.ts'],
@@ -17,4 +17,4 @@ export const tsup: Options = {
};
}
},
};
});

View File

@@ -2,7 +2,13 @@
All notable changes to this project will be documented in this file.
# [0.6.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@0.5.0...@discordjs/collection@0.6.0) (2022-04-17)
# [@discordjs/collection@0.7.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@0.6.0...@discordjs/collection@0.7.0) - (2022-06-04)
## Styling
- Cleanup tests and tsup configs ([6b8ef20](https://github.com/discordjs/discord.js/commit/6b8ef20cb3af5b5cfd176dd0aa0a1a1e98551629))
# [@discordjs/collection@0.6.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@0.5.0...@discordjs/collection@0.6.0) - (2022-04-17)
## Features
@@ -10,16 +16,7 @@ All notable changes to this project will be documented in this file.
- **builders:** Add attachment command option type (#7203) ([ae0f35f](https://github.com/discordjs/discord.js/commit/ae0f35f51d68dfa5a7dc43d161ef9365171debdb))
- **Collection:** Add merging functions (#7299) ([e4bd07b](https://github.com/discordjs/discord.js/commit/e4bd07b2394f227ea06b72eb6999de9ab3127b25))
## Refactor
- Make `intersect` perform a true intersection (#7211) ([d8efba2](https://github.com/discordjs/discord.js/commit/d8efba24e09aa2a8dbf028fc57a561a56e7833fd))
## Typings
- Add `ReadonlyCollection` (#7245) ([db25f52](https://github.com/discordjs/discord.js/commit/db25f529b26d7c819c1c42ad3e26c2263ea2da0e))
- **Collection:** Union types on `intersect` and `difference` (#7196) ([1f9b922](https://github.com/discordjs/discord.js/commit/1f9b9225f2066e9cc66c3355417139fd25cc403c))
# [0.5.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@0.4.0...@discordjs/collection@0.5.0) (2021-12-08)
# [@discordjs/collection@0.5.0](https://github.com/discordjs/discord.js/compare/@discordjs/collection@0.4.0...@discordjs/collection@0.5.0) - (2022-01-24)
## Refactor

View File

@@ -6,29 +6,32 @@ All notable changes to this project will be documented in this file.\n
"""
body = """
{% if version %}\
# [{{ version | trim_start_matches(pat="v") }}]\
{% if previous %}\
{% if previous.version %}\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
# [{{ version | trim_start_matches(pat="v") }}]\
{% if previous %}\
{% if previous.version %}\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
# [unreleased]
# [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
## {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}\
[**breaking**] \
{% endif %}\
{% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% endfor %}
## {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% if commit.breaking %}\
\n\n {% raw %} {% endraw %} ### Breaking Changes:\n \
{% for breakingChange in commit.footers %}\
{% raw %} {% endraw %} - {{ breakingChange }}\n\
{% endfor %}\
{% endif %}\
{% endfor %}
{% endfor %}\n
"""
trim = true
@@ -38,25 +41,24 @@ footer = ""
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^docs", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^typings", group = "Typings"},
{ message = "^types", group = "Typings"},
{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
{ message = "^revert", skip = true},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore", skip = true},
{ message = "^ci", skip = true},
{ message = "^build", skip = true},
{ body = ".*security", group = "Security"},
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^docs", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^typings", group = "Typings"},
{ message = "^types", group = "Typings"},
{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
{ message = "^revert", skip = true},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore", skip = true},
{ message = "^ci", skip = true},
{ message = "^build", skip = true},
{ body = ".*security", group = "Security"},
]
filter_commits = true
tag_pattern = "@discordjs\\/collection@.*"
skip_tags = "v[0-9]*|11|12"
tag_pattern = "@discordjs/collection@[0-9]*"
ignore_tags = ""
topo_order = false
sort_commits = "newest"

View File

@@ -1,6 +1,6 @@
{
"name": "@discordjs/collection",
"version": "0.7.0-dev",
"version": "0.7.0",
"description": "Utility data structure used in discord.js",
"scripts": {
"test": "jest --pass-with-no-tests",
@@ -9,7 +9,7 @@
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"docs": "typedoc --json docs/typedoc-out.json src/index.ts && node scripts/docs.mjs",
"prepublishOnly": "yarn build && yarn lint && yarn test",
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/collection/*'"
"changelog": "git cliff --prepend ./CHANGELOG.md -l -c ./cliff.toml -r ../../ --include-path 'packages/collection/*'"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -48,23 +48,23 @@
},
"homepage": "https://discord.js.org",
"devDependencies": {
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
"@babel/core": "^7.18.2",
"@babel/preset-env": "^7.18.2",
"@babel/preset-typescript": "^7.17.12",
"@discordjs/ts-docgen": "^0.4.1",
"@types/jest": "^27.4.1",
"@types/node": "^16.11.27",
"@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0",
"eslint": "^8.13.0",
"@types/jest": "^28.1.0",
"@types/node": "^16.11.38",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"eslint": "^8.17.0",
"eslint-config-marine": "^9.4.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"jest": "^27.5.1",
"jest": "^28.1.0",
"prettier": "^2.6.2",
"tsup": "^5.12.5",
"typedoc": "^0.22.15",
"typescript": "^4.6.3"
"tsup": "^6.0.1",
"typedoc": "^0.22.17",
"typescript": "^4.7.3"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -185,7 +185,7 @@ export class Collection<K, V> extends Map<K, V> {
if (!arr.length || !amount) return [];
return Array.from(
{ length: Math.min(amount, arr.length) },
(): V => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
(): V => arr.splice(Math.floor(Math.random() * arr.length), 1)[0]!,
);
}
@@ -204,7 +204,7 @@ export class Collection<K, V> extends Map<K, V> {
if (!arr.length || !amount) return [];
return Array.from(
{ length: Math.min(amount, arr.length) },
(): K => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
(): K => arr.splice(Math.floor(Math.random() * arr.length), 1)[0]!,
);
}

View File

@@ -1,6 +1,6 @@
import type { Options } from 'tsup';
import { defineConfig } from 'tsup';
export const tsup: Options = {
export default defineConfig({
clean: true,
dts: true,
entryPoints: ['src/index.ts'],
@@ -18,4 +18,4 @@ export const tsup: Options = {
};
}
},
};
});

View File

@@ -2,6 +2,74 @@
All notable changes to this project will be documented in this file.
# [13.7.0](https://github.com/discordjs/discord.js/compare/13.6.0...13.7.0) - (2022-05-13)
## Bug Fixes
- **MessageEmbed:** Fix a typo (#7906) ([ea28638](https://github.com/discordjs/discord.js/commit/ea28638a0c7cebb40aab36c01ed29fed02935540))
- **GuildEditData:** Some fields can be null for v13 (#7633) ([816936e](https://github.com/discordjs/discord.js/commit/816936eafbe4b8caee10a15709a771919754f5ad))
- Apply v14 fix (#7756) ([ab6c2ba](https://github.com/discordjs/discord.js/commit/ab6c2bad845d44b5d822b131e5e458a3637431c6))
- **GuildChannelManager:** `delete` method accessing wrong id (#7771) ([c9e4562](https://github.com/discordjs/discord.js/commit/c9e4562fd58999452829501a3410da2b1709342f))
- **GuildScheduledEvent:** Handle missing `image` for v13 (#7627) ([dfea9c2](https://github.com/discordjs/discord.js/commit/dfea9c27cef4ec7472c39675c93de985b43fee87))
- **messagementions:** Fix `has` method for v13 (#7591) ([7a52785](https://github.com/discordjs/discord.js/commit/7a52785f7d3a0d2211b2ceff68f7152161a0b1f5))
- Check if member has admininistrator on `moderatable` (v13) (#7578) ([13dd82d](https://github.com/discordjs/discord.js/commit/13dd82d7fa6bc5906103cf8c1f4bb7de5d3e8270))
- **ThreadChannel:** Require `sendable` for `unarchivable` (#7555) ([49397c0](https://github.com/discordjs/discord.js/commit/49397c0ca4ec468a2046167afa64b7a82aa5e7fa))
- Backport `MessageReaction#me` being incorrectly `false` (#7553) ([5f621c1](https://github.com/discordjs/discord.js/commit/5f621c19959e892066b23f06f0b6485b6cecdb4c))
- **typings:** SweepStageInstances typo (#7521) ([f096069](https://github.com/discordjs/discord.js/commit/f0960698d2ef51d4586f3c89d8b7c94faf5d921b))
- **MessagePayload:** V13 don't set reply flags to target flags (#7515) ([30baff7](https://github.com/discordjs/discord.js/commit/30baff7ecbe5fdb5bea9ed5b14a28a76922d507c))
- **Shard:** V13 EventEmitter listener warning (#7479) ([77b8e01](https://github.com/discordjs/discord.js/commit/77b8e0191123b0e76832c642454adceab9b8ad13))
- **MessageEmbed:** Set footer to undefined (#7358) ([bc5ddc3](https://github.com/discordjs/discord.js/commit/bc5ddc36fa2e1935ffc8e871c333f6613a5f2396))
## Documentation
- **shardingmanager:** Fix type of `execArgv` option (v13) (#7863) ([43a7870](https://github.com/discordjs/discord.js/commit/43a7870b2337ebdc362640a799fe5e81cfbaf739))
- Fix and improve localization docs (v13 backport) (#7807) ([6dcf0bd](https://github.com/discordjs/discord.js/commit/6dcf0bda05ee79baa173dce02a4f8985f6f654df))
- **ApplicationCommand:** Fix ApplicationCommandOptionChoice (#7798) ([1040ce0](https://github.com/discordjs/discord.js/commit/1040ce0e710e0e805ad75302b9078a0230309ae8))
- Backport version 13 fixes (#7552) ([69ba067](https://github.com/discordjs/discord.js/commit/69ba067a6512fffd2be99ad0b5db2cbfcbb19c88))
## Features
- Backport (#7776) ([5165b18](https://github.com/discordjs/discord.js/commit/5165b18b85379411645ca377389b09a911fd3fc8))
- Backport (#7787) ([3eb45e3](https://github.com/discordjs/discord.js/commit/3eb45e30b3a3b1b5624d36698b7f1af6bff3cb6d))
- Backport (#7786) ([ab324ea](https://github.com/discordjs/discord.js/commit/ab324ea6ae042e8312e28dee1665729e6db29193))
- Add support for localized slash commands (v13 backport) (#7766) ([022e138](https://github.com/discordjs/discord.js/commit/022e138b9a5a9038e61b5bdabade7606c9341982))
- App authorization links and tags for v13 (#7731) ([9e4a900](https://github.com/discordjs/discord.js/commit/9e4a900e6d540ad611675ec54ba35e2b9da984dd))
- Backport (#7777) ([6c56132](https://github.com/discordjs/discord.js/commit/6c5613255ade937384525b17f2179f4086a501f1))
- Backport (#7778) ([ff49b82](https://github.com/discordjs/discord.js/commit/ff49b82db773f0407ac0e890897156fb52843b11))
- Backport (#7779) ([ae7f991](https://github.com/discordjs/discord.js/commit/ae7f991e8d08222483a1e92d6740fedadc479bd5))
- Backport (#7783) ([cedc333](https://github.com/discordjs/discord.js/commit/cedc3339401349dfa00990be204b203ef46a3545))
- **VoiceChannel:** Support `video_quality_mode` (v13) (#7785) ([6daee1b](https://github.com/discordjs/discord.js/commit/6daee1b235fc29eb09d9dd97cbbea619225ee2e1))
- **StageInstance:** Add support for associated guild event (#7713) ([68498a8](https://github.com/discordjs/discord.js/commit/68498a87be9436be95456768f50db638c06a6ca8))
- **modals:** Modals, input text components and modal submits, v13 style (#7431) ([e1cdcfa](https://github.com/discordjs/discord.js/commit/e1cdcfa9a6baed1d373cc5474630d32ce38db31e))
- Backport `Interaction#isRepliable` (#7563) ([5e8162a](https://github.com/discordjs/discord.js/commit/5e8162a1379cb3b8c02cc5b29e70911eb1389218))
- Add methods to managers for v13 (#7611) ([9f09702](https://github.com/discordjs/discord.js/commit/9f09702854d21fd11ab3f4e2f0eec445f294130e))
- Add `premiumSubscriptionCount` to `InviteGuild` (#7629) ([8e7d15e](https://github.com/discordjs/discord.js/commit/8e7d15e49d0b75687d4ae813d8274b7086959004))
- **scheduledevents:** Event cover images for v13 (#7613) ([a7535a2](https://github.com/discordjs/discord.js/commit/a7535a2232c4de4553d0d2a2cee315124e1bdfaa))
- Backport `MessageMentions` channel type fixes (#7562) ([93cdb2f](https://github.com/discordjs/discord.js/commit/93cdb2f2fa3ebde8f06cefe9de4a351b99f3b5e6))
- Backport cache types resolving to `never` (#7561) ([611d3a7](https://github.com/discordjs/discord.js/commit/611d3a7b2f76c8be2655d8f27ec4667e6c2054cf))
- Backport sending message flags (#7560) ([29d42ed](https://github.com/discordjs/discord.js/commit/29d42ed31959a0b5e518b46e45029b99cb15aa59))
- **ThreadChannel:** Backport creation timestamp (#7559) ([1d97dcf](https://github.com/discordjs/discord.js/commit/1d97dcff087b75e36d8d4b20e79ec3b820868024))
- Add custom image support to version 13 (#7557) ([679b87c](https://github.com/discordjs/discord.js/commit/679b87c4f88a7bd56bf81a9ade0fc2bf7e54495a))
- Backport `reason` on `pin` and `unpin` (#7556) ([b231bec](https://github.com/discordjs/discord.js/commit/b231bece0e0a1600bd4e2337a2ec83c9a8df11fd))
- **GuildPreview:** Add stickers to version 13 (#7554) ([215dfe0](https://github.com/discordjs/discord.js/commit/215dfe02d5482fcabccbc2373386eae15bdd866a))
- Backport `sweepStickers` method (#7558) ([ee1698d](https://github.com/discordjs/discord.js/commit/ee1698d92869280dcfdbfb353712ef97dff90b56))
- **scheduledevents:** Add image option (v13) (#7549) ([2fcf8af](https://github.com/discordjs/discord.js/commit/2fcf8af421b34b4908bb01b59cf748a1376e5535))
- **thread:** V13 add `newlyCreated` to `threadCreate` event (#7481) ([2b3db73](https://github.com/discordjs/discord.js/commit/2b3db734dfd0e9a3cbfdd45fd3aa490b82ddaa08))
- **commands:** Attachment options (#7441) ([5bcca8b](https://github.com/discordjs/discord.js/commit/5bcca8b97fc09d9f3149325e2b8d8bccdd61e354))
## Refactor
- Deprecate v13 properties and methods (#7782) ([b9802f4](https://github.com/discordjs/discord.js/commit/b9802f4b6f25da62a0bff049ccc165cce8c9d856))
- Remove non-breaking stuff (#7636) ([b9c5676](https://github.com/discordjs/discord.js/commit/b9c5676006a702f704e970b3027829663b9b0a65))
## Typings
- Fix ModalSubmitInteraction (#7768) ([1d09ad4](https://github.com/discordjs/discord.js/commit/1d09ad4652796ecf39a3146631120d5600f36b6a))
- **threadchannel:** Fix autoArchiveDuration types (#7817) ([7afcd95](https://github.com/discordjs/discord.js/commit/7afcd9594a5706526be20cb26f0de388b094c1b4))
- **InteractionCollector:** Fix guild and channel types (#7624) ([7814074](https://github.com/discordjs/discord.js/commit/78140748ce4a64977426a93fd72c9e2783e5919d))
- V13 channel create overloads fix (#7480) ([0b54089](https://github.com/discordjs/discord.js/commit/0b54089c43b60a325e02b78dd0126771ac71f746))
# [13.6.0](https://github.com/discordjs/discord.js/compare/13.5.1...13.6.0) - (2022-01-13)
## Documentation

View File

@@ -6,29 +6,32 @@ All notable changes to this project will be documented in this file.\n
"""
body = """
{% if version %}\
# [{{ version | trim_start_matches(pat="v") }}]\
{% if previous %}\
{% if previous.version %}\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
# [{{ version | trim_start_matches(pat="v") }}]\
{% if previous %}\
{% if previous.version %}\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
# [unreleased]
# [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
## {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}\
[**breaking**] \
{% endif %}\
{% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% endfor %}
## {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% if commit.breaking %}\
\n\n {% raw %} {% endraw %} ### Breaking Changes:\n \
{% for breakingChange in commit.footers %}\
{% raw %} {% endraw %} - {{ breakingChange }}\n\
{% endfor %}\
{% endif %}\
{% endfor %}
{% endfor %}\n
"""
trim = true
@@ -38,21 +41,21 @@ footer = ""
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^docs", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^typings", group = "Typings"},
{ message = "^types", group = "Typings"},
{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
{ message = "^revert", skip = true},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore", skip = true},
{ message = "^ci", skip = true},
{ message = "^build", skip = true},
{ body = ".*security", group = "Security"},
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^docs", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^typings", group = "Typings"},
{ message = "^types", group = "Typings"},
{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
{ message = "^revert", skip = true},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore", skip = true},
{ message = "^ci", skip = true},
{ message = "^build", skip = true},
{ body = ".*security", group = "Security"},
]
filter_commits = true
tag_pattern = "[0-9]*"

View File

@@ -50,30 +50,30 @@
"@discordjs/builders": "workspace:^",
"@discordjs/collection": "workspace:^",
"@discordjs/rest": "workspace:^",
"@sapphire/snowflake": "^3.2.1",
"@sapphire/snowflake": "^3.2.2",
"@types/ws": "^8.5.3",
"discord-api-types": "^0.31.1",
"discord-api-types": "^0.33.3",
"fast-deep-equal": "^3.1.3",
"lodash.snakecase": "^4.1.1",
"tslib": "^2.3.1",
"undici": "^4.16.0",
"ws": "^8.5.0"
"tslib": "^2.4.0",
"undici": "^5.4.0",
"ws": "^8.7.0"
},
"devDependencies": {
"@discordjs/docgen": "^0.11.1",
"@types/node": "^16.11.27",
"@types/node": "^16.11.38",
"dtslint": "^4.2.1",
"eslint": "^8.13.0",
"eslint": "^8.17.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^7.0.4",
"husky": "^8.0.1",
"is-ci": "^3.0.1",
"jest": "^27.5.1",
"jest": "^28.1.0",
"prettier": "^2.6.2",
"tsd": "^0.20.0",
"tslint": "^6.1.3",
"typescript": "^4.6.3"
"typescript": "^4.7.3"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -10,7 +10,7 @@ class WebhooksUpdate extends Action {
/**
* Emitted whenever a channel has its webhooks changed.
* @event Client#webhookUpdate
* @param {TextChannel|NewsChannel} channel The channel that had a webhook update
* @param {TextChannel|NewsChannel|VoiceChannel} channel The channel that had a webhook update
*/
if (channel) client.emit(Events.WebhooksUpdate, channel);
}

View File

@@ -71,7 +71,7 @@ const Messages = {
MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.',
MESSAGE_NONCE_TYPE: 'Message nonce must be an integer or a string.',
MESSAGE_CONTENT_TYPE: 'Message content must be a non-empty string.',
MESSAGE_CONTENT_TYPE: 'Message content must be a string.',
SPLIT_MAX_LEN: 'Chunk exceeds the max length and contains no split characters.',

View File

@@ -19,7 +19,6 @@ exports.Collection = require('@discordjs/collection').Collection;
exports.Constants = require('./util/Constants');
exports.Colors = require('./util/Colors');
exports.DataResolver = require('./util/DataResolver');
exports.EnumResolvers = require('./util/EnumResolvers');
exports.Events = require('./util/Events');
exports.Formatters = require('./util/Formatters');
exports.IntentsBitField = require('./util/IntentsBitField');
@@ -132,7 +131,7 @@ exports.MessageMentions = require('./structures/MessageMentions');
exports.MessagePayload = require('./structures/MessagePayload');
exports.MessageReaction = require('./structures/MessageReaction');
exports.ModalSubmitInteraction = require('./structures/ModalSubmitInteraction');
exports.ModalSubmitFieldsResolver = require('./structures/ModalSubmitFieldsResolver');
exports.ModalSubmitFields = require('./structures/ModalSubmitFields');
exports.NewsChannel = require('./structures/NewsChannel');
exports.OAuth2Guild = require('./structures/OAuth2Guild');
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
@@ -172,44 +171,9 @@ exports.WelcomeScreen = require('./structures/WelcomeScreen');
exports.WebSocket = require('./WebSocket');
// External
exports.ActivityType = require('discord-api-types/v10').ActivityType;
exports.ApplicationCommandType = require('discord-api-types/v10').ApplicationCommandType;
exports.ApplicationCommandOptionType = require('discord-api-types/v10').ApplicationCommandOptionType;
exports.ApplicationCommandPermissionType = require('discord-api-types/v10').ApplicationCommandPermissionType;
exports.AuditLogEvent = require('discord-api-types/v10').AuditLogEvent;
exports.ButtonStyle = require('discord-api-types/v10').ButtonStyle;
exports.ChannelType = require('discord-api-types/v10').ChannelType;
exports.ComponentType = require('discord-api-types/v10').ComponentType;
exports.GatewayCloseCodes = require('discord-api-types/v10').GatewayCloseCodes;
exports.GatewayDispatchEvents = require('discord-api-types/v10').GatewayDispatchEvents;
exports.GatewayIntentBits = require('discord-api-types/v10').GatewayIntentBits;
exports.GatewayOpcodes = require('discord-api-types/v10').GatewayOpcodes;
exports.GuildFeature = require('discord-api-types/v10').GuildFeature;
exports.GuildMFALevel = require('discord-api-types/v10').GuildMFALevel;
exports.GuildNSFWLevel = require('discord-api-types/v10').GuildNSFWLevel;
exports.GuildPremiumTier = require('discord-api-types/v10').GuildPremiumTier;
exports.GuildScheduledEventEntityType = require('discord-api-types/v10').GuildScheduledEventEntityType;
exports.GuildScheduledEventPrivacyLevel = require('discord-api-types/v10').GuildScheduledEventPrivacyLevel;
exports.GuildScheduledEventStatus = require('discord-api-types/v10').GuildScheduledEventStatus;
exports.GuildSystemChannelFlags = require('discord-api-types/v10').GuildSystemChannelFlags;
exports.GuildVerificationLevel = require('discord-api-types/v10').GuildVerificationLevel;
exports.InteractionType = require('discord-api-types/v10').InteractionType;
exports.InteractionResponseType = require('discord-api-types/v10').InteractionResponseType;
exports.InviteTargetType = require('discord-api-types/v10').InviteTargetType;
exports.Locale = require('discord-api-types/v10').Locale;
exports.MessageType = require('discord-api-types/v10').MessageType;
exports.MessageFlags = require('discord-api-types/v10').MessageFlags;
exports.OAuth2Scopes = require('discord-api-types/v10').OAuth2Scopes;
exports.PermissionFlagsBits = require('discord-api-types/v10').PermissionFlagsBits;
exports.RESTJSONErrorCodes = require('discord-api-types/v10').RESTJSONErrorCodes;
exports.StageInstancePrivacyLevel = require('discord-api-types/v10').StageInstancePrivacyLevel;
exports.StickerType = require('discord-api-types/v10').StickerType;
exports.StickerFormatType = require('discord-api-types/v10').StickerFormatType;
exports.TextInputStyle = require('discord-api-types/v10').TextInputStyle;
exports.UserFlags = require('discord-api-types/v10').UserFlags;
exports.WebhookType = require('discord-api-types/v10').WebhookType;
exports.DiscordAPIError = require('@discordjs/rest').DiscordAPIError;
exports.HTTPError = require('@discordjs/rest').HTTPError;
exports.RateLimitError = require('@discordjs/rest').RateLimitError;
__exportStar(require('discord-api-types/v10'), exports);
__exportStar(require('@discordjs/builders'), exports);

View File

@@ -48,6 +48,7 @@ class CategoryChannelChildManager extends DataManager {
* @property {number} [position] Position of the new channel
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
* @property {string} [rtcRegion] The specific region of the new channel.
* @property {VideoQualityMode} [videoQualityMode] The camera video quality mode of the voice channel
* @property {string} [reason] Reason for creating the new channel
*/

View File

@@ -61,6 +61,11 @@ class ChannelManager extends CachedManager {
_remove(id) {
const channel = this.cache.get(id);
channel?.guild?.channels.cache.delete(id);
for (const [code, invite] of channel?.guild?.invites.cache ?? []) {
if (invite.channelId === id) channel.guild.invites.cache.delete(code);
}
channel?.parent?.threads?.cache.delete(id);
this.cache.delete(id);
}

View File

@@ -13,7 +13,6 @@ const Webhook = require('../structures/Webhook');
const { ThreadChannelTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Util = require('../util/Util');
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
let cacheWarningEmitted = false;
@@ -138,6 +137,7 @@ class GuildChannelManager extends CachedManager {
position,
rateLimitPerUser,
rtcRegion,
videoQualityMode,
reason,
} = {},
) {
@@ -157,6 +157,7 @@ class GuildChannelManager extends CachedManager {
permission_overwrites: permissionOverwrites,
rate_limit_per_user: rateLimitPerUser,
rtc_region: rtcRegion,
video_quality_mode: videoQualityMode,
},
reason,
});
@@ -253,9 +254,6 @@ class GuildChannelManager extends CachedManager {
}
}
let defaultAutoArchiveDuration = data.defaultAutoArchiveDuration;
if (defaultAutoArchiveDuration === 'MAX') defaultAutoArchiveDuration = resolveAutoArchiveMaxLimit(this.guild);
const newData = await this.client.rest.patch(Routes.channel(channel.id), {
body: {
name: (data.name ?? channel.name).trim(),
@@ -269,7 +267,7 @@ class GuildChannelManager extends CachedManager {
parent_id: parent,
lock_permissions: data.lockPermissions,
rate_limit_per_user: data.rateLimitPerUser,
default_auto_archive_duration: defaultAutoArchiveDuration,
default_auto_archive_duration: data.defaultAutoArchiveDuration,
permission_overwrites,
},
reason,
@@ -433,6 +431,7 @@ class GuildChannelManager extends CachedManager {
const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
await this.client.rest.delete(Routes.channel(id), { reason });
this.client.actions.ChannelDelete.handle({ id });
}
}

View File

@@ -153,7 +153,7 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
throw new Error('EMOJI_MANAGED');
}
const { me } = this.guild;
const { me } = this.guild.members;
if (!me) throw new Error('GUILD_UNCACHED_ME');
if (!me.permissions.has(PermissionFlagsBits.ManageEmojisAndStickers)) {
throw new Error('MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION', this.guild);

View File

@@ -12,6 +12,7 @@ const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel');
const { GuildMember } = require('../structures/GuildMember');
const { Role } = require('../structures/Role');
const Events = require('../util/Events');
const Partials = require('../util/Partials');
/**
* Manages API methods for GuildMembers and stores their cache.
@@ -120,6 +121,20 @@ class GuildMemberManager extends CachedManager {
return data instanceof Buffer ? (options.fetchWhenExisting === false ? null : this.fetch(userId)) : this._add(data);
}
/**
* The client user as a GuildMember of this guild
* @type {?GuildMember}
* @readonly
*/
get me() {
return (
this.resolve(this.client.user.id) ??
(this.client.options.partials.includes(Partials.GuildMember)
? this._add({ user: { id: this.client.user.id } }, true)
: null)
);
}
/**
* Options used to fetch a single member from a guild.
* @typedef {BaseFetchOptions} FetchMemberOptions

View File

@@ -44,7 +44,7 @@ class MessageManager extends CachedManager {
/**
* Options used to fetch a message.
* @typedef {BaseFetchOptions} FetchMessageOptions
* @property {MessageResolvable} [message] The message to fetch
* @property {MessageResolvable} message The message to fetch
*/
/**

View File

@@ -6,7 +6,6 @@ const { ChannelType, Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const ThreadChannel = require('../structures/ThreadChannel');
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
/**
* Manages API methods for {@link ThreadChannel} objects and stores their cache.
@@ -122,8 +121,6 @@ class ThreadManager extends CachedManager {
resolvedType = type ?? resolvedType;
}
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
const data = await this.client.rest.post(Routes.threads(this.channel.id, startMessageId), {
body: {
name,

View File

@@ -37,6 +37,15 @@ class ThreadMemberManager extends CachedManager {
return member;
}
/**
* The client user as a ThreadMember of this ThreadChannel
* @type {?ThreadMember}
* @readonly
*/
get me() {
return this.resolve(this.client.user.id);
}
/**
* Data that resolves to give a ThreadMember object. This can be:
* * A ThreadMember object

View File

@@ -1,6 +1,7 @@
'use strict';
const { ActionRowBuilder: BuildersActionRow, ComponentBuilder } = require('@discordjs/builders');
const Components = require('../util/Components');
const Transformers = require('../util/Transformers');
/**
@@ -11,7 +12,7 @@ class ActionRowBuilder extends BuildersActionRow {
constructor({ components, ...data } = {}) {
super({
...Transformers.toSnakeCase(data),
components: components?.map(c => (c instanceof ComponentBuilder ? c : Transformers.toSnakeCase(c))),
components: components?.map(c => (c instanceof ComponentBuilder ? c : Components.createComponentBuilder(c))),
});
}
}

View File

@@ -2,6 +2,7 @@
const { DiscordSnowflake } = require('@sapphire/snowflake');
const { ApplicationCommandOptionType } = require('discord-api-types/v10');
const isEqual = require('fast-deep-equal');
const Base = require('./Base');
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
@@ -65,7 +66,7 @@ class ApplicationCommand extends Base {
if ('name_localizations' in data) {
/**
* The name localizations for this command
* @type {?Object<string, string>}
* @type {?Object<Locale, string>}
*/
this.nameLocalizations = data.name_localizations;
} else {
@@ -75,7 +76,7 @@ class ApplicationCommand extends Base {
if ('name_localized' in data) {
/**
* The localized name for this command
* @type {?Object<string, string>}
* @type {?string}
*/
this.nameLocalized = data.name_localized;
} else {
@@ -93,7 +94,7 @@ class ApplicationCommand extends Base {
if ('description_localizations' in data) {
/**
* The description localizations for this command
* @type {?string}
* @type {?Object<Locale, string>}
*/
this.descriptionLocalizations = data.description_localizations;
} else {
@@ -169,9 +170,9 @@ class ApplicationCommand extends Base {
* @typedef {Object} ApplicationCommandData
* @property {string} name The name of the command, must be in all lowercase if type is
* {@link ApplicationCommandType.ChatInput}
* @property {Object<string, string>} [nameLocalizations] The localizations for the command name
* @property {Object<Locale, string>} [nameLocalizations] The localizations for the command name
* @property {string} description The description of the command, if type is {@link ApplicationCommandType.ChatInput}
* @property {Object<string, string>} [descriptionLocalizations] The localizations for the command description,
* @property {Object<Locale, string>} [descriptionLocalizations] The localizations for the command description,
* if type is {@link ApplicationCommandType.ChatInput}
* @property {ApplicationCommandType} [type=ApplicationCommandType.ChatInput] The type of the command
* @property {ApplicationCommandOptionData[]} [options] Options for the command
@@ -188,9 +189,9 @@ class ApplicationCommand extends Base {
* @typedef {Object} ApplicationCommandOptionData
* @property {ApplicationCommandOptionType} type The type of the option
* @property {string} name The name of the option
* @property {Object<string, string>} [nameLocalizations] The name localizations for the option
* @property {Object<Locale, string>} [nameLocalizations] The name localizations for the option
* @property {string} description The description of the option
* @property {Object<string, string>} [descriptionLocalizations] The description localizations for the option
* @property {Object<Locale, string>} [descriptionLocalizations] The description localizations for the option
* @property {boolean} [autocomplete] Whether the autocomplete interaction is enabled for a
* {@link ApplicationCommandOptionType.String}, {@link ApplicationCommandOptionType.Integer} or
* {@link ApplicationCommandOptionType.Number} option
@@ -208,15 +209,10 @@ class ApplicationCommand extends Base {
/**
* @typedef {Object} ApplicationCommandOptionChoiceData
* @property {string} name The name of the choice
* @property {Object<string, string>} [nameLocalizations] The localized names for this choice
* @property {Object<Locale, string>} [nameLocalizations] The localized names for this choice
* @property {string|number} value The value of the choice
*/
/**
* @typedef {ApplicationCommandOptionChoiceData} ApplicationCommandOptionChoice
* @property {string} [nameLocalized] The localized name for this choice
*/
/**
* Edits this application command.
* @param {ApplicationCommandData} data The data to update the command with
@@ -244,7 +240,7 @@ class ApplicationCommand extends Base {
/**
* Edits the localized names of this ApplicationCommand
* @param {Object<string, string>} nameLocalizations The new localized names for the command
* @param {Object<Locale, string>} nameLocalizations The new localized names for the command
* @returns {Promise<ApplicationCommand>}
* @example
* // Edit the name localizations of this command
@@ -270,11 +266,11 @@ class ApplicationCommand extends Base {
/**
* Edits the localized descriptions of this ApplicationCommand
* @param {Object<string, string>} descriptionLocalizations The new localized descriptions for the command
* @param {Object<Locale, string>} descriptionLocalizations The new localized descriptions for the command
* @returns {Promise<ApplicationCommand>}
* @example
* // Edit the description localizations of this command
* command.setLocalizedDescriptions({
* command.setDescriptionLocalizations({
* 'en-GB': 'A test command',
* 'pt-BR': 'Um comando de teste',
* })
@@ -339,7 +335,12 @@ class ApplicationCommand extends Base {
// Future proof for options being nullable
// TODO: remove ?? 0 on each when nullable
(command.options?.length ?? 0) !== (this.options?.length ?? 0) ||
(command.defaultPermission ?? command.default_permission ?? true) !== this.defaultPermission
(command.defaultPermission ?? command.default_permission ?? true) !== this.defaultPermission ||
!isEqual(command.nameLocalizations ?? command.name_localizations ?? {}, this.nameLocalizations ?? {}) ||
!isEqual(
command.descriptionLocalizations ?? command.description_localizations ?? {},
this.descriptionLocalizations ?? {},
)
) {
return false;
}
@@ -398,7 +399,12 @@ class ApplicationCommand extends Base {
option.options?.length !== existing.options?.length ||
(option.channelTypes ?? option.channel_types)?.length !== existing.channelTypes?.length ||
(option.minValue ?? option.min_value) !== existing.minValue ||
(option.maxValue ?? option.max_value) !== existing.maxValue
(option.maxValue ?? option.max_value) !== existing.maxValue ||
!isEqual(option.nameLocalizations ?? option.name_localizations ?? {}, existing.nameLocalizations ?? {}) ||
!isEqual(
option.descriptionLocalizations ?? option.description_localizations ?? {},
existing.descriptionLocalizations ?? {},
)
) {
return false;
}
@@ -407,7 +413,13 @@ class ApplicationCommand extends Base {
if (
enforceOptionOrder &&
!existing.choices.every(
(choice, index) => choice.name === option.choices[index].name && choice.value === option.choices[index].value,
(choice, index) =>
choice.name === option.choices[index].name &&
choice.value === option.choices[index].value &&
isEqual(
choice.nameLocalizations ?? {},
option.choices[index].nameLocalizations ?? option.choices[index].name_localizations ?? {},
),
)
) {
return false;
@@ -439,10 +451,10 @@ class ApplicationCommand extends Base {
* @typedef {Object} ApplicationCommandOption
* @property {ApplicationCommandOptionType} type The type of the option
* @property {string} name The name of the option
* @property {Object<string, string>} [nameLocalizations] The localizations for the option name
* @property {Object<Locale, string>} [nameLocalizations] The localizations for the option name
* @property {string} [nameLocalized] The localized name for this option
* @property {string} description The description of the option
* @property {Object<string, string>} [descriptionLocalizations] The localizations for the option description
* @property {Object<Locale, string>} [descriptionLocalizations] The localizations for the option description
* @property {string} [descriptionLocalized] The localized description for this option
* @property {boolean} [required] Whether the option is required
* @property {boolean} [autocomplete] Whether the autocomplete interaction is enabled for a
@@ -458,6 +470,15 @@ class ApplicationCommand extends Base {
* {@link ApplicationCommandOptionType.Number} option
*/
/**
* A choice for an application command option.
* @typedef {Object} ApplicationCommandOptionChoice
* @property {string} name The name of the choice
* @property {?string} nameLocalized The localized name of the choice in the provided locale, if any
* @property {?Object<string, string>} [nameLocalizations] The localized names for this choice
* @property {string|number} value The value of the choice
*/
/**
* Transforms an {@link ApplicationCommandOptionData} object into something that can be used with the API.
* @param {ApplicationCommandOptionData|ApplicationCommandOption} option The option to transform

View File

@@ -109,44 +109,6 @@ class BaseGuildTextChannel extends GuildChannel {
return this.edit({ type }, reason);
}
/**
* Fetches all webhooks for the channel.
* @returns {Promise<Collection<Snowflake, Webhook>>}
* @example
* // Fetch webhooks
* channel.fetchWebhooks()
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
* .catch(console.error);
*/
fetchWebhooks() {
return this.guild.channels.fetchWebhooks(this.id);
}
/**
* Options used to create a {@link Webhook} in a {@link TextChannel} or a {@link NewsChannel}.
* @typedef {Object} ChannelWebhookCreateOptions
* @property {?(BufferResolvable|Base64Resolvable)} [avatar] Avatar for the webhook
* @property {string} [reason] Reason for creating the webhook
*/
/**
* Creates a webhook for the channel.
* @param {string} name The name of the webhook
* @param {ChannelWebhookCreateOptions} [options] Options for creating the webhook
* @returns {Promise<Webhook>} Returns the created Webhook
* @example
* // Create a webhook for the current channel
* channel.createWebhook('Snek', {
* avatar: 'https://i.imgur.com/mI8XcpG.jpg',
* reason: 'Needed a cool new Webhook'
* })
* .then(console.log)
* .catch(console.error)
*/
createWebhook(name, options = {}) {
return this.guild.channels.createWebhook(this.id, name, options);
}
/**
* Sets a new topic for the guild channel.
* @param {?string} topic The new topic for the guild channel
@@ -222,6 +184,8 @@ class BaseGuildTextChannel extends GuildChannel {
createMessageComponentCollector() {}
awaitMessageComponent() {}
bulkDelete() {}
fetchWebhooks() {}
createWebhook() {}
}
TextBasedChannel.applyToClass(BaseGuildTextChannel, true);

View File

@@ -75,7 +75,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
return (
this.guild.me.communicationDisabledUntilTimestamp < Date.now() &&
this.guild.members.me.communicationDisabledUntilTimestamp < Date.now() &&
permissions.has(PermissionFlagsBits.Connect, false)
);
}

View File

@@ -112,8 +112,10 @@ class DMChannel extends Channel {
createMessageComponentCollector() {}
awaitMessageComponent() {}
// Doesn't work on DM channels; bulkDelete() {}
// Doesn't work on DM channels; fetchWebhooks() {}
// Doesn't work on DM channels; createWebhook() {}
}
TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete']);
TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete', 'fetchWebhooks', 'createWebhook']);
module.exports = DMChannel;

View File

@@ -25,7 +25,6 @@ const RoleManager = require('../managers/RoleManager');
const StageInstanceManager = require('../managers/StageInstanceManager');
const VoiceStateManager = require('../managers/VoiceStateManager');
const DataResolver = require('../util/DataResolver');
const Partials = require('../util/Partials');
const Status = require('../util/Status');
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
const Util = require('../util/Util');
@@ -336,8 +335,7 @@ class Guild extends AnonymousGuild {
if ('preferred_locale' in data) {
/**
* The preferred locale of the guild, defaults to `en-US`
* @type {string}
* @see {@link https://discord.com/developers/docs/reference#locales}
* @type {Locale}
*/
this.preferredLocale = data.preferred_locale;
}
@@ -506,20 +504,6 @@ class Guild extends AnonymousGuild {
return this.client.channels.resolve(this.publicUpdatesChannelId);
}
/**
* The client user as a GuildMember of this guild
* @type {?GuildMember}
* @readonly
*/
get me() {
return (
this.members.resolve(this.client.user.id) ??
(this.client.options.partials.includes(Partials.GuildMember)
? this.members._add({ user: { id: this.client.user.id } }, true)
: null)
);
}
/**
* The maximum bitrate available for this guild
* @type {number}

View File

@@ -55,6 +55,17 @@ class GuildAuditLogs {
}
}
/**
* Cached {@link GuildScheduledEvent}s.
* @type {Collection<Snowflake, GuildScheduledEvent>}
* @private
*/
this.guildScheduledEvents = data.guild_scheduled_events.reduce(
(guildScheduledEvents, guildScheduledEvent) =>
guildScheduledEvents.set(guildScheduledEvent.id, guild.scheduledEvents._add(guildScheduledEvent)),
new Collection(),
);
/**
* The entries for this guild's audit logs
* @type {Collection<Snowflake, GuildAuditLogsEntry>}

View File

@@ -116,9 +116,9 @@ class GuildAuditLogsEntry {
/**
* Specific property changes
* @type {?AuditLogChange[]}
* @type {AuditLogChange[]}
*/
this.changes = data.changes?.map(c => ({ key: c.key, old: c.old_value, new: c.new_value })) ?? null;
this.changes = data.changes?.map(c => ({ key: c.key, old: c.old_value, new: c.new_value })) ?? [];
/**
* The entry's id

View File

@@ -416,7 +416,7 @@ class GuildChannel extends Channel {
// This flag allows managing even if timed out
if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
if (this.guild.me.communicationDisabledUntilTimestamp > Date.now()) return false;
if (this.guild.members.me.communicationDisabledUntilTimestamp > Date.now()) return false;
const bitfield = VoiceBasedChannelTypes.includes(this.type)
? PermissionFlagsBits.ManageChannels | PermissionFlagsBits.Connect

View File

@@ -55,8 +55,8 @@ class GuildEmoji extends BaseGuildEmoji {
* @readonly
*/
get deletable() {
if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
return !this.managed && this.guild.me.permissions.has(PermissionFlagsBits.ManageEmojisAndStickers);
if (!this.guild.members.me) throw new Error('GUILD_UNCACHED_ME');
return !this.managed && this.guild.members.me.permissions.has(PermissionFlagsBits.ManageEmojisAndStickers);
}
/**

View File

@@ -239,8 +239,8 @@ class GuildMember extends Base {
if (this.user.id === this.guild.ownerId) return false;
if (this.user.id === this.client.user.id) return false;
if (this.client.user.id === this.guild.ownerId) return true;
if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
return this.guild.me.roles.highest.comparePositionTo(this.roles.highest) > 0;
if (!this.guild.members.me) throw new Error('GUILD_UNCACHED_ME');
return this.guild.members.me.roles.highest.comparePositionTo(this.roles.highest) > 0;
}
/**
@@ -249,8 +249,8 @@ class GuildMember extends Base {
* @readonly
*/
get kickable() {
if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
return this.manageable && this.guild.me.permissions.has(PermissionFlagsBits.KickMembers);
if (!this.guild.members.me) throw new Error('GUILD_UNCACHED_ME');
return this.manageable && this.guild.members.me.permissions.has(PermissionFlagsBits.KickMembers);
}
/**
@@ -259,8 +259,8 @@ class GuildMember extends Base {
* @readonly
*/
get bannable() {
if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
return this.manageable && this.guild.me.permissions.has(PermissionFlagsBits.BanMembers);
if (!this.guild.members.me) throw new Error('GUILD_UNCACHED_ME');
return this.manageable && this.guild.members.me.permissions.has(PermissionFlagsBits.BanMembers);
}
/**
@@ -272,7 +272,7 @@ class GuildMember extends Base {
return (
!this.permissions.has(PermissionFlagsBits.Administrator) &&
this.manageable &&
(this.guild.me?.permissions.has(PermissionFlagsBits.ModerateMembers) ?? false)
(this.guild.members.me?.permissions.has(PermissionFlagsBits.ModerateMembers) ?? false)
);
}

View File

@@ -52,9 +52,9 @@ class Integration extends Base {
/**
* Whether this integration is enabled
* @type {boolean}
* @type {?boolean}
*/
this.enabled = data.enabled;
this.enabled = data.enabled ?? null;
if ('syncing' in data) {
/**

View File

@@ -78,15 +78,50 @@ class Interaction extends Base {
: null;
/**
* The locale of the user who invoked this interaction
* @type {string}
* A Discord locale string, possible values are:
* * en-US (English, US)
* * en-GB (English, UK)
* * bg (Bulgarian)
* * zh-CN (Chinese, China)
* * zh-TW (Chinese, Taiwan)
* * hr (Croatian)
* * cs (Czech)
* * da (Danish)
* * nl (Dutch)
* * fi (Finnish)
* * fr (French)
* * de (German)
* * el (Greek)
* * hi (Hindi)
* * hu (Hungarian)
* * it (Italian)
* * ja (Japanese)
* * ko (Korean)
* * lt (Lithuanian)
* * no (Norwegian)
* * pl (Polish)
* * pt-BR (Portuguese, Brazilian)
* * ro (Romanian, Romania)
* * ru (Russian)
* * es-ES (Spanish)
* * sv-SE (Swedish)
* * th (Thai)
* * tr (Turkish)
* * uk (Ukrainian)
* * vi (Vietnamese)
* @see {@link https://discord.com/developers/docs/reference#locales}
* @typedef {string} Locale
*/
/**
* The locale of the user who invoked this interaction
* @type {Locale}
*/
this.locale = data.locale;
/**
* The preferred locale from the guild this interaction was sent in
* @type {?string}
* @type {?Locale}
*/
this.guildLocale = data.guild_locale ?? null;
}

View File

@@ -197,7 +197,7 @@ class InteractionCollector extends Collector {
if (this.options.max && this.total >= this.options.max) return 'limit';
if (this.options.maxComponents && this.collected.size >= this.options.maxComponents) return 'componentLimit';
if (this.options.maxUsers && this.users.size >= this.options.maxUsers) return 'userLimit';
return null;
return super.endReason;
}
/**
@@ -211,7 +211,7 @@ class InteractionCollector extends Collector {
this.stop('messageDelete');
}
if (message.interaction.id === this.messageInteractionId) {
if (message.interaction?.id === this.messageInteractionId) {
this.stop('messageDelete');
}
}

View File

@@ -233,10 +233,10 @@ class Invite extends Base {
get deletable() {
const guild = this.guild;
if (!guild || !this.client.guilds.cache.has(guild.id)) return false;
if (!guild.me) throw new Error('GUILD_UNCACHED_ME');
if (!guild.members.me) throw new Error('GUILD_UNCACHED_ME');
return Boolean(
this.channel?.permissionsFor(this.client.user).has(PermissionFlagsBits.ManageChannels, false) ||
guild.me.permissions.has(PermissionFlagsBits.ManageGuild),
guild.members.me.permissions.has(PermissionFlagsBits.ManageGuild),
);
}

View File

@@ -351,7 +351,7 @@ class Message extends Base {
/**
* The channel that the message was sent in
* @type {TextChannel|DMChannel|NewsChannel|ThreadChannel}
* @type {TextBasedChannel}
* @readonly
*/
get channel() {
@@ -583,7 +583,7 @@ class Message extends Base {
return Boolean(
this.author.id === this.client.user.id ||
(permissions.has(PermissionFlagsBits.ManageMessages, false) &&
this.guild.me.communicationDisabledUntilTimestamp < Date.now()),
this.guild.members.me.communicationDisabledUntilTimestamp < Date.now()),
);
}
@@ -797,9 +797,8 @@ class Message extends Base {
* archived. This can be:
* * `60` (1 hour)
* * `1440` (1 day)
* * `4320` (3 days) <warn>This is only available when the guild has the `THREE_DAY_THREAD_ARCHIVE` feature.</warn>
* * `10080` (7 days) <warn>This is only available when the guild has the `SEVEN_DAY_THREAD_ARCHIVE` feature.</warn>
* * `'MAX'` Based on the guild's features
* * `4320` (3 days)
* * `10080` (7 days)
* @typedef {number|string} ThreadAutoArchiveDuration
*/

View File

@@ -103,7 +103,7 @@ class MessageCollector extends Collector {
get endReason() {
if (this.options.max && this.collected.size >= this.options.max) return 'limit';
if (this.options.maxProcessed && this.received === this.options.maxProcessed) return 'processedLimit';
return null;
return super.endReason;
}
/**

View File

@@ -105,7 +105,7 @@ class MessagePayload {
if (this.options.content === null) {
content = '';
} else if (typeof this.options.content !== 'undefined') {
content = Util.verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', false);
content = Util.verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', true);
}
return content;
@@ -276,7 +276,7 @@ module.exports = MessagePayload;
/**
* A target for a message.
* @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
* @typedef {TextBasedChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
* Message|MessageManager} MessageTarget
*/

View File

@@ -7,17 +7,17 @@ const { TypeError } = require('../errors');
/**
* Represents the serialized fields from a modal submit interaction
*/
class ModalSubmitFieldsResolver {
class ModalSubmitFields {
constructor(components) {
/**
* The components within the modal
* @type {Array<ActionRow<ModalFieldData>>} The components in the modal
* @type {ActionRowModalData[]} The components in the modal
*/
this.components = components;
/**
* The extracted fields from the modal
* @type {Collection<string, ModalFieldData>} The fields in the modal
* @type {Collection<string, ModalData>} The fields in the modal
*/
this.fields = components.reduce((accumulator, next) => {
next.components.forEach(c => accumulator.set(c.customId, c));
@@ -28,11 +28,17 @@ class ModalSubmitFieldsResolver {
/**
* Gets a field given a custom id from a component
* @param {string} customId The custom id of the component
* @returns {ModalFieldData}
* @param {ComponentType} [type] The type of the component
* @returns {ModalData}
*/
getField(customId) {
getField(customId, type) {
const field = this.fields.get(customId);
if (!field) throw new TypeError('MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND', customId);
if (type !== undefined && type !== field.type) {
throw new TypeError('MODAL_SUBMIT_INTERACTION_FIELD_TYPE', customId, field.type, type);
}
return field;
}
@@ -42,13 +48,8 @@ class ModalSubmitFieldsResolver {
* @returns {string}
*/
getTextInputValue(customId) {
const field = this.getField(customId);
const expectedType = ComponentType.TextInput;
if (field.type !== expectedType) {
throw new TypeError('MODAL_SUBMIT_INTERACTION_FIELD_TYPE', customId, field.type, expectedType);
}
return field.value;
return this.getField(customId, ComponentType.TextInput).value;
}
}
module.exports = ModalSubmitFieldsResolver;
module.exports = ModalSubmitFields;

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