mirror of
https://github.com/discordjs/discord.js.git
synced 2026-05-23 12:00:09 +00:00
Compare commits
210 Commits
@discordjs
...
@discordjs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52a9e213c2 | ||
|
|
b7e62380f2 | ||
|
|
cafde77d73 | ||
|
|
b7d4e55419 | ||
|
|
5aeed99350 | ||
|
|
452dec57ca | ||
|
|
64f814066c | ||
|
|
33a7a5cbdc | ||
|
|
edf83f02ea | ||
|
|
25bd771559 | ||
|
|
f2ca0ca6f9 | ||
|
|
6712de9752 | ||
|
|
28cd293f14 | ||
|
|
3f5690afe1 | ||
|
|
015ab69956 | ||
|
|
caecc574f0 | ||
|
|
3bf30b1e6d | ||
|
|
103a3584c9 | ||
|
|
bf65b37d1a | ||
|
|
7e6dbaaed9 | ||
|
|
1a6ddbbe7b | ||
|
|
32f9056b15 | ||
|
|
3648f6d567 | ||
|
|
bddb6a461c | ||
|
|
fe34f48efb | ||
|
|
30a8d3231f | ||
|
|
1ed605eaa4 | ||
|
|
787654816d | ||
|
|
f0b68d5736 | ||
|
|
75256153a9 | ||
|
|
33ae7df000 | ||
|
|
feb3bdda0a | ||
|
|
e78c9c9ee9 | ||
|
|
43f62bb667 | ||
|
|
96c8d21f95 | ||
|
|
10ba0080cc | ||
|
|
0b979b04f2 | ||
|
|
a4d1862982 | ||
|
|
34531c45e3 | ||
|
|
8198da5cd0 | ||
|
|
ba10637529 | ||
|
|
68c3d8743e | ||
|
|
a51f7215ec | ||
|
|
c271e05223 | ||
|
|
d2e74003d5 | ||
|
|
fd1c24036f | ||
|
|
eb9ad46d4f | ||
|
|
5bd6b28b3e | ||
|
|
f6db285c07 | ||
|
|
68ade870f8 | ||
|
|
c7a205f7b9 | ||
|
|
c5750d59f5 | ||
|
|
31d5930464 | ||
|
|
cdd9214212 | ||
|
|
fa010b5162 | ||
|
|
6b20645740 | ||
|
|
50d55bd6b8 | ||
|
|
2d9dfa3c6e | ||
|
|
ab238a9046 | ||
|
|
94ee60d3d4 | ||
|
|
f10f4cdcd8 | ||
|
|
d95197cc78 | ||
|
|
e0c8282490 | ||
|
|
741b3c8e27 | ||
|
|
819a1fdf7d | ||
|
|
ce9afbb8e4 | ||
|
|
b2776c22d4 | ||
|
|
525bf031a5 | ||
|
|
27d8deb471 | ||
|
|
11b1739319 | ||
|
|
b83e0c0caf | ||
|
|
8421f9203b | ||
|
|
cb3dca4ae0 | ||
|
|
002d6a5aed | ||
|
|
c4653f97b1 | ||
|
|
e24970e3c3 | ||
|
|
65dc8d677e | ||
|
|
0ffbef506a | ||
|
|
5d3dd55a26 | ||
|
|
70b42bb64a | ||
|
|
af04992ed3 | ||
|
|
bbdb5d980b | ||
|
|
7279f9c31b | ||
|
|
5e5853a4e8 | ||
|
|
cd17aad720 | ||
|
|
ee36d60dc6 | ||
|
|
c34c02ab8d | ||
|
|
31f658247f | ||
|
|
a3799f9ebb | ||
|
|
a061233510 | ||
|
|
203bc4a2cf | ||
|
|
65d1879c0a | ||
|
|
c6f285b7b0 | ||
|
|
2eeaad6f27 | ||
|
|
f1ac17c961 | ||
|
|
db2b0333d9 | ||
|
|
94f7ca9474 | ||
|
|
e68effa822 | ||
|
|
358c3f4a46 | ||
|
|
268a9b4be5 | ||
|
|
95e6d6ede0 | ||
|
|
17867f9154 | ||
|
|
3a77ce0b18 | ||
|
|
23e183a9ac | ||
|
|
b3c3a94d5d | ||
|
|
8610700c87 | ||
|
|
db663a55c2 | ||
|
|
0673ea377a | ||
|
|
2681929e42 | ||
|
|
0a138dab95 | ||
|
|
415513696c | ||
|
|
90a98fee16 | ||
|
|
386c41f24f | ||
|
|
d54bf5d286 | ||
|
|
e5ec1c4dbc | ||
|
|
ad9ab2b177 | ||
|
|
4df491ce85 | ||
|
|
0a44b05db8 | ||
|
|
b4e28a8ff6 | ||
|
|
9c0f190de1 | ||
|
|
093117d938 | ||
|
|
e53d162198 | ||
|
|
51eadf3737 | ||
|
|
552ec72542 | ||
|
|
ac7bf692bf | ||
|
|
9964454c29 | ||
|
|
476b7d519c | ||
|
|
10a6c4287d | ||
|
|
a2eebf6c66 | ||
|
|
96053babe1 | ||
|
|
f527dea36e | ||
|
|
c7391db11b | ||
|
|
c5176be14b | ||
|
|
fbe67e1025 | ||
|
|
3bb9c0e5c3 | ||
|
|
9c8b3102ce | ||
|
|
2392a6f5de | ||
|
|
c0f079d232 | ||
|
|
d8077c6839 | ||
|
|
d4b41dd081 | ||
|
|
0415300243 | ||
|
|
caee94897f | ||
|
|
b3346f4b9b | ||
|
|
2791c86cdf | ||
|
|
c8f1690896 | ||
|
|
da9107c007 | ||
|
|
f57d6768ad | ||
|
|
d7b8357dcb | ||
|
|
16810f3e41 | ||
|
|
598f61b992 | ||
|
|
50401453e7 | ||
|
|
94bdcaca62 | ||
|
|
eea139b346 | ||
|
|
50822f5254 | ||
|
|
f2b267c079 | ||
|
|
b2eea1c900 | ||
|
|
3ae2633c3f | ||
|
|
3937b402c0 | ||
|
|
256c4f955c | ||
|
|
33cdcdbb7a | ||
|
|
bc466a5997 | ||
|
|
127931d1df | ||
|
|
5259639c2c | ||
|
|
5de9b80814 | ||
|
|
bc4fbcef2e | ||
|
|
3279b40912 | ||
|
|
1afae909d7 | ||
|
|
65cb36166f | ||
|
|
ecc6600df2 | ||
|
|
314d76e907 | ||
|
|
769ea0bfe7 | ||
|
|
8b979c0245 | ||
|
|
5475767c2c | ||
|
|
86d8fbc023 | ||
|
|
fba9710fc9 | ||
|
|
5ca3974301 | ||
|
|
3202c91c5a | ||
|
|
f3f34f07b3 | ||
|
|
f8ed71bfca | ||
|
|
e1176faa27 | ||
|
|
aa59a409b3 | ||
|
|
7fa698d23e | ||
|
|
b9df37a89f | ||
|
|
ad75be9a9c | ||
|
|
70c733bb9a | ||
|
|
a3287782b5 | ||
|
|
0ccc243c8f | ||
|
|
0a7953e463 | ||
|
|
6aa623240e | ||
|
|
6266b0c1e3 | ||
|
|
9720e55534 | ||
|
|
ad36c0be77 | ||
|
|
5987dbe5cf | ||
|
|
5244fe3c1c | ||
|
|
349766dd69 | ||
|
|
7a1095b66b | ||
|
|
10009a43ee | ||
|
|
8d8e6c03de | ||
|
|
dfadcbc2fd | ||
|
|
546d48655f | ||
|
|
f8739bd9c0 | ||
|
|
f2ae1f9348 | ||
|
|
3401fd4eb6 | ||
|
|
fdbd229832 | ||
|
|
9e8e2411c1 | ||
|
|
c02ced9a22 | ||
|
|
8c5a7f80ef | ||
|
|
14018b0118 | ||
|
|
8095723604 | ||
|
|
e17bb54c85 |
38
.dockerignore
Normal file
38
.dockerignore
Normal file
@@ -0,0 +1,38 @@
|
||||
# Packages
|
||||
node_modules/
|
||||
|
||||
# Log files
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Env
|
||||
.env
|
||||
|
||||
# Dist
|
||||
dist/
|
||||
|
||||
# Miscellaneous
|
||||
.tmp/
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
.idea/
|
||||
.DS_Store
|
||||
.turbo
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
# yarn
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/cache
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
11
.eslintrc.json
Normal file
11
.eslintrc.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "marine/prettier/node",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json"
|
||||
},
|
||||
"ignorePatterns": ["**/dist/*"],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -16,6 +16,7 @@ body:
|
||||
- collection
|
||||
- rest
|
||||
- proxy
|
||||
- proxy-container
|
||||
- voice
|
||||
validations:
|
||||
required: true
|
||||
@@ -127,6 +128,7 @@ body:
|
||||
- DirectMessages
|
||||
- DirectMessageReactions
|
||||
- DirectMessageTyping
|
||||
- MessageContent
|
||||
- GuildScheduledEvents
|
||||
multiple: true
|
||||
validations:
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -18,6 +18,7 @@ body:
|
||||
- collection
|
||||
- rest
|
||||
- proxy
|
||||
- proxy-container
|
||||
- voice
|
||||
validations:
|
||||
required: true
|
||||
|
||||
12
.github/labeler.yml
vendored
12
.github/labeler.yml
vendored
@@ -14,10 +14,18 @@ chore:
|
||||
- packages/discord.js/*
|
||||
- packages/discord.js/**/*
|
||||
|
||||
'packages:docgen':
|
||||
- packages/docgen/*
|
||||
- packages/docgen/**/*
|
||||
|
||||
'packages:proxy':
|
||||
- packages/proxy/*
|
||||
- packages/proxy/**/*
|
||||
|
||||
'packages:proxy-container':
|
||||
- packages/proxy-container/*
|
||||
- packages/proxy-container/**/*
|
||||
|
||||
'packages:rest':
|
||||
- packages/rest/*
|
||||
- packages/rest/**/*
|
||||
@@ -26,6 +34,10 @@ chore:
|
||||
- packages/voice/*
|
||||
- packages/voice/**/*
|
||||
|
||||
'packages:website':
|
||||
- packages/website/*
|
||||
- packages/website/**/*
|
||||
|
||||
'packages:ws':
|
||||
- packages/ws/*
|
||||
- packages/ws/**/*
|
||||
|
||||
8
.github/labels.yml
vendored
8
.github/labels.yml
vendored
@@ -4,6 +4,8 @@
|
||||
color: '5663e9'
|
||||
- name: 'backlog'
|
||||
color: '7ef7ef'
|
||||
- name: 'blocked'
|
||||
color: 'fc1423'
|
||||
- name: 'bug'
|
||||
color: 'd73a4a'
|
||||
- name: 'caching'
|
||||
@@ -50,14 +52,20 @@
|
||||
color: 'fbca04'
|
||||
- name: 'packages:discord.js'
|
||||
color: 'fbca04'
|
||||
- name: 'packages:docgen'
|
||||
color: 'fbca04'
|
||||
- name: 'packages:proxy'
|
||||
color: 'fbca04'
|
||||
- name: 'packages:proxy-container'
|
||||
color: 'fbca04'
|
||||
- name: 'packages:rest'
|
||||
color: 'fbca04'
|
||||
- name: 'packages:voice'
|
||||
color: 'fbca04'
|
||||
- name: 'packages:ws'
|
||||
color: 'fbca04'
|
||||
- name: 'packages:website'
|
||||
color: 'fbca04'
|
||||
- name: 'performance'
|
||||
color: '80c042'
|
||||
- name: 'permissions'
|
||||
|
||||
4
.github/workflows/auto-deprecate.yml
vendored
4
.github/workflows/auto-deprecate.yml
vendored
@@ -10,10 +10,10 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
|
||||
62
.github/workflows/documentation.yml
vendored
62
.github/workflows/documentation.yml
vendored
@@ -7,6 +7,11 @@ on:
|
||||
- '!docs'
|
||||
tags:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'The branch, tag or SHA to checkout'
|
||||
required: true
|
||||
jobs:
|
||||
build:
|
||||
name: Build documentation
|
||||
@@ -18,10 +23,12 @@ jobs:
|
||||
SHA: ${{ steps.env.outputs.SHA }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref || '' }}
|
||||
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
@@ -29,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Turbo cache
|
||||
id: turbo-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
@@ -39,15 +46,24 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: yarn --immutable
|
||||
|
||||
- name: Build dependencies
|
||||
run: yarn build --cache-dir=".turbo"
|
||||
|
||||
- name: Build docs
|
||||
run: yarn docs --cache-dir=".turbo"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Upload docgen artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docs
|
||||
name: docgen
|
||||
path: packages/*/docs/docs.json
|
||||
|
||||
- name: Upload api-extractor artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: api-extractor
|
||||
path: packages/*/docs/docs.api.json
|
||||
|
||||
- name: Set outputs for upload job
|
||||
id: env
|
||||
run: |
|
||||
@@ -65,15 +81,15 @@ jobs:
|
||||
package: ['builders', 'collection', 'discord.js', 'proxy', 'rest', 'voice']
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BRANCH_NAME: ${{ needs.build.outputs.BRANCH_NAME }}
|
||||
BRANCH_NAME: ${{ github.event.inputs.ref || 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
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
@@ -81,7 +97,7 @@ jobs:
|
||||
|
||||
- name: Turbo cache
|
||||
id: turbo-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
@@ -94,42 +110,54 @@ jobs:
|
||||
- name: Build actions
|
||||
run: yarn build --cache-dir=".turbo"
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
- name: Download docgen artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docs
|
||||
name: docgen
|
||||
path: docs
|
||||
|
||||
- name: Download api-extractor artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: api-extractor
|
||||
path: docs
|
||||
|
||||
- name: Checkout docs repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'discordjs/docs'
|
||||
token: ${{ secrets.DJS_DOCS }}
|
||||
path: 'out'
|
||||
|
||||
- name: Extract package and semver from tag
|
||||
if: env.BRANCH_OR_TAG == 'tag'
|
||||
if: ${{ github.event.inputs.ref || env.BRANCH_OR_TAG == 'tag' }}
|
||||
id: extract-tag
|
||||
uses: ./packages/actions/src/formatTag
|
||||
with:
|
||||
tag: ${{ env.BRANCH_NAME }}
|
||||
|
||||
- name: Move docs to correct directory
|
||||
if: env.BRANCH_OR_TAG == 'tag'
|
||||
if: ${{ (github.event.inputs.ref || env.BRANCH_OR_TAG == 'tag') && matrix.package == steps.extract-tag.outputs.package }}
|
||||
env:
|
||||
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
|
||||
if [[ $PACKAGE != "discord.js" ]]; then
|
||||
mv docs/${PACKAGE}/docs/docs.api.json out/${PACKAGE}/${SEMVER}.api.json
|
||||
fi
|
||||
|
||||
- name: Move docs to correct directory
|
||||
if: env.BRANCH_OR_TAG == 'branch'
|
||||
if: ${{ !github.event.inputs.ref && env.BRANCH_OR_TAG == 'branch' }}
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
mkdir -p out/${PACKAGE}
|
||||
mv docs/${PACKAGE}/docs/docs.json out/${PACKAGE}/${BRANCH_NAME}.json
|
||||
if [[ $PACKAGE != "discord.js" ]]; then
|
||||
mv docs/${PACKAGE}/docs/docs.api.json out/${PACKAGE}/${BRANCH_NAME}.api.json
|
||||
fi
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
|
||||
4
.github/workflows/labelsync.yml
vendored
4
.github/workflows/labelsync.yml
vendored
@@ -14,9 +14,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Label sync
|
||||
uses: crazy-max/ghaction-github-labeler@v3
|
||||
uses: crazy-max/ghaction-github-labeler@v4
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/pr-automation.yml
vendored
2
.github/workflows/pr-automation.yml
vendored
@@ -6,7 +6,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Automatically label PR
|
||||
uses: actions/labeler@v3
|
||||
uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
sync-labels: true
|
||||
|
||||
26
.github/workflows/publish-dev-docker.yml
vendored
Normal file
26
.github/workflows/publish-dev-docker.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Publish dev Docker Images
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 */12 * * *'
|
||||
jobs:
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'discordjs'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
|
||||
- name: Build the image
|
||||
run: docker build -t discordjs/proxy:latest -f packages/proxy-container/Dockerfile .
|
||||
|
||||
- name: Push image to DockerHub
|
||||
run: docker push discordjs/proxy:latest
|
||||
6
.github/workflows/publish-dev.yml
vendored
6
.github/workflows/publish-dev.yml
vendored
@@ -26,10 +26,10 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: https://registry.npmjs.org/
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Turbo cache
|
||||
id: turbo-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
|
||||
26
.github/workflows/publish-docker.yml
vendored
Normal file
26
.github/workflows/publish-docker.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Publish Docker Images
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
|
||||
- name: Build the image
|
||||
run: docker build -t discordjs/proxy:latest -f packages/proxy-container/Dockerfile .
|
||||
|
||||
- name: Tag the image with major
|
||||
run: docker tag discordjs/proxy discordjs/proxy:$(cut -d '.' -f1 <<< $(jq --raw-output '.version' packages/proxy-container/package.json))
|
||||
|
||||
- name: Push image to DockerHub
|
||||
run: docker push --all-tags discordjs/proxy
|
||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@@ -6,10 +6,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
- name: Turbo cache
|
||||
id: turbo-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
@@ -31,7 +31,11 @@ jobs:
|
||||
run: yarn lint --cache-dir=".turbo"
|
||||
|
||||
- name: Tests
|
||||
run: yarn test --cache-dir=".turbo"
|
||||
run: yarn test
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: ./packages/actions/src/uploadCoverage
|
||||
if: github.repository_owner == 'discordjs'
|
||||
|
||||
- name: Build
|
||||
run: yarn build --cache-dir=".turbo"
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -26,6 +26,7 @@ dist/
|
||||
.DS_Store
|
||||
.turbo
|
||||
tsconfig.tsbuildinfo
|
||||
coverage/
|
||||
|
||||
# yarn
|
||||
.pnp.*
|
||||
@@ -35,3 +36,7 @@ tsconfig.tsbuildinfo
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Cache
|
||||
.prettiercache
|
||||
.eslintcache
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn format
|
||||
yarn build && yarn lint && yarn test
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -7,6 +7,7 @@
|
||||
"codezombiech.gitignore",
|
||||
"eamodio.gitlens",
|
||||
"christian-kohler.npm-intellisense",
|
||||
"christian-kohler.path-intellisense"
|
||||
"christian-kohler.path-intellisense",
|
||||
"antfu.unocss"
|
||||
]
|
||||
}
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -6,7 +6,5 @@
|
||||
"source.fixAll": true,
|
||||
"source.organizeImports": false
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/node_modules": true
|
||||
}
|
||||
"unocss.root": "./packages/website"
|
||||
}
|
||||
|
||||
13
README.md
13
README.md
@@ -9,6 +9,7 @@
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="npm version" /></a>
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.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="Tests status" /></a>
|
||||
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2" alt="Code coverage" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -45,16 +46,16 @@ pnpm add discord.js
|
||||
Install all required dependencies:
|
||||
|
||||
```sh-session
|
||||
npm install discord.js @discordjs/rest discord-api-types
|
||||
yarn add discord.js @discordjs/rest discord-api-types
|
||||
pnpm add discord.js @discordjs/rest discord-api-types
|
||||
npm install discord.js @discordjs/rest
|
||||
yarn add discord.js @discordjs/rest
|
||||
pnpm add discord.js @discordjs/rest
|
||||
```
|
||||
|
||||
Register a slash command against the Discord API:
|
||||
|
||||
```js
|
||||
const { REST } = require('@discordjs/rest');
|
||||
const { Routes } = require('discord-api-types/v10');
|
||||
const { Routes } = require('discord.js');
|
||||
|
||||
const commands = [
|
||||
{
|
||||
@@ -89,7 +90,7 @@ client.on('ready', () => {
|
||||
});
|
||||
|
||||
client.on('interactionCreate', async (interaction) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'ping') {
|
||||
await interaction.reply('Pong!');
|
||||
@@ -101,7 +102,7 @@ client.login('token');
|
||||
|
||||
## Links
|
||||
|
||||
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
|
||||
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/discord.js/tree/main/packages/website))
|
||||
- [Documentation](https://discord.js.org/#/docs)
|
||||
- [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.
|
||||
|
||||
26
codecov.yml
Normal file
26
codecov.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
codecov:
|
||||
notify:
|
||||
after_n_builds: 6
|
||||
strict_yaml_branch: main
|
||||
|
||||
coverage:
|
||||
range: '50...90'
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 5%
|
||||
informational: true
|
||||
patch: off
|
||||
|
||||
flag_management:
|
||||
default_rules:
|
||||
statuses:
|
||||
- type: project
|
||||
target: auto
|
||||
threshold: 2%
|
||||
informational: true
|
||||
|
||||
comment:
|
||||
require_changes: true
|
||||
after_n_builds: 6
|
||||
18
package.json
18
package.json
@@ -38,13 +38,23 @@
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.0.2",
|
||||
"@commitlint/config-angular": "^17.0.0",
|
||||
"@commitlint/cli": "^17.0.3",
|
||||
"@commitlint/config-angular": "^17.0.3",
|
||||
"@favware/cliff-jumper": "^1.8.5",
|
||||
"@favware/npm-deprecate": "^1.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-config-marine": "^9.4.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.2.7",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"husky": "^8.0.1",
|
||||
"prettier": "^2.6.2",
|
||||
"turbo": "^1.2.16"
|
||||
"is-ci": "^3.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"turbo": "^1.3.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "marine/prettier/node",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json",
|
||||
"extraFileExtensions": [".mjs"]
|
||||
},
|
||||
"ignorePatterns": ["**/dist/*"],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
"extends": "../../.eslintrc.json"
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@ docs/**/*
|
||||
!docs/index.yml
|
||||
!docs/README.md
|
||||
coverage/
|
||||
tsup.config.*.mjs
|
||||
|
||||
1
packages/actions/.prettierrc.js
Normal file
1
packages/actions/.prettierrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('../../.prettierrc.json');
|
||||
@@ -175,8 +175,7 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2021 Noel Buechler
|
||||
Copyright 2021 Vlad Frangu
|
||||
Copyright 2022 Noel Buechler
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -6,45 +6,23 @@
|
||||
<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)
|
||||
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/discord.js/tree/main/packages/website))
|
||||
- [Documentation](https://discord.js.org/#/docs)
|
||||
- [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)
|
||||
- [GitHub](https://github.com/discordjs/discord.js/tree/main/packages/scripts)
|
||||
- [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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { formatTag } from '../src';
|
||||
|
||||
describe('Format Tag', () => {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @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 }]],
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 70%
|
||||
threshold: 5%
|
||||
patch:
|
||||
default:
|
||||
target: 70%
|
||||
threshold: 5%
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @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'],
|
||||
};
|
||||
@@ -4,8 +4,8 @@
|
||||
"description": "A set of actions that we use for our workflows",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"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"
|
||||
},
|
||||
@@ -44,28 +44,17 @@
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.8.2",
|
||||
"@actions/core": "^1.9.0",
|
||||
"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"
|
||||
"@types/node": "^16.11.45",
|
||||
"c8": "^7.11.3",
|
||||
"eslint": "^8.20.0",
|
||||
"prettier": "^2.7.1",
|
||||
"tsup": "^6.1.3",
|
||||
"typescript": "^4.7.4",
|
||||
"vitest": "^0.18.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
|
||||
@@ -11,4 +11,4 @@ outputs:
|
||||
description: 'The semver string that was extracted from this tag'
|
||||
runs:
|
||||
using: node16
|
||||
main: ../../dist/formatTag/index.mjs
|
||||
main: ../../dist/formatTag/index.js
|
||||
|
||||
@@ -4,7 +4,7 @@ import { formatTag } from './formatTag';
|
||||
const tag = getInput('tag', { required: true });
|
||||
const parsed = formatTag(tag);
|
||||
|
||||
if (parsed?.groups) {
|
||||
if (parsed) {
|
||||
setOutput('package', parsed.package);
|
||||
setOutput('semver', parsed.semver);
|
||||
}
|
||||
|
||||
52
packages/actions/src/uploadCoverage/action.yml
Normal file
52
packages/actions/src/uploadCoverage/action.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
name: 'Upload Coverage'
|
||||
description: 'Uploads code coverage reports to codecov with separate flags for separate packages'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Upload Builders Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/builders/coverage/cobertura-coverage.xml
|
||||
flags: builders
|
||||
|
||||
- name: Upload Collection Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/collection/coverage/cobertura-coverage.xml
|
||||
flags: collection
|
||||
|
||||
- name: Upload Discord.js Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/discord.js/coverage/cobertura-coverage.xml
|
||||
flags: discord.js
|
||||
|
||||
- name: Upload Proxy Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/proxy/coverage/cobertura-coverage.xml
|
||||
flags: proxy
|
||||
|
||||
- name: Upload Rest Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/rest/coverage/cobertura-coverage.xml
|
||||
flags: rest
|
||||
|
||||
- name: Upload Voice Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/voice/coverage/cobertura-coverage.xml
|
||||
flags: voice
|
||||
|
||||
- name: Upload Website Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/website/coverage/cobertura-coverage.xml
|
||||
flags: website
|
||||
|
||||
- name: Upload Utilities Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/actions/coverage/cobertura-coverage.xml, ./packages/scripts/coverage/cobertura-coverage.xml
|
||||
flags: utilities
|
||||
@@ -1,13 +1,9 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
import { createTsupConfig } from '../../tsup.config';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
dts: true,
|
||||
entryPoints: ['src/index.ts', 'src/formatTag/index.ts'],
|
||||
format: ['esm'],
|
||||
minify: true,
|
||||
export default createTsupConfig({
|
||||
entry: ['src/index.ts', 'src/formatTag/index.ts'],
|
||||
format: ['cjs'],
|
||||
skipNodeModulesBundle: false,
|
||||
noExternal: ['@actions/core'],
|
||||
sourcemap: true,
|
||||
target: 'es2021',
|
||||
minify: true,
|
||||
});
|
||||
|
||||
5
packages/builders/.cliff-jumperrc.json
Normal file
5
packages/builders/.cliff-jumperrc.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "builders",
|
||||
"org": "discordjs",
|
||||
"packagePath": "packages/builders"
|
||||
}
|
||||
@@ -1,12 +1,3 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "marine/prettier/node",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json",
|
||||
"extraFileExtensions": [".mjs"]
|
||||
},
|
||||
"ignorePatterns": ["**/dist/*"],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
"extends": "../../.eslintrc.json"
|
||||
}
|
||||
|
||||
2
packages/builders/.gitignore
vendored
2
packages/builders/.gitignore
vendored
@@ -19,7 +19,7 @@ dist/
|
||||
typings/
|
||||
|
||||
docs/**/*
|
||||
!docs/index.yml
|
||||
!docs/index.json
|
||||
!docs/README.md
|
||||
!docs/examples/
|
||||
!docs/examples/*.md
|
||||
|
||||
@@ -6,3 +6,4 @@ docs/**/*
|
||||
!docs/index.yml
|
||||
!docs/README.md
|
||||
coverage/
|
||||
tsup.config.*.mjs
|
||||
|
||||
1
packages/builders/.prettierrc.js
Normal file
1
packages/builders/.prettierrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('../../.prettierrc.json');
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"releaseCommitMessageFormat": "chore(Release): publish"
|
||||
}
|
||||
@@ -2,6 +2,53 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
# [@discordjs/builders@0.16.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.15.0...@discordjs/builders@0.16.0) - (2022-07-17)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Slash command name regex (#8265) ([32f9056](https://github.com/discordjs/discord.js/commit/32f9056b15edede3bab07de96afb4b56d3a9ecca))
|
||||
- **TextInputBuilder:** Parse `custom_id`, `label`, and `style` (#8216) ([2d9dfa3](https://github.com/discordjs/discord.js/commit/2d9dfa3c6ea4bb972da2f7e088d148b798c866d9))
|
||||
|
||||
## Documentation
|
||||
|
||||
- Add codecov coverage badge to readmes (#8226) ([f6db285](https://github.com/discordjs/discord.js/commit/f6db285c073898a749fe4591cbd4463d1896daf5))
|
||||
|
||||
## Features
|
||||
|
||||
- **builder:** Add max min length in string option (#8214) ([96c8d21](https://github.com/discordjs/discord.js/commit/96c8d21f95eb366c46ae23505ba9054f44821b25))
|
||||
- Codecov (#8219) ([f10f4cd](https://github.com/discordjs/discord.js/commit/f10f4cdcd88ca6be7ec735ed3a415ba13da83db0))
|
||||
- **docgen:** Update typedoc ([b3346f4](https://github.com/discordjs/discord.js/commit/b3346f4b9b3d4f96443506643d4631dc1c6d7b21))
|
||||
- Website (#8043) ([127931d](https://github.com/discordjs/discord.js/commit/127931d1df7a2a5c27923c2f2151dbf3824e50cc))
|
||||
- **docgen:** Typescript support ([3279b40](https://github.com/discordjs/discord.js/commit/3279b40912e6aa61507bedb7db15a2b8668de44b))
|
||||
- Docgen package (#8029) ([8b979c0](https://github.com/discordjs/discord.js/commit/8b979c0245c42fd824d8e98745ee869f5360fc86))
|
||||
|
||||
## Refactor
|
||||
|
||||
- **builder:** Remove `unsafe*Builder`s (#8074) ([a4d1862](https://github.com/discordjs/discord.js/commit/a4d18629828234f43f03d1bd4851d4b727c6903b))
|
||||
- Remove @sindresorhus/is as it's now esm only (#8133) ([c6f285b](https://github.com/discordjs/discord.js/commit/c6f285b7b089b004776fbeb444fe973a68d158d8))
|
||||
- Move all the config files to root (#8033) ([769ea0b](https://github.com/discordjs/discord.js/commit/769ea0bfe78c4f1d413c6b397c604ffe91e39c6a))
|
||||
|
||||
## Typings
|
||||
|
||||
- Remove expect error (#8242) ([7e6dbaa](https://github.com/discordjs/discord.js/commit/7e6dbaaed900c07d1a04e23bbbf9cd0d1b0501c5))
|
||||
- **builder:** Remove casting (#8241) ([8198da5](https://github.com/discordjs/discord.js/commit/8198da5cd0898e06954615a2287853321e7ebbd4))
|
||||
|
||||
# [@discordjs/builders@0.15.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.14.0...@discordjs/builders@0.15.0) - (2022-06-06)
|
||||
|
||||
## Features
|
||||
|
||||
- Allow builders to accept rest params and arrays (#7874) ([ad75be9](https://github.com/discordjs/discord.js/commit/ad75be9a9cf90c8624495df99b75177e6c24022f))
|
||||
- Use vitest instead of jest for more speed ([8d8e6c0](https://github.com/discordjs/discord.js/commit/8d8e6c03decd7352a2aa180f6e5bc1a13602539b))
|
||||
- Add scripts package for locally used scripts ([f2ae1f9](https://github.com/discordjs/discord.js/commit/f2ae1f9348bfd893332a9060f71a8a5f272a1b8b))
|
||||
|
||||
# [@discordjs/builders@0.15.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.14.0...@discordjs/builders@0.15.0) - (2022-06-05)
|
||||
|
||||
## Features
|
||||
|
||||
- Allow builders to accept rest params and arrays (#7874) ([ad75be9](https://github.com/discordjs/discord.js/commit/ad75be9a9cf90c8624495df99b75177e6c24022f))
|
||||
- Use vitest instead of jest for more speed ([8d8e6c0](https://github.com/discordjs/discord.js/commit/8d8e6c03decd7352a2aa180f6e5bc1a13602539b))
|
||||
- Add scripts package for locally used scripts ([f2ae1f9](https://github.com/discordjs/discord.js/commit/f2ae1f9348bfd893332a9060f71a8a5f272a1b8b))
|
||||
|
||||
# [@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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<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>
|
||||
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=builders" alt="Code coverage" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ Here are some examples for the builders and utilities you can find in this packa
|
||||
|
||||
## Links
|
||||
|
||||
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
|
||||
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/discord.js/tree/main/packages/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.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { APIActionRowComponent, APIMessageActionRowComponent, ButtonStyle, ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
@@ -44,6 +45,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();
|
||||
});
|
||||
@@ -82,6 +85,7 @@ describe('Action Row Components', () => {
|
||||
expect(new ActionRowBuilder().toJSON()).toEqual({ type: ComponentType.ActionRow, components: [] });
|
||||
expect(() => createComponentBuilder({ type: ComponentType.ActionRow, components: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
@@ -122,17 +126,24 @@ describe('Action Row Components', () => {
|
||||
expect(new ActionRowBuilder().toJSON()).toEqual({ type: ComponentType.ActionRow, components: [] });
|
||||
expect(() => createComponentBuilder({ type: ComponentType.ActionRow, components: [] })).not.toThrowError();
|
||||
});
|
||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
|
||||
const selectMenu = new SelectMenuBuilder()
|
||||
.setCustomId('1234')
|
||||
.setMaxValues(10)
|
||||
.setMinValues(12)
|
||||
.setOptions(
|
||||
new SelectMenuOptionBuilder().setLabel('one').setValue('one'),
|
||||
new SelectMenuOptionBuilder().setLabel('two').setValue('two'),
|
||||
)
|
||||
.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);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { buttonLabelValidator, buttonStyleValidator } from '../../src/components/Assertions';
|
||||
import { ButtonBuilder } from '../../src/components/button/Button';
|
||||
|
||||
@@ -124,7 +125,7 @@ describe('Button Components', () => {
|
||||
expect(
|
||||
buttonComponent()
|
||||
.setCustomId(interactionData.custom_id)
|
||||
.setLabel(interactionData.label)
|
||||
.setLabel(interactionData.label!)
|
||||
.setStyle(interactionData.style)
|
||||
.setDisabled(interactionData.disabled)
|
||||
.toJSON(),
|
||||
@@ -140,7 +141,7 @@ describe('Button Components', () => {
|
||||
|
||||
expect(new ButtonBuilder(linkData).toJSON()).toEqual(linkData);
|
||||
|
||||
expect(buttonComponent().setLabel(linkData.label).setDisabled(true).setURL(linkData.url));
|
||||
expect(buttonComponent().setLabel(linkData.label!).setDisabled(true).setURL(linkData.url));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
73
packages/builders/__tests__/components/components.test.ts
Normal file
73
packages/builders/__tests__/components/components.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
APIActionRowComponent,
|
||||
APIButtonComponent,
|
||||
APIMessageActionRowComponent,
|
||||
APISelectMenuComponent,
|
||||
APITextInputComponent,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
TextInputStyle,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
createComponentBuilder,
|
||||
SelectMenuBuilder,
|
||||
TextInputBuilder,
|
||||
} from '../../src/index';
|
||||
|
||||
describe('createComponentBuilder', () => {
|
||||
test.each([ButtonBuilder, SelectMenuBuilder, TextInputBuilder])(
|
||||
'passing an instance of %j should return itself',
|
||||
(Builder) => {
|
||||
const builder = new Builder();
|
||||
expect(createComponentBuilder(builder)).toBe(builder);
|
||||
},
|
||||
);
|
||||
|
||||
test('GIVEN an action row component THEN returns a ActionRowBuilder', () => {
|
||||
const actionRow: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
components: [],
|
||||
type: ComponentType.ActionRow,
|
||||
};
|
||||
|
||||
expect(createComponentBuilder(actionRow)).toBeInstanceOf(ActionRowBuilder);
|
||||
});
|
||||
|
||||
test('GIVEN a button component THEN returns a ButtonBuilder', () => {
|
||||
const button: APIButtonComponent = {
|
||||
custom_id: 'abc',
|
||||
style: ButtonStyle.Primary,
|
||||
type: ComponentType.Button,
|
||||
};
|
||||
|
||||
expect(createComponentBuilder(button)).toBeInstanceOf(ButtonBuilder);
|
||||
});
|
||||
|
||||
test('GIVEN a select menu component THEN returns a SelectMenuBuilder', () => {
|
||||
const selectMenu: APISelectMenuComponent = {
|
||||
custom_id: 'abc',
|
||||
options: [],
|
||||
type: ComponentType.SelectMenu,
|
||||
};
|
||||
|
||||
expect(createComponentBuilder(selectMenu)).toBeInstanceOf(SelectMenuBuilder);
|
||||
});
|
||||
|
||||
test('GIVEN a text input component THEN returns a TextInputBuilder', () => {
|
||||
const textInput: APITextInputComponent = {
|
||||
custom_id: 'abc',
|
||||
label: 'abc',
|
||||
style: TextInputStyle.Short,
|
||||
type: ComponentType.TextInput,
|
||||
};
|
||||
|
||||
expect(createComponentBuilder(textInput)).toBeInstanceOf(TextInputBuilder);
|
||||
});
|
||||
|
||||
test('GIVEN an unknown component type THEN throws error', () => {
|
||||
// @ts-expect-error
|
||||
expect(() => createComponentBuilder({ type: 'invalid' })).toThrowError();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { APISelectMenuComponent, APISelectMenuOption, ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { SelectMenuBuilder, SelectMenuOptionBuilder } from '../../src/index';
|
||||
|
||||
const selectMenu = () => new SelectMenuBuilder();
|
||||
@@ -43,12 +44,15 @@ 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([
|
||||
{
|
||||
selectMenu()
|
||||
.addOptions({
|
||||
label: 'test',
|
||||
value: 'test',
|
||||
emoji: {
|
||||
@@ -56,14 +60,31 @@ describe('Select Menu Components', () => {
|
||||
name: 'test',
|
||||
animated: true,
|
||||
},
|
||||
},
|
||||
]),
|
||||
})
|
||||
.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' })),
|
||||
).not.toThrowError();
|
||||
expect(() =>
|
||||
selectMenu()
|
||||
.addOptions([{ label: 'test', value: 'test' }])
|
||||
@@ -79,6 +100,17 @@ 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();
|
||||
// @ts-expect-error
|
||||
expect(() => selectMenu().addOptions({ label: 'test', value: 'test', default: 100 })).toThrowError();
|
||||
// @ts-expect-error
|
||||
expect(() => selectMenu().addOptions({ value: 'test' })).toThrowError();
|
||||
// @ts-expect-error
|
||||
expect(() => selectMenu().addOptions({ default: true })).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();
|
||||
@@ -91,8 +123,14 @@ describe('Select Menu Components', () => {
|
||||
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),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
selectMenu()
|
||||
.addOptions([{ label: 'test', value: 'test' }])
|
||||
@@ -112,6 +150,11 @@ describe('Select Menu Components', () => {
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON input THEN valid JSON history is correct', () => {
|
||||
expect(
|
||||
new SelectMenuBuilder(selectMenuDataWithoutOptions)
|
||||
.addOptions(new SelectMenuOptionBuilder(selectMenuOptionData))
|
||||
.toJSON(),
|
||||
).toEqual(selectMenuData);
|
||||
expect(
|
||||
new SelectMenuBuilder(selectMenuDataWithoutOptions)
|
||||
.addOptions([new SelectMenuOptionBuilder(selectMenuOptionData)])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { APITextInputComponent, ComponentType, TextInputStyle } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
labelValidator,
|
||||
maxLengthValidator,
|
||||
@@ -45,7 +46,7 @@ describe('Text Input Components', () => {
|
||||
expect(() => maxLengthValidator.parse(10)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid min length THEN validator does throw', () => {
|
||||
test('GIVEN invalid min length THEN validator does throw 2', () => {
|
||||
expect(() => maxLengthValidator.parse(4001)).toThrowError();
|
||||
});
|
||||
|
||||
@@ -61,7 +62,7 @@ describe('Text Input Components', () => {
|
||||
expect(() => placeholderValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid value THEN validator does throw', () => {
|
||||
test('GIVEN invalid value THEN validator does throw 2', () => {
|
||||
expect(() => placeholderValidator.parse(superLongStr)).toThrowError();
|
||||
});
|
||||
|
||||
@@ -81,6 +82,12 @@ describe('Text Input Components', () => {
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.toJSON();
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
// Issue #8107
|
||||
// @ts-expect-error: shapeshift maps the enum key to the value when parsing
|
||||
textInputComponent().setCustomId('Custom').setLabel('Guess').setStyle('Short').toJSON();
|
||||
}).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -114,10 +121,10 @@ describe('Text Input Components', () => {
|
||||
textInputComponent()
|
||||
.setCustomId(textInputData.custom_id)
|
||||
.setLabel(textInputData.label)
|
||||
.setPlaceholder(textInputData.placeholder)
|
||||
.setMaxLength(textInputData.max_length)
|
||||
.setMinLength(textInputData.min_length)
|
||||
.setValue(textInputData.value)
|
||||
.setPlaceholder(textInputData.placeholder!)
|
||||
.setMaxLength(textInputData.max_length!)
|
||||
.setMinLength(textInputData.min_length!)
|
||||
.setValue(textInputData.value!)
|
||||
.setRequired(textInputData.required)
|
||||
.setStyle(textInputData.style)
|
||||
.toJSON(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PermissionFlagsBits } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { ContextMenuCommandAssertions, ContextMenuCommandBuilder } from '../../src/index';
|
||||
|
||||
const getBuilder = () => new ContextMenuCommandBuilder();
|
||||
@@ -85,6 +86,10 @@ describe('Context Menu Commands', () => {
|
||||
test('GIVEN valid builder with defaultPermission false THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('foo').setDefaultPermission(false)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with dmPermission false THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('foo').setDMPermission(false)).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Context menu command localizations', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
APIApplicationCommandAttachmentOption,
|
||||
APIApplicationCommandBooleanOption,
|
||||
APIApplicationCommandChannelOption,
|
||||
APIApplicationCommandIntegerOption,
|
||||
@@ -10,7 +11,9 @@ import {
|
||||
ApplicationCommandOptionType,
|
||||
ChannelType,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
SlashCommandAttachmentOption,
|
||||
SlashCommandBooleanOption,
|
||||
SlashCommandChannelOption,
|
||||
SlashCommandIntegerOption,
|
||||
@@ -57,6 +60,9 @@ const getRoleOption = () => new SlashCommandRoleOption().setName('owo').setDescr
|
||||
const getMentionableOption = () =>
|
||||
new SlashCommandMentionableOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getAttachmentOption = () =>
|
||||
new SlashCommandAttachmentOption().setName('attachment').setDescription('attachment').setRequired(true);
|
||||
|
||||
describe('Application Command toJSON() results', () => {
|
||||
test('GIVEN a boolean option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getBooleanOption().toJSON()).toEqual<APIApplicationCommandBooleanOption>({
|
||||
@@ -166,11 +172,13 @@ describe('Application Command toJSON() results', () => {
|
||||
});
|
||||
|
||||
test('GIVEN a string option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getStringOption().toJSON()).toEqual<APIApplicationCommandStringOption>({
|
||||
expect(getStringOption().setMinLength(1).setMaxLength(10).toJSON()).toEqual<APIApplicationCommandStringOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
max_length: 10,
|
||||
min_length: 1,
|
||||
});
|
||||
|
||||
expect(getStringOption().setAutocomplete(true).setChoices().toJSON()).toEqual<APIApplicationCommandStringOption>({
|
||||
@@ -202,4 +210,13 @@ describe('Application Command toJSON() results', () => {
|
||||
required: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an attachment option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getAttachmentOption().toJSON()).toEqual<APIApplicationCommandAttachmentOption>({
|
||||
name: 'attachment',
|
||||
description: 'attachment',
|
||||
type: ApplicationCommandOptionType.Attachment,
|
||||
required: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { APIApplicationCommandOptionChoice, ChannelType, PermissionFlagsBits } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
SlashCommandAssertions,
|
||||
SlashCommandBooleanOption,
|
||||
@@ -41,6 +42,8 @@ describe('Slash Commands', () => {
|
||||
describe('Assertions tests', () => {
|
||||
test('GIVEN valid name THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateName('ping')).not.toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('hello-world_command')).not.toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('aˇ㐆1٢〣²अก')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
@@ -50,7 +53,10 @@ describe('Slash Commands', () => {
|
||||
expect(() => SlashCommandAssertions.validateName('')).toThrowError();
|
||||
|
||||
// Invalid characters used
|
||||
expect(() => SlashCommandAssertions.validateName('ABC')).toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('ABC123$%^&')).toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('help ping')).toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('🦦')).toThrowError();
|
||||
|
||||
// Too long of a name
|
||||
expect(() =>
|
||||
@@ -87,6 +93,7 @@ describe('Slash Commands', () => {
|
||||
test('GIVEN valid array of options or choices THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateMaxOptionsLength([])).not.toThrowError();
|
||||
|
||||
expect(() => SlashCommandAssertions.validateChoicesLength(25)).not.toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateChoicesLength(25, [])).not.toThrowError();
|
||||
});
|
||||
|
||||
@@ -127,6 +134,7 @@ describe('Slash Commands', () => {
|
||||
getBuilder()
|
||||
.setName('example')
|
||||
.setDescription('Example command')
|
||||
.setDMPermission(false)
|
||||
.addBooleanOption((boolean) =>
|
||||
boolean.setName('iscool').setDescription('Are we cool or what?').setRequired(true),
|
||||
)
|
||||
@@ -313,8 +321,10 @@ describe('Slash Commands', () => {
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(true)).toThrowError();
|
||||
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(null)).toThrowError();
|
||||
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(undefined)).toThrowError();
|
||||
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { APIModalInteractionResponseCallbackData, ComponentType, TextInputStyle } from 'discord-api-types/v10';
|
||||
import {
|
||||
APIModalInteractionResponseCallbackData,
|
||||
APITextInputComponent,
|
||||
ComponentType,
|
||||
TextInputStyle,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
@@ -48,12 +54,18 @@ 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();
|
||||
|
||||
expect(() =>
|
||||
// @ts-expect-error: You can pass a TextInputBuilder and it will add it to an action row
|
||||
modal().setTitle('test').setCustomId('foobar').addComponents(new TextInputBuilder()),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid fields THEN builder does throw', () => {
|
||||
expect(() => modal().setTitle('test').setCustomId('foobar').toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error
|
||||
expect(() => modal().setTitle('test').setCustomId(42).toJSON()).toThrowError();
|
||||
});
|
||||
@@ -74,6 +86,17 @@ describe('Modals', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextInput,
|
||||
label: 'label',
|
||||
style: TextInputStyle.Paragraph,
|
||||
custom_id: 'custom id',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -83,12 +106,68 @@ 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),
|
||||
]),
|
||||
),
|
||||
)
|
||||
.addComponents([
|
||||
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
|
||||
new TextInputBuilder().setCustomId('custom id').setLabel('label').setStyle(TextInputStyle.Paragraph),
|
||||
),
|
||||
])
|
||||
.toJSON(),
|
||||
).toEqual(modalData);
|
||||
});
|
||||
|
||||
describe('equals()', () => {
|
||||
const textInput1 = new TextInputBuilder()
|
||||
.setCustomId('custom id')
|
||||
.setLabel('label')
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
|
||||
const textInput2: APITextInputComponent = {
|
||||
type: ComponentType.TextInput,
|
||||
custom_id: 'custom id',
|
||||
label: 'label',
|
||||
style: TextInputStyle.Paragraph,
|
||||
};
|
||||
|
||||
test('GIVEN equal builders THEN returns true', () => {
|
||||
const equalTextInput = new TextInputBuilder()
|
||||
.setCustomId('custom id')
|
||||
.setLabel('label')
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
|
||||
expect(textInput1.equals(equalTextInput)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('GIVEN the same builder THEN returns true', () => {
|
||||
expect(textInput1.equals(textInput1)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('GIVEN equal builder and data THEN returns true', () => {
|
||||
expect(textInput1.equals(textInput2)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('GIVEN different builders THEN returns false', () => {
|
||||
const diffTextInput = new TextInputBuilder()
|
||||
.setCustomId('custom id')
|
||||
.setLabel('label 2')
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
|
||||
expect(textInput1.equals(diffTextInput)).toBeFalsy();
|
||||
});
|
||||
|
||||
test('GIVEN different text input builder and data THEN returns false', () => {
|
||||
const diffTextInputData: APITextInputComponent = {
|
||||
type: ComponentType.TextInput,
|
||||
custom_id: 'custom id',
|
||||
label: 'label 2',
|
||||
style: TextInputStyle.Short,
|
||||
};
|
||||
|
||||
expect(textInput1.equals(diffTextInputData)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { EmbedBuilder, embedLength } from '../../src';
|
||||
|
||||
const alpha = 'abcdefghijklmnopqrstuvwxyz';
|
||||
@@ -322,28 +323,29 @@ 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' }],
|
||||
fields: [
|
||||
{ name: 'foo', value: 'bar' },
|
||||
{ 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' }],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data', () => {
|
||||
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data 2', () => {
|
||||
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' }))),
|
||||
@@ -352,7 +354,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' }))),
|
||||
@@ -362,6 +364,9 @@ describe('Embed', () => {
|
||||
test('GIVEN an embed using Embed#setFields THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() =>
|
||||
embed.setFields(...Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).not.toThrowError();
|
||||
expect(() =>
|
||||
embed.setFields(Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).not.toThrowError();
|
||||
@@ -370,38 +375,43 @@ describe('Embed', () => {
|
||||
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('', () => {
|
||||
test('1', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field name THEN throws error', () => {
|
||||
test('', () => {
|
||||
test('2', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields([{ name: '', value: 'bar' }])).toThrowError();
|
||||
expect(() => embed.addFields({ name: '', value: 'bar' })).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field name length THEN throws error', () => {
|
||||
test('', () => {
|
||||
test('3', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields([{ name: 'a'.repeat(257), value: 'bar' }])).toThrowError();
|
||||
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field value length THEN throws error', () => {
|
||||
test('', () => {
|
||||
test('4', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields([{ name: '', value: 'a'.repeat(1025) }])).toThrowError();
|
||||
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1025) })).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { URL } from 'node:url';
|
||||
import { describe, test, expect, vitest } from 'vitest';
|
||||
import {
|
||||
blockQuote,
|
||||
bold,
|
||||
@@ -150,12 +152,12 @@ describe('Message formatters', () => {
|
||||
|
||||
describe('time', () => {
|
||||
test('GIVEN no arguments THEN returns "<t:${bigint}>"', () => {
|
||||
jest.useFakeTimers('modern');
|
||||
jest.setSystemTime(1566424897579);
|
||||
vitest.useFakeTimers();
|
||||
vitest.setSystemTime(1566424897579);
|
||||
|
||||
expect<`<t:${bigint}>`>(time()).toEqual('<t:1566424897>');
|
||||
|
||||
jest.useRealTimers();
|
||||
vitest.useRealTimers();
|
||||
});
|
||||
|
||||
test('GIVEN a date THEN returns "<t:${bigint}>"', () => {
|
||||
|
||||
42
packages/builders/__tests__/util.test.ts
Normal file
42
packages/builders/__tests__/util.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
isJSONEncodable,
|
||||
isEquatable,
|
||||
ActionRowBuilder,
|
||||
enableValidators,
|
||||
disableValidators,
|
||||
isValidationEnabled,
|
||||
} from '../src/index';
|
||||
|
||||
describe('isEquatable', () => {
|
||||
test('returns true if the object is equatable', () => {
|
||||
expect(isEquatable({ equals: () => true })).toBeTruthy();
|
||||
});
|
||||
|
||||
test('returns false if the object is not equatable', () => {
|
||||
expect(isEquatable({})).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isJSONEncodable', () => {
|
||||
test('returns true if the object is JSON encodable', () => {
|
||||
expect(isJSONEncodable({ toJSON: () => ({}) })).toBeTruthy();
|
||||
expect(isJSONEncodable(new ActionRowBuilder())).toBeTruthy();
|
||||
});
|
||||
|
||||
test('returns false if the object is not JSON encodable', () => {
|
||||
expect(isJSONEncodable({})).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
test('enables validation', () => {
|
||||
enableValidators();
|
||||
expect(isValidationEnabled()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('disables validation', () => {
|
||||
disableValidators();
|
||||
expect(isValidationEnabled()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
376
packages/builders/api-extractor.json
Normal file
376
packages/builders/api-extractor.json
Normal file
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
|
||||
/**
|
||||
* Optionally specifies another JSON config file that this file extends from. This provides a way for
|
||||
* standard settings to be shared across multiple projects.
|
||||
*
|
||||
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
|
||||
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
|
||||
* resolved using NodeJS require().
|
||||
*
|
||||
* SUPPORTED TOKENS: none
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "extends": "./shared/api-extractor-base.json"
|
||||
// "extends": "my-package/include/api-extractor-base.json"
|
||||
|
||||
/**
|
||||
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
|
||||
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting.
|
||||
*
|
||||
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
|
||||
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
|
||||
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
|
||||
* will be reported.
|
||||
*
|
||||
* SUPPORTED TOKENS: <lookup>
|
||||
* DEFAULT VALUE: "<lookup>"
|
||||
*/
|
||||
// "projectFolder": "..",
|
||||
|
||||
/**
|
||||
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
|
||||
* analyzes the symbols exported by this module.
|
||||
*
|
||||
* The file extension must be ".d.ts" and not ".ts".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
*/
|
||||
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts",
|
||||
|
||||
/**
|
||||
* A list of NPM package names whose exports should be treated as part of this package.
|
||||
*
|
||||
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
|
||||
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
|
||||
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
|
||||
* imports library2. To avoid this, we can specify:
|
||||
*
|
||||
* "bundledPackages": [ "library2" ],
|
||||
*
|
||||
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
|
||||
* local files for library1.
|
||||
*/
|
||||
"bundledPackages": [],
|
||||
|
||||
/**
|
||||
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
|
||||
*/
|
||||
"compiler": {
|
||||
/**
|
||||
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* Note: This setting will be ignored if "overrideTsconfig" is used.
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
|
||||
*/
|
||||
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
|
||||
/**
|
||||
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
|
||||
* The object must conform to the TypeScript tsconfig schema:
|
||||
*
|
||||
* http://json.schemastore.org/tsconfig
|
||||
*
|
||||
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
|
||||
*
|
||||
* DEFAULT VALUE: no overrideTsconfig section
|
||||
*/
|
||||
// "overrideTsconfig": {
|
||||
// . . .
|
||||
// }
|
||||
/**
|
||||
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
|
||||
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
|
||||
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
|
||||
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "skipLibCheck": true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures how the API report file (*.api.md) will be generated.
|
||||
*/
|
||||
"apiReport": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate an API report.
|
||||
*/
|
||||
"enabled": false
|
||||
|
||||
/**
|
||||
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
|
||||
* a full file path.
|
||||
*
|
||||
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
|
||||
*
|
||||
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
|
||||
*/
|
||||
// "reportFileName": "<unscopedPackageName>.api.md",
|
||||
|
||||
/**
|
||||
* Specifies the folder where the API report file is written. The file name portion is determined by
|
||||
* the "reportFileName" setting.
|
||||
*
|
||||
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
|
||||
* e.g. for an API review.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/"
|
||||
*/
|
||||
// "reportFolder": "<projectFolder>/temp/",
|
||||
|
||||
/**
|
||||
* Specifies the folder where the temporary report file is written. The file name portion is determined by
|
||||
* the "reportFileName" setting.
|
||||
*
|
||||
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
|
||||
* If they are different, a production build will fail.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/"
|
||||
*/
|
||||
// "reportTempFolder": "<projectFolder>/temp/"
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures how the doc model file (*.api.json) will be generated.
|
||||
*/
|
||||
"docModel": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate a doc model file.
|
||||
*/
|
||||
"enabled": true,
|
||||
|
||||
/**
|
||||
* The output path for the doc model file. The file extension should be ".api.json".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||
*/
|
||||
"apiJsonFilePath": "<projectFolder>/docs/docs.api.json"
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures how the .d.ts rollup file will be generated.
|
||||
*/
|
||||
"dtsRollup": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate the .d.ts rollup file.
|
||||
*/
|
||||
"enabled": false
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
|
||||
* This file will include all declarations that are exported by the main entry point.
|
||||
*
|
||||
* If the path is an empty string, then this file will not be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
|
||||
*/
|
||||
// "untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts",
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release.
|
||||
* This file will include only declarations that are marked as "@public", "@beta", or "@alpha".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "alphaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-alpha.d.ts",
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
|
||||
* This file will include only declarations that are marked as "@public" or "@beta".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
|
||||
* This file will include only declarations that are marked as "@public".
|
||||
*
|
||||
* If the path is an empty string, then this file will not be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
|
||||
|
||||
/**
|
||||
* When a declaration is trimmed, by default it will be replaced by a code comment such as
|
||||
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
|
||||
* declaration completely.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "omitTrimmingComments": true
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures how the tsdoc-metadata.json file will be generated.
|
||||
*/
|
||||
"tsdocMetadata": {
|
||||
/**
|
||||
* Whether to generate the tsdoc-metadata.json file.
|
||||
*
|
||||
* DEFAULT VALUE: true
|
||||
*/
|
||||
// "enabled": true,
|
||||
/**
|
||||
* Specifies where the TSDoc metadata file should be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
|
||||
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
|
||||
* falls back to "tsdoc-metadata.json" in the package folder.
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<lookup>"
|
||||
*/
|
||||
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
|
||||
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
|
||||
* To use the OS's default newline kind, specify "os".
|
||||
*
|
||||
* DEFAULT VALUE: "crlf"
|
||||
*/
|
||||
"newlineKind": "lf",
|
||||
|
||||
/**
|
||||
* Configures how API Extractor reports error and warning messages produced during analysis.
|
||||
*
|
||||
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
|
||||
*/
|
||||
"messages": {
|
||||
/**
|
||||
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
|
||||
* the input .d.ts files.
|
||||
*
|
||||
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
|
||||
*
|
||||
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||
*/
|
||||
"compilerMessageReporting": {
|
||||
/**
|
||||
* Configures the default routing for messages that don't match an explicit rule in this table.
|
||||
*/
|
||||
"default": {
|
||||
/**
|
||||
* Specifies whether the message should be written to the the tool's output log. Note that
|
||||
* the "addToApiReportFile" property may supersede this option.
|
||||
*
|
||||
* Possible values: "error", "warning", "none"
|
||||
*
|
||||
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
|
||||
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
|
||||
* the "--local" option), the warning is displayed but the build will not fail.
|
||||
*
|
||||
* DEFAULT VALUE: "warning"
|
||||
*/
|
||||
"logLevel": "warning"
|
||||
|
||||
/**
|
||||
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
|
||||
* then the message will be written inside that file; otherwise, the message is instead logged according to
|
||||
* the "logLevel" option.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
|
||||
// "TS2551": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures handling of messages reported by API Extractor during its analysis.
|
||||
*
|
||||
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
|
||||
*
|
||||
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
|
||||
*/
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
|
||||
// "ae-extra-release-tag": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
|
||||
*
|
||||
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
|
||||
*
|
||||
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||
*/
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
|
||||
// "tsdoc-link-tag-unescaped-text": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @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 }]],
|
||||
};
|
||||
@@ -10,7 +10,7 @@ body = """
|
||||
{% if previous %}\
|
||||
{% if previous.version %}\
|
||||
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
|
||||
{% else %}
|
||||
{% else %}\
|
||||
(https://github.com/discordjs/discord.js/tree/{{ version }})\
|
||||
{% endif %}\
|
||||
{% endif %} \
|
||||
@@ -26,9 +26,8 @@ body = """
|
||||
{% 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\
|
||||
\n{% raw %} {% endraw %}- **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
|
||||
{% endfor %}\
|
||||
{% endif %}\
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 70%
|
||||
threshold: 5%
|
||||
patch:
|
||||
default:
|
||||
target: 70%
|
||||
threshold: 5%
|
||||
1
packages/builders/docs/index.json
Normal file
1
packages/builders/docs/index.json
Normal file
@@ -0,0 +1 @@
|
||||
[{ "name": "General", "files": [{ "name": "Welcome", "id": "welcome", "path": "../../README.md" }] }]
|
||||
@@ -1,5 +0,0 @@
|
||||
- name: General
|
||||
files:
|
||||
- name: Welcome
|
||||
id: welcome
|
||||
path: ../../README.md
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @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'],
|
||||
};
|
||||
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "@discordjs/builders",
|
||||
"version": "0.14.0",
|
||||
"version": "0.17.0-dev",
|
||||
"description": "A set of builders that you can use when creating your bot",
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"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",
|
||||
"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 -l -c ./cliff.toml -r ../../ --include-path 'packages/builders/*'"
|
||||
"docs": "docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local",
|
||||
"prepack": "yarn build && yarn lint",
|
||||
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/builders/*'",
|
||||
"release": "cliff-jumper"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -52,33 +53,24 @@
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"dependencies": {
|
||||
"@sapphire/shapeshift": "^3.1.0",
|
||||
"@sindresorhus/is": "^4.6.0",
|
||||
"discord-api-types": "^0.33.3",
|
||||
"@sapphire/shapeshift": "^3.5.1",
|
||||
"discord-api-types": "^0.36.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.1",
|
||||
"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",
|
||||
"@discordjs/ts-docgen": "^0.4.1",
|
||||
"@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"
|
||||
"@discordjs/docgen": "workspace:^",
|
||||
"@discordjs/scripts": "workspace:^",
|
||||
"@favware/cliff-jumper": "^1.8.5",
|
||||
"@microsoft/api-extractor": "^7.28.4",
|
||||
"@types/node": "^16.11.45",
|
||||
"c8": "^7.11.3",
|
||||
"eslint": "^8.20.0",
|
||||
"prettier": "^2.7.1",
|
||||
"tsup": "^6.1.3",
|
||||
"typescript": "^4.7.4",
|
||||
"vitest": "^0.18.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { runGenerator } from '@discordjs/ts-docgen';
|
||||
|
||||
runGenerator({
|
||||
existingOutput: 'docs/typedoc-out.json',
|
||||
custom: 'docs/index.yml',
|
||||
output: 'docs/docs.json',
|
||||
});
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
} from 'discord-api-types/v10';
|
||||
import { ComponentBuilder } from './Component';
|
||||
import { createComponentBuilder } from './Components';
|
||||
import type { ButtonBuilder, SelectMenuBuilder, TextInputBuilder } from '..';
|
||||
import type { ButtonBuilder } from './button/Button';
|
||||
import type { SelectMenuBuilder } from './selectMenu/SelectMenu';
|
||||
import type { TextInputBuilder } from './textInput/TextInput';
|
||||
import { normalizeArray, type RestOrArray } from '../util/normalizeArray';
|
||||
|
||||
export type MessageComponentBuilder =
|
||||
| MessageActionRowComponentBuilder
|
||||
@@ -35,20 +38,21 @@ export class ActionRowBuilder<T extends AnyComponentBuilder> extends ComponentBu
|
||||
|
||||
/**
|
||||
* Adds components to this action row.
|
||||
* @param components The components to add to this action row.
|
||||
* @returns
|
||||
*
|
||||
* @param components - The components to add to this action row.
|
||||
*/
|
||||
public addComponents(components: T[]) {
|
||||
this.components.push(...components);
|
||||
public addComponents(...components: RestOrArray<T>) {
|
||||
this.components.push(...normalizeArray(components));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the components in this action row
|
||||
* @param components The components to set this row to
|
||||
*
|
||||
* @param components - The components to set this row to
|
||||
*/
|
||||
public setComponents(components: T[]) {
|
||||
this.components.splice(0, this.components.length, ...components);
|
||||
public setComponents(...components: RestOrArray<T>) {
|
||||
this.components.splice(0, this.components.length, ...normalizeArray(components));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,78 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { APIMessageComponentEmoji, ButtonStyle } from 'discord-api-types/v10';
|
||||
import type { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption';
|
||||
import { UnsafeSelectMenuOptionBuilder } from './selectMenu/UnsafeSelectMenuOption';
|
||||
import { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption';
|
||||
import { isValidationEnabled } from '../util/validation';
|
||||
|
||||
export const customIdValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
|
||||
export const customIdValidator = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(100)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const emojiValidator = s.object({
|
||||
id: s.string,
|
||||
name: s.string,
|
||||
animated: s.boolean,
|
||||
}).partial.strict;
|
||||
export const emojiValidator = s
|
||||
.object({
|
||||
id: s.string,
|
||||
name: s.string,
|
||||
animated: s.boolean,
|
||||
})
|
||||
.partial.strict.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const disabledValidator = s.boolean;
|
||||
|
||||
export const buttonLabelValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(80);
|
||||
export const buttonLabelValidator = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(80)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const buttonStyleValidator = s.nativeEnum(ButtonStyle);
|
||||
|
||||
export const placeholderValidator = s.string.lengthLessThanOrEqual(150);
|
||||
export const minMaxValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(25);
|
||||
export const placeholderValidator = s.string.lengthLessThanOrEqual(150).setValidationEnabled(isValidationEnabled);
|
||||
export const minMaxValidator = s.number.int
|
||||
.greaterThanOrEqual(0)
|
||||
.lessThanOrEqual(25)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const labelValueDescriptionValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
|
||||
export const optionValidator = s.union(
|
||||
s.object({
|
||||
label: labelValueDescriptionValidator,
|
||||
value: labelValueDescriptionValidator,
|
||||
description: labelValueDescriptionValidator.optional,
|
||||
emoji: emojiValidator.optional,
|
||||
default: s.boolean.optional,
|
||||
}),
|
||||
s.instance(UnsafeSelectMenuOptionBuilder),
|
||||
);
|
||||
export const optionsValidator = optionValidator.array.lengthGreaterThanOrEqual(0);
|
||||
export const optionsLengthValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(25);
|
||||
export const labelValueDescriptionValidator = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(100)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const optionValidator = s
|
||||
.union(
|
||||
s.object({
|
||||
label: labelValueDescriptionValidator,
|
||||
value: labelValueDescriptionValidator,
|
||||
description: labelValueDescriptionValidator.optional,
|
||||
emoji: emojiValidator.optional,
|
||||
default: s.boolean.optional,
|
||||
}),
|
||||
s.instance(SelectMenuOptionBuilder),
|
||||
)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const optionsValidator = optionValidator.array
|
||||
.lengthGreaterThanOrEqual(0)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const optionsLengthValidator = s.number.int
|
||||
.greaterThanOrEqual(0)
|
||||
.lessThanOrEqual(25)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateRequiredSelectMenuParameters(options: SelectMenuOptionBuilder[], customId?: string) {
|
||||
customIdValidator.parse(customId);
|
||||
optionsValidator.parse(options);
|
||||
}
|
||||
|
||||
export const labelValueValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
|
||||
export const defaultValidator = s.boolean;
|
||||
|
||||
export function validateRequiredSelectMenuOptionParameters(label?: string, value?: string) {
|
||||
labelValueValidator.parse(label);
|
||||
labelValueValidator.parse(value);
|
||||
labelValueDescriptionValidator.parse(label);
|
||||
labelValueDescriptionValidator.parse(value);
|
||||
}
|
||||
|
||||
export const urlValidator = s.string.url({
|
||||
allowedProtocols: ['http:', 'https:', 'discord:'],
|
||||
});
|
||||
export const urlValidator = s.string
|
||||
.url({
|
||||
allowedProtocols: ['http:', 'https:', 'discord:'],
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateRequiredButtonParameters(
|
||||
style?: ButtonStyle,
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { APIMessageComponent, APIModalComponent, ComponentType } from 'discord-api-types/v10';
|
||||
import type { AnyComponentBuilder, MessageComponentBuilder, ModalComponentBuilder } from './ActionRow';
|
||||
import { ActionRowBuilder, ButtonBuilder, ComponentBuilder, SelectMenuBuilder, TextInputBuilder } from '../index';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
type AnyComponentBuilder,
|
||||
type MessageComponentBuilder,
|
||||
type ModalComponentBuilder,
|
||||
} from './ActionRow';
|
||||
import { ComponentBuilder } from './Component';
|
||||
import { ButtonBuilder } from './button/Button';
|
||||
import { SelectMenuBuilder } from './selectMenu/SelectMenu';
|
||||
import { TextInputBuilder } from './textInput/TextInput';
|
||||
|
||||
export interface MappedComponentTypes {
|
||||
[ComponentType.ActionRow]: ActionRowBuilder<AnyComponentBuilder>;
|
||||
@@ -11,7 +19,8 @@ export interface MappedComponentTypes {
|
||||
|
||||
/**
|
||||
* Factory for creating components from API data
|
||||
* @param data The api data to transform to a component class
|
||||
*
|
||||
* @param data - The api data to transform to a component class
|
||||
*/
|
||||
export function createComponentBuilder<T extends keyof MappedComponentTypes>(
|
||||
data: (APIMessageComponent | APIModalComponent) & { type: T },
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type {
|
||||
import {
|
||||
ComponentType,
|
||||
ButtonStyle,
|
||||
APIMessageComponentEmoji,
|
||||
APIButtonComponent,
|
||||
APIButtonComponentWithCustomId,
|
||||
APIButtonComponentWithURL,
|
||||
type APIMessageComponentEmoji,
|
||||
type APIButtonComponent,
|
||||
type APIButtonComponentWithURL,
|
||||
type APIButtonComponentWithCustomId,
|
||||
} from 'discord-api-types/v10';
|
||||
import { UnsafeButtonBuilder } from './UnsafeButton';
|
||||
import {
|
||||
buttonLabelValidator,
|
||||
buttonStyleValidator,
|
||||
@@ -15,36 +15,77 @@ import {
|
||||
urlValidator,
|
||||
validateRequiredButtonParameters,
|
||||
} from '../Assertions';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
|
||||
/**
|
||||
* Represents a validated button component
|
||||
* Represents a button component
|
||||
*/
|
||||
export class ButtonBuilder extends UnsafeButtonBuilder {
|
||||
public override setStyle(style: ButtonStyle) {
|
||||
return super.setStyle(buttonStyleValidator.parse(style));
|
||||
export class ButtonBuilder extends ComponentBuilder<APIButtonComponent> {
|
||||
public constructor(data?: Partial<APIButtonComponent>) {
|
||||
super({ type: ComponentType.Button, ...data });
|
||||
}
|
||||
|
||||
public override setURL(url: string) {
|
||||
return super.setURL(urlValidator.parse(url));
|
||||
/**
|
||||
* Sets the style of this button
|
||||
*
|
||||
* @param style - The style of the button
|
||||
*/
|
||||
public setStyle(style: ButtonStyle) {
|
||||
this.data.style = buttonStyleValidator.parse(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setCustomId(customId: string) {
|
||||
return super.setCustomId(customIdValidator.parse(customId));
|
||||
/**
|
||||
* Sets the URL for this button
|
||||
*
|
||||
* @param url - The URL to open when this button is clicked
|
||||
*/
|
||||
public setURL(url: string) {
|
||||
(this.data as APIButtonComponentWithURL).url = urlValidator.parse(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
return super.setEmoji(emojiValidator.parse(emoji));
|
||||
/**
|
||||
* Sets the custom id for this button
|
||||
*
|
||||
* @param customId - The custom id to use for this button
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
(this.data as APIButtonComponentWithCustomId).custom_id = customIdValidator.parse(customId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setDisabled(disabled = true) {
|
||||
return super.setDisabled(disabledValidator.parse(disabled));
|
||||
/**
|
||||
* Sets the emoji to display on this button
|
||||
*
|
||||
* @param emoji - The emoji to display on this button
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
this.data.emoji = emojiValidator.parse(emoji);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setLabel(label: string) {
|
||||
return super.setLabel(buttonLabelValidator.parse(label));
|
||||
/**
|
||||
* Sets whether this button is disabled
|
||||
*
|
||||
* @param disabled - Whether to disable this button
|
||||
*/
|
||||
public setDisabled(disabled = true) {
|
||||
this.data.disabled = disabledValidator.parse(disabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override toJSON(): APIButtonComponent {
|
||||
/**
|
||||
* Sets the label for this button
|
||||
*
|
||||
* @param label - The label to display on this button
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = buttonLabelValidator.parse(label);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIButtonComponent {
|
||||
validateRequiredButtonParameters(
|
||||
this.data.style,
|
||||
this.data.label,
|
||||
@@ -52,6 +93,9 @@ export class ButtonBuilder extends UnsafeButtonBuilder {
|
||||
(this.data as APIButtonComponentWithCustomId).custom_id,
|
||||
(this.data as APIButtonComponentWithURL).url,
|
||||
);
|
||||
return super.toJSON();
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
} as APIButtonComponent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import {
|
||||
ComponentType,
|
||||
ButtonStyle,
|
||||
type APIMessageComponentEmoji,
|
||||
type APIButtonComponent,
|
||||
type APIButtonComponentWithURL,
|
||||
type APIButtonComponentWithCustomId,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
|
||||
/**
|
||||
* Represents a non-validated button component
|
||||
*/
|
||||
export class UnsafeButtonBuilder extends ComponentBuilder<APIButtonComponent> {
|
||||
public constructor(data?: Partial<APIButtonComponent>) {
|
||||
super({ type: ComponentType.Button, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style of this button
|
||||
* @param style The style of the button
|
||||
*/
|
||||
public setStyle(style: ButtonStyle) {
|
||||
this.data.style = style;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL for this button
|
||||
* @param url The URL to open when this button is clicked
|
||||
*/
|
||||
public setURL(url: string) {
|
||||
(this.data as APIButtonComponentWithURL).url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom Id for this button
|
||||
* @param customId The custom id to use for this button
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
(this.data as APIButtonComponentWithCustomId).custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the emoji to display on this button
|
||||
* @param emoji The emoji to display on this button
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
this.data.emoji = emoji;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this button is disable or not
|
||||
* @param disabled Whether or not to disable this button or not
|
||||
*/
|
||||
public setDisabled(disabled = true) {
|
||||
this.data.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for this button
|
||||
* @param label The label to display on this button
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIButtonComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
} as APIButtonComponent;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { APISelectMenuComponent, APISelectMenuOption } from 'discord-api-types/v10';
|
||||
import { UnsafeSelectMenuBuilder } from './UnsafeSelectMenu';
|
||||
import { UnsafeSelectMenuOptionBuilder } from './UnsafeSelectMenuOption';
|
||||
import { APISelectMenuOption, ComponentType, type APISelectMenuComponent } from 'discord-api-types/v10';
|
||||
import { SelectMenuOptionBuilder } from './SelectMenuOption';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray';
|
||||
import {
|
||||
customIdValidator,
|
||||
disabledValidator,
|
||||
@@ -10,59 +10,118 @@ import {
|
||||
placeholderValidator,
|
||||
validateRequiredSelectMenuParameters,
|
||||
} from '../Assertions';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
|
||||
/**
|
||||
* Represents a validated select menu component
|
||||
* Represents a select menu component
|
||||
*/
|
||||
export class SelectMenuBuilder extends UnsafeSelectMenuBuilder {
|
||||
public override setPlaceholder(placeholder: string) {
|
||||
return super.setPlaceholder(placeholderValidator.parse(placeholder));
|
||||
export class SelectMenuBuilder extends ComponentBuilder<APISelectMenuComponent> {
|
||||
/**
|
||||
* The options within this select menu
|
||||
*/
|
||||
public readonly options: SelectMenuOptionBuilder[];
|
||||
|
||||
public constructor(data?: Partial<APISelectMenuComponent>) {
|
||||
const { options, ...initData } = data ?? {};
|
||||
super({ type: ComponentType.SelectMenu, ...initData });
|
||||
this.options = options?.map((o) => new SelectMenuOptionBuilder(o)) ?? [];
|
||||
}
|
||||
|
||||
public override setMinValues(minValues: number) {
|
||||
return super.setMinValues(minMaxValidator.parse(minValues));
|
||||
/**
|
||||
* Sets the placeholder for this select menu
|
||||
*
|
||||
* @param placeholder - The placeholder to use for this select menu
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
this.data.placeholder = placeholderValidator.parse(placeholder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setMaxValues(maxValues: number) {
|
||||
return super.setMaxValues(minMaxValidator.parse(maxValues));
|
||||
/**
|
||||
* Sets the minimum values that must be selected in the select menu
|
||||
*
|
||||
* @param minValues - The minimum values that must be selected
|
||||
*/
|
||||
public setMinValues(minValues: number) {
|
||||
this.data.min_values = minMaxValidator.parse(minValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setCustomId(customId: string) {
|
||||
return super.setCustomId(customIdValidator.parse(customId));
|
||||
/**
|
||||
* Sets the maximum values that must be selected in the select menu
|
||||
*
|
||||
* @param maxValues - The maximum values that must be selected
|
||||
*/
|
||||
public setMaxValues(maxValues: number) {
|
||||
this.data.max_values = minMaxValidator.parse(maxValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setDisabled(disabled = true) {
|
||||
return super.setDisabled(disabledValidator.parse(disabled));
|
||||
/**
|
||||
* Sets the custom id for this select menu
|
||||
*
|
||||
* @param customId - The custom id to use for this select menu
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customIdValidator.parse(customId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override addOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
|
||||
/**
|
||||
* Sets whether this select menu is disabled
|
||||
*
|
||||
* @param disabled - Whether this select menu is disabled
|
||||
*/
|
||||
public setDisabled(disabled = true) {
|
||||
this.data.disabled = disabledValidator.parse(disabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to this select menu
|
||||
*
|
||||
* @param options - The options to add to this select menu
|
||||
* @returns
|
||||
*/
|
||||
public addOptions(...options: RestOrArray<SelectMenuOptionBuilder | APISelectMenuOption>) {
|
||||
options = normalizeArray(options);
|
||||
optionsLengthValidator.parse(this.options.length + options.length);
|
||||
this.options.push(
|
||||
...options.map((option) =>
|
||||
option instanceof UnsafeSelectMenuOptionBuilder
|
||||
option instanceof SelectMenuOptionBuilder
|
||||
? option
|
||||
: new UnsafeSelectMenuOptionBuilder(optionValidator.parse(option) as APISelectMenuOption),
|
||||
: new SelectMenuOptionBuilder(optionValidator.parse<APISelectMenuOption>(option)),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
|
||||
/**
|
||||
* Sets the options on this select menu
|
||||
*
|
||||
* @param options - The options to set on this select menu
|
||||
*/
|
||||
public setOptions(...options: RestOrArray<SelectMenuOptionBuilder | APISelectMenuOption>) {
|
||||
options = normalizeArray(options);
|
||||
optionsLengthValidator.parse(options.length);
|
||||
this.options.splice(
|
||||
0,
|
||||
this.options.length,
|
||||
...options.map((option) =>
|
||||
option instanceof UnsafeSelectMenuOptionBuilder
|
||||
option instanceof SelectMenuOptionBuilder
|
||||
? option
|
||||
: new UnsafeSelectMenuOptionBuilder(optionValidator.parse(option) as APISelectMenuOption),
|
||||
: new SelectMenuOptionBuilder(optionValidator.parse<APISelectMenuOption>(option)),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override toJSON(): APISelectMenuComponent {
|
||||
public toJSON(): APISelectMenuComponent {
|
||||
validateRequiredSelectMenuParameters(this.options, this.data.custom_id);
|
||||
return super.toJSON();
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
options: this.options.map((o) => o.toJSON()),
|
||||
} as APISelectMenuComponent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,73 @@
|
||||
import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10';
|
||||
import { UnsafeSelectMenuOptionBuilder } from './UnsafeSelectMenuOption';
|
||||
|
||||
import {
|
||||
defaultValidator,
|
||||
emojiValidator,
|
||||
labelValueValidator,
|
||||
labelValueDescriptionValidator,
|
||||
validateRequiredSelectMenuOptionParameters,
|
||||
} from '../Assertions';
|
||||
|
||||
/**
|
||||
* Represents a validated option within a select menu component
|
||||
* Represents a option within a select menu component
|
||||
*/
|
||||
export class SelectMenuOptionBuilder extends UnsafeSelectMenuOptionBuilder {
|
||||
public override setDescription(description: string) {
|
||||
return super.setDescription(labelValueValidator.parse(description));
|
||||
export class SelectMenuOptionBuilder {
|
||||
public constructor(public data: Partial<APISelectMenuOption> = {}) {}
|
||||
|
||||
/**
|
||||
* Sets the label of this option
|
||||
*
|
||||
* @param label - The label to show on this option
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = labelValueDescriptionValidator.parse(label);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setDefault(isDefault = true) {
|
||||
return super.setDefault(defaultValidator.parse(isDefault));
|
||||
/**
|
||||
* Sets the value of this option
|
||||
*
|
||||
* @param value - The value of this option
|
||||
*/
|
||||
public setValue(value: string) {
|
||||
this.data.value = labelValueDescriptionValidator.parse(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
return super.setEmoji(emojiValidator.parse(emoji));
|
||||
/**
|
||||
* Sets the description of this option
|
||||
*
|
||||
* @param description - The description of this option
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
this.data.description = labelValueDescriptionValidator.parse(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override toJSON(): APISelectMenuOption {
|
||||
/**
|
||||
* Sets whether this option is selected by default
|
||||
*
|
||||
* @param isDefault - Whether this option is selected by default
|
||||
*/
|
||||
public setDefault(isDefault = true) {
|
||||
this.data.default = defaultValidator.parse(isDefault);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the emoji to display on this option
|
||||
*
|
||||
* @param emoji - The emoji to display on this option
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
this.data.emoji = emojiValidator.parse(emoji);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuOption {
|
||||
validateRequiredSelectMenuOptionParameters(this.data.label, this.data.value);
|
||||
return super.toJSON();
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
} as APISelectMenuOption;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
import { APISelectMenuOption, ComponentType, type APISelectMenuComponent } from 'discord-api-types/v10';
|
||||
import { UnsafeSelectMenuOptionBuilder } from './UnsafeSelectMenuOption';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
|
||||
/**
|
||||
* Represents a non-validated select menu component
|
||||
*/
|
||||
export class UnsafeSelectMenuBuilder extends ComponentBuilder<APISelectMenuComponent> {
|
||||
/**
|
||||
* The options within this select menu
|
||||
*/
|
||||
public readonly options: UnsafeSelectMenuOptionBuilder[];
|
||||
|
||||
public constructor(data?: Partial<APISelectMenuComponent>) {
|
||||
const { options, ...initData } = data ?? {};
|
||||
super({ type: ComponentType.SelectMenu, ...initData });
|
||||
this.options = options?.map((o) => new UnsafeSelectMenuOptionBuilder(o)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder for this select menu
|
||||
* @param placeholder The placeholder to use for this select menu
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
this.data.placeholder = placeholder;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum values that must be selected in the select menu
|
||||
* @param minValues The minimum values that must be selected
|
||||
*/
|
||||
public setMinValues(minValues: number) {
|
||||
this.data.min_values = minValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum values that must be selected in the select menu
|
||||
* @param minValues The maximum values that must be selected
|
||||
*/
|
||||
public setMaxValues(maxValues: number) {
|
||||
this.data.max_values = maxValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom Id for this select menu
|
||||
* @param customId The custom id to use for this select menu
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this select menu is disabled
|
||||
* @param disabled Whether or not this select menu is disabled
|
||||
*/
|
||||
public setDisabled(disabled = true) {
|
||||
this.data.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to this select menu
|
||||
* @param options The options to add to this select menu
|
||||
* @returns
|
||||
*/
|
||||
public addOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
|
||||
this.options.push(
|
||||
...options.map((option) =>
|
||||
option instanceof UnsafeSelectMenuOptionBuilder ? option : new UnsafeSelectMenuOptionBuilder(option),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options on this select menu
|
||||
* @param options The options to set on this select menu
|
||||
*/
|
||||
public setOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
|
||||
this.options.splice(
|
||||
0,
|
||||
this.options.length,
|
||||
...options.map((option) =>
|
||||
option instanceof UnsafeSelectMenuOptionBuilder ? option : new UnsafeSelectMenuOptionBuilder(option),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
options: this.options.map((o) => o.toJSON()),
|
||||
} as APISelectMenuComponent;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10';
|
||||
|
||||
/**
|
||||
* Represents a non-validated option within a select menu component
|
||||
*/
|
||||
export class UnsafeSelectMenuOptionBuilder {
|
||||
public constructor(public data: Partial<APISelectMenuOption> = {}) {}
|
||||
|
||||
/**
|
||||
* Sets the label of this option
|
||||
* @param label The label to show on this option
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this option
|
||||
* @param value The value of this option
|
||||
*/
|
||||
public setValue(value: string) {
|
||||
this.data.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this option.
|
||||
* @param description The description of this option
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
this.data.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this option is selected by default
|
||||
* @param isDefault Whether this option is selected by default
|
||||
*/
|
||||
public setDefault(isDefault = true) {
|
||||
this.data.default = isDefault;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the emoji to display on this option
|
||||
* @param emoji The emoji to display on this option
|
||||
*/
|
||||
public setEmoji(emoji: APIMessageComponentEmoji) {
|
||||
this.data.emoji = emoji;
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APISelectMenuOption {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
} as APISelectMenuOption;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { TextInputStyle } from 'discord-api-types/v10';
|
||||
import { isValidationEnabled } from '../../util/validation';
|
||||
import { customIdValidator } from '../Assertions';
|
||||
|
||||
export const textInputStyleValidator = s.nativeEnum(TextInputStyle);
|
||||
export const minLengthValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(4000);
|
||||
export const maxLengthValidator = s.number.int.greaterThanOrEqual(1).lessThanOrEqual(4000);
|
||||
export const minLengthValidator = s.number.int
|
||||
.greaterThanOrEqual(0)
|
||||
.lessThanOrEqual(4000)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const maxLengthValidator = s.number.int
|
||||
.greaterThanOrEqual(1)
|
||||
.lessThanOrEqual(4000)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const requiredValidator = s.boolean;
|
||||
export const valueValidator = s.string.lengthLessThanOrEqual(4000);
|
||||
export const placeholderValidator = s.string.lengthLessThanOrEqual(100);
|
||||
export const labelValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(45);
|
||||
export const valueValidator = s.string.lengthLessThanOrEqual(4000).setValidationEnabled(isValidationEnabled);
|
||||
export const placeholderValidator = s.string.lengthLessThanOrEqual(100).setValidationEnabled(isValidationEnabled);
|
||||
export const labelValidator = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(45)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateRequiredParameters(customId?: string, style?: TextInputStyle, label?: string) {
|
||||
customIdValidator.parse(customId);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { APITextInputComponent } from 'discord-api-types/v10';
|
||||
import { ComponentType, type TextInputStyle, type APITextInputComponent } from 'discord-api-types/v10';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import {
|
||||
maxLengthValidator,
|
||||
minLengthValidator,
|
||||
@@ -6,32 +7,111 @@ import {
|
||||
requiredValidator,
|
||||
valueValidator,
|
||||
validateRequiredParameters,
|
||||
labelValidator,
|
||||
textInputStyleValidator,
|
||||
} from './Assertions';
|
||||
import { UnsafeTextInputBuilder } from './UnsafeTextInput';
|
||||
import { isJSONEncodable, type JSONEncodable } from '../../util/jsonEncodable';
|
||||
import { customIdValidator } from '../Assertions';
|
||||
import { ComponentBuilder } from '../Component';
|
||||
|
||||
export class TextInputBuilder extends UnsafeTextInputBuilder {
|
||||
public override setMinLength(minLength: number) {
|
||||
return super.setMinLength(minLengthValidator.parse(minLength));
|
||||
export class TextInputBuilder extends ComponentBuilder<APITextInputComponent> {
|
||||
public constructor(data?: APITextInputComponent & { type?: ComponentType.TextInput }) {
|
||||
super({ type: ComponentType.TextInput, ...data });
|
||||
}
|
||||
|
||||
public override setMaxLength(maxLength: number) {
|
||||
return super.setMaxLength(maxLengthValidator.parse(maxLength));
|
||||
/**
|
||||
* Sets the custom id for this text input
|
||||
*
|
||||
* @param customId - The custom id of this text input
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customIdValidator.parse(customId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setRequired(required = true) {
|
||||
return super.setRequired(requiredValidator.parse(required));
|
||||
/**
|
||||
* Sets the label for this text input
|
||||
*
|
||||
* @param label - The label for this text input
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = labelValidator.parse(label);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setValue(value: string) {
|
||||
return super.setValue(valueValidator.parse(value));
|
||||
/**
|
||||
* Sets the style for this text input
|
||||
*
|
||||
* @param style - The style for this text input
|
||||
*/
|
||||
public setStyle(style: TextInputStyle) {
|
||||
this.data.style = textInputStyleValidator.parse(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setPlaceholder(placeholder: string) {
|
||||
return super.setPlaceholder(placeholderValidator.parse(placeholder));
|
||||
/**
|
||||
* Sets the minimum length of text for this text input
|
||||
*
|
||||
* @param minLength - The minimum length of text for this text input
|
||||
*/
|
||||
public setMinLength(minLength: number) {
|
||||
this.data.min_length = minLengthValidator.parse(minLength);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override toJSON(): APITextInputComponent {
|
||||
/**
|
||||
* Sets the maximum length of text for this text input
|
||||
*
|
||||
* @param maxLength - The maximum length of text for this text input
|
||||
*/
|
||||
public setMaxLength(maxLength: number) {
|
||||
this.data.max_length = maxLengthValidator.parse(maxLength);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder of this text input
|
||||
*
|
||||
* @param placeholder - The placeholder of this text input
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
this.data.placeholder = placeholderValidator.parse(placeholder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this text input
|
||||
*
|
||||
* @param value - The value for this text input
|
||||
*/
|
||||
public setValue(value: string) {
|
||||
this.data.value = valueValidator.parse(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this text input is required
|
||||
*
|
||||
* @param required - Whether this text input is required
|
||||
*/
|
||||
public setRequired(required = true) {
|
||||
this.data.required = requiredValidator.parse(required);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APITextInputComponent {
|
||||
validateRequiredParameters(this.data.custom_id, this.data.style, this.data.label);
|
||||
return super.toJSON();
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
} as APITextInputComponent;
|
||||
}
|
||||
|
||||
public equals(other: JSONEncodable<APITextInputComponent> | APITextInputComponent): boolean {
|
||||
if (isJSONEncodable(other)) {
|
||||
return isEqual(other.toJSON(), this.data);
|
||||
}
|
||||
|
||||
return isEqual(other, this.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import { ComponentType, type TextInputStyle, type APITextInputComponent } from 'discord-api-types/v10';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { ComponentBuilder } from '../../index';
|
||||
|
||||
export class UnsafeTextInputBuilder extends ComponentBuilder<APITextInputComponent> {
|
||||
public constructor(data?: APITextInputComponent & { type?: ComponentType.TextInput }) {
|
||||
super({ type: ComponentType.TextInput, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id for this text input
|
||||
* @param customId The custom id of this text input
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for this text input
|
||||
* @param label The label for this text input
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style for this text input
|
||||
* @param style The style for this text input
|
||||
*/
|
||||
public setStyle(style: TextInputStyle) {
|
||||
this.data.style = style;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum length of text for this text input
|
||||
* @param minLength The minimum length of text for this text input
|
||||
*/
|
||||
public setMinLength(minLength: number) {
|
||||
this.data.min_length = minLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum length of text for this text input
|
||||
* @param maxLength The maximum length of text for this text input
|
||||
*/
|
||||
public setMaxLength(maxLength: number) {
|
||||
this.data.max_length = maxLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder of this text input
|
||||
* @param placeholder The placeholder of this text input
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
this.data.placeholder = placeholder;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this text input
|
||||
* @param value The value for this text input
|
||||
*/
|
||||
public setValue(value: string) {
|
||||
this.data.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this text input is required or not
|
||||
* @param required Whether this text input is required or not
|
||||
*/
|
||||
public setRequired(required = true) {
|
||||
this.data.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APITextInputComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
} as APITextInputComponent;
|
||||
}
|
||||
|
||||
public equals(other: UnsafeTextInputBuilder | APITextInputComponent): boolean {
|
||||
if (other instanceof UnsafeTextInputBuilder) {
|
||||
return isEqual(other.data, this.data);
|
||||
}
|
||||
|
||||
return isEqual(other, this.data);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
export * as EmbedAssertions from './messages/embed/Assertions';
|
||||
export * from './messages/embed/Embed';
|
||||
export * from './messages/formatters';
|
||||
export * from './messages/embed/UnsafeEmbed';
|
||||
|
||||
export * as ComponentAssertions from './components/Assertions';
|
||||
export * from './components/ActionRow';
|
||||
@@ -10,15 +9,10 @@ export * from './components/Component';
|
||||
export * from './components/Components';
|
||||
export * from './components/textInput/TextInput';
|
||||
export * as TextInputAssertions from './components/textInput/Assertions';
|
||||
export * from './components/textInput/UnsafeTextInput';
|
||||
export * from './interactions/modals/UnsafeModal';
|
||||
export * from './interactions/modals/Modal';
|
||||
export * as ModalAssertions from './interactions/modals/Assertions';
|
||||
export * from './components/selectMenu/SelectMenu';
|
||||
export * from './components/selectMenu/SelectMenuOption';
|
||||
export * from './components/button/UnsafeButton';
|
||||
export * from './components/selectMenu/UnsafeSelectMenu';
|
||||
export * from './components/selectMenu/UnsafeSelectMenuOption';
|
||||
|
||||
export * as SlashCommandAssertions from './interactions/slashCommands/Assertions';
|
||||
export * from './interactions/slashCommands/SlashCommandBuilder';
|
||||
@@ -45,3 +39,5 @@ export * from './interactions/contextMenuCommands/ContextMenuCommandBuilder';
|
||||
export * from './util/jsonEncodable';
|
||||
export * from './util/equatable';
|
||||
export * from './util/componentUtil';
|
||||
export * from './util/normalizeArray';
|
||||
export * from './util/validation';
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { ApplicationCommandType } from 'discord-api-types/v10';
|
||||
import type { ContextMenuCommandType } from './ContextMenuCommandBuilder';
|
||||
import { isValidationEnabled } from '../../util/validation';
|
||||
|
||||
const namePredicate = s.string
|
||||
.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));
|
||||
|
||||
.regex(/^( *[\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+ *)+$/u)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
const typePredicate = s
|
||||
.union(s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message))
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
const booleanPredicate = s.boolean;
|
||||
|
||||
export function validateDefaultPermission(value: unknown): asserts value is boolean {
|
||||
|
||||
@@ -53,7 +53,7 @@ export class ContextMenuCommandBuilder {
|
||||
/**
|
||||
* Sets the name
|
||||
*
|
||||
* @param name The name
|
||||
* @param name - The name
|
||||
*/
|
||||
public setName(name: string) {
|
||||
// Assert the name matches the conditions
|
||||
@@ -67,7 +67,7 @@ export class ContextMenuCommandBuilder {
|
||||
/**
|
||||
* Sets the type
|
||||
*
|
||||
* @param type The type
|
||||
* @param type - The type
|
||||
*/
|
||||
public setType(type: ContextMenuCommandType) {
|
||||
// Assert the type is valid
|
||||
@@ -83,7 +83,7 @@ export class ContextMenuCommandBuilder {
|
||||
*
|
||||
* **Note**: If set to `false`, you will have to later `PUT` the permissions for this command.
|
||||
*
|
||||
* @param value Whether or not to enable this command by default
|
||||
* @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.
|
||||
@@ -102,7 +102,7 @@ export class ContextMenuCommandBuilder {
|
||||
*
|
||||
* **Note:** You can set this to `'0'` to disable the command by default.
|
||||
*
|
||||
* @param permissions The permissions bit field to set
|
||||
* @param permissions - The permissions bit field to set
|
||||
*
|
||||
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
|
||||
*/
|
||||
@@ -119,7 +119,7 @@ export class ContextMenuCommandBuilder {
|
||||
* 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
|
||||
* @param enabled - If the command should be enabled in DMs
|
||||
*
|
||||
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
|
||||
*/
|
||||
@@ -135,8 +135,8 @@ export class ContextMenuCommandBuilder {
|
||||
/**
|
||||
* Sets a name localization
|
||||
*
|
||||
* @param locale The locale to set a description for
|
||||
* @param localizedName The localized description for the given locale
|
||||
* @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) {
|
||||
@@ -159,7 +159,7 @@ export class ContextMenuCommandBuilder {
|
||||
/**
|
||||
* Sets the name localizations
|
||||
*
|
||||
* @param localizedNames The dictionary of localized descriptions to set
|
||||
* @param localizedNames - The dictionary of localized descriptions to set
|
||||
*/
|
||||
public setNameLocalizations(localizedNames: LocalizationMap | null) {
|
||||
if (localizedNames === null) {
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../..';
|
||||
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow';
|
||||
import { customIdValidator } from '../../components/Assertions';
|
||||
import { isValidationEnabled } from '../../util/validation';
|
||||
|
||||
export const titleValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(45);
|
||||
export const componentsValidator = s.instance(ActionRowBuilder).array.lengthGreaterThanOrEqual(1);
|
||||
export const titleValidator = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(45)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const componentsValidator = s
|
||||
.instance(ActionRowBuilder)
|
||||
.array.lengthGreaterThanOrEqual(1)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateRequiredParameters(
|
||||
customId?: string,
|
||||
|
||||
@@ -1,19 +1,81 @@
|
||||
import type { APIModalInteractionResponseCallbackData } from 'discord-api-types/v10';
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIModalActionRowComponent,
|
||||
APIModalInteractionResponseCallbackData,
|
||||
} from 'discord-api-types/v10';
|
||||
import { titleValidator, validateRequiredParameters } from './Assertions';
|
||||
import { UnsafeModalBuilder } from './UnsafeModal';
|
||||
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow';
|
||||
import { customIdValidator } from '../../components/Assertions';
|
||||
import { createComponentBuilder } from '../../components/Components';
|
||||
import type { JSONEncodable } from '../../util/jsonEncodable';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray';
|
||||
|
||||
export class ModalBuilder extends UnsafeModalBuilder {
|
||||
public override setCustomId(customId: string): this {
|
||||
return super.setCustomId(customIdValidator.parse(customId));
|
||||
export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCallbackData> {
|
||||
public readonly data: Partial<APIModalInteractionResponseCallbackData>;
|
||||
public readonly components: ActionRowBuilder<ModalActionRowComponentBuilder>[] = [];
|
||||
|
||||
public constructor({ components, ...data }: Partial<APIModalInteractionResponseCallbackData> = {}) {
|
||||
this.data = { ...data };
|
||||
this.components = (components?.map((c) => createComponentBuilder(c)) ??
|
||||
[]) as ActionRowBuilder<ModalActionRowComponentBuilder>[];
|
||||
}
|
||||
|
||||
public override setTitle(title: string) {
|
||||
return super.setTitle(titleValidator.parse(title));
|
||||
/**
|
||||
* Sets the title of the modal
|
||||
*
|
||||
* @param title - The title of the modal
|
||||
*/
|
||||
public setTitle(title: string) {
|
||||
this.data.title = titleValidator.parse(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override toJSON(): APIModalInteractionResponseCallbackData {
|
||||
/**
|
||||
* Sets the custom id of the modal
|
||||
*
|
||||
* @param customId - The custom id of this modal
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customIdValidator.parse(customId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to this modal
|
||||
*
|
||||
* @param components - The components to add to this modal
|
||||
*/
|
||||
public addComponents(
|
||||
...components: RestOrArray<
|
||||
ActionRowBuilder<ModalActionRowComponentBuilder> | APIActionRowComponent<APIModalActionRowComponent>
|
||||
>
|
||||
) {
|
||||
this.components.push(
|
||||
...normalizeArray(components).map((component) =>
|
||||
component instanceof ActionRowBuilder
|
||||
? component
|
||||
: new ActionRowBuilder<ModalActionRowComponentBuilder>(component),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the components in this modal
|
||||
*
|
||||
* @param components - The components to set this modal to
|
||||
*/
|
||||
public setComponents(...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder>>) {
|
||||
this.components.splice(0, this.components.length, ...normalizeArray(components));
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIModalInteractionResponseCallbackData {
|
||||
validateRequiredParameters(this.data.custom_id, this.data.title, this.components);
|
||||
return super.toJSON();
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map((component) => component.toJSON()),
|
||||
} as APIModalInteractionResponseCallbackData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIModalActionRowComponent,
|
||||
APIModalInteractionResponseCallbackData,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ActionRowBuilder, createComponentBuilder, JSONEncodable, ModalActionRowComponentBuilder } from '../../index';
|
||||
|
||||
export class UnsafeModalBuilder implements JSONEncodable<APIModalInteractionResponseCallbackData> {
|
||||
public readonly data: Partial<APIModalInteractionResponseCallbackData>;
|
||||
public readonly components: ActionRowBuilder<ModalActionRowComponentBuilder>[] = [];
|
||||
|
||||
public constructor({ components, ...data }: Partial<APIModalInteractionResponseCallbackData> = {}) {
|
||||
this.data = { ...data };
|
||||
this.components = (components?.map((c) => createComponentBuilder(c)) ??
|
||||
[]) as ActionRowBuilder<ModalActionRowComponentBuilder>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of the modal
|
||||
* @param title The title of the modal
|
||||
*/
|
||||
public setTitle(title: string) {
|
||||
this.data.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id of the modal
|
||||
* @param customId The custom id of this modal
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to this modal
|
||||
* @param components The components to add to this modal
|
||||
*/
|
||||
public addComponents(
|
||||
components: (
|
||||
| ActionRowBuilder<ModalActionRowComponentBuilder>
|
||||
| APIActionRowComponent<APIModalActionRowComponent>
|
||||
)[],
|
||||
) {
|
||||
this.components.push(
|
||||
...components.map((component) =>
|
||||
component instanceof ActionRowBuilder
|
||||
? component
|
||||
: new ActionRowBuilder<ModalActionRowComponentBuilder>(component),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the components in this modal
|
||||
* @param components The components to set this modal to
|
||||
*/
|
||||
public setComponents(components: ActionRowBuilder<ModalActionRowComponentBuilder>[]) {
|
||||
this.components.splice(0, this.components.length, ...components);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIModalInteractionResponseCallbackData {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map((component) => component.toJSON()),
|
||||
} as APIModalInteractionResponseCallbackData;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,31 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import is from '@sindresorhus/is';
|
||||
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';
|
||||
import { isValidationEnabled } from '../../util/validation';
|
||||
|
||||
const namePredicate = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(32)
|
||||
.regex(/^[\P{Lu}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u);
|
||||
.regex(/^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateName(name: unknown): asserts name is string {
|
||||
namePredicate.parse(name);
|
||||
}
|
||||
|
||||
const descriptionPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
|
||||
const descriptionPredicate = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(100)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
const localePredicate = s.nativeEnum(Locale);
|
||||
|
||||
export function validateDescription(description: unknown): asserts description is string {
|
||||
descriptionPredicate.parse(description);
|
||||
}
|
||||
|
||||
const maxArrayLengthPredicate = s.unknown.array.lengthLessThanOrEqual(25);
|
||||
const maxArrayLengthPredicate = s.unknown.array.lengthLessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
|
||||
export function validateLocale(locale: unknown) {
|
||||
return localePredicate.parse(locale);
|
||||
}
|
||||
@@ -55,7 +59,7 @@ export function validateRequired(required: unknown): asserts required is boolean
|
||||
booleanPredicate.parse(required);
|
||||
}
|
||||
|
||||
const choicesLengthPredicate = s.number.lessThanOrEqual(25);
|
||||
const choicesLengthPredicate = s.number.lessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateChoicesLength(amountAdding: number, choices?: APIApplicationCommandOptionChoice[]): void {
|
||||
choicesLengthPredicate.parse((choices?.length ?? 0) + amountAdding);
|
||||
@@ -64,33 +68,12 @@ export function validateChoicesLength(amountAdding: number, choices?: APIApplica
|
||||
export function assertReturnOfBuilder<
|
||||
T extends ApplicationCommandOptionBase | SlashCommandSubcommandBuilder | SlashCommandSubcommandGroupBuilder,
|
||||
>(input: unknown, ExpectedInstanceOf: new () => T): asserts input is T {
|
||||
const instanceName = ExpectedInstanceOf.name;
|
||||
|
||||
if (is.nullOrUndefined(input)) {
|
||||
throw new TypeError(
|
||||
`Expected to receive a ${instanceName} builder, got ${input === null ? 'null' : 'undefined'} instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (is.primitive(input)) {
|
||||
throw new TypeError(`Expected to receive a ${instanceName} builder, got a primitive (${typeof input}) instead.`);
|
||||
}
|
||||
|
||||
if (!(input instanceof ExpectedInstanceOf)) {
|
||||
const casted = input as Record<PropertyKey, unknown>;
|
||||
|
||||
const constructorName = is.function_(input) ? input.name : casted.constructor.name;
|
||||
const stringTag = Reflect.get(casted, Symbol.toStringTag) as string | undefined;
|
||||
|
||||
const fullResultName = stringTag ? `${constructorName} [${stringTag}]` : constructorName;
|
||||
|
||||
throw new TypeError(`Expected to receive a ${instanceName} builder, got ${fullResultName} instead.`);
|
||||
}
|
||||
s.instance(ExpectedInstanceOf).parse(input);
|
||||
}
|
||||
|
||||
export const localizationMapPredicate = s.object<LocalizationMap>(
|
||||
Object.fromEntries(Object.values(Locale).map((locale) => [locale, s.string.nullish])),
|
||||
).strict.nullish;
|
||||
export const localizationMapPredicate = s
|
||||
.object<LocalizationMap>(Object.fromEntries(Object.values(Locale).map((locale) => [locale, s.string.nullish])))
|
||||
.strict.nullish.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateLocalizationMap(value: unknown): asserts value is LocalizationMap {
|
||||
localizationMapPredicate.parse(value);
|
||||
|
||||
@@ -86,7 +86,7 @@ export class SlashCommandBuilder {
|
||||
*
|
||||
* **Note**: If set to `false`, you will have to later `PUT` the permissions for this command.
|
||||
*
|
||||
* @param value Whether or not to enable this command by default
|
||||
* @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.
|
||||
@@ -105,7 +105,7 @@ export class SlashCommandBuilder {
|
||||
*
|
||||
* **Note:** You can set this to `'0'` to disable the command by default.
|
||||
*
|
||||
* @param permissions The permissions bit field to set
|
||||
* @param permissions - The permissions bit field to set
|
||||
*
|
||||
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
|
||||
*/
|
||||
@@ -122,7 +122,7 @@ export class SlashCommandBuilder {
|
||||
* 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
|
||||
* @param enabled - If the command should be enabled in DMs
|
||||
*
|
||||
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
|
||||
*/
|
||||
@@ -138,7 +138,7 @@ export class SlashCommandBuilder {
|
||||
/**
|
||||
* Adds a new subcommand group to this command
|
||||
*
|
||||
* @param input A function that returns a subcommand group builder, or an already built builder
|
||||
* @param input - A function that returns a subcommand group builder, or an already built builder
|
||||
*/
|
||||
public addSubcommandGroup(
|
||||
input:
|
||||
@@ -164,7 +164,7 @@ export class SlashCommandBuilder {
|
||||
/**
|
||||
* Adds a new subcommand to this command
|
||||
*
|
||||
* @param input A function that returns a subcommand builder, or an already built builder
|
||||
* @param input - A function that returns a subcommand builder, or an already built builder
|
||||
*/
|
||||
public addSubcommand(
|
||||
input:
|
||||
|
||||
@@ -35,7 +35,7 @@ export class SlashCommandSubcommandGroupBuilder implements ToAPIApplicationComma
|
||||
/**
|
||||
* Adds a new subcommand to this group
|
||||
*
|
||||
* @param input A function that returns a subcommand builder, or an already built builder
|
||||
* @param input - A function that returns a subcommand builder, or an already built builder
|
||||
*/
|
||||
public addSubcommand(
|
||||
input:
|
||||
|
||||
@@ -4,13 +4,15 @@ export abstract class ApplicationCommandNumericOptionMinMaxValueMixin {
|
||||
|
||||
/**
|
||||
* Sets the maximum number value of this option
|
||||
* @param max The maximum value this option can be
|
||||
*
|
||||
* @param max - The maximum value this option can be
|
||||
*/
|
||||
public abstract setMaxValue(max: number): this;
|
||||
|
||||
/**
|
||||
* Sets the minimum number value of this option
|
||||
* @param min The minimum value this option can be
|
||||
*
|
||||
* @param min - The minimum value this option can be
|
||||
*/
|
||||
public abstract setMinValue(min: number): this;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export abstract class ApplicationCommandOptionBase extends SharedNameAndDescript
|
||||
/**
|
||||
* Marks the option as required
|
||||
*
|
||||
* @param required If this option should be required
|
||||
* @param required - If this option should be required
|
||||
*/
|
||||
public setRequired(required: boolean) {
|
||||
// Assert that you actually passed a boolean
|
||||
|
||||
@@ -23,7 +23,7 @@ export class ApplicationCommandOptionChannelTypesMixin {
|
||||
/**
|
||||
* Adds channel types to this option
|
||||
*
|
||||
* @param channelTypes The channel types to add
|
||||
* @param channelTypes - The channel types to add
|
||||
*/
|
||||
public addChannelTypes(...channelTypes: ApplicationCommandOptionAllowedChannelTypes[]) {
|
||||
if (this.channel_types === undefined) {
|
||||
|
||||
@@ -21,7 +21,7 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
|
||||
/**
|
||||
* Adds multiple choices for this option
|
||||
*
|
||||
* @param choices The choices to add
|
||||
* @param choices - The choices to add
|
||||
*/
|
||||
public addChoices(...choices: APIApplicationCommandOptionChoice<T>[]): this {
|
||||
if (choices.length > 0 && this.autocomplete) {
|
||||
@@ -65,7 +65,7 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
|
||||
|
||||
/**
|
||||
* Marks the option as autocompletable
|
||||
* @param autocomplete If this option should be autocompletable
|
||||
* @param autocomplete - If this option should be autocompletable
|
||||
*/
|
||||
public setAutocomplete(autocomplete: boolean): this {
|
||||
// Assert that you actually passed a boolean
|
||||
|
||||
@@ -10,7 +10,7 @@ export class SharedNameAndDescription {
|
||||
/**
|
||||
* Sets the name
|
||||
*
|
||||
* @param name The name
|
||||
* @param name - The name
|
||||
*/
|
||||
public setName(name: string): this {
|
||||
// Assert the name matches the conditions
|
||||
@@ -24,7 +24,7 @@ export class SharedNameAndDescription {
|
||||
/**
|
||||
* Sets the description
|
||||
*
|
||||
* @param description The description
|
||||
* @param description - The description
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
// Assert the description matches the conditions
|
||||
@@ -38,8 +38,8 @@ export class SharedNameAndDescription {
|
||||
/**
|
||||
* Sets a name localization
|
||||
*
|
||||
* @param locale The locale to set a description for
|
||||
* @param localizedName The localized description for the given locale
|
||||
* @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) {
|
||||
@@ -62,7 +62,7 @@ export class SharedNameAndDescription {
|
||||
/**
|
||||
* Sets the name localizations
|
||||
*
|
||||
* @param localizedNames The dictionary of localized descriptions to set
|
||||
* @param localizedNames - The dictionary of localized descriptions to set
|
||||
*/
|
||||
public setNameLocalizations(localizedNames: LocalizationMap | null) {
|
||||
if (localizedNames === null) {
|
||||
@@ -81,8 +81,8 @@ export class SharedNameAndDescription {
|
||||
/**
|
||||
* Sets a description localization
|
||||
*
|
||||
* @param locale The locale to set a description for
|
||||
* @param localizedDescription The localized description for the given locale
|
||||
* @param locale - The locale to set a description for
|
||||
* @param localizedDescription - The localized description for the given locale
|
||||
*/
|
||||
public setDescriptionLocalization(locale: LocaleString, localizedDescription: string | null) {
|
||||
if (!this.description_localizations) {
|
||||
@@ -105,7 +105,7 @@ export class SharedNameAndDescription {
|
||||
/**
|
||||
* Sets the description localizations
|
||||
*
|
||||
* @param localizedDescriptions The dictionary of localized descriptions to set
|
||||
* @param localizedDescriptions - The dictionary of localized descriptions to set
|
||||
*/
|
||||
public setDescriptionLocalizations(localizedDescriptions: LocalizationMap | null) {
|
||||
if (localizedDescriptions === null) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds a boolean option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addBooleanOption(
|
||||
input: SlashCommandBooleanOption | ((builder: SlashCommandBooleanOption) => SlashCommandBooleanOption),
|
||||
@@ -28,7 +28,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds a user option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addUserOption(input: SlashCommandUserOption | ((builder: SlashCommandUserOption) => SlashCommandUserOption)) {
|
||||
return this._sharedAddOptionMethod(input, SlashCommandUserOption);
|
||||
@@ -37,7 +37,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds a channel option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addChannelOption(
|
||||
input: SlashCommandChannelOption | ((builder: SlashCommandChannelOption) => SlashCommandChannelOption),
|
||||
@@ -48,7 +48,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds a role option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addRoleOption(input: SlashCommandRoleOption | ((builder: SlashCommandRoleOption) => SlashCommandRoleOption)) {
|
||||
return this._sharedAddOptionMethod(input, SlashCommandRoleOption);
|
||||
@@ -57,7 +57,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds an attachment option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addAttachmentOption(
|
||||
input: SlashCommandAttachmentOption | ((builder: SlashCommandAttachmentOption) => SlashCommandAttachmentOption),
|
||||
@@ -68,7 +68,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds a mentionable option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addMentionableOption(
|
||||
input: SlashCommandMentionableOption | ((builder: SlashCommandMentionableOption) => SlashCommandMentionableOption),
|
||||
@@ -79,7 +79,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds a string option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addStringOption(
|
||||
input:
|
||||
@@ -99,7 +99,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds an integer option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addIntegerOption(
|
||||
input:
|
||||
@@ -119,7 +119,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
/**
|
||||
* Adds a number option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
* @param input - A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addNumberOption(
|
||||
input:
|
||||
|
||||
@@ -1,11 +1,43 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import { APIApplicationCommandStringOption, ApplicationCommandOptionType } from 'discord-api-types/v10';
|
||||
import { mix } from 'ts-mixer';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
import { ApplicationCommandOptionWithChoicesAndAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithChoicesAndAutocompleteMixin';
|
||||
|
||||
const minLengthValidator = s.number.greaterThanOrEqual(0).lessThanOrEqual(6000);
|
||||
const maxLengthValidator = s.number.greaterThanOrEqual(1).lessThanOrEqual(6000);
|
||||
|
||||
@mix(ApplicationCommandOptionWithChoicesAndAutocompleteMixin)
|
||||
export class SlashCommandStringOption extends ApplicationCommandOptionBase {
|
||||
public readonly type = ApplicationCommandOptionType.String as const;
|
||||
public readonly max_length?: number;
|
||||
public readonly min_length?: number;
|
||||
|
||||
/**
|
||||
* Sets the maximum length of this string option.
|
||||
*
|
||||
* @param max - The maximum length this option can be
|
||||
*/
|
||||
public setMaxLength(max: number): this {
|
||||
maxLengthValidator.parse(max);
|
||||
|
||||
Reflect.set(this, 'max_length', max);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum length of this string option.
|
||||
*
|
||||
* @param min - The minimum length this option can be
|
||||
*/
|
||||
public setMinLength(min: number): this {
|
||||
minLengthValidator.parse(min);
|
||||
|
||||
Reflect.set(this, 'min_length', min);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIApplicationCommandStringOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
@@ -1,57 +1,84 @@
|
||||
import { s } from '@sapphire/shapeshift';
|
||||
import type { APIEmbedField } from 'discord-api-types/v10';
|
||||
import { isValidationEnabled } from '../../util/validation';
|
||||
|
||||
export const fieldNamePredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(256);
|
||||
export const fieldNamePredicate = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(256)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const fieldValuePredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(1024);
|
||||
export const fieldValuePredicate = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(1024)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const fieldInlinePredicate = s.boolean.optional;
|
||||
|
||||
export const embedFieldPredicate = s.object({
|
||||
name: fieldNamePredicate,
|
||||
value: fieldValuePredicate,
|
||||
inline: fieldInlinePredicate,
|
||||
});
|
||||
export const embedFieldPredicate = s
|
||||
.object({
|
||||
name: fieldNamePredicate,
|
||||
value: fieldValuePredicate,
|
||||
inline: fieldInlinePredicate,
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const embedFieldsArrayPredicate = embedFieldPredicate.array;
|
||||
export const embedFieldsArrayPredicate = embedFieldPredicate.array.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const fieldLengthPredicate = s.number.lessThanOrEqual(25);
|
||||
export const fieldLengthPredicate = s.number.lessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export function validateFieldLength(amountAdding: number, fields?: APIEmbedField[]): void {
|
||||
fieldLengthPredicate.parse((fields?.length ?? 0) + amountAdding);
|
||||
}
|
||||
|
||||
export const authorNamePredicate = fieldNamePredicate.nullable;
|
||||
export const authorNamePredicate = fieldNamePredicate.nullable.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const imageURLPredicate = s.string.url({
|
||||
allowedProtocols: ['http:', 'https:', 'attachment:'],
|
||||
}).nullish;
|
||||
export const imageURLPredicate = s.string
|
||||
.url({
|
||||
allowedProtocols: ['http:', 'https:', 'attachment:'],
|
||||
})
|
||||
.nullish.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const urlPredicate = s.string.url({
|
||||
allowedProtocols: ['http:', 'https:'],
|
||||
}).nullish;
|
||||
export const urlPredicate = s.string
|
||||
.url({
|
||||
allowedProtocols: ['http:', 'https:'],
|
||||
})
|
||||
.nullish.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const embedAuthorPredicate = s.object({
|
||||
name: authorNamePredicate,
|
||||
iconURL: imageURLPredicate,
|
||||
url: urlPredicate,
|
||||
});
|
||||
export const embedAuthorPredicate = s
|
||||
.object({
|
||||
name: authorNamePredicate,
|
||||
iconURL: imageURLPredicate,
|
||||
url: urlPredicate,
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const RGBPredicate = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(255);
|
||||
export const RGBPredicate = s.number.int
|
||||
.greaterThanOrEqual(0)
|
||||
.lessThanOrEqual(255)
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
export const colorPredicate = s.number.int
|
||||
.greaterThanOrEqual(0)
|
||||
.lessThanOrEqual(0xffffff)
|
||||
.or(s.tuple([RGBPredicate, RGBPredicate, RGBPredicate])).nullable;
|
||||
.or(s.tuple([RGBPredicate, RGBPredicate, RGBPredicate]))
|
||||
.nullable.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const descriptionPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(4096).nullable;
|
||||
export const descriptionPredicate = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(4096)
|
||||
.nullable.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const footerTextPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(2048).nullable;
|
||||
export const footerTextPredicate = s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(2048)
|
||||
.nullable.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const embedFooterPredicate = s.object({
|
||||
text: footerTextPredicate,
|
||||
iconURL: imageURLPredicate,
|
||||
});
|
||||
export const embedFooterPredicate = s
|
||||
.object({
|
||||
text: footerTextPredicate,
|
||||
iconURL: imageURLPredicate,
|
||||
})
|
||||
.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const timestampPredicate = s.union(s.number, s.date).nullable;
|
||||
export const timestampPredicate = s.union(s.number, s.date).nullable.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
export const titlePredicate = fieldNamePredicate.nullable;
|
||||
export const titlePredicate = fieldNamePredicate.nullable.setValidationEnabled(isValidationEnabled);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { APIEmbedField } from 'discord-api-types/v10';
|
||||
import type { APIEmbed, APIEmbedAuthor, APIEmbedField, APIEmbedFooter, APIEmbedImage } from 'discord-api-types/v10';
|
||||
import {
|
||||
colorPredicate,
|
||||
descriptionPredicate,
|
||||
@@ -11,82 +11,228 @@ import {
|
||||
urlPredicate,
|
||||
validateFieldLength,
|
||||
} from './Assertions';
|
||||
import { EmbedAuthorOptions, EmbedFooterOptions, RGBTuple, UnsafeEmbedBuilder } from './UnsafeEmbed';
|
||||
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray';
|
||||
|
||||
export type RGBTuple = [red: number, green: number, blue: number];
|
||||
|
||||
export interface IconData {
|
||||
/**
|
||||
* The URL of the icon
|
||||
*/
|
||||
iconURL?: string;
|
||||
/**
|
||||
* The proxy URL of the icon
|
||||
*/
|
||||
proxyIconURL?: string;
|
||||
}
|
||||
|
||||
export type EmbedAuthorData = Omit<APIEmbedAuthor, 'icon_url' | 'proxy_icon_url'> & IconData;
|
||||
|
||||
export type EmbedAuthorOptions = Omit<EmbedAuthorData, 'proxyIconURL'>;
|
||||
|
||||
export type EmbedFooterData = Omit<APIEmbedFooter, 'icon_url' | 'proxy_icon_url'> & IconData;
|
||||
|
||||
export type EmbedFooterOptions = Omit<EmbedFooterData, 'proxyIconURL'>;
|
||||
|
||||
export interface EmbedImageData extends Omit<APIEmbedImage, 'proxy_url'> {
|
||||
/**
|
||||
* The proxy URL for the image
|
||||
*/
|
||||
proxyURL?: string;
|
||||
}
|
||||
/**
|
||||
* Represents a validated embed in a message (image/video preview, rich embed, etc.)
|
||||
* Represents a embed in a message (image/video preview, rich embed, etc.)
|
||||
*/
|
||||
export class EmbedBuilder extends UnsafeEmbedBuilder {
|
||||
public override addFields(fields: APIEmbedField[]): this {
|
||||
export class EmbedBuilder {
|
||||
public readonly data: APIEmbed;
|
||||
|
||||
public constructor(data: APIEmbed = {}) {
|
||||
this.data = { ...data };
|
||||
if (data.timestamp) this.data.timestamp = new Date(data.timestamp).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields to the embed (max 25)
|
||||
*
|
||||
* @param fields The fields to add
|
||||
*/
|
||||
public addFields(...fields: RestOrArray<APIEmbedField>): this {
|
||||
fields = normalizeArray(fields);
|
||||
// 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));
|
||||
embedFieldsArrayPredicate.parse(fields);
|
||||
|
||||
if (this.data.fields) this.data.fields.push(...fields);
|
||||
else this.data.fields = fields;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
|
||||
/**
|
||||
* Removes, replaces, or inserts fields in the embed (max 25)
|
||||
*
|
||||
* @param index The index to start at
|
||||
* @param deleteCount The number of fields to remove
|
||||
* @param fields The replacing field objects
|
||||
*/
|
||||
public spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
|
||||
// Ensure adding these fields won't exceed the 25 field limit
|
||||
validateFieldLength(fields.length - deleteCount, this.data.fields);
|
||||
|
||||
// Data assertions
|
||||
return super.spliceFields(index, deleteCount, ...embedFieldsArrayPredicate.parse(fields));
|
||||
embedFieldsArrayPredicate.parse(fields);
|
||||
if (this.data.fields) this.data.fields.splice(index, deleteCount, ...fields);
|
||||
else this.data.fields = fields;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setAuthor(options: EmbedAuthorOptions | null): this {
|
||||
/**
|
||||
* Sets the embed's fields (max 25).
|
||||
* @param fields The fields to set
|
||||
*/
|
||||
public setFields(...fields: RestOrArray<APIEmbedField>) {
|
||||
this.spliceFields(0, this.data.fields?.length ?? 0, ...normalizeArray(fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the author of this embed
|
||||
*
|
||||
* @param options The options for the author
|
||||
*/
|
||||
|
||||
public setAuthor(options: EmbedAuthorOptions | null): this {
|
||||
if (options === null) {
|
||||
return super.setAuthor(null);
|
||||
this.data.author = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Data assertions
|
||||
embedAuthorPredicate.parse(options);
|
||||
|
||||
return super.setAuthor(options);
|
||||
this.data.author = { name: options.name, url: options.url, icon_url: options.iconURL };
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setColor(color: number | RGBTuple | null): this {
|
||||
/**
|
||||
* Sets the color of this embed
|
||||
*
|
||||
* @param color The color of the embed
|
||||
*/
|
||||
public setColor(color: number | RGBTuple | null): this {
|
||||
// Data assertions
|
||||
return super.setColor(colorPredicate.parse(color));
|
||||
colorPredicate.parse(color);
|
||||
|
||||
if (Array.isArray(color)) {
|
||||
const [red, green, blue] = color;
|
||||
this.data.color = (red << 16) + (green << 8) + blue;
|
||||
return this;
|
||||
}
|
||||
this.data.color = color ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setDescription(description: string | null): this {
|
||||
/**
|
||||
* Sets the description of this embed
|
||||
*
|
||||
* @param description The description
|
||||
*/
|
||||
public setDescription(description: string | null): this {
|
||||
// Data assertions
|
||||
return super.setDescription(descriptionPredicate.parse(description));
|
||||
descriptionPredicate.parse(description);
|
||||
|
||||
this.data.description = description ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setFooter(options: EmbedFooterOptions | null): this {
|
||||
/**
|
||||
* Sets the footer of this embed
|
||||
*
|
||||
* @param options The options for the footer
|
||||
*/
|
||||
public setFooter(options: EmbedFooterOptions | null): this {
|
||||
if (options === null) {
|
||||
return super.setFooter(null);
|
||||
this.data.footer = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Data assertions
|
||||
embedFooterPredicate.parse(options);
|
||||
|
||||
return super.setFooter(options);
|
||||
this.data.footer = { text: options.text, icon_url: options.iconURL };
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setImage(url: string | null): this {
|
||||
/**
|
||||
* Sets the image of this embed
|
||||
*
|
||||
* @param url The URL of the image
|
||||
*/
|
||||
public setImage(url: string | null): this {
|
||||
// Data assertions
|
||||
return super.setImage(imageURLPredicate.parse(url)!);
|
||||
imageURLPredicate.parse(url);
|
||||
|
||||
this.data.image = url ? { url } : undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setThumbnail(url: string | null): this {
|
||||
/**
|
||||
* Sets the thumbnail of this embed
|
||||
*
|
||||
* @param url The URL of the thumbnail
|
||||
*/
|
||||
public setThumbnail(url: string | null): this {
|
||||
// Data assertions
|
||||
return super.setThumbnail(imageURLPredicate.parse(url)!);
|
||||
imageURLPredicate.parse(url);
|
||||
|
||||
this.data.thumbnail = url ? { url } : undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setTimestamp(timestamp: number | Date | null = Date.now()): this {
|
||||
/**
|
||||
* Sets the timestamp of this embed
|
||||
*
|
||||
* @param timestamp The timestamp or date
|
||||
*/
|
||||
public setTimestamp(timestamp: number | Date | null = Date.now()): this {
|
||||
// Data assertions
|
||||
return super.setTimestamp(timestampPredicate.parse(timestamp));
|
||||
timestampPredicate.parse(timestamp);
|
||||
|
||||
this.data.timestamp = timestamp ? new Date(timestamp).toISOString() : undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setTitle(title: string | null): this {
|
||||
/**
|
||||
* Sets the title of this embed
|
||||
*
|
||||
* @param title The title
|
||||
*/
|
||||
public setTitle(title: string | null): this {
|
||||
// Data assertions
|
||||
return super.setTitle(titlePredicate.parse(title));
|
||||
titlePredicate.parse(title);
|
||||
|
||||
this.data.title = title ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override setURL(url: string | null): this {
|
||||
/**
|
||||
* Sets the URL of this embed
|
||||
*
|
||||
* @param url The URL
|
||||
*/
|
||||
public setURL(url: string | null): this {
|
||||
// Data assertions
|
||||
return super.setURL(urlPredicate.parse(url)!);
|
||||
urlPredicate.parse(url);
|
||||
|
||||
this.data.url = url ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the embed to a plain object
|
||||
*/
|
||||
public toJSON(): APIEmbed {
|
||||
return { ...this.data };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
import type { APIEmbed, APIEmbedAuthor, APIEmbedField, APIEmbedFooter, APIEmbedImage } from 'discord-api-types/v10';
|
||||
|
||||
export type RGBTuple = [red: number, green: number, blue: number];
|
||||
|
||||
export interface IconData {
|
||||
/**
|
||||
* The URL of the icon
|
||||
*/
|
||||
iconURL?: string;
|
||||
/**
|
||||
* The proxy URL of the icon
|
||||
*/
|
||||
proxyIconURL?: string;
|
||||
}
|
||||
|
||||
export type EmbedAuthorData = Omit<APIEmbedAuthor, 'icon_url' | 'proxy_icon_url'> & IconData;
|
||||
|
||||
export type EmbedAuthorOptions = Omit<EmbedAuthorData, 'proxyIconURL'>;
|
||||
|
||||
export type EmbedFooterData = Omit<APIEmbedFooter, 'icon_url' | 'proxy_icon_url'> & IconData;
|
||||
|
||||
export type EmbedFooterOptions = Omit<EmbedFooterData, 'proxyIconURL'>;
|
||||
|
||||
export interface EmbedImageData extends Omit<APIEmbedImage, 'proxy_url'> {
|
||||
/**
|
||||
* The proxy URL for the image
|
||||
*/
|
||||
proxyURL?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a non-validated embed in a message (image/video preview, rich embed, etc.)
|
||||
*/
|
||||
export class UnsafeEmbedBuilder {
|
||||
public readonly data: APIEmbed;
|
||||
|
||||
public constructor(data: APIEmbed = {}) {
|
||||
this.data = { ...data };
|
||||
if (data.timestamp) this.data.timestamp = new Date(data.timestamp).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields to the embed (max 25)
|
||||
*
|
||||
* @param fields The fields to add
|
||||
*/
|
||||
public addFields(fields: APIEmbedField[]): this {
|
||||
if (this.data.fields) this.data.fields.push(...fields);
|
||||
else this.data.fields = fields;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, or inserts fields in the embed (max 25)
|
||||
*
|
||||
* @param index The index to start at
|
||||
* @param deleteCount The number of fields to remove
|
||||
* @param fields The replacing field objects
|
||||
*/
|
||||
public spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
|
||||
if (this.data.fields) this.data.fields.splice(index, deleteCount, ...fields);
|
||||
else this.data.fields = fields;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the embed's fields (max 25).
|
||||
* @param fields The fields to set
|
||||
*/
|
||||
public setFields(fields: APIEmbedField[]) {
|
||||
this.spliceFields(0, this.data.fields?.length ?? 0, ...fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the author of this embed
|
||||
*
|
||||
* @param options The options for the author
|
||||
*/
|
||||
public setAuthor(options: EmbedAuthorOptions | null): this {
|
||||
if (options === null) {
|
||||
this.data.author = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.data.author = { name: options.name, url: options.url, icon_url: options.iconURL };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of this embed
|
||||
*
|
||||
* @param color The color of the embed
|
||||
*/
|
||||
public setColor(color: number | RGBTuple | null): this {
|
||||
if (Array.isArray(color)) {
|
||||
const [red, green, blue] = color;
|
||||
this.data.color = (red << 16) + (green << 8) + blue;
|
||||
return this;
|
||||
}
|
||||
this.data.color = color ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this embed
|
||||
*
|
||||
* @param description The description
|
||||
*/
|
||||
public setDescription(description: string | null): this {
|
||||
this.data.description = description ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the footer of this embed
|
||||
*
|
||||
* @param options The options for the footer
|
||||
*/
|
||||
public setFooter(options: EmbedFooterOptions | null): this {
|
||||
if (options === null) {
|
||||
this.data.footer = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.data.footer = { text: options.text, icon_url: options.iconURL };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image of this embed
|
||||
*
|
||||
* @param url The URL of the image
|
||||
*/
|
||||
public setImage(url: string | null): this {
|
||||
this.data.image = url ? { url } : undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the thumbnail of this embed
|
||||
*
|
||||
* @param url The URL of the thumbnail
|
||||
*/
|
||||
public setThumbnail(url: string | null): this {
|
||||
this.data.thumbnail = url ? { url } : undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timestamp of this embed
|
||||
*
|
||||
* @param timestamp The timestamp or date
|
||||
*/
|
||||
public setTimestamp(timestamp: number | Date | null = Date.now()): this {
|
||||
this.data.timestamp = timestamp ? new Date(timestamp).toISOString() : undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this embed
|
||||
*
|
||||
* @param title The title
|
||||
*/
|
||||
public setTitle(title: string | null): this {
|
||||
this.data.title = title ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this embed
|
||||
*
|
||||
* @param url The URL
|
||||
*/
|
||||
public setURL(url: string | null): this {
|
||||
this.data.url = url ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the embed to a plain object
|
||||
*/
|
||||
public toJSON(): APIEmbed {
|
||||
return { ...this.data };
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,15 @@ import type { Snowflake } from 'discord-api-types/globals';
|
||||
/**
|
||||
* Wraps the content inside a codeblock with no language
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function codeBlock<C extends string>(content: C): `\`\`\`\n${C}\`\`\``;
|
||||
|
||||
/**
|
||||
* Wraps the content inside a codeblock with the specified language
|
||||
*
|
||||
* @param language The language for the codeblock
|
||||
* @param content The content to wrap
|
||||
* @param language - The language for the codeblock
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function codeBlock<L extends string, C extends string>(language: L, content: C): `\`\`\`${L}\n${C}\`\`\``;
|
||||
export function codeBlock(language: string, content?: string): string {
|
||||
@@ -22,7 +22,7 @@ export function codeBlock(language: string, content?: string): string {
|
||||
/**
|
||||
* Wraps the content inside \`backticks\`, which formats it as inline code
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function inlineCode<C extends string>(content: C): `\`${C}\`` {
|
||||
return `\`${content}\``;
|
||||
@@ -31,7 +31,7 @@ export function inlineCode<C extends string>(content: C): `\`${C}\`` {
|
||||
/**
|
||||
* Formats the content into italic text
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function italic<C extends string>(content: C): `_${C}_` {
|
||||
return `_${content}_`;
|
||||
@@ -40,7 +40,7 @@ export function italic<C extends string>(content: C): `_${C}_` {
|
||||
/**
|
||||
* Formats the content into bold text
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function bold<C extends string>(content: C): `**${C}**` {
|
||||
return `**${content}**`;
|
||||
@@ -49,7 +49,7 @@ export function bold<C extends string>(content: C): `**${C}**` {
|
||||
/**
|
||||
* Formats the content into underscored text
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function underscore<C extends string>(content: C): `__${C}__` {
|
||||
return `__${content}__`;
|
||||
@@ -58,7 +58,7 @@ export function underscore<C extends string>(content: C): `__${C}__` {
|
||||
/**
|
||||
* Formats the content into strike-through text
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function strikethrough<C extends string>(content: C): `~~${C}~~` {
|
||||
return `~~${content}~~`;
|
||||
@@ -67,7 +67,7 @@ export function strikethrough<C extends string>(content: C): `~~${C}~~` {
|
||||
/**
|
||||
* Formats the content into a quote. This needs to be at the start of the line for Discord to format it
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function quote<C extends string>(content: C): `> ${C}` {
|
||||
return `> ${content}`;
|
||||
@@ -76,7 +76,7 @@ export function quote<C extends string>(content: C): `> ${C}` {
|
||||
/**
|
||||
* Formats the content into a block quote. This needs to be at the start of the line for Discord to format it
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function blockQuote<C extends string>(content: C): `>>> ${C}` {
|
||||
return `>>> ${content}`;
|
||||
@@ -85,14 +85,14 @@ export function blockQuote<C extends string>(content: C): `>>> ${C}` {
|
||||
/**
|
||||
* Wraps the URL into `<>`, which stops it from embedding
|
||||
*
|
||||
* @param url The URL to wrap
|
||||
* @param url - The URL to wrap
|
||||
*/
|
||||
export function hideLinkEmbed<C extends string>(url: C): `<${C}>`;
|
||||
|
||||
/**
|
||||
* Wraps the URL into `<>`, which stops it from embedding
|
||||
*
|
||||
* @param url The URL to wrap
|
||||
* @param url - The URL to wrap
|
||||
*/
|
||||
export function hideLinkEmbed(url: URL): `<${string}>`;
|
||||
export function hideLinkEmbed(url: string | URL) {
|
||||
@@ -103,25 +103,25 @@ export function hideLinkEmbed(url: string | URL) {
|
||||
/**
|
||||
* Formats the content and the URL into a masked URL
|
||||
*
|
||||
* @param content The content to display
|
||||
* @param url The URL the content links to
|
||||
* @param content - The content to display
|
||||
* @param url - The URL the content links to
|
||||
*/
|
||||
export function hyperlink<C extends string>(content: C, url: URL): `[${C}](${string})`;
|
||||
|
||||
/**
|
||||
* Formats the content and the URL into a masked URL
|
||||
*
|
||||
* @param content The content to display
|
||||
* @param url The URL the content links to
|
||||
* @param content - The content to display
|
||||
* @param url - The URL the content links to
|
||||
*/
|
||||
export function hyperlink<C extends string, U extends string>(content: C, url: U): `[${C}](${U})`;
|
||||
|
||||
/**
|
||||
* Formats the content and the URL into a masked URL
|
||||
*
|
||||
* @param content The content to display
|
||||
* @param url The URL the content links to
|
||||
* @param title The title shown when hovering on the masked link
|
||||
* @param content - The content to display
|
||||
* @param url - The URL the content links to
|
||||
* @param title - The title shown when hovering on the masked link
|
||||
*/
|
||||
export function hyperlink<C extends string, T extends string>(
|
||||
content: C,
|
||||
@@ -132,9 +132,9 @@ export function hyperlink<C extends string, T extends string>(
|
||||
/**
|
||||
* Formats the content and the URL into a masked URL
|
||||
*
|
||||
* @param content The content to display
|
||||
* @param url The URL the content links to
|
||||
* @param title The title shown when hovering on the masked link
|
||||
* @param content - The content to display
|
||||
* @param url - The URL the content links to
|
||||
* @param title - The title shown when hovering on the masked link
|
||||
*/
|
||||
export function hyperlink<C extends string, U extends string, T extends string>(
|
||||
content: C,
|
||||
@@ -149,7 +149,7 @@ export function hyperlink(content: string, url: string | URL, title?: string) {
|
||||
/**
|
||||
* Wraps the content inside spoiler (hidden text)
|
||||
*
|
||||
* @param content The content to wrap
|
||||
* @param content - The content to wrap
|
||||
*/
|
||||
export function spoiler<C extends string>(content: C): `||${C}||` {
|
||||
return `||${content}||`;
|
||||
@@ -158,7 +158,7 @@ export function spoiler<C extends string>(content: C): `||${C}||` {
|
||||
/**
|
||||
* Formats a user ID into a user mention
|
||||
*
|
||||
* @param userId The user ID to format
|
||||
* @param userId - The user ID to format
|
||||
*/
|
||||
export function userMention<C extends Snowflake>(userId: C): `<@${C}>` {
|
||||
return `<@${userId}>`;
|
||||
@@ -167,7 +167,7 @@ export function userMention<C extends Snowflake>(userId: C): `<@${C}>` {
|
||||
/**
|
||||
* Formats a channel ID into a channel mention
|
||||
*
|
||||
* @param channelId The channel ID to format
|
||||
* @param channelId - The channel ID to format
|
||||
*/
|
||||
export function channelMention<C extends Snowflake>(channelId: C): `<#${C}>` {
|
||||
return `<#${channelId}>`;
|
||||
@@ -176,7 +176,7 @@ export function channelMention<C extends Snowflake>(channelId: C): `<#${C}>` {
|
||||
/**
|
||||
* Formats a role ID into a role mention
|
||||
*
|
||||
* @param roleId The role ID to format
|
||||
* @param roleId - The role ID to format
|
||||
*/
|
||||
export function roleMention<C extends Snowflake>(roleId: C): `<@&${C}>` {
|
||||
return `<@&${roleId}>`;
|
||||
@@ -185,23 +185,23 @@ export function roleMention<C extends Snowflake>(roleId: C): `<@&${C}>` {
|
||||
/**
|
||||
* Formats an emoji ID into a fully qualified emoji identifier
|
||||
*
|
||||
* @param emojiId The emoji ID to format
|
||||
* @param emojiId - The emoji ID to format
|
||||
*/
|
||||
export function formatEmoji<C extends Snowflake>(emojiId: C, animated?: false): `<:_:${C}>`;
|
||||
|
||||
/**
|
||||
* Formats an emoji ID into a fully qualified emoji identifier
|
||||
*
|
||||
* @param emojiId The emoji ID to format
|
||||
* @param animated Whether the emoji is animated or not. Defaults to `false`
|
||||
* @param emojiId - The emoji ID to format
|
||||
* @param animated - Whether the emoji is animated or not. Defaults to `false`
|
||||
*/
|
||||
export function formatEmoji<C extends Snowflake>(emojiId: C, animated?: true): `<a:_:${C}>`;
|
||||
|
||||
/**
|
||||
* Formats an emoji ID into a fully qualified emoji identifier
|
||||
*
|
||||
* @param emojiId The emoji ID to format
|
||||
* @param animated Whether the emoji is animated or not. Defaults to `false`
|
||||
* @param emojiId - The emoji ID to format
|
||||
* @param animated - Whether the emoji is animated or not. Defaults to `false`
|
||||
*/
|
||||
export function formatEmoji<C extends Snowflake>(emojiId: C, animated = false): `<a:_:${C}>` | `<:_:${C}>` {
|
||||
return `<${animated ? 'a' : ''}:_:${emojiId}>`;
|
||||
@@ -210,30 +210,30 @@ export function formatEmoji<C extends Snowflake>(emojiId: C, animated = false):
|
||||
/**
|
||||
* Formats a date into a short date-time string
|
||||
*
|
||||
* @param date The date to format, defaults to the current time
|
||||
* @param date - The date to format, defaults to the current time
|
||||
*/
|
||||
export function time(date?: Date): `<t:${bigint}>`;
|
||||
|
||||
/**
|
||||
* Formats a date given a format style
|
||||
*
|
||||
* @param date The date to format
|
||||
* @param style The style to use
|
||||
* @param date - The date to format
|
||||
* @param style - The style to use
|
||||
*/
|
||||
export function time<S extends TimestampStylesString>(date: Date, style: S): `<t:${bigint}:${S}>`;
|
||||
|
||||
/**
|
||||
* Formats the given timestamp into a short date-time string
|
||||
*
|
||||
* @param seconds The time to format, represents an UNIX timestamp in seconds
|
||||
* @param seconds - The time to format, represents an UNIX timestamp in seconds
|
||||
*/
|
||||
export function time<C extends number>(seconds: C): `<t:${C}>`;
|
||||
|
||||
/**
|
||||
* Formats the given timestamp into a short date-time string
|
||||
*
|
||||
* @param seconds The time to format, represents an UNIX timestamp in seconds
|
||||
* @param style The style to use
|
||||
* @param seconds - The time to format, represents an UNIX timestamp in seconds
|
||||
* @param style - The style to use
|
||||
*/
|
||||
export function time<C extends number, S extends TimestampStylesString>(seconds: C, style: S): `<t:${C}:${S}>`;
|
||||
export function time(timeOrSeconds?: number | Date, style?: TimestampStylesString): string {
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface Equatable<T> {
|
||||
|
||||
/**
|
||||
* Indicates if an object is equatable or not.
|
||||
* @param maybeEquatable The object to check against
|
||||
* @param maybeEquatable - The object to check against
|
||||
*/
|
||||
export function isEquatable(maybeEquatable: unknown): maybeEquatable is Equatable<unknown> {
|
||||
return maybeEquatable !== null && typeof maybeEquatable === 'object' && 'equals' in maybeEquatable;
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface JSONEncodable<T> {
|
||||
|
||||
/**
|
||||
* Indicates if an object is encodable or not.
|
||||
* @param maybeEncodable The object to check against
|
||||
* @param maybeEncodable - The object to check against
|
||||
*/
|
||||
export function isJSONEncodable(maybeEncodable: unknown): maybeEncodable is JSONEncodable<unknown> {
|
||||
return maybeEncodable !== null && typeof maybeEncodable === 'object' && 'toJSON' in maybeEncodable;
|
||||
|
||||
6
packages/builders/src/util/normalizeArray.ts
Normal file
6
packages/builders/src/util/normalizeArray.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function normalizeArray<T>(arr: RestOrArray<T>): T[] {
|
||||
if (Array.isArray(arr[0])) return arr[0];
|
||||
return arr as T[];
|
||||
}
|
||||
|
||||
export type RestOrArray<T> = T[] | [T[]];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user