mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 08:03:30 +01:00
chore: monorepo setup (#7175)
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-angular"],
|
||||
"rules": {
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
["chore", "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "types", "typings"]
|
||||
]
|
||||
}
|
||||
"extends": ["@commitlint/config-angular"],
|
||||
"rules": {
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
["chore", "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "types", "typings"]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,7 +1,5 @@
|
||||
**Please describe the changes this PR makes and why it should be merged:**
|
||||
|
||||
|
||||
|
||||
**Status and versioning classification:**
|
||||
|
||||
<!--
|
||||
|
||||
32
.github/tsc.json
vendored
32
.github/tsc.json
vendored
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "tsc",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+),(\\d+)\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"code": 5,
|
||||
"message": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "tsc",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+),(\\d+)\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"code": 5,
|
||||
"message": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
9
.github/workflows/auto-deprecate.yml
vendored
9
.github/workflows/auto-deprecate.yml
vendored
@@ -11,16 +11,17 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v16
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
cache: npm
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: yarn.lock
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
run: yarn --immutable
|
||||
|
||||
- name: Deprecate versions
|
||||
run: 'npm exec --no npm-deprecate -- --name "*dev*" --package "discord.js"'
|
||||
run: 'yarn npm-deprecate --name "*dev*" --package "discord.js"'
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
|
||||
29
.github/workflows/deploy.yml
vendored
29
.github/workflows/deploy.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: Deployment
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '!docs'
|
||||
tags:
|
||||
- '*'
|
||||
jobs:
|
||||
docs:
|
||||
name: Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build and deploy documentation
|
||||
uses: discordjs/action-docs@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
97
.github/workflows/documentation.yml
vendored
Normal file
97
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: Documentation
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'stable'
|
||||
- '!docs'
|
||||
tags:
|
||||
- '*'
|
||||
jobs:
|
||||
build:
|
||||
name: Build documentation
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
BRANCH_NAME: ${{ steps.env.outputs.BRANCH_NAME }}
|
||||
BRANCH_OR_TAG: ${{ steps.env.outputs.BRANCH_OR_TAG }}
|
||||
SHA: ${{ steps.env.outputs.SHA }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: yarn.lock
|
||||
|
||||
- name: Turbo cache
|
||||
id: turbo-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
turbo-${{ github.job }}-${{ github.ref_name }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --immutable
|
||||
|
||||
- name: Build docs
|
||||
run: yarn docs --cache-dir=".turbo"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docs
|
||||
path: packages/*/docs/docs.json
|
||||
|
||||
- name: Set outputs for upload job
|
||||
id: env
|
||||
run: |
|
||||
echo "::set-output name=BRANCH_NAME::${GITHUB_REF_NAME}"
|
||||
echo "::set-output name=BRANCH_OR_TAG::${GITHUB_REF_TYPE}"
|
||||
echo "::set-output name=SHA::${GITHUB_SHA}"
|
||||
|
||||
upload:
|
||||
name: Upload Documentation
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package: ['builders', 'collection', 'discord.js', 'voice']
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BRANCH_NAME: ${{ needs.build.outputs.BRANCH_NAME }}
|
||||
BRANCH_OR_TAG: ${{ needs.build.outputs.BRANCH_OR_TAG }}
|
||||
SHA: ${{ needs.build.outputs.SHA }}
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: docs
|
||||
path: docs
|
||||
|
||||
- name: Checkout docs repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'discordjs/docs'
|
||||
token: ${{ secrets.DJS_DOCS }}
|
||||
path: 'out'
|
||||
|
||||
- name: Move docs to correct directory
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
mkdir -p out/${PACKAGE}
|
||||
mv docs/${PACKAGE}/docs/docs.json out/${PACKAGE}/${BRANCH_NAME}.json
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
cd out
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
git add .
|
||||
git commit -m "Docs build for ${BRANCH_OR_TAG} ${BRANCH_NAME}: ${SHA}" || true
|
||||
git push
|
||||
2
.github/workflows/labelsync.yml
vendored
2
.github/workflows/labelsync.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Run Label Sync
|
||||
- name: Run label sync
|
||||
uses: crazy-max/ghaction-github-labeler@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
15
.github/workflows/publish-dev.yml
vendored
15
.github/workflows/publish-dev.yml
vendored
@@ -11,34 +11,35 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v16
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: npm
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: yarn.lock
|
||||
|
||||
- name: Check previous released version
|
||||
id: pre-release
|
||||
run: |
|
||||
if [[ $(npm view discord.js@dev version | grep -e "$(jq --raw-output '.version' package.json).*.$(git rev-parse --short HEAD | cut -b1-3)") ]]; \
|
||||
if [[ $(npm view discord.js@dev version | grep -e "$(jq --raw-output '.version' packages/discord.js/package.json).*.$(git rev-parse --short HEAD | cut -b1-3)") ]]; \
|
||||
then echo '::set-output name=release::false'; \
|
||||
else echo '::set-output name=release::true'; fi
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.pre-release.outputs.release == 'true'
|
||||
run: npm ci --ignore-scripts
|
||||
run: yarn --immutable
|
||||
|
||||
- name: Deprecate old versions
|
||||
if: steps.pre-release.outputs.release == 'true'
|
||||
run: npm deprecate discord.js@"~$(jq --raw-output '.version' package.json)" "no longer supported" || true
|
||||
run: npm deprecate discord.js@"~$(jq --raw-output '.version' packages/discord.js/package.json)" "no longer supported" || true
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
|
||||
- name: Publish
|
||||
if: steps.pre-release.outputs.release == 'true'
|
||||
run: |
|
||||
npm version --git-tag-version=false $(jq --raw-output '.version' package.json).$(date +%s).$(git rev-parse --short HEAD)
|
||||
npm publish --tag dev || true
|
||||
npm version --git-tag-version=false $(jq --raw-output '.version' packages/discord.js/package.json).$(date +%s).$(git rev-parse --short HEAD)
|
||||
yarn publish --tag dev || true
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
|
||||
78
.github/workflows/test.yml
vendored
78
.github/workflows/test.yml
vendored
@@ -2,80 +2,30 @@ name: Tests
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
lint:
|
||||
name: ESLint
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v16
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
cache: npm
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: yarn.lock
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run ESLint
|
||||
run: npm run lint
|
||||
|
||||
typings:
|
||||
name: TSLint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v16
|
||||
uses: actions/setup-node@v2
|
||||
- name: Turbo cache
|
||||
id: turbo-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
node-version: 16
|
||||
cache: npm
|
||||
path: .turbo
|
||||
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
turbo-${{ github.job }}-${{ github.ref_name }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: yarn --immutable
|
||||
|
||||
- name: Run TSLint
|
||||
run: npm run lint:typings
|
||||
|
||||
typescript:
|
||||
name: TypeScript
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Register Problem Matcher
|
||||
run: echo "##[add-matcher].github/tsc.json"
|
||||
|
||||
- name: Run Type Tests
|
||||
run: npm run test:typescript
|
||||
|
||||
docs:
|
||||
name: Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Test documentation
|
||||
run: npm run docs:test
|
||||
- name: Run eslint
|
||||
run: yarn lint --cache-dir=".turbo"
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -13,18 +13,13 @@ pids
|
||||
|
||||
# Env
|
||||
.env
|
||||
test/auth.json
|
||||
test/auth.js
|
||||
docs/deploy/deploy_key
|
||||
docs/deploy/deploy_key.pub
|
||||
deploy/deploy_key
|
||||
deploy/deploy_key.pub
|
||||
|
||||
# Dist
|
||||
dist/
|
||||
docs/docs.json
|
||||
|
||||
# Miscellaneous
|
||||
.tmp/
|
||||
.idea/
|
||||
.DS_Store
|
||||
.turbo
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit $1
|
||||
yarn commitlint --edit $1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install lint-staged
|
||||
yarn lint-staged && yarn lint:fix
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"*.{mjs,js}": "eslint --fix --ext mjs,js,ts",
|
||||
"*.{ts,json,yml,yaml}": "prettier --write"
|
||||
"*.{json,yml,yaml}": "prettier --write"
|
||||
}
|
||||
|
||||
5
.npmrc
5
.npmrc
@@ -1,5 +0,0 @@
|
||||
audit=false
|
||||
fund=false
|
||||
legacy-peer-deps=true
|
||||
tag-version-prefix=""
|
||||
message="chore(Release): %s"
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf",
|
||||
"arrowParens": "avoid"
|
||||
"printWidth": 120,
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"ecmaVersion": 7,
|
||||
"libs": [],
|
||||
"loadEagerly": ["./src/*.js"],
|
||||
"dontLoad": ["node_modules/**"],
|
||||
"plugins": {
|
||||
"es_modules": {},
|
||||
"node": {},
|
||||
"doc_comment": {
|
||||
"fullDocs": true,
|
||||
"strong": true
|
||||
},
|
||||
}
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"eslint.workingDirectories": [{ "pattern": "./packages/*" }]
|
||||
}
|
||||
44
README.md
44
README.md
@@ -24,7 +24,7 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to
|
||||
|
||||
## Installation
|
||||
|
||||
**Node.js 16.6.0 or newer is required.**
|
||||
**Node.js 16.6.0 or newer is required.**
|
||||
|
||||
```sh-session
|
||||
npm install discord.js
|
||||
@@ -43,6 +43,7 @@ pnpm add discord.js
|
||||
## Example usage
|
||||
|
||||
Install all required dependencies:
|
||||
|
||||
```sh-session
|
||||
npm install discord.js @discordjs/rest discord-api-types
|
||||
yarn add discord.js @discordjs/rest discord-api-types
|
||||
@@ -50,48 +51,49 @@ pnpm add discord.js @discordjs/rest discord-api-types
|
||||
```
|
||||
|
||||
Register a slash command against the Discord API:
|
||||
|
||||
```js
|
||||
const { REST } = require('@discordjs/rest');
|
||||
const { Routes } = require('discord-api-types/v9');
|
||||
|
||||
const commands = [{
|
||||
name: 'ping',
|
||||
description: 'Replies with Pong!'
|
||||
}];
|
||||
const commands = [
|
||||
{
|
||||
name: 'ping',
|
||||
description: 'Replies with Pong!',
|
||||
},
|
||||
];
|
||||
|
||||
const rest = new REST({ version: '9' }).setToken('token');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log('Started refreshing application (/) commands.');
|
||||
try {
|
||||
console.log('Started refreshing application (/) commands.');
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID),
|
||||
{ body: commands },
|
||||
);
|
||||
await rest.put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), { body: commands });
|
||||
|
||||
console.log('Successfully reloaded application (/) commands.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
console.log('Successfully reloaded application (/) commands.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
Afterwards we can create a quite simple example bot:
|
||||
|
||||
```js
|
||||
const { Client, Intents } = require('discord.js');
|
||||
const client = new Client({ intents: [Intents.FLAGS.GUILDS] });
|
||||
|
||||
client.on('ready', () => {
|
||||
console.log(`Logged in as ${client.user.tag}!`);
|
||||
console.log(`Logged in as ${client.user.tag}!`);
|
||||
});
|
||||
|
||||
client.on('interactionCreate', async interaction => {
|
||||
if (!interaction.isCommand()) return;
|
||||
client.on('interactionCreate', async (interaction) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'ping') {
|
||||
await interaction.reply('Pong!');
|
||||
}
|
||||
if (interaction.commandName === 'ping') {
|
||||
await interaction.reply('Pong!');
|
||||
}
|
||||
});
|
||||
|
||||
client.login('token');
|
||||
|
||||
@@ -10,7 +10,7 @@ body = """
|
||||
{% if previous.version %}\
|
||||
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
|
||||
{% else %}
|
||||
(https://github.com/discordjs/discord.js/tree/{{ version }}\
|
||||
(https://github.com/discordjs/discord.js/tree/{{ version }})\
|
||||
{% endif %}\
|
||||
{% endif %} \
|
||||
- ({{ timestamp | date(format="%Y-%m-%d") }})
|
||||
|
||||
22508
package-lock.json
generated
22508
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
189
package.json
189
package.json
@@ -1,88 +1,105 @@
|
||||
{
|
||||
"name": "discord.js",
|
||||
"version": "14.0.0-dev",
|
||||
"description": "A powerful library for interacting with the Discord API",
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run docs:test && npm run lint:typings && npm run test:typescript",
|
||||
"test:typescript": "tsc --noEmit && tsd",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"lint:typings": "tslint typings/index.d.ts",
|
||||
"format": "prettier --write src/**/*.js typings/**/*.ts",
|
||||
"prepare": "is-ci || husky install",
|
||||
"docs": "docgen --source src --custom docs/index.yml --output docs/docs.json",
|
||||
"docs:test": "docgen --source src --custom docs/index.yml",
|
||||
"prepublishOnly": "npm run test",
|
||||
"changelog": "git cliff --prepend CHANGELOG.md -l"
|
||||
},
|
||||
"main": "./src/index.js",
|
||||
"types": "./typings/index.d.ts",
|
||||
"files": [
|
||||
"src",
|
||||
"typings"
|
||||
],
|
||||
"directories": {
|
||||
"lib": "src",
|
||||
"test": "test"
|
||||
},
|
||||
"contributors": [
|
||||
"Crawl <icrawltogo@gmail.com>",
|
||||
"Amish Shah <amishshah.2k@gmail.com>",
|
||||
"Vlad Frangu <kingdgrizzle@gmail.com>",
|
||||
"SpaceEEC <spaceeec@yahoo.com>",
|
||||
"Antonio Roman <kyradiscord@gmail.com>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"discord",
|
||||
"api",
|
||||
"bot",
|
||||
"client",
|
||||
"node",
|
||||
"discordapp"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/discordjs/discord.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/discordjs/discord.js/issues"
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.11.0",
|
||||
"@discordjs/collection": "^0.4.0",
|
||||
"@sapphire/async-queue": "^1.1.9",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/ws": "^8.2.2",
|
||||
"discord-api-types": "^0.26.0",
|
||||
"form-data": "^4.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"ws": "^8.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^16.0.1",
|
||||
"@commitlint/config-angular": "^16.0.0",
|
||||
"@discordjs/docgen": "^0.11.0",
|
||||
"@favware/npm-deprecate": "^1.0.4",
|
||||
"@types/node": "^16.11.12",
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"dtslint": "^4.2.1",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"husky": "^7.0.4",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest": "^27.4.5",
|
||||
"lint-staged": "^12.1.4",
|
||||
"prettier": "^2.5.1",
|
||||
"tsd": "^0.19.0",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.6.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
"name": "@discordjs/discord.js",
|
||||
"version": "0.0.0",
|
||||
"description": "A powerful library for interacting with the Discord API",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"test": "turbo run test",
|
||||
"lint": "turbo run lint",
|
||||
"lint:fix": "turbo run lint:fix",
|
||||
"format": "turbo run format",
|
||||
"fmt": "turbo run format",
|
||||
"postinstall": "is-ci || husky install",
|
||||
"docs": "turbo run docs",
|
||||
"changelog": "turbo run changelog",
|
||||
"update": "yarn upgrade-interactive --latest"
|
||||
},
|
||||
"contributors": [
|
||||
"Crawl <icrawltogo@gmail.com>",
|
||||
"Amish Shah <amishshah.2k@gmail.com>",
|
||||
"Vlad Frangu <kingdgrizzle@gmail.com>",
|
||||
"SpaceEEC <spaceeec@yahoo.com>",
|
||||
"Antonio Roman <kyradiscord@gmail.com>"
|
||||
],
|
||||
"keywords": [
|
||||
"discord",
|
||||
"api",
|
||||
"bot",
|
||||
"client",
|
||||
"node",
|
||||
"discordapp"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/discordjs/discord.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/discordjs/discord.js/issues"
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^16.0.1",
|
||||
"@commitlint/config-angular": "^16.0.0",
|
||||
"@favware/npm-deprecate": "^1.0.4",
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.1.4",
|
||||
"prettier": "^2.5.1",
|
||||
"turbo": "^1.0.24-canary.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.6.0"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"turbo": {
|
||||
"baseBranch": "origin/main",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
"dist/**",
|
||||
"docs/docs.json"
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
"lint": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
"lint:fix": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
"format": {
|
||||
"outputs": []
|
||||
},
|
||||
"docs": {
|
||||
"outputs": [
|
||||
"docs/docs.json"
|
||||
]
|
||||
},
|
||||
"changelog": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
packages/builders/.eslintrc.json
Normal file
16
packages/builders/.eslintrc.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "marine/prettier/node",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json",
|
||||
"extraFileExtensions": [".mjs"]
|
||||
},
|
||||
"ignorePatterns": ["**/dist/*"],
|
||||
"env": {
|
||||
"jest": true
|
||||
},
|
||||
"rules": {
|
||||
"no-redeclare": 0,
|
||||
"@typescript-eslint/naming-convention": 0
|
||||
}
|
||||
}
|
||||
26
packages/builders/.gitignore
vendored
Normal file
26
packages/builders/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Packages
|
||||
node_modules/
|
||||
|
||||
# Log files
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Env
|
||||
.env
|
||||
|
||||
# Dist
|
||||
dist/
|
||||
typings/
|
||||
docs/**/*
|
||||
!docs/index.yml
|
||||
!docs/README.md
|
||||
|
||||
# Miscellaneous
|
||||
.tmp/
|
||||
coverage/
|
||||
8
packages/builders/.prettierrc.json
Normal file
8
packages/builders/.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
3
packages/builders/.versionrc
Normal file
3
packages/builders/.versionrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"releaseCommitMessageFormat": "chore(Release): publish"
|
||||
}
|
||||
153
packages/builders/CHANGELOG.md
Normal file
153
packages/builders/CHANGELOG.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
|
||||
# [0.11.0](https://github.com/discordjs/builders/compare/v0.10.0...v0.11.0) (2021-12-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ApplicationCommandOptions:** clean up code for builder options ([#68](https://github.com/discordjs/builders/issues/68)) ([b5d0b15](https://github.com/discordjs/builders/commit/b5d0b157b1262bd01fa011f8e0cf33adb82776e7))
|
||||
|
||||
|
||||
|
||||
# [0.10.0](https://github.com/discordjs/builders/compare/v0.9.0...v0.10.0) (2021-12-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use zod instead of ow for max/min option validation ([#66](https://github.com/discordjs/builders/issues/66)) ([beb35fb](https://github.com/discordjs/builders/commit/beb35fb1f65bd6be2321e17cc792f67e8615fd48))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add max/min option for int and number builder options ([#47](https://github.com/discordjs/builders/issues/47)) ([2e1e860](https://github.com/discordjs/builders/commit/2e1e860b46e3453398b20df63dabb6d4325e32d1))
|
||||
|
||||
|
||||
|
||||
# [0.9.0](https://github.com/discordjs/builders/compare/v0.8.2...v0.9.0) (2021-12-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* replace ow with zod ([#58](https://github.com/discordjs/builders/issues/58)) ([0b6fb81](https://github.com/discordjs/builders/commit/0b6fb8161b858e42781855fb73aaa873fec58160))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **SlashCommandBuilder:** add autocomplete ([#53](https://github.com/discordjs/builders/issues/53)) ([05b07a7](https://github.com/discordjs/builders/commit/05b07a7e88848188c27d7380d9f948cba25ef778))
|
||||
|
||||
|
||||
|
||||
## [0.8.2](https://github.com/discordjs/builders/compare/v0.8.1...v0.8.2) (2021-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* downgrade ow because of esm issues ([#55](https://github.com/discordjs/builders/issues/55)) ([3722d2c](https://github.com/discordjs/builders/commit/3722d2c1109a7a5c0abad63c1a7eb944df6e46c8))
|
||||
|
||||
|
||||
|
||||
## [0.8.1](https://github.com/discordjs/builders/compare/v0.8.0...v0.8.1) (2021-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* documentation ([e33ec8d](https://github.com/discordjs/builders/commit/e33ec8dfd5785312f82e0afb017a3dac614fd71d))
|
||||
|
||||
|
||||
|
||||
# [0.8.0](https://github.com/discordjs/builders/compare/v0.7.0...v0.8.0) (2021-10-29)
|
||||
|
||||
|
||||
|
||||
# [0.7.0](https://github.com/discordjs/builders/compare/v0.6.0...v0.7.0) (2021-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly type `toJSON` methods ([#34](https://github.com/discordjs/builders/issues/34)) ([7723ad0](https://github.com/discordjs/builders/commit/7723ad0da169386e638188de220451a97513bc25))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ContextMenus:** add context menu command builder ([#29](https://github.com/discordjs/builders/issues/29)) ([f0641e5](https://github.com/discordjs/builders/commit/f0641e55733de8992600f3082bcf054e6f815cf7))
|
||||
* add support for channel types on channel options ([#41](https://github.com/discordjs/builders/issues/41)) ([f6c187e](https://github.com/discordjs/builders/commit/f6c187e0ad6ebe03e65186ece3e95cb1db5aeb50))
|
||||
|
||||
|
||||
|
||||
# [0.6.0](https://github.com/discordjs/builders/compare/v0.5.0...v0.6.0) (2021-08-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SlashCommandBuilder:** allow subcommands and groups to coexist at the root level ([#26](https://github.com/discordjs/builders/issues/26)) ([0be4daf](https://github.com/discordjs/builders/commit/0be4dafdfc0b5747c880be0078c00ada913eb4fb))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* create `Embed` builder ([#11](https://github.com/discordjs/builders/issues/11)) ([eb942a4](https://github.com/discordjs/builders/commit/eb942a4d1f3bcec9a4e370b6af602a713ad8f9b7))
|
||||
* **SlashCommandBuilder:** create setDefaultPermission function ([#19](https://github.com/discordjs/builders/issues/19)) ([5d53759](https://github.com/discordjs/builders/commit/5d537593937a8da330153ce4711b7d093a80330e))
|
||||
* **SlashCommands:** add number option type ([#23](https://github.com/discordjs/builders/issues/23)) ([1563991](https://github.com/discordjs/builders/commit/1563991d421bb07bf7a412c87e7613692d770f04))
|
||||
|
||||
|
||||
|
||||
# [0.5.0](https://github.com/discordjs/builders/compare/v0.3.0...v0.5.0) (2021-08-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Formatters:** add `formatEmoji` ([#20](https://github.com/discordjs/builders/issues/20)) ([c3d8bb5](https://github.com/discordjs/builders/commit/c3d8bb5363a1d46b45c0def4277da6921e2ba209))
|
||||
|
||||
|
||||
|
||||
# [0.4.0](https://github.com/discordjs/builders/compare/v0.3.0...v0.4.0) (2021-08-05)
|
||||
|
||||
### Features
|
||||
|
||||
* `sub command` => `subcommand` ([#18](https://github.com/discordjs/builders/pull/18)) ([95599c5](https://github.com/discordjs/builders/commit/95599c5b5366ebd054c4c277c52f1a44cda1209d))
|
||||
|
||||
|
||||
|
||||
# [0.3.0](https://github.com/discordjs/builders/compare/v0.2.0...v0.3.0) (2021-08-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Shrug:** Update comment ([#14](https://github.com/discordjs/builders/issues/14)) ([6fa6c40](https://github.com/discordjs/builders/commit/6fa6c405f2ea733811677d3d1bfb1e2806d504d5))
|
||||
* shrug face rendering ([#13](https://github.com/discordjs/builders/issues/13)) ([6ad24ec](https://github.com/discordjs/builders/commit/6ad24ecd96c82b0f576e78e9e53fc7bf9c36ef5d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **formatters:** mentions ([#9](https://github.com/discordjs/builders/issues/9)) ([f83fe99](https://github.com/discordjs/builders/commit/f83fe99b83188ed999845751ffb005c687dbd60a))
|
||||
* **Formatters:** Add a spoiler function ([#16](https://github.com/discordjs/builders/issues/16)) ([c213a6a](https://github.com/discordjs/builders/commit/c213a6abb114f65653017a4edec4bdba2162d771))
|
||||
* **SlashCommands:** add slash command builders ([#3](https://github.com/discordjs/builders/issues/3)) ([6aa3af0](https://github.com/discordjs/builders/commit/6aa3af07b0ee342fff91f080914bb12b3ab773f8))
|
||||
* shrug, tableflip and unflip strings ([#5](https://github.com/discordjs/builders/issues/5)) ([de5fa82](https://github.com/discordjs/builders/commit/de5fa823cd6f1feba5b2d0a63b2cb1761dfd1814))
|
||||
|
||||
|
||||
|
||||
# [0.2.0](https://github.com/discordjs/builders/compare/v0.1.1...v0.2.0) (2021-07-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Formatters:** added `hyperlink` and `hideLinkEmbed` ([#4](https://github.com/discordjs/builders/issues/4)) ([c532daf](https://github.com/discordjs/builders/commit/c532daf2ba2feae75bf9668f63462e96a5314cff))
|
||||
|
||||
|
||||
|
||||
## [0.1.1](https://github.com/discordjs/builders/compare/v0.1.0...v0.1.1) (2021-06-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Deps:** added `tslib` as dependency ([#2](https://github.com/discordjs/builders/issues/2)) ([5576ff3](https://github.com/discordjs/builders/commit/5576ff3b67136b957bed0ab8a4c655d5de322813))
|
||||
|
||||
|
||||
|
||||
# 0.1.0 (2021-06-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added message formatters ([#1](https://github.com/discordjs/builders/issues/1)) ([765e46d](https://github.com/discordjs/builders/commit/765e46dac96c4e49d350243e5fad34c2bc738a7c))
|
||||
191
packages/builders/LICENSE
Normal file
191
packages/builders/LICENSE
Normal file
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2021 Noel Buechler
|
||||
Copyright 2021 Vlad Frangu
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
53
packages/builders/README.md
Normal file
53
packages/builders/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
<div align="center">
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
|
||||
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/v/@discordjs/builders.svg?maxAge=3600" alt="npm version" /></a>
|
||||
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/dt/@discordjs/builders.svg?maxAge=3600" alt="npm downloads" /></a>
|
||||
<a href="https://github.com/discordjs/builders/actions"><img src="https://github.com/discordjs/builders/workflows/Tests/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.6.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](./docs/examples/Slash%20Command%20Builders.md)
|
||||
|
||||
## Links
|
||||
|
||||
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
|
||||
- [Documentation](https://discord.js.org/#/docs/builders)
|
||||
- [Guide](https://discordjs.guide/) ([source](https://github.com/discordjs/guide))
|
||||
See also the [Update Guide](https://discordjs.guide/additional-info/changes-in-v13.html), including updated and removed items in the library.
|
||||
- [discord.js Discord server](https://discord.gg/djs)
|
||||
- [Discord API Discord server](https://discord.gg/discord-api)
|
||||
- [GitHub](https://github.com/discordjs/builders)
|
||||
- [npm](https://www.npmjs.com/package/@discordjs/builders)
|
||||
- [Related libraries](https://discord.com/developers/docs/topics/community-resources#libraries)
|
||||
|
||||
## Contributing
|
||||
|
||||
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
|
||||
[documentation](https://discord.js.org/#/docs/builders).
|
||||
See [the contribution guide](https://github.com/discordjs/builders/blob/main/.github/CONTRIBUTING.md) if you'd like to submit a PR.
|
||||
|
||||
## Help
|
||||
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
|
||||
nudge in the right direction, please don't hesitate to join our official [discord.js Server](https://discord.gg/djs).
|
||||
@@ -0,0 +1,89 @@
|
||||
import { ContextMenuCommandAssertions, ContextMenuCommandBuilder } from '../../src/index';
|
||||
|
||||
const getBuilder = () => new ContextMenuCommandBuilder();
|
||||
|
||||
describe('Context Menu Commands', () => {
|
||||
describe('Assertions tests', () => {
|
||||
test('GIVEN valid name THEN does not throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateName('ping')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateName(null)).toThrowError();
|
||||
|
||||
// Too short of a name
|
||||
expect(() => ContextMenuCommandAssertions.validateName('')).toThrowError();
|
||||
|
||||
// Invalid characters used
|
||||
expect(() => ContextMenuCommandAssertions.validateName('ABC123$%^&')).toThrowError();
|
||||
|
||||
// Too long of a name
|
||||
expect(() =>
|
||||
ContextMenuCommandAssertions.validateName('qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid type THEN does not throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateType(3)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid type THEN throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateType(null)).toThrowError();
|
||||
|
||||
// Out of range
|
||||
expect(() => ContextMenuCommandAssertions.validateType(1)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid required parameters THEN does not throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateRequiredParameters('owo', 2)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid default_permission THEN does not throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateDefaultPermission(true)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid default_permission THEN throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateDefaultPermission(null)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ContextMenuCommandBuilder', () => {
|
||||
describe('Builder tests', () => {
|
||||
test('GIVEN empty builder THEN throw error when calling toJSON', () => {
|
||||
expect(() => getBuilder().toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('example').setType(3).toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => getBuilder().setName('$$$')).toThrowError();
|
||||
|
||||
expect(() => getBuilder().setName(' ')).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid names THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('hi_there')).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().setName('A COMMAND')).not.toThrowError();
|
||||
|
||||
// Translation: a_command
|
||||
expect(() => getBuilder().setName('o_comandă')).not.toThrowError();
|
||||
|
||||
// Translation: thx (according to GTranslate)
|
||||
expect(() => getBuilder().setName('どうも')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid types THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setType(2)).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().setType(3)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with defaultPermission false THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('foo').setDefaultPermission(false)).not.toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
import {
|
||||
APIApplicationCommandBooleanOption,
|
||||
APIApplicationCommandChannelOption,
|
||||
APIApplicationCommandIntegerOption,
|
||||
APIApplicationCommandMentionableOption,
|
||||
APIApplicationCommandNumberOption,
|
||||
APIApplicationCommandRoleOption,
|
||||
APIApplicationCommandStringOption,
|
||||
APIApplicationCommandUserOption,
|
||||
ApplicationCommandOptionType,
|
||||
ChannelType,
|
||||
} from 'discord-api-types/v9';
|
||||
import {
|
||||
SlashCommandBooleanOption,
|
||||
SlashCommandChannelOption,
|
||||
SlashCommandIntegerOption,
|
||||
SlashCommandMentionableOption,
|
||||
SlashCommandNumberOption,
|
||||
SlashCommandRoleOption,
|
||||
SlashCommandStringOption,
|
||||
SlashCommandUserOption,
|
||||
} from '../../../src/index';
|
||||
|
||||
const getBooleanOption = () =>
|
||||
new SlashCommandBooleanOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getChannelOption = () =>
|
||||
new SlashCommandChannelOption()
|
||||
.setName('owo')
|
||||
.setDescription('Testing 123')
|
||||
.setRequired(true)
|
||||
.addChannelType(ChannelType.GuildText);
|
||||
|
||||
const getStringOption = () =>
|
||||
new SlashCommandStringOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getIntegerOption = () =>
|
||||
new SlashCommandIntegerOption()
|
||||
.setName('owo')
|
||||
.setDescription('Testing 123')
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(10);
|
||||
|
||||
const getNumberOption = () =>
|
||||
new SlashCommandNumberOption()
|
||||
.setName('owo')
|
||||
.setDescription('Testing 123')
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(10);
|
||||
|
||||
const getUserOption = () => new SlashCommandUserOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getRoleOption = () => new SlashCommandRoleOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getMentionableOption = () =>
|
||||
new SlashCommandMentionableOption().setName('owo').setDescription('Testing 123').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>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Boolean,
|
||||
required: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a channel option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getChannelOption().toJSON()).toEqual<APIApplicationCommandChannelOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Channel,
|
||||
required: true,
|
||||
channel_types: [ChannelType.GuildText],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a integer option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getIntegerOption().toJSON()).toEqual<APIApplicationCommandIntegerOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Integer,
|
||||
required: true,
|
||||
max_value: 10,
|
||||
min_value: 1,
|
||||
});
|
||||
|
||||
expect(
|
||||
getIntegerOption().setAutocomplete(true).setChoices([]).toJSON(),
|
||||
).toEqual<APIApplicationCommandIntegerOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Integer,
|
||||
required: true,
|
||||
max_value: 10,
|
||||
min_value: 1,
|
||||
autocomplete: true,
|
||||
// @ts-expect-error TODO: you *can* send an empty array with autocomplete: true, should correct that in types
|
||||
choices: [],
|
||||
});
|
||||
|
||||
expect(getIntegerOption().addChoice('uwu', 1).toJSON()).toEqual<APIApplicationCommandIntegerOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Integer,
|
||||
required: true,
|
||||
max_value: 10,
|
||||
min_value: 1,
|
||||
choices: [{ name: 'uwu', value: 1 }],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a mentionable option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getMentionableOption().toJSON()).toEqual<APIApplicationCommandMentionableOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Mentionable,
|
||||
required: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a number option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getNumberOption().toJSON()).toEqual<APIApplicationCommandNumberOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Number,
|
||||
required: true,
|
||||
max_value: 10,
|
||||
min_value: 1,
|
||||
});
|
||||
|
||||
expect(getNumberOption().setAutocomplete(true).setChoices([]).toJSON()).toEqual<APIApplicationCommandNumberOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Number,
|
||||
required: true,
|
||||
max_value: 10,
|
||||
min_value: 1,
|
||||
autocomplete: true,
|
||||
// @ts-expect-error TODO: you *can* send an empty array with autocomplete: true, should correct that in types
|
||||
choices: [],
|
||||
});
|
||||
|
||||
expect(getNumberOption().addChoice('uwu', 1).toJSON()).toEqual<APIApplicationCommandNumberOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Number,
|
||||
required: true,
|
||||
max_value: 10,
|
||||
min_value: 1,
|
||||
choices: [{ name: 'uwu', value: 1 }],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a role option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getRoleOption().toJSON()).toEqual<APIApplicationCommandRoleOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.Role,
|
||||
required: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a string option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getStringOption().toJSON()).toEqual<APIApplicationCommandStringOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
});
|
||||
|
||||
expect(getStringOption().setAutocomplete(true).setChoices([]).toJSON()).toEqual<APIApplicationCommandStringOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
// @ts-expect-error TODO: you *can* send an empty array with autocomplete: true, should correct that in types
|
||||
choices: [],
|
||||
});
|
||||
|
||||
expect(getStringOption().addChoice('uwu', '1').toJSON()).toEqual<APIApplicationCommandStringOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
choices: [{ name: 'uwu', value: '1' }],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a user option THEN calling toJSON should return a valid JSON', () => {
|
||||
expect(getUserOption().toJSON()).toEqual<APIApplicationCommandUserOption>({
|
||||
name: 'owo',
|
||||
description: 'Testing 123',
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,427 @@
|
||||
import { APIApplicationCommandOptionChoice, ChannelType } from 'discord-api-types/v9';
|
||||
import {
|
||||
SlashCommandAssertions,
|
||||
SlashCommandBooleanOption,
|
||||
SlashCommandBuilder,
|
||||
SlashCommandChannelOption,
|
||||
SlashCommandIntegerOption,
|
||||
SlashCommandMentionableOption,
|
||||
SlashCommandNumberOption,
|
||||
SlashCommandRoleOption,
|
||||
SlashCommandStringOption,
|
||||
SlashCommandSubcommandBuilder,
|
||||
SlashCommandSubcommandGroupBuilder,
|
||||
SlashCommandUserOption,
|
||||
} from '../../../src/index';
|
||||
|
||||
const largeArray = Array.from({ length: 26 }, () => 1 as unknown as APIApplicationCommandOptionChoice);
|
||||
|
||||
const getBuilder = () => new SlashCommandBuilder();
|
||||
const getNamedBuilder = () => getBuilder().setName('example').setDescription('Example command');
|
||||
const getStringOption = () => new SlashCommandStringOption().setName('owo').setDescription('Testing 123');
|
||||
const getIntegerOption = () => new SlashCommandIntegerOption().setName('owo').setDescription('Testing 123');
|
||||
const getNumberOption = () => new SlashCommandNumberOption().setName('owo').setDescription('Testing 123');
|
||||
const getBooleanOption = () => new SlashCommandBooleanOption().setName('owo').setDescription('Testing 123');
|
||||
const getUserOption = () => new SlashCommandUserOption().setName('owo').setDescription('Testing 123');
|
||||
const getChannelOption = () => new SlashCommandChannelOption().setName('owo').setDescription('Testing 123');
|
||||
const getRoleOption = () => new SlashCommandRoleOption().setName('owo').setDescription('Testing 123');
|
||||
const getMentionableOption = () => new SlashCommandMentionableOption().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommandGroup = () => new SlashCommandSubcommandGroupBuilder().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommand = () => new SlashCommandSubcommandBuilder().setName('owo').setDescription('Testing 123');
|
||||
|
||||
class Collection {
|
||||
public get [Symbol.toStringTag]() {
|
||||
return 'Map';
|
||||
}
|
||||
}
|
||||
|
||||
describe('Slash Commands', () => {
|
||||
describe('Assertions tests', () => {
|
||||
test('GIVEN valid name THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateName('ping')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateName(null)).toThrowError();
|
||||
|
||||
// Too short of a name
|
||||
expect(() => SlashCommandAssertions.validateName('')).toThrowError();
|
||||
|
||||
// Invalid characters used
|
||||
expect(() => SlashCommandAssertions.validateName('ABC123$%^&')).toThrowError();
|
||||
|
||||
// Too long of a name
|
||||
expect(() =>
|
||||
SlashCommandAssertions.validateName('qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid description THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateDescription('This is an OwO moment fur sure!~')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid description THEN throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateDescription(null)).toThrowError();
|
||||
|
||||
// Too short of a description
|
||||
expect(() => SlashCommandAssertions.validateDescription('')).toThrowError();
|
||||
|
||||
// Too long of a description
|
||||
expect(() =>
|
||||
SlashCommandAssertions.validateDescription(
|
||||
'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Magnam autem libero expedita vitae accusamus nostrum ipsam tempore repudiandae deserunt ipsum facilis, velit fugiat facere accusantium, explicabo corporis aliquam non quos.',
|
||||
),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid default_permission THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateDefaultPermission(true)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid default_permission THEN throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateDefaultPermission(null)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid array of options or choices THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateMaxOptionsLength([])).not.toThrowError();
|
||||
|
||||
expect(() => SlashCommandAssertions.validateMaxChoicesLength([])).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid options or choices THEN throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateMaxOptionsLength(null)).toThrowError();
|
||||
|
||||
expect(() => SlashCommandAssertions.validateMaxChoicesLength(null)).toThrowError();
|
||||
|
||||
// Given an array that's too big
|
||||
expect(() => SlashCommandAssertions.validateMaxOptionsLength(largeArray)).toThrowError();
|
||||
|
||||
expect(() => SlashCommandAssertions.validateMaxChoicesLength(largeArray)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid required parameters THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
SlashCommandAssertions.validateRequiredParameters(
|
||||
'owo',
|
||||
'My fancy command that totally exists, to test assertions',
|
||||
[],
|
||||
),
|
||||
).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SlashCommandBuilder', () => {
|
||||
describe('Builder with no options', () => {
|
||||
test('GIVEN empty builder THEN throw error when calling toJSON', () => {
|
||||
expect(() => getBuilder().toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('example').setDescription('Example command').toJSON()).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Builder with simple options', () => {
|
||||
test('GIVEN valid builder with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getBuilder()
|
||||
.setName('example')
|
||||
.setDescription('Example command')
|
||||
.addBooleanOption((boolean) =>
|
||||
boolean.setName('iscool').setDescription('Are we cool or what?').setRequired(true),
|
||||
)
|
||||
.addChannelOption((channel) => channel.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addMentionableOption((mentionable) => mentionable.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addRoleOption((role) => role.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addUserOption((user) => user.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addIntegerOption((integer) =>
|
||||
integer
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices([['Very cool', 1_000]]),
|
||||
)
|
||||
.addNumberOption((number) =>
|
||||
number
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices([['Very cool', 1.5]]),
|
||||
)
|
||||
.addStringOption((string) =>
|
||||
string
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices([
|
||||
['Fancy Pants', 'fp_1'],
|
||||
['Fancy Shoes', 'fs_1'],
|
||||
['The Whole shebang', 'all'],
|
||||
]),
|
||||
)
|
||||
.addIntegerOption((integer) =>
|
||||
integer.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.addNumberOption((number) =>
|
||||
number.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.addStringOption((string) =>
|
||||
string.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid autocomplete THEN does throw an error', () => {
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addStringOption(getStringOption().setAutocomplete('not a boolean'))).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with both choices and autocomplete THEN does throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(
|
||||
// @ts-expect-error Checking if check works JS-side too
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
getStringOption().setAutocomplete(true).addChoice('Fancy Pants', 'fp_1'),
|
||||
),
|
||||
).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
getStringOption()
|
||||
.setAutocomplete(true)
|
||||
// @ts-expect-error Checking if check works JS-side too
|
||||
.addChoices([
|
||||
['Fancy Pants', 'fp_1'],
|
||||
['Fancy Shoes', 'fs_1'],
|
||||
['The Whole shebang', 'all'],
|
||||
]),
|
||||
),
|
||||
).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(
|
||||
// @ts-expect-error Checking if check works JS-side too
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
getStringOption().addChoice('Fancy Pants', 'fp_1').setAutocomplete(true),
|
||||
),
|
||||
).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const option = getStringOption();
|
||||
Reflect.set(option, 'autocomplete', true);
|
||||
Reflect.set(option, 'choices', [{ name: 'Fancy Pants', value: 'fp_1' }]);
|
||||
return option.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const option = getNumberOption();
|
||||
Reflect.set(option, 'autocomplete', true);
|
||||
Reflect.set(option, 'choices', [{ name: 'Fancy Pants', value: 'fp_1' }]);
|
||||
return option.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const option = getIntegerOption();
|
||||
Reflect.set(option, 'autocomplete', true);
|
||||
Reflect.set(option, 'choices', [{ name: 'Fancy Pants', value: 'fp_1' }]);
|
||||
return option.toJSON();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid channel options and channel_types THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addChannelOption(getChannelOption().addChannelType(ChannelType.GuildText)),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
getBuilder().addChannelOption(
|
||||
getChannelOption().addChannelTypes([ChannelType.GuildNews, ChannelType.GuildText]),
|
||||
);
|
||||
}).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid channel options and channel_types THEN does throw an error', () => {
|
||||
expect(() => getBuilder().addChannelOption(getChannelOption().addChannelType(100))).toThrowError();
|
||||
|
||||
expect(() => getBuilder().addChannelOption(getChannelOption().addChannelTypes([100, 200]))).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid number min/max options THEN does throw an error', () => {
|
||||
// @ts-expect-error
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption().setMaxValue('test'))).toThrowError();
|
||||
|
||||
// @ts-expect-error
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMaxValue('test'))).toThrowError();
|
||||
|
||||
// @ts-expect-error
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption().setMinValue('test'))).toThrowError();
|
||||
|
||||
// @ts-expect-error
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMinValue('test'))).toThrowError();
|
||||
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMinValue(1.5))).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid number min/max options THEN does not throw an error', () => {
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMinValue(1))).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption().setMinValue(1.5))).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMaxValue(1))).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption().setMaxValue(1.5))).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an already built builder THEN does not throw an error', () => {
|
||||
expect(() => getBuilder().addStringOption(getStringOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addBooleanOption(getBooleanOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addUserOption(getUserOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addChannelOption(getChannelOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addRoleOption(getRoleOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addMentionableOption(getMentionableOption())).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN no valid return for an addOption method THEN throw error', () => {
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption()).toThrowError();
|
||||
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(getRoleOption())).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => getBuilder().setName('TEST_COMMAND')).toThrowError();
|
||||
|
||||
expect(() => getBuilder().setName('ĂĂĂĂĂĂ')).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid names THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('hi_there')).not.toThrowError();
|
||||
|
||||
// Translation: a_command
|
||||
expect(() => getBuilder().setName('o_comandă')).not.toThrowError();
|
||||
|
||||
// Translation: thx (according to GTranslate)
|
||||
expect(() => getBuilder().setName('どうも')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid returns for builder THEN throw error', () => {
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(true)).toThrowError();
|
||||
|
||||
expect(() => getBuilder().addBooleanOption(null)).toThrowError();
|
||||
|
||||
expect(() => getBuilder().addBooleanOption(undefined)).toThrowError();
|
||||
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(() => SlashCommandStringOption)).toThrowError();
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(() => new Collection())).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with defaultPermission false THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('foo').setDescription('foo').setDefaultPermission(false)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option that is autocompletable and has choices, THEN setting choices to an empty array should not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(getStringOption().setAutocomplete(true).setChoices([])),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option that is autocompletable, THEN setting choices should throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(
|
||||
getStringOption()
|
||||
.setAutocomplete(true)
|
||||
.setChoices([['owo', 'uwu']]),
|
||||
),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option, THEN setting choices should not throw an error', () => {
|
||||
expect(() => getBuilder().addStringOption(getStringOption().setChoices([['owo', 'uwu']]))).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Builder with subcommand (group) options', () => {
|
||||
test('GIVEN builder with subcommand group THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addSubcommandGroup((group) => group.setName('group').setDescription('Group us together!')),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with subcommand THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addSubcommand((subcommand) =>
|
||||
subcommand.setName('boop').setDescription('Boops a fellow nerd (you)'),
|
||||
),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand group THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().addSubcommandGroup(getSubcommandGroup())).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().addSubcommand(getSubcommand())).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addSubcommand(getSubcommand().addBooleanOption(getBooleanOption())),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with a subcommand that tries to add an invalid result THEN throw error', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error Checking if check works JS-side too
|
||||
getNamedBuilder().addSubcommand(getSubcommand()).addInteger(getInteger()),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN no valid return for an addSubcommand(Group) method THEN throw error', () => {
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addSubcommandGroup()).toThrowError();
|
||||
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addSubcommand()).toThrowError();
|
||||
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addSubcommand(getSubcommandGroup())).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subcommand group builder', () => {
|
||||
test('GIVEN no valid subcommand THEN throw error', () => {
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getSubcommandGroup().addSubcommand()).toThrowError();
|
||||
|
||||
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getSubcommandGroup().addSubcommand(getSubcommandGroup())).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a valid subcommand THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getSubcommandGroup()
|
||||
.addSubcommand((sub) => sub.setName('sub').setDescription('Testing 123'))
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subcommand builder', () => {
|
||||
test('GIVEN a valid subcommand with options THEN does not throw error', () => {
|
||||
expect(() => getSubcommand().addBooleanOption(getBooleanOption()).toJSON()).not.toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
428
packages/builders/__tests__/messages/embed.test.ts
Normal file
428
packages/builders/__tests__/messages/embed.test.ts
Normal file
@@ -0,0 +1,428 @@
|
||||
import { Embed } from '../../src';
|
||||
import type { APIEmbed } from 'discord-api-types/v9';
|
||||
|
||||
const emptyEmbed: APIEmbed = {
|
||||
author: undefined,
|
||||
color: undefined,
|
||||
description: undefined,
|
||||
fields: [],
|
||||
footer: undefined,
|
||||
image: undefined,
|
||||
provider: undefined,
|
||||
thumbnail: undefined,
|
||||
title: undefined,
|
||||
url: undefined,
|
||||
video: undefined,
|
||||
};
|
||||
|
||||
const alpha = 'abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
describe('Embed', () => {
|
||||
describe('Embed getters', () => {
|
||||
test('GIVEN an embed with specific amount of characters THEN returns amount of characters', () => {
|
||||
const embed = new Embed({
|
||||
title: alpha,
|
||||
description: alpha,
|
||||
fields: [{ name: alpha, value: alpha }],
|
||||
author: { name: alpha },
|
||||
footer: { text: alpha },
|
||||
});
|
||||
|
||||
expect(embed.length).toBe(alpha.length * 6);
|
||||
});
|
||||
|
||||
test('GIVEN an embed with zero characters THEN returns amount of characters', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(embed.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed title', () => {
|
||||
test('GIVEN an embed with a pre-defined title THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ title: 'foo' });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, title: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setTitle THEN return valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setTitle('foo');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, title: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined title THEN unset title THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ title: 'foo' });
|
||||
embed.setTitle(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid title THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.setTitle('a'.repeat(257))).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed description', () => {
|
||||
test('GIVEN an embed with a pre-defined description THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ ...emptyEmbed, description: 'foo' });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setDescription THEN return valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setDescription('foo');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined description THEN unset description THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ description: 'foo' });
|
||||
embed.setDescription(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid description THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.setDescription('a'.repeat(4097))).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed URL', () => {
|
||||
test('GIVEN an embed with a pre-defined url THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed({ url: 'https://discord.js.org/' });
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
url: 'https://discord.js.org/',
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setURL THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setURL('https://discord.js.org/');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
url: 'https://discord.js.org/',
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined title THEN unset title THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ url: 'https://discord.js.org' });
|
||||
embed.setURL(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid URL THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.setURL('owo')).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Color', () => {
|
||||
test('GIVEN an embed with a pre-defined color THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed({ color: 0xff0000 });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, color: 0xff0000 });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setColor THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setColor(0xff0000);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, color: 0xff0000 });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined color THEN unset color THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ color: 0xff0000 });
|
||||
embed.setColor(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid color THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
// @ts-expect-error
|
||||
expect(() => embed.setColor('RED')).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Timestamp', () => {
|
||||
const now = new Date();
|
||||
|
||||
test('GIVEN an embed with a pre-defined timestamp THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed({ timestamp: now.toISOString() });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, timestamp: now.toISOString() });
|
||||
});
|
||||
|
||||
test('given an embed using Embed#setTimestamp (with Date) THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setTimestamp(now);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, timestamp: now.toISOString() });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setTimestamp (with int) THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setTimestamp(now.getTime());
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, timestamp: now.toISOString() });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setTimestamp (default) THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setTimestamp();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, timestamp: embed.timestamp });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined timestamp THEN unset timestamp THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ timestamp: now.toISOString() });
|
||||
embed.setTimestamp(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed, timestamp: undefined });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Thumbnail', () => {
|
||||
test('GIVEN an embed with a pre-defined thumbnail THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed({ thumbnail: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
thumbnail: { url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setThumbnail THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setThumbnail('https://discord.js.org/static/logo.svg');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
thumbnail: { url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined thumbnail THEN unset thumbnail THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ thumbnail: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
embed.setThumbnail(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid thumbnail THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.setThumbnail('owo')).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Image', () => {
|
||||
test('GIVEN an embed with a pre-defined image THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed({ image: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
image: { url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setImage THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setImage('https://discord.js.org/static/logo.svg');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
image: { url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined image THEN unset image THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ image: { url: 'https://discord.js/org/static/logo.svg' } });
|
||||
embed.setImage(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid image THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.setImage('owo')).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Author', () => {
|
||||
test('GIVEN an embed with a pre-defined author THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed({
|
||||
author: { name: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg', url: 'https://discord.js.org' },
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
author: { name: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg', url: 'https://discord.js.org' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setAuthor THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setAuthor({
|
||||
name: 'Wumpus',
|
||||
iconURL: 'https://discord.js.org/static/logo.svg',
|
||||
url: 'https://discord.js.org',
|
||||
});
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
author: { name: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg', url: 'https://discord.js.org' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined author THEN unset author THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({
|
||||
author: { name: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg', url: 'https://discord.js.org' },
|
||||
});
|
||||
embed.setAuthor(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid author name THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.setAuthor({ name: 'a'.repeat(257) })).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Footer', () => {
|
||||
test('GIVEN an embed with a pre-defined footer THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed({
|
||||
footer: { text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
footer: { text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setAuthor THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.setFooter({ text: 'Wumpus', iconURL: 'https://discord.js.org/static/logo.svg' });
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
footer: { text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined footer THEN unset footer THEN return valid toJSON data', () => {
|
||||
const embed = new Embed({ footer: { text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' } });
|
||||
embed.setFooter(null);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ ...emptyEmbed });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with invalid footer text THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.setFooter({ text: 'a'.repeat(2049) })).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Fields', () => {
|
||||
test('GIVEN an embed with a pre-defined field THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed({
|
||||
fields: [{ name: 'foo', value: 'bar', inline: undefined }],
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
fields: [{ name: 'foo', value: 'bar', inline: undefined }],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#addField THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.addField({ name: 'foo', value: 'bar' });
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
fields: [{ name: 'foo', value: 'bar', inline: undefined }],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#addFields THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.addFields({ name: 'foo', value: 'bar' });
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
fields: [{ name: 'foo', value: 'bar', inline: undefined }],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.addFields({ name: 'foo', value: 'bar' }, { name: 'foo', value: 'baz' });
|
||||
|
||||
expect(embed.spliceFields(0, 1).toJSON()).toStrictEqual({
|
||||
...emptyEmbed,
|
||||
fields: [{ name: 'foo', value: 'baz', inline: undefined }],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data', () => {
|
||||
const embed = new Embed();
|
||||
embed.addFields(...Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
|
||||
expect(() =>
|
||||
embed.spliceFields(0, 3, ...Array.from({ length: 5 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#spliceFields that adds additional fields resulting in fields > 25 THEN throws error', () => {
|
||||
const embed = new Embed();
|
||||
embed.addFields(...Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
|
||||
expect(() =>
|
||||
embed.spliceFields(0, 3, ...Array.from({ length: 8 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field amount THEN throws error', () => {
|
||||
test('', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() =>
|
||||
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field name THEN throws error', () => {
|
||||
test('', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.addFields({ name: '', value: 'bar' })).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field name length THEN throws error', () => {
|
||||
test('', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field value length THEN throws error', () => {
|
||||
test('', () => {
|
||||
const embed = new Embed();
|
||||
|
||||
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1025) })).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
206
packages/builders/__tests__/messages/formatters.test.ts
Normal file
206
packages/builders/__tests__/messages/formatters.test.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
blockQuote,
|
||||
bold,
|
||||
channelMention,
|
||||
codeBlock,
|
||||
Faces,
|
||||
formatEmoji,
|
||||
hideLinkEmbed,
|
||||
hyperlink,
|
||||
inlineCode,
|
||||
italic,
|
||||
memberNicknameMention,
|
||||
quote,
|
||||
roleMention,
|
||||
spoiler,
|
||||
strikethrough,
|
||||
time,
|
||||
TimestampStyles,
|
||||
underscore,
|
||||
userMention,
|
||||
} from '../../src';
|
||||
|
||||
describe('Message formatters', () => {
|
||||
describe('codeBlock', () => {
|
||||
test('GIVEN "discord.js" with no language THEN returns "```\\ndiscord.js```"', () => {
|
||||
expect<'```\ndiscord.js```'>(codeBlock('discord.js')).toBe('```\ndiscord.js```');
|
||||
});
|
||||
|
||||
test('GIVEN "discord.js" with "js" as language THEN returns "```js\\ndiscord.js```"', () => {
|
||||
expect<'```js\ndiscord.js```'>(codeBlock('js', 'discord.js')).toBe('```js\ndiscord.js```');
|
||||
});
|
||||
});
|
||||
|
||||
describe('inlineCode', () => {
|
||||
test('GIVEN "discord.js" THEN returns "`discord.js`"', () => {
|
||||
expect<'`discord.js`'>(inlineCode('discord.js')).toBe('`discord.js`');
|
||||
});
|
||||
});
|
||||
|
||||
describe('italic', () => {
|
||||
test('GIVEN "discord.js" THEN returns "_discord.js_"', () => {
|
||||
expect<'_discord.js_'>(italic('discord.js')).toBe('_discord.js_');
|
||||
});
|
||||
});
|
||||
|
||||
describe('bold', () => {
|
||||
test('GIVEN "discord.js" THEN returns "**discord.js**"', () => {
|
||||
expect<'**discord.js**'>(bold('discord.js')).toBe('**discord.js**');
|
||||
});
|
||||
});
|
||||
|
||||
describe('underscore', () => {
|
||||
test('GIVEN "discord.js" THEN returns "__discord.js__"', () => {
|
||||
expect<'__discord.js__'>(underscore('discord.js')).toBe('__discord.js__');
|
||||
});
|
||||
});
|
||||
|
||||
describe('strikethrough', () => {
|
||||
test('GIVEN "discord.js" THEN returns "~~discord.js~~"', () => {
|
||||
expect<'~~discord.js~~'>(strikethrough('discord.js')).toBe('~~discord.js~~');
|
||||
});
|
||||
});
|
||||
|
||||
describe('quote', () => {
|
||||
test('GIVEN "discord.js" THEN returns "> discord.js"', () => {
|
||||
expect<'> discord.js'>(quote('discord.js')).toBe('> discord.js');
|
||||
});
|
||||
});
|
||||
|
||||
describe('blockQuote', () => {
|
||||
test('GIVEN "discord.js" THEN returns ">>> discord.js"', () => {
|
||||
expect<'>>> discord.js'>(blockQuote('discord.js')).toBe('>>> discord.js');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideLinkEmbed', () => {
|
||||
test('GIVEN "https://discord.js.org" THEN returns "<https://discord.js.org>"', () => {
|
||||
expect<'<https://discord.js.org>'>(hideLinkEmbed('https://discord.js.org')).toBe('<https://discord.js.org>');
|
||||
});
|
||||
|
||||
test('GIVEN new URL("https://discord.js.org") THEN returns "<https://discord.js.org>"', () => {
|
||||
expect<`<${string}>`>(hideLinkEmbed(new URL('https://discord.js.org/'))).toBe('<https://discord.js.org/>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hyperlink', () => {
|
||||
test('GIVEN content and string URL THEN returns "[content](url)"', () => {
|
||||
expect<'[discord.js](https://discord.js.org)'>(hyperlink('discord.js', 'https://discord.js.org')).toBe(
|
||||
'[discord.js](https://discord.js.org)',
|
||||
);
|
||||
});
|
||||
|
||||
test('GIVEN content and URL THEN returns "[content](url)"', () => {
|
||||
expect<`[discord.js](${string})`>(hyperlink('discord.js', new URL('https://discord.js.org'))).toBe(
|
||||
'[discord.js](https://discord.js.org/)',
|
||||
);
|
||||
});
|
||||
|
||||
test('GIVEN content, string URL, and title THEN returns "[content](url "title")"', () => {
|
||||
expect<'[discord.js](https://discord.js.org "Official Documentation")'>(
|
||||
hyperlink('discord.js', 'https://discord.js.org', 'Official Documentation'),
|
||||
).toBe('[discord.js](https://discord.js.org "Official Documentation")');
|
||||
});
|
||||
|
||||
test('GIVEN content, URL, and title THEN returns "[content](url "title")"', () => {
|
||||
expect<`[discord.js](${string} "Official Documentation")`>(
|
||||
hyperlink('discord.js', new URL('https://discord.js.org'), 'Official Documentation'),
|
||||
).toBe('[discord.js](https://discord.js.org/ "Official Documentation")');
|
||||
});
|
||||
});
|
||||
|
||||
describe('spoiler', () => {
|
||||
test('GIVEN "discord.js" THEN returns "||discord.js||"', () => {
|
||||
expect<'||discord.js||'>(spoiler('discord.js')).toBe('||discord.js||');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mentions', () => {
|
||||
describe('userMention', () => {
|
||||
test('GIVEN userId THEN returns "<@[userId]>"', () => {
|
||||
expect(userMention('139836912335716352')).toBe('<@139836912335716352>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('memberNicknameMention', () => {
|
||||
test('GIVEN memberId THEN returns "<@![memberId]>"', () => {
|
||||
expect(memberNicknameMention('139836912335716352')).toBe('<@!139836912335716352>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('channelMention', () => {
|
||||
test('GIVEN channelId THEN returns "<#[channelId]>"', () => {
|
||||
expect(channelMention('829924760309334087')).toBe('<#829924760309334087>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('roleMention', () => {
|
||||
test('GIVEN roleId THEN returns "<&[roleId]>"', () => {
|
||||
expect(roleMention('815434166602170409')).toBe('<@&815434166602170409>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatEmoji', () => {
|
||||
test('GIVEN static emojiId THEN returns "<:_:${emojiId}>"', () => {
|
||||
expect<`<:_:851461487498493952>`>(formatEmoji('851461487498493952')).toBe('<:_:851461487498493952>');
|
||||
});
|
||||
|
||||
test('GIVEN static emojiId WITH animated explicitly false THEN returns "<:_:[emojiId]>"', () => {
|
||||
expect<`<:_:851461487498493952>`>(formatEmoji('851461487498493952', false)).toBe('<:_:851461487498493952>');
|
||||
});
|
||||
|
||||
test('GIVEN animated emojiId THEN returns "<a:_:${emojiId}>"', () => {
|
||||
expect<`<a:_:827220205352255549>`>(formatEmoji('827220205352255549', true)).toBe('<a:_:827220205352255549>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('time', () => {
|
||||
test('GIVEN no arguments THEN returns "<t:${bigint}>"', () => {
|
||||
jest.useFakeTimers('modern');
|
||||
jest.setSystemTime(1566424897579);
|
||||
|
||||
expect<`<t:${bigint}>`>(time()).toBe('<t:1566424897>');
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('GIVEN a date THEN returns "<t:${bigint}>"', () => {
|
||||
expect<`<t:${bigint}>`>(time(new Date(1867424897579))).toBe('<t:1867424897>');
|
||||
});
|
||||
|
||||
test('GIVEN a date and a style from string THEN returns "<t:${bigint}:${style}>"', () => {
|
||||
expect<`<t:${bigint}:d>`>(time(new Date(1867424897579), 'd')).toBe('<t:1867424897:d>');
|
||||
});
|
||||
|
||||
test('GIVEN a date and a format from enum THEN returns "<t:${bigint}:${style}>"', () => {
|
||||
expect<`<t:${bigint}:R>`>(time(new Date(1867424897579), TimestampStyles.RelativeTime)).toBe('<t:1867424897:R>');
|
||||
});
|
||||
|
||||
test('GIVEN a date THEN returns "<t:${time}>"', () => {
|
||||
expect<'<t:1867424897>'>(time(1867424897)).toBe('<t:1867424897>');
|
||||
});
|
||||
|
||||
test('GIVEN a date and a style from string THEN returns "<t:${time}:${style}>"', () => {
|
||||
expect<'<t:1867424897:d>'>(time(1867424897, 'd')).toBe('<t:1867424897:d>');
|
||||
});
|
||||
|
||||
test('GIVEN a date and a format from enum THEN returns "<t:${time}:${style}>"', () => {
|
||||
expect<'<t:1867424897:R>'>(time(1867424897, TimestampStyles.RelativeTime)).toBe('<t:1867424897:R>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Faces', () => {
|
||||
test('GIVEN Faces.Shrug THEN returns "¯\\_(ツ)\\_/¯"', () => {
|
||||
expect<'¯\\_(ツ)\\_/¯'>(Faces.Shrug).toBe('¯\\_(ツ)\\_/¯');
|
||||
});
|
||||
|
||||
test('GIVEN Faces.Tableflip THEN returns "(╯°□°)╯︵ ┻━┻"', () => {
|
||||
expect<'(╯°□°)╯︵ ┻━┻'>(Faces.Tableflip).toBe('(╯°□°)╯︵ ┻━┻');
|
||||
});
|
||||
|
||||
test('GIVEN Faces.Unflip THEN returns "┬─┬ ノ( ゜-゜ノ)"', () => {
|
||||
expect<'┬─┬ ノ( ゜-゜ノ)'>(Faces.Unflip).toBe('┬─┬ ノ( ゜-゜ノ)');
|
||||
});
|
||||
});
|
||||
});
|
||||
18
packages/builders/babel.config.js
Normal file
18
packages/builders/babel.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @type {import('@babel/core').TransformOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
parserOpts: { strictMode: true },
|
||||
sourceMaps: 'inline',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: { node: 'current' },
|
||||
modules: 'commonjs',
|
||||
},
|
||||
],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: ['babel-plugin-transform-typescript-metadata', ['@babel/plugin-proposal-decorators', { legacy: true }]],
|
||||
};
|
||||
10
packages/builders/codecov.yml
Normal file
10
packages/builders/codecov.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 70%
|
||||
threshold: 5%
|
||||
patch:
|
||||
default:
|
||||
target: 70%
|
||||
threshold: 5%
|
||||
1
packages/builders/docs/README.md
Normal file
1
packages/builders/docs/README.md
Normal file
@@ -0,0 +1 @@
|
||||
## [View the documentation here.](https://discord.js.org/#/docs/builders)
|
||||
19
packages/builders/jest.config.js
Normal file
19
packages/builders/jest.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @type {import('@jest/types').Config.InitialOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
|
||||
testEnvironment: 'node',
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ['src/**/*.ts'],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'clover'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 70,
|
||||
lines: 70,
|
||||
statements: 70,
|
||||
},
|
||||
},
|
||||
coveragePathIgnorePatterns: ['src/index.ts'],
|
||||
};
|
||||
88
packages/builders/package.json
Normal file
88
packages/builders/package.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "@discordjs/builders",
|
||||
"version": "0.11.0",
|
||||
"description": "A set of builders that you can use when creating your bot",
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"test": "jest --pass-with-no-tests",
|
||||
"lint": "eslint src --ext mjs,js,ts",
|
||||
"lint:fix": "eslint src --ext mjs,js,ts --fix",
|
||||
"format": "prettier --write **/*.{ts,js,json,yml,yaml}",
|
||||
"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 './*'"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "src",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"contributors": [
|
||||
"Vlad Frangu <kingdgrizzle@gmail.com>",
|
||||
"Crawl <icrawltogo@gmail.com>",
|
||||
"Amish Shah <amishshah.2k@gmail.com>",
|
||||
"SpaceEEC <spaceeec@yahoo.com>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"discord",
|
||||
"api",
|
||||
"bot",
|
||||
"client",
|
||||
"node",
|
||||
"discordapp",
|
||||
"discordjs"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/discordjs/discord.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/discordjs/discord.js/issues"
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^4.2.0",
|
||||
"discord-api-types": "^0.26.0",
|
||||
"ts-mixer": "^6.0.0",
|
||||
"tslib": "^2.3.1",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.5",
|
||||
"@babel/plugin-proposal-decorators": "^7.16.5",
|
||||
"@babel/preset-env": "^7.16.5",
|
||||
"@babel/preset-typescript": "^7.16.5",
|
||||
"@discordjs/ts-docgen": "^0.3.4",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.8.0",
|
||||
"@typescript-eslint/parser": "^5.8.0",
|
||||
"babel-plugin-transform-typescript-metadata": "^0.3.2",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-config-marine": "^9.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "^27.4.5",
|
||||
"prettier": "^2.5.1",
|
||||
"standard-version": "^9.3.2",
|
||||
"tsup": "^5.11.8",
|
||||
"typedoc": "^0.22.10",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
7
packages/builders/scripts/docs.mjs
Normal file
7
packages/builders/scripts/docs.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
import { runGenerator } from '@discordjs/ts-docgen';
|
||||
|
||||
runGenerator({
|
||||
existingOutput: 'docs/typedoc-out.json',
|
||||
custom: 'docs/index.yml',
|
||||
output: 'docs/docs.json',
|
||||
});
|
||||
18
packages/builders/src/index.ts
Normal file
18
packages/builders/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export * as EmbedAssertions from './messages/embed/Assertions';
|
||||
export * from './messages/embed/Embed';
|
||||
export * from './messages/formatters';
|
||||
|
||||
export * as SlashCommandAssertions from './interactions/slashCommands/Assertions';
|
||||
export * from './interactions/slashCommands/SlashCommandBuilder';
|
||||
export * from './interactions/slashCommands/SlashCommandSubcommands';
|
||||
export * from './interactions/slashCommands/options/boolean';
|
||||
export * from './interactions/slashCommands/options/channel';
|
||||
export * from './interactions/slashCommands/options/integer';
|
||||
export * from './interactions/slashCommands/options/mentionable';
|
||||
export * from './interactions/slashCommands/options/number';
|
||||
export * from './interactions/slashCommands/options/role';
|
||||
export * from './interactions/slashCommands/options/string';
|
||||
export * from './interactions/slashCommands/options/user';
|
||||
|
||||
export * as ContextMenuCommandAssertions from './interactions/contextMenuCommands/Assertions';
|
||||
export * from './interactions/contextMenuCommands/ContextMenuCommandBuilder';
|
||||
@@ -0,0 +1,33 @@
|
||||
import { z } from 'zod';
|
||||
import { ApplicationCommandType } from 'discord-api-types/v9';
|
||||
import type { ContextMenuCommandType } from './ContextMenuCommandBuilder';
|
||||
|
||||
export function validateRequiredParameters(name: string, type: number) {
|
||||
// Assert name matches all conditions
|
||||
validateName(name);
|
||||
|
||||
// Assert type is valid
|
||||
validateType(type);
|
||||
}
|
||||
|
||||
const namePredicate = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(32)
|
||||
.regex(/^( *[\p{L}\p{N}_-]+ *)+$/u);
|
||||
|
||||
export function validateName(name: unknown): asserts name is string {
|
||||
namePredicate.parse(name);
|
||||
}
|
||||
|
||||
const typePredicate = z.union([z.literal(ApplicationCommandType.User), z.literal(ApplicationCommandType.Message)]);
|
||||
|
||||
export function validateType(type: unknown): asserts type is ContextMenuCommandType {
|
||||
typePredicate.parse(type);
|
||||
}
|
||||
|
||||
const booleanPredicate = z.boolean();
|
||||
|
||||
export function validateDefaultPermission(value: unknown): asserts value is boolean {
|
||||
booleanPredicate.parse(value);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { validateRequiredParameters, validateName, validateType, validateDefaultPermission } from './Assertions';
|
||||
import type { ApplicationCommandType, RESTPostAPIApplicationCommandsJSONBody } from 'discord-api-types/v9';
|
||||
|
||||
export class ContextMenuCommandBuilder {
|
||||
/**
|
||||
* The name of this context menu command
|
||||
*/
|
||||
public readonly name: string = undefined!;
|
||||
|
||||
/**
|
||||
* The type of this context menu command
|
||||
*/
|
||||
public readonly type: ContextMenuCommandType = undefined!;
|
||||
|
||||
/**
|
||||
* Whether the command is enabled by default when the app is added to a guild
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
public readonly defaultPermission: boolean | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Sets the name
|
||||
*
|
||||
* @param name The name
|
||||
*/
|
||||
public setName(name: string) {
|
||||
// Assert the name matches the conditions
|
||||
validateName(name);
|
||||
|
||||
Reflect.set(this, 'name', name);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type
|
||||
*
|
||||
* @param type The type
|
||||
*/
|
||||
public setType(type: ContextMenuCommandType) {
|
||||
// Assert the type is valid
|
||||
validateType(type);
|
||||
|
||||
Reflect.set(this, 'type', type);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the command is enabled by default when the application is added to a guild.
|
||||
*
|
||||
* **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
|
||||
*
|
||||
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
|
||||
*/
|
||||
public setDefaultPermission(value: boolean) {
|
||||
// Assert the value matches the conditions
|
||||
validateDefaultPermission(value);
|
||||
|
||||
Reflect.set(this, 'defaultPermission', value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final data that should be sent to Discord.
|
||||
*
|
||||
* **Note:** Calling this function will validate required properties based on their conditions.
|
||||
*/
|
||||
public toJSON(): RESTPostAPIApplicationCommandsJSONBody {
|
||||
validateRequiredParameters(this.name, this.type);
|
||||
return {
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
default_permission: this.defaultPermission,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type ContextMenuCommandType = ApplicationCommandType.User | ApplicationCommandType.Message;
|
||||
@@ -0,0 +1,84 @@
|
||||
import is from '@sindresorhus/is';
|
||||
import type { APIApplicationCommandOptionChoice } from 'discord-api-types/v9';
|
||||
import { z } from 'zod';
|
||||
import type { ApplicationCommandOptionBase } from './mixins/ApplicationCommandOptionBase';
|
||||
import type { ToAPIApplicationCommandOptions } from './SlashCommandBuilder';
|
||||
import type { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from './SlashCommandSubcommands';
|
||||
|
||||
export function validateRequiredParameters(
|
||||
name: string,
|
||||
description: string,
|
||||
options: ToAPIApplicationCommandOptions[],
|
||||
) {
|
||||
// Assert name matches all conditions
|
||||
validateName(name);
|
||||
|
||||
// Assert description conditions
|
||||
validateDescription(description);
|
||||
|
||||
// Assert options conditions
|
||||
validateMaxOptionsLength(options);
|
||||
}
|
||||
|
||||
const namePredicate = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(32)
|
||||
.regex(/^[\P{Lu}\p{N}_-]+$/u);
|
||||
|
||||
export function validateName(name: unknown): asserts name is string {
|
||||
namePredicate.parse(name);
|
||||
}
|
||||
|
||||
const descriptionPredicate = z.string().min(1).max(100);
|
||||
|
||||
export function validateDescription(description: unknown): asserts description is string {
|
||||
descriptionPredicate.parse(description);
|
||||
}
|
||||
|
||||
const booleanPredicate = z.boolean();
|
||||
|
||||
export function validateDefaultPermission(value: unknown): asserts value is boolean {
|
||||
booleanPredicate.parse(value);
|
||||
}
|
||||
|
||||
export function validateRequired(required: unknown): asserts required is boolean {
|
||||
booleanPredicate.parse(required);
|
||||
}
|
||||
|
||||
const maxArrayLengthPredicate = z.unknown().array().max(25);
|
||||
|
||||
export function validateMaxOptionsLength(options: unknown): asserts options is ToAPIApplicationCommandOptions[] {
|
||||
maxArrayLengthPredicate.parse(options);
|
||||
}
|
||||
|
||||
export function validateMaxChoicesLength(choices: APIApplicationCommandOptionChoice[]) {
|
||||
maxArrayLengthPredicate.parse(choices);
|
||||
}
|
||||
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import type { APIApplicationCommandOption, RESTPostAPIApplicationCommandsJSONBody } from 'discord-api-types/v9';
|
||||
import { mix } from 'ts-mixer';
|
||||
import {
|
||||
assertReturnOfBuilder,
|
||||
validateDefaultPermission,
|
||||
validateMaxOptionsLength,
|
||||
validateRequiredParameters,
|
||||
} from './Assertions';
|
||||
import { SharedSlashCommandOptions } from './mixins/SharedSlashCommandOptions';
|
||||
import { SharedNameAndDescription } from './mixins/NameAndDescription';
|
||||
import { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from './SlashCommandSubcommands';
|
||||
|
||||
@mix(SharedSlashCommandOptions, SharedNameAndDescription)
|
||||
export class SlashCommandBuilder {
|
||||
/**
|
||||
* The name of this slash command
|
||||
*/
|
||||
public readonly name: string = undefined!;
|
||||
|
||||
/**
|
||||
* The description of this slash command
|
||||
*/
|
||||
public readonly description: string = undefined!;
|
||||
|
||||
/**
|
||||
* The options of this slash command
|
||||
*/
|
||||
public readonly options: ToAPIApplicationCommandOptions[] = [];
|
||||
|
||||
/**
|
||||
* Whether the command is enabled by default when the app is added to a guild
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
public readonly defaultPermission: boolean | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Returns the final data that should be sent to Discord.
|
||||
*
|
||||
* **Note:** Calling this function will validate required properties based on their conditions.
|
||||
*/
|
||||
public toJSON(): RESTPostAPIApplicationCommandsJSONBody {
|
||||
validateRequiredParameters(this.name, this.description, this.options);
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
options: this.options.map((option) => option.toJSON()),
|
||||
default_permission: this.defaultPermission,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the command is enabled by default when the application is added to a guild.
|
||||
*
|
||||
* **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
|
||||
*
|
||||
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
|
||||
*/
|
||||
public setDefaultPermission(value: boolean) {
|
||||
// Assert the value matches the conditions
|
||||
validateDefaultPermission(value);
|
||||
|
||||
Reflect.set(this, 'defaultPermission', value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new subcommand group to this command
|
||||
*
|
||||
* @param input A function that returns a subcommand group builder, or an already built builder
|
||||
*/
|
||||
public addSubcommandGroup(
|
||||
input:
|
||||
| SlashCommandSubcommandGroupBuilder
|
||||
| ((subcommandGroup: SlashCommandSubcommandGroupBuilder) => SlashCommandSubcommandGroupBuilder),
|
||||
): SlashCommandSubcommandsOnlyBuilder {
|
||||
const { options } = this;
|
||||
|
||||
// First, assert options conditions - we cannot have more than 25 options
|
||||
validateMaxOptionsLength(options);
|
||||
|
||||
// Get the final result
|
||||
const result = typeof input === 'function' ? input(new SlashCommandSubcommandGroupBuilder()) : input;
|
||||
|
||||
assertReturnOfBuilder(result, SlashCommandSubcommandGroupBuilder);
|
||||
|
||||
// Push it
|
||||
options.push(result);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new subcommand to this command
|
||||
*
|
||||
* @param input A function that returns a subcommand builder, or an already built builder
|
||||
*/
|
||||
public addSubcommand(
|
||||
input:
|
||||
| SlashCommandSubcommandBuilder
|
||||
| ((subcommandGroup: SlashCommandSubcommandBuilder) => SlashCommandSubcommandBuilder),
|
||||
): SlashCommandSubcommandsOnlyBuilder {
|
||||
const { options } = this;
|
||||
|
||||
// First, assert options conditions - we cannot have more than 25 options
|
||||
validateMaxOptionsLength(options);
|
||||
|
||||
// Get the final result
|
||||
const result = typeof input === 'function' ? input(new SlashCommandSubcommandBuilder()) : input;
|
||||
|
||||
assertReturnOfBuilder(result, SlashCommandSubcommandBuilder);
|
||||
|
||||
// Push it
|
||||
options.push(result);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlashCommandBuilder extends SharedNameAndDescription, SharedSlashCommandOptions {}
|
||||
|
||||
export interface SlashCommandSubcommandsOnlyBuilder
|
||||
extends SharedNameAndDescription,
|
||||
Pick<SlashCommandBuilder, 'toJSON' | 'addSubcommand' | 'addSubcommandGroup'> {}
|
||||
|
||||
export interface SlashCommandOptionsOnlyBuilder
|
||||
extends SharedNameAndDescription,
|
||||
SharedSlashCommandOptions,
|
||||
Pick<SlashCommandBuilder, 'toJSON'> {}
|
||||
|
||||
export interface ToAPIApplicationCommandOptions {
|
||||
toJSON(): APIApplicationCommandOption;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
APIApplicationCommandSubcommandGroupOption,
|
||||
APIApplicationCommandSubcommandOption,
|
||||
ApplicationCommandOptionType,
|
||||
} from 'discord-api-types/v9';
|
||||
import { mix } from 'ts-mixer';
|
||||
import { assertReturnOfBuilder, validateMaxOptionsLength, validateRequiredParameters } from './Assertions';
|
||||
import type { ApplicationCommandOptionBase } from './mixins/ApplicationCommandOptionBase';
|
||||
import { SharedNameAndDescription } from './mixins/NameAndDescription';
|
||||
import { SharedSlashCommandOptions } from './mixins/SharedSlashCommandOptions';
|
||||
import type { ToAPIApplicationCommandOptions } from './SlashCommandBuilder';
|
||||
|
||||
/**
|
||||
* Represents a folder for subcommands
|
||||
*
|
||||
* For more information, go to https://discord.com/developers/docs/interactions/slash-commands#subcommands-and-subcommand-groups
|
||||
*/
|
||||
@mix(SharedNameAndDescription)
|
||||
export class SlashCommandSubcommandGroupBuilder implements ToAPIApplicationCommandOptions {
|
||||
/**
|
||||
* The name of this subcommand group
|
||||
*/
|
||||
public readonly name: string = undefined!;
|
||||
|
||||
/**
|
||||
* The description of this subcommand group
|
||||
*/
|
||||
public readonly description: string = undefined!;
|
||||
|
||||
/**
|
||||
* The subcommands part of this subcommand group
|
||||
*/
|
||||
public readonly options: SlashCommandSubcommandBuilder[] = [];
|
||||
|
||||
/**
|
||||
* Adds a new subcommand to this group
|
||||
*
|
||||
* @param input A function that returns a subcommand builder, or an already built builder
|
||||
*/
|
||||
public addSubcommand(
|
||||
input:
|
||||
| SlashCommandSubcommandBuilder
|
||||
| ((subcommandGroup: SlashCommandSubcommandBuilder) => SlashCommandSubcommandBuilder),
|
||||
) {
|
||||
const { options } = this;
|
||||
|
||||
// First, assert options conditions - we cannot have more than 25 options
|
||||
validateMaxOptionsLength(options);
|
||||
|
||||
// Get the final result
|
||||
const result = typeof input === 'function' ? input(new SlashCommandSubcommandBuilder()) : input;
|
||||
|
||||
assertReturnOfBuilder(result, SlashCommandSubcommandBuilder);
|
||||
|
||||
// Push it
|
||||
options.push(result);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIApplicationCommandSubcommandGroupOption {
|
||||
validateRequiredParameters(this.name, this.description, this.options);
|
||||
|
||||
return {
|
||||
type: ApplicationCommandOptionType.SubcommandGroup,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
options: this.options.map((option) => option.toJSON()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlashCommandSubcommandGroupBuilder extends SharedNameAndDescription {}
|
||||
|
||||
/**
|
||||
* Represents a subcommand
|
||||
*
|
||||
* For more information, go to https://discord.com/developers/docs/interactions/slash-commands#subcommands-and-subcommand-groups
|
||||
*/
|
||||
@mix(SharedNameAndDescription, SharedSlashCommandOptions)
|
||||
export class SlashCommandSubcommandBuilder implements ToAPIApplicationCommandOptions {
|
||||
/**
|
||||
* The name of this subcommand
|
||||
*/
|
||||
public readonly name: string = undefined!;
|
||||
|
||||
/**
|
||||
* The description of this subcommand
|
||||
*/
|
||||
public readonly description: string = undefined!;
|
||||
|
||||
/**
|
||||
* The options of this subcommand
|
||||
*/
|
||||
public readonly options: ApplicationCommandOptionBase[] = [];
|
||||
|
||||
public toJSON(): APIApplicationCommandSubcommandOption {
|
||||
validateRequiredParameters(this.name, this.description, this.options);
|
||||
|
||||
return {
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
options: this.options.map((option) => option.toJSON()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlashCommandSubcommandBuilder extends SharedNameAndDescription, SharedSlashCommandOptions<false> {}
|
||||
@@ -0,0 +1,16 @@
|
||||
export abstract class ApplicationCommandNumericOptionMinMaxValueMixin {
|
||||
public readonly max_value?: number;
|
||||
public readonly min_value?: number;
|
||||
|
||||
/**
|
||||
* Sets the maximum number value of this option
|
||||
* @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
|
||||
*/
|
||||
public abstract setMinValue(min: number): this;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { APIApplicationCommandBasicOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { validateRequiredParameters, validateRequired } from '../Assertions';
|
||||
import { SharedNameAndDescription } from './NameAndDescription';
|
||||
|
||||
export abstract class ApplicationCommandOptionBase extends SharedNameAndDescription {
|
||||
public abstract readonly type: ApplicationCommandOptionType;
|
||||
|
||||
public readonly required = false;
|
||||
|
||||
/**
|
||||
* Marks the option as required
|
||||
*
|
||||
* @param required If this option should be required
|
||||
*/
|
||||
public setRequired(required: boolean) {
|
||||
// Assert that you actually passed a boolean
|
||||
validateRequired(required);
|
||||
|
||||
Reflect.set(this, 'required', required);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract toJSON(): APIApplicationCommandBasicOption;
|
||||
|
||||
protected runRequiredValidations() {
|
||||
validateRequiredParameters(this.name, this.description, []);
|
||||
|
||||
// Assert that you actually passed a boolean
|
||||
validateRequired(this.required);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ChannelType } from 'discord-api-types/v9';
|
||||
import { z, ZodLiteral } from 'zod';
|
||||
|
||||
// Only allow valid channel types to be used. (This can't be dynamic because const enums are erased at runtime)
|
||||
const allowedChannelTypes = [
|
||||
ChannelType.GuildText,
|
||||
ChannelType.GuildVoice,
|
||||
ChannelType.GuildCategory,
|
||||
ChannelType.GuildNews,
|
||||
ChannelType.GuildStore,
|
||||
ChannelType.GuildNewsThread,
|
||||
ChannelType.GuildPublicThread,
|
||||
ChannelType.GuildPrivateThread,
|
||||
ChannelType.GuildStageVoice,
|
||||
] as const;
|
||||
|
||||
export type ApplicationCommandOptionAllowedChannelTypes = typeof allowedChannelTypes[number];
|
||||
|
||||
const channelTypePredicate = z.union(
|
||||
allowedChannelTypes.map((type) => z.literal(type)) as [
|
||||
ZodLiteral<ChannelType>,
|
||||
ZodLiteral<ChannelType>,
|
||||
...ZodLiteral<ChannelType>[]
|
||||
],
|
||||
);
|
||||
|
||||
export class ApplicationCommandOptionChannelTypesMixin {
|
||||
public readonly channel_types?: ApplicationCommandOptionAllowedChannelTypes[];
|
||||
|
||||
/**
|
||||
* Adds a channel type to this option
|
||||
*
|
||||
* @param channelType The type of channel to allow
|
||||
*/
|
||||
public addChannelType(channelType: ApplicationCommandOptionAllowedChannelTypes) {
|
||||
if (this.channel_types === undefined) {
|
||||
Reflect.set(this, 'channel_types', []);
|
||||
}
|
||||
|
||||
channelTypePredicate.parse(channelType);
|
||||
this.channel_types!.push(channelType);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds channel types to this option
|
||||
*
|
||||
* @param channelTypes The channel types to add
|
||||
*/
|
||||
public addChannelTypes(channelTypes: ApplicationCommandOptionAllowedChannelTypes[]) {
|
||||
channelTypes.forEach((channelType) => this.addChannelType(channelType));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { APIApplicationCommandOptionChoice, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { z } from 'zod';
|
||||
import { validateMaxChoicesLength } from '../Assertions';
|
||||
|
||||
const stringPredicate = z.string().min(1).max(100);
|
||||
const numberPredicate = z.number().gt(-Infinity).lt(Infinity);
|
||||
const choicesPredicate = z.tuple([stringPredicate, z.union([stringPredicate, numberPredicate])]).array();
|
||||
const booleanPredicate = z.boolean();
|
||||
|
||||
export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends string | number> {
|
||||
public readonly choices?: APIApplicationCommandOptionChoice<T>[];
|
||||
public readonly autocomplete?: boolean;
|
||||
|
||||
// Since this is present and this is a mixin, this is needed
|
||||
public readonly type!: ApplicationCommandOptionType;
|
||||
|
||||
/**
|
||||
* Adds a choice for this option
|
||||
*
|
||||
* @param name The name of the choice
|
||||
* @param value The value of the choice
|
||||
*/
|
||||
public addChoice(name: string, value: T): Omit<this, 'setAutocomplete'> {
|
||||
if (this.autocomplete) {
|
||||
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
|
||||
}
|
||||
|
||||
if (this.choices === undefined) {
|
||||
Reflect.set(this, 'choices', []);
|
||||
}
|
||||
|
||||
validateMaxChoicesLength(this.choices!);
|
||||
|
||||
// Validate name
|
||||
stringPredicate.parse(name);
|
||||
|
||||
// Validate the value
|
||||
if (this.type === ApplicationCommandOptionType.String) {
|
||||
stringPredicate.parse(value);
|
||||
} else {
|
||||
numberPredicate.parse(value);
|
||||
}
|
||||
|
||||
this.choices!.push({ name, value });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple choices for this option
|
||||
*
|
||||
* @param choices The choices to add
|
||||
*/
|
||||
public addChoices(choices: [name: string, value: T][]): Omit<this, 'setAutocomplete'> {
|
||||
if (this.autocomplete) {
|
||||
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
|
||||
}
|
||||
|
||||
choicesPredicate.parse(choices);
|
||||
|
||||
for (const [label, value] of choices) this.addChoice(label, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setChoices<Input extends [name: string, value: T][]>(
|
||||
choices: Input,
|
||||
): Input extends []
|
||||
? this & Pick<ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T>, 'setAutocomplete'>
|
||||
: Omit<this, 'setAutocomplete'> {
|
||||
if (choices.length > 0 && this.autocomplete) {
|
||||
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
|
||||
}
|
||||
|
||||
choicesPredicate.parse(choices);
|
||||
|
||||
Reflect.set(this, 'choices', []);
|
||||
for (const [label, value] of choices) this.addChoice(label, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the option as autocompletable
|
||||
* @param autocomplete If this option should be autocompletable
|
||||
*/
|
||||
public setAutocomplete<U extends boolean>(
|
||||
autocomplete: U,
|
||||
): U extends true
|
||||
? Omit<this, 'addChoice' | 'addChoices'>
|
||||
: this & Pick<ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T>, 'addChoice' | 'addChoices'> {
|
||||
// Assert that you actually passed a boolean
|
||||
booleanPredicate.parse(autocomplete);
|
||||
|
||||
if (autocomplete && Array.isArray(this.choices) && this.choices.length > 0) {
|
||||
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
|
||||
}
|
||||
|
||||
Reflect.set(this, 'autocomplete', autocomplete);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { validateDescription, validateName } from '../Assertions';
|
||||
|
||||
export class SharedNameAndDescription {
|
||||
public readonly name!: string;
|
||||
public readonly description!: string;
|
||||
|
||||
/**
|
||||
* Sets the name
|
||||
*
|
||||
* @param name The name
|
||||
*/
|
||||
public setName(name: string): this {
|
||||
// Assert the name matches the conditions
|
||||
validateName(name);
|
||||
|
||||
Reflect.set(this, 'name', name);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description
|
||||
*
|
||||
* @param description The description
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
// Assert the description matches the conditions
|
||||
validateDescription(description);
|
||||
|
||||
Reflect.set(this, 'description', description);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { assertReturnOfBuilder, validateMaxOptionsLength } from '../Assertions';
|
||||
import type { ApplicationCommandOptionBase } from './ApplicationCommandOptionBase';
|
||||
import { SlashCommandBooleanOption } from '../options/boolean';
|
||||
import { SlashCommandChannelOption } from '../options/channel';
|
||||
import { SlashCommandIntegerOption } from '../options/integer';
|
||||
import { SlashCommandMentionableOption } from '../options/mentionable';
|
||||
import { SlashCommandNumberOption } from '../options/number';
|
||||
import { SlashCommandRoleOption } from '../options/role';
|
||||
import { SlashCommandStringOption } from '../options/string';
|
||||
import { SlashCommandUserOption } from '../options/user';
|
||||
import type { ToAPIApplicationCommandOptions } from '../SlashCommandBuilder';
|
||||
|
||||
export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
|
||||
public readonly options!: ToAPIApplicationCommandOptions[];
|
||||
|
||||
/**
|
||||
* Adds a boolean option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addBooleanOption(
|
||||
input: SlashCommandBooleanOption | ((builder: SlashCommandBooleanOption) => SlashCommandBooleanOption),
|
||||
) {
|
||||
return this._sharedAddOptionMethod(input, SlashCommandBooleanOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a user option
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a channel option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addChannelOption(
|
||||
input: SlashCommandChannelOption | ((builder: SlashCommandChannelOption) => SlashCommandChannelOption),
|
||||
) {
|
||||
return this._sharedAddOptionMethod(input, SlashCommandChannelOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a role option
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mentionable option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addMentionableOption(
|
||||
input: SlashCommandMentionableOption | ((builder: SlashCommandMentionableOption) => SlashCommandMentionableOption),
|
||||
) {
|
||||
return this._sharedAddOptionMethod(input, SlashCommandMentionableOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a string option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addStringOption(
|
||||
input:
|
||||
| SlashCommandStringOption
|
||||
| Omit<SlashCommandStringOption, 'setAutocomplete'>
|
||||
| Omit<SlashCommandStringOption, 'addChoice' | 'addChoices'>
|
||||
| ((
|
||||
builder: SlashCommandStringOption,
|
||||
) =>
|
||||
| SlashCommandStringOption
|
||||
| Omit<SlashCommandStringOption, 'setAutocomplete'>
|
||||
| Omit<SlashCommandStringOption, 'addChoice' | 'addChoices'>),
|
||||
) {
|
||||
return this._sharedAddOptionMethod(input, SlashCommandStringOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an integer option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addIntegerOption(
|
||||
input:
|
||||
| SlashCommandIntegerOption
|
||||
| Omit<SlashCommandIntegerOption, 'setAutocomplete'>
|
||||
| Omit<SlashCommandIntegerOption, 'addChoice' | 'addChoices'>
|
||||
| ((
|
||||
builder: SlashCommandIntegerOption,
|
||||
) =>
|
||||
| SlashCommandIntegerOption
|
||||
| Omit<SlashCommandIntegerOption, 'setAutocomplete'>
|
||||
| Omit<SlashCommandIntegerOption, 'addChoice' | 'addChoices'>),
|
||||
) {
|
||||
return this._sharedAddOptionMethod(input, SlashCommandIntegerOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number option
|
||||
*
|
||||
* @param input A function that returns an option builder, or an already built builder
|
||||
*/
|
||||
public addNumberOption(
|
||||
input:
|
||||
| SlashCommandNumberOption
|
||||
| Omit<SlashCommandNumberOption, 'setAutocomplete'>
|
||||
| Omit<SlashCommandNumberOption, 'addChoice' | 'addChoices'>
|
||||
| ((
|
||||
builder: SlashCommandNumberOption,
|
||||
) =>
|
||||
| SlashCommandNumberOption
|
||||
| Omit<SlashCommandNumberOption, 'setAutocomplete'>
|
||||
| Omit<SlashCommandNumberOption, 'addChoice' | 'addChoices'>),
|
||||
) {
|
||||
return this._sharedAddOptionMethod(input, SlashCommandNumberOption);
|
||||
}
|
||||
|
||||
private _sharedAddOptionMethod<T extends ApplicationCommandOptionBase>(
|
||||
input:
|
||||
| T
|
||||
| Omit<T, 'setAutocomplete'>
|
||||
| Omit<T, 'addChoice' | 'addChoices'>
|
||||
| ((builder: T) => T | Omit<T, 'setAutocomplete'> | Omit<T, 'addChoice' | 'addChoices'>),
|
||||
Instance: new () => T,
|
||||
): ShouldOmitSubcommandFunctions extends true ? Omit<this, 'addSubcommand' | 'addSubcommandGroup'> : this {
|
||||
const { options } = this;
|
||||
|
||||
// First, assert options conditions - we cannot have more than 25 options
|
||||
validateMaxOptionsLength(options);
|
||||
|
||||
// Get the final result
|
||||
const result = typeof input === 'function' ? input(new Instance()) : input;
|
||||
|
||||
assertReturnOfBuilder(result, Instance);
|
||||
|
||||
// Push it
|
||||
options.push(result);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { APIApplicationCommandBooleanOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
|
||||
export class SlashCommandBooleanOption extends ApplicationCommandOptionBase {
|
||||
public readonly type = ApplicationCommandOptionType.Boolean as const;
|
||||
|
||||
public toJSON(): APIApplicationCommandBooleanOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
return { ...this };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { APIApplicationCommandChannelOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { mix } from 'ts-mixer';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
import { ApplicationCommandOptionChannelTypesMixin } from '../mixins/ApplicationCommandOptionChannelTypesMixin';
|
||||
|
||||
@mix(ApplicationCommandOptionChannelTypesMixin)
|
||||
export class SlashCommandChannelOption extends ApplicationCommandOptionBase {
|
||||
public override readonly type = ApplicationCommandOptionType.Channel as const;
|
||||
|
||||
public toJSON(): APIApplicationCommandChannelOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
return { ...this };
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlashCommandChannelOption extends ApplicationCommandOptionChannelTypesMixin {}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { APIApplicationCommandIntegerOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { mix } from 'ts-mixer';
|
||||
import { z } from 'zod';
|
||||
import { ApplicationCommandNumericOptionMinMaxValueMixin } from '../mixins/ApplicationCommandNumericOptionMinMaxValueMixin';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
import { ApplicationCommandOptionWithChoicesAndAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithChoicesAndAutocompleteMixin';
|
||||
|
||||
const numberValidator = z.number().int().nonnegative();
|
||||
|
||||
@mix(ApplicationCommandNumericOptionMinMaxValueMixin, ApplicationCommandOptionWithChoicesAndAutocompleteMixin)
|
||||
export class SlashCommandIntegerOption
|
||||
extends ApplicationCommandOptionBase
|
||||
implements ApplicationCommandNumericOptionMinMaxValueMixin
|
||||
{
|
||||
public readonly type = ApplicationCommandOptionType.Integer as const;
|
||||
|
||||
public setMaxValue(max: number): this {
|
||||
numberValidator.parse(max);
|
||||
|
||||
Reflect.set(this, 'max_value', max);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setMinValue(min: number): this {
|
||||
numberValidator.parse(min);
|
||||
|
||||
Reflect.set(this, 'min_value', min);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIApplicationCommandIntegerOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
if (this.autocomplete && Array.isArray(this.choices) && this.choices.length > 0) {
|
||||
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
|
||||
}
|
||||
|
||||
return { ...this };
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlashCommandIntegerOption
|
||||
extends ApplicationCommandNumericOptionMinMaxValueMixin,
|
||||
ApplicationCommandOptionWithChoicesAndAutocompleteMixin<number> {}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { APIApplicationCommandMentionableOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
|
||||
export class SlashCommandMentionableOption extends ApplicationCommandOptionBase {
|
||||
public readonly type = ApplicationCommandOptionType.Mentionable as const;
|
||||
|
||||
public toJSON(): APIApplicationCommandMentionableOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
return { ...this };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { APIApplicationCommandNumberOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { mix } from 'ts-mixer';
|
||||
import { z } from 'zod';
|
||||
import { ApplicationCommandNumericOptionMinMaxValueMixin } from '../mixins/ApplicationCommandNumericOptionMinMaxValueMixin';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
import { ApplicationCommandOptionWithChoicesAndAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithChoicesAndAutocompleteMixin';
|
||||
|
||||
const numberValidator = z.number().nonnegative();
|
||||
|
||||
@mix(ApplicationCommandNumericOptionMinMaxValueMixin, ApplicationCommandOptionWithChoicesAndAutocompleteMixin)
|
||||
export class SlashCommandNumberOption
|
||||
extends ApplicationCommandOptionBase
|
||||
implements ApplicationCommandNumericOptionMinMaxValueMixin
|
||||
{
|
||||
public readonly type = ApplicationCommandOptionType.Number as const;
|
||||
|
||||
public setMaxValue(max: number): this {
|
||||
numberValidator.parse(max);
|
||||
|
||||
Reflect.set(this, 'max_value', max);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setMinValue(min: number): this {
|
||||
numberValidator.parse(min);
|
||||
|
||||
Reflect.set(this, 'min_value', min);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIApplicationCommandNumberOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
if (this.autocomplete && Array.isArray(this.choices) && this.choices.length > 0) {
|
||||
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
|
||||
}
|
||||
|
||||
return { ...this };
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlashCommandNumberOption
|
||||
extends ApplicationCommandNumericOptionMinMaxValueMixin,
|
||||
ApplicationCommandOptionWithChoicesAndAutocompleteMixin<number> {}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { APIApplicationCommandRoleOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
|
||||
export class SlashCommandRoleOption extends ApplicationCommandOptionBase {
|
||||
public override readonly type = ApplicationCommandOptionType.Role as const;
|
||||
|
||||
public toJSON(): APIApplicationCommandRoleOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
return { ...this };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { APIApplicationCommandStringOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { mix } from 'ts-mixer';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
import { ApplicationCommandOptionWithChoicesAndAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithChoicesAndAutocompleteMixin';
|
||||
|
||||
@mix(ApplicationCommandOptionWithChoicesAndAutocompleteMixin)
|
||||
export class SlashCommandStringOption extends ApplicationCommandOptionBase {
|
||||
public readonly type = ApplicationCommandOptionType.String as const;
|
||||
|
||||
public toJSON(): APIApplicationCommandStringOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
if (this.autocomplete && Array.isArray(this.choices) && this.choices.length > 0) {
|
||||
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
|
||||
}
|
||||
|
||||
return { ...this };
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlashCommandStringOption extends ApplicationCommandOptionWithChoicesAndAutocompleteMixin<string> {}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { APIApplicationCommandUserOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
|
||||
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
|
||||
|
||||
export class SlashCommandUserOption extends ApplicationCommandOptionBase {
|
||||
public readonly type = ApplicationCommandOptionType.User as const;
|
||||
|
||||
public toJSON(): APIApplicationCommandUserOption {
|
||||
this.runRequiredValidations();
|
||||
|
||||
return { ...this };
|
||||
}
|
||||
}
|
||||
36
packages/builders/src/messages/embed/Assertions.ts
Normal file
36
packages/builders/src/messages/embed/Assertions.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { APIEmbedField } from 'discord-api-types/v9';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const fieldNamePredicate = z.string().min(1).max(256);
|
||||
|
||||
export const fieldValuePredicate = z.string().min(1).max(1024);
|
||||
|
||||
export const fieldInlinePredicate = z.boolean().optional();
|
||||
|
||||
export const embedFieldPredicate = z.object({
|
||||
name: fieldNamePredicate,
|
||||
value: fieldValuePredicate,
|
||||
inline: fieldInlinePredicate,
|
||||
});
|
||||
|
||||
export const embedFieldsArrayPredicate = embedFieldPredicate.array();
|
||||
|
||||
export const fieldLengthPredicate = z.number().lte(25);
|
||||
|
||||
export function validateFieldLength(fields: APIEmbedField[], amountAdding: number): void {
|
||||
fieldLengthPredicate.parse(fields.length + amountAdding);
|
||||
}
|
||||
|
||||
export const authorNamePredicate = fieldNamePredicate.nullable();
|
||||
|
||||
export const urlPredicate = z.string().url().nullish();
|
||||
|
||||
export const colorPredicate = z.number().gte(0).lte(0xffffff).nullable();
|
||||
|
||||
export const descriptionPredicate = z.string().min(1).max(4096).nullable();
|
||||
|
||||
export const footerTextPredicate = z.string().min(1).max(2048).nullable();
|
||||
|
||||
export const timestampPredicate = z.union([z.number(), z.date()]).nullable();
|
||||
|
||||
export const titlePredicate = fieldNamePredicate.nullable();
|
||||
326
packages/builders/src/messages/embed/Embed.ts
Normal file
326
packages/builders/src/messages/embed/Embed.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
import type {
|
||||
APIEmbed,
|
||||
APIEmbedAuthor,
|
||||
APIEmbedField,
|
||||
APIEmbedFooter,
|
||||
APIEmbedImage,
|
||||
APIEmbedProvider,
|
||||
APIEmbedThumbnail,
|
||||
APIEmbedVideo,
|
||||
} from 'discord-api-types/v9';
|
||||
import {
|
||||
authorNamePredicate,
|
||||
colorPredicate,
|
||||
descriptionPredicate,
|
||||
embedFieldsArrayPredicate,
|
||||
fieldInlinePredicate,
|
||||
fieldNamePredicate,
|
||||
fieldValuePredicate,
|
||||
footerTextPredicate,
|
||||
timestampPredicate,
|
||||
titlePredicate,
|
||||
urlPredicate,
|
||||
validateFieldLength,
|
||||
} from './Assertions';
|
||||
|
||||
export interface AuthorOptions {
|
||||
name: string;
|
||||
url?: string;
|
||||
iconURL?: string;
|
||||
}
|
||||
|
||||
export interface FooterOptions {
|
||||
text: string;
|
||||
iconURL?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an embed in a message (image/video preview, rich embed, etc.)
|
||||
*/
|
||||
export class Embed implements APIEmbed {
|
||||
/**
|
||||
* An array of fields of this embed
|
||||
*/
|
||||
public fields: APIEmbedField[];
|
||||
|
||||
/**
|
||||
* The embed title
|
||||
*/
|
||||
public title?: string;
|
||||
|
||||
/**
|
||||
* The embed description
|
||||
*/
|
||||
public description?: string;
|
||||
|
||||
/**
|
||||
* The embed url
|
||||
*/
|
||||
public url?: string;
|
||||
|
||||
/**
|
||||
* The embed color
|
||||
*/
|
||||
public color?: number;
|
||||
|
||||
/**
|
||||
* The timestamp of the embed in the ISO format
|
||||
*/
|
||||
public timestamp?: string;
|
||||
|
||||
/**
|
||||
* The embed thumbnail data
|
||||
*/
|
||||
public thumbnail?: APIEmbedThumbnail;
|
||||
|
||||
/**
|
||||
* The embed image data
|
||||
*/
|
||||
public image?: APIEmbedImage;
|
||||
|
||||
/**
|
||||
* Received video data
|
||||
*/
|
||||
public video?: APIEmbedVideo;
|
||||
|
||||
/**
|
||||
* The embed author data
|
||||
*/
|
||||
public author?: APIEmbedAuthor;
|
||||
|
||||
/**
|
||||
* Received data about the embed provider
|
||||
*/
|
||||
public provider?: APIEmbedProvider;
|
||||
|
||||
/**
|
||||
* The embed footer data
|
||||
*/
|
||||
public footer?: APIEmbedFooter;
|
||||
|
||||
public constructor(data: APIEmbed = {}) {
|
||||
this.title = data.title;
|
||||
this.description = data.description;
|
||||
this.url = data.url;
|
||||
this.color = data.color;
|
||||
this.thumbnail = data.thumbnail;
|
||||
this.image = data.image;
|
||||
this.video = data.video;
|
||||
this.author = data.author;
|
||||
this.provider = data.provider;
|
||||
this.footer = data.footer;
|
||||
this.fields = data.fields ?? [];
|
||||
|
||||
if (data.timestamp) this.timestamp = new Date(data.timestamp).toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The accumulated length for the embed title, description, fields, footer text, and author name
|
||||
*/
|
||||
public get length(): number {
|
||||
return (
|
||||
(this.title?.length ?? 0) +
|
||||
(this.description?.length ?? 0) +
|
||||
this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0) +
|
||||
(this.footer?.text.length ?? 0) +
|
||||
(this.author?.name.length ?? 0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to the embed (max 25)
|
||||
*
|
||||
* @param field The field to add.
|
||||
*/
|
||||
public addField(field: APIEmbedField): this {
|
||||
return this.addFields(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields to the embed (max 25)
|
||||
*
|
||||
* @param fields The fields to add
|
||||
*/
|
||||
public addFields(...fields: APIEmbedField[]): this {
|
||||
// Data assertions
|
||||
embedFieldsArrayPredicate.parse(fields);
|
||||
|
||||
// Ensure adding these fields won't exceed the 25 field limit
|
||||
validateFieldLength(this.fields, fields.length);
|
||||
|
||||
this.fields.push(...Embed.normalizeFields(...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 {
|
||||
// Data assertions
|
||||
embedFieldsArrayPredicate.parse(fields);
|
||||
|
||||
// Ensure adding these fields won't exceed the 25 field limit
|
||||
validateFieldLength(this.fields, fields.length - deleteCount);
|
||||
|
||||
this.fields.splice(index, deleteCount, ...Embed.normalizeFields(...fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the author of this embed
|
||||
*
|
||||
* @param options The options for the author
|
||||
*/
|
||||
public setAuthor(options: AuthorOptions | null): this {
|
||||
if (options === null) {
|
||||
this.author = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
const { name, iconURL, url } = options;
|
||||
// Data assertions
|
||||
authorNamePredicate.parse(name);
|
||||
urlPredicate.parse(iconURL);
|
||||
urlPredicate.parse(url);
|
||||
|
||||
this.author = { name, url, icon_url: iconURL };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of this embed
|
||||
*
|
||||
* @param color The color of the embed
|
||||
*/
|
||||
public setColor(color: number | null): this {
|
||||
// Data assertions
|
||||
colorPredicate.parse(color);
|
||||
|
||||
this.color = color ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this embed
|
||||
*
|
||||
* @param description The description
|
||||
*/
|
||||
public setDescription(description: string | null): this {
|
||||
// Data assertions
|
||||
descriptionPredicate.parse(description);
|
||||
|
||||
this.description = description ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the footer of this embed
|
||||
*
|
||||
* @param options The options for the footer
|
||||
*/
|
||||
public setFooter(options: FooterOptions | null): this {
|
||||
if (options === null) {
|
||||
this.footer = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
const { text, iconURL } = options;
|
||||
// Data assertions
|
||||
footerTextPredicate.parse(text);
|
||||
urlPredicate.parse(iconURL);
|
||||
|
||||
this.footer = { text, icon_url: iconURL };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image of this embed
|
||||
*
|
||||
* @param url The URL of the image
|
||||
*/
|
||||
public setImage(url: string | null): this {
|
||||
// Data assertions
|
||||
urlPredicate.parse(url);
|
||||
|
||||
this.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 {
|
||||
// Data assertions
|
||||
urlPredicate.parse(url);
|
||||
|
||||
this.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 {
|
||||
// Data assertions
|
||||
timestampPredicate.parse(timestamp);
|
||||
|
||||
this.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 {
|
||||
// Data assertions
|
||||
titlePredicate.parse(title);
|
||||
|
||||
this.title = title ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this embed
|
||||
*
|
||||
* @param url The URL
|
||||
*/
|
||||
public setURL(url: string | null): this {
|
||||
// Data assertions
|
||||
urlPredicate.parse(url);
|
||||
|
||||
this.url = url ?? undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the embed to a plain object
|
||||
*/
|
||||
public toJSON(): APIEmbed {
|
||||
return { ...this };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes field input and resolves strings
|
||||
*
|
||||
* @param fields Fields to normalize
|
||||
*/
|
||||
public static normalizeFields(...fields: APIEmbedField[]): APIEmbedField[] {
|
||||
return fields.flat(Infinity).map((field) => {
|
||||
fieldNamePredicate.parse(field.name);
|
||||
fieldValuePredicate.parse(field.value);
|
||||
fieldInlinePredicate.parse(field.inline);
|
||||
|
||||
return { name: field.name, value: field.value, inline: field.inline ?? undefined };
|
||||
});
|
||||
}
|
||||
}
|
||||
319
packages/builders/src/messages/formatters.ts
Normal file
319
packages/builders/src/messages/formatters.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
import type { Snowflake } from 'discord-api-types/globals';
|
||||
import type { URL } from 'url';
|
||||
|
||||
/**
|
||||
* Wraps the content inside a codeblock with no language
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
export function codeBlock<L extends string, C extends string>(language: L, content: C): `\`\`\`${L}\n${C}\`\`\``;
|
||||
export function codeBlock(language: string, content?: string): string {
|
||||
return typeof content === 'undefined' ? `\`\`\`\n${language}\`\`\`` : `\`\`\`${language}\n${content}\`\`\``;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the content inside \`backticks\`, which formats it as inline code
|
||||
*
|
||||
* @param content The content to wrap
|
||||
*/
|
||||
export function inlineCode<C extends string>(content: C): `\`${C}\`` {
|
||||
return `\`${content}\``;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the content into italic text
|
||||
*
|
||||
* @param content The content to wrap
|
||||
*/
|
||||
export function italic<C extends string>(content: C): `_${C}_` {
|
||||
return `_${content}_`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the content into bold text
|
||||
*
|
||||
* @param content The content to wrap
|
||||
*/
|
||||
export function bold<C extends string>(content: C): `**${C}**` {
|
||||
return `**${content}**`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the content into underscored text
|
||||
*
|
||||
* @param content The content to wrap
|
||||
*/
|
||||
export function underscore<C extends string>(content: C): `__${C}__` {
|
||||
return `__${content}__`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the content into strike-through text
|
||||
*
|
||||
* @param content The content to wrap
|
||||
*/
|
||||
export function strikethrough<C extends string>(content: C): `~~${C}~~` {
|
||||
return `~~${content}~~`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export function quote<C extends string>(content: C): `> ${C}` {
|
||||
return `> ${content}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export function blockQuote<C extends string>(content: C): `>>> ${C}` {
|
||||
return `>>> ${content}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the URL into `<>`, which stops it from embedding
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
export function hideLinkEmbed(url: URL): `<${string}>`;
|
||||
export function hideLinkEmbed(url: string | URL) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
return `<${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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
export function hyperlink<C extends string, T extends string>(
|
||||
content: C,
|
||||
url: URL,
|
||||
title: T,
|
||||
): `[${C}](${string} "${T}")`;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export function hyperlink<C extends string, U extends string, T extends string>(
|
||||
content: C,
|
||||
url: U,
|
||||
title: T,
|
||||
): `[${C}](${U} "${T}")`;
|
||||
export function hyperlink(content: string, url: string | URL, title?: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
return title ? `[${content}](${url} "${title}")` : `[${content}](${url})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the content inside spoiler (hidden text)
|
||||
*
|
||||
* @param content The content to wrap
|
||||
*/
|
||||
export function spoiler<C extends string>(content: C): `||${C}||` {
|
||||
return `||${content}||`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a user ID into a user mention
|
||||
*
|
||||
* @param userId The user ID to format
|
||||
*/
|
||||
export function userMention<C extends Snowflake>(userId: C): `<@${C}>` {
|
||||
return `<@${userId}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a user ID into a member-nickname mention
|
||||
*
|
||||
* @param memberId The user ID to format
|
||||
*/
|
||||
export function memberNicknameMention<C extends Snowflake>(memberId: C): `<@!${C}>` {
|
||||
return `<@!${memberId}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a channel ID into a channel mention
|
||||
*
|
||||
* @param channelId The channel ID to format
|
||||
*/
|
||||
export function channelMention<C extends Snowflake>(channelId: C): `<#${C}>` {
|
||||
return `<#${channelId}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a role ID into a role mention
|
||||
*
|
||||
* @param roleId The role ID to format
|
||||
*/
|
||||
export function roleMention<C extends Snowflake>(roleId: C): `<@&${C}>` {
|
||||
return `<@&${roleId}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an emoji ID into a fully qualified emoji identifier
|
||||
*
|
||||
* @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`
|
||||
*/
|
||||
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`
|
||||
*/
|
||||
export function formatEmoji<C extends Snowflake>(emojiId: C, animated = false): `<a:_:${C}>` | `<:_:${C}>` {
|
||||
return `<${animated ? 'a' : ''}:_:${emojiId}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a date into a short date-time string
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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 {
|
||||
if (typeof timeOrSeconds !== 'number') {
|
||||
timeOrSeconds = Math.floor((timeOrSeconds?.getTime() ?? Date.now()) / 1000);
|
||||
}
|
||||
|
||||
return typeof style === 'string' ? `<t:${timeOrSeconds}:${style}>` : `<t:${timeOrSeconds}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The [message formatting timestamp styles](https://discord.com/developers/docs/reference#message-formatting-timestamp-styles) supported by Discord
|
||||
*/
|
||||
export const TimestampStyles = {
|
||||
/**
|
||||
* Short time format, consisting of hours and minutes, e.g. 16:20
|
||||
*/
|
||||
ShortTime: 't',
|
||||
|
||||
/**
|
||||
* Long time format, consisting of hours, minutes, and seconds, e.g. 16:20:30
|
||||
*/
|
||||
LongTime: 'T',
|
||||
|
||||
/**
|
||||
* Short date format, consisting of day, month, and year, e.g. 20/04/2021
|
||||
*/
|
||||
ShortDate: 'd',
|
||||
|
||||
/**
|
||||
* Long date format, consisting of day, month, and year, e.g. 20 April 2021
|
||||
*/
|
||||
LongDate: 'D',
|
||||
|
||||
/**
|
||||
* Short date-time format, consisting of short date and short time formats, e.g. 20 April 2021 16:20
|
||||
*/
|
||||
ShortDateTime: 'f',
|
||||
|
||||
/**
|
||||
* Long date-time format, consisting of long date and short time formats, e.g. Tuesday, 20 April 2021 16:20
|
||||
*/
|
||||
LongDateTime: 'F',
|
||||
|
||||
/**
|
||||
* Relative time format, consisting of a relative duration format, e.g. 2 months ago
|
||||
*/
|
||||
RelativeTime: 'R',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* The possible values, see {@link TimestampStyles} for more information
|
||||
*/
|
||||
export type TimestampStylesString = typeof TimestampStyles[keyof typeof TimestampStyles];
|
||||
|
||||
/**
|
||||
* An enum with all the available faces from Discord's native slash commands
|
||||
*/
|
||||
export enum Faces {
|
||||
/**
|
||||
* ¯\\_(ツ)\\_/¯
|
||||
*/
|
||||
Shrug = '¯\\_(ツ)\\_/¯',
|
||||
|
||||
/**
|
||||
* (╯°□°)╯︵ ┻━┻
|
||||
*/
|
||||
Tableflip = '(╯°□°)╯︵ ┻━┻',
|
||||
|
||||
/**
|
||||
* ┬─┬ ノ( ゜-゜ノ)
|
||||
*/
|
||||
Unflip = '┬─┬ ノ( ゜-゜ノ)',
|
||||
}
|
||||
20
packages/builders/tsconfig.eslint.json
Normal file
20
packages/builders/tsconfig.eslint.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.mjs",
|
||||
"**/*.jsx",
|
||||
"**/*.test.ts",
|
||||
"**/*.test.js",
|
||||
"**/*.test.mjs",
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.mjs"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
4
packages/builders/tsconfig.json
Normal file
4
packages/builders/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
12
packages/builders/tsup.config.ts
Normal file
12
packages/builders/tsup.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Options } from 'tsup';
|
||||
|
||||
export const tsup: Options = {
|
||||
clean: true,
|
||||
dts: true,
|
||||
entryPoints: ['src/index.ts'],
|
||||
format: ['esm', 'cjs'],
|
||||
minify: true,
|
||||
skipNodeModulesBundle: true,
|
||||
sourcemap: true,
|
||||
target: 'es2021',
|
||||
};
|
||||
16
packages/collection/.eslintrc.json
Normal file
16
packages/collection/.eslintrc.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "marine/prettier/node",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json",
|
||||
"extraFileExtensions": [".mjs"]
|
||||
},
|
||||
"ignorePatterns": ["**/dist/*"],
|
||||
"env": {
|
||||
"jest": true
|
||||
},
|
||||
"rules": {
|
||||
"no-redeclare": 0,
|
||||
"@typescript-eslint/naming-convention": 0
|
||||
}
|
||||
}
|
||||
27
packages/collection/.gitignore
vendored
Normal file
27
packages/collection/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Packages
|
||||
node_modules/
|
||||
|
||||
# Log files
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Env
|
||||
.env
|
||||
|
||||
# Dist
|
||||
dist/
|
||||
typings/
|
||||
docs/**/*
|
||||
!docs/index.yml
|
||||
!docs/README.md
|
||||
|
||||
# Miscellaneous
|
||||
.tmp/
|
||||
coverage/
|
||||
tsconfig.tsbuildinfo
|
||||
8
packages/collection/.prettierrc.json
Normal file
8
packages/collection/.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
3
packages/collection/.versionrc
Normal file
3
packages/collection/.versionrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"releaseCommitMessageFormat": "chore(Release): publish"
|
||||
}
|
||||
63
packages/collection/CHANGELOG.md
Normal file
63
packages/collection/CHANGELOG.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
|
||||
# [0.4.0](https://github.com/discordjs/collection/compare/v0.3.2...v0.4.0) (2021-12-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add #reverse ([#48](https://github.com/discordjs/collection/issues/48)) ([8bcb5e2](https://github.com/discordjs/collection/commit/8bcb5e21bcc15f5b77612d8ff03dec6c37f4d449))
|
||||
* add Collection#ensure ([#52](https://github.com/discordjs/collection/issues/52)) ([3809eb4](https://github.com/discordjs/collection/commit/3809eb4d18e70459355d310919a3f57747eee3dd))
|
||||
|
||||
|
||||
|
||||
## [0.3.2](https://github.com/discordjs/collection/compare/v0.3.1...v0.3.2) (2021-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update doc engine ([4c0e24f](https://github.com/discordjs/collection/commit/4c0e24fae0323db9de1991db9cfacc093d529abc))
|
||||
|
||||
|
||||
|
||||
## [0.3.1](https://github.com/discordjs/collection/compare/v0.3.0...v0.3.1) (2021-10-29)
|
||||
|
||||
|
||||
|
||||
# [0.3.0](https://github.com/discordjs/collection/compare/v0.2.4...v0.3.0) (2021-10-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Collection#at() and Collection#keyAt() ([#46](https://github.com/discordjs/collection/issues/46)) ([66b30b9](https://github.com/discordjs/collection/commit/66b30b91069502493383c059cc38e27c152bf541))
|
||||
* improve documentation and resolve [#49](https://github.com/discordjs/collection/issues/49) ([aec01c6](https://github.com/discordjs/collection/commit/aec01c6ae3ff50b0b5f7c070bff10f01bf98d803))
|
||||
* ts-docgen ([463b131](https://github.com/discordjs/collection/commit/463b1314e60f2debc526454a6ccd7ce8a9a4ae8a))
|
||||
|
||||
|
||||
|
||||
## [0.2.4](https://github.com/discordjs/collection/compare/v0.2.3...v0.2.4) (2021-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* minification of names ([bd2fe2a](https://github.com/discordjs/collection/commit/bd2fe2a47c38f634b0334fe6e89f30f6f6a0b1f5))
|
||||
|
||||
|
||||
|
||||
## [0.2.3](https://github.com/discordjs/collection/compare/v0.2.2...v0.2.3) (2021-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* building with useDefineForClassFields false ([2a571d5](https://github.com/discordjs/collection/commit/2a571d5a2c90ed8b708c3c5c017e2f225cd494e9))
|
||||
|
||||
|
||||
|
||||
## [0.2.2](https://github.com/discordjs/collection/compare/v0.2.1...v0.2.2) (2021-10-27)
|
||||
|
||||
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
46
packages/collection/README.md
Normal file
46
packages/collection/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
<div align="center">
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
|
||||
<a href="https://www.npmjs.com/package/@discordjs/collection"><img src="https://img.shields.io/npm/v/@discordjs/collection.svg?maxAge=3600" alt="npm version" /></a>
|
||||
<a href="https://www.npmjs.com/package/@discordjs/collection"><img src="https://img.shields.io/npm/dt/@discordjs/collection.svg?maxAge=3600" alt="npm downloads" /></a>
|
||||
<a href="https://github.com/discordjs/collection/actions"><img src="https://github.com/discordjs/collection/workflows/Tests/badge.svg" alt="Build status" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## About
|
||||
|
||||
`@discordjs/collection` is a powerful utility data structure used in discord.js.
|
||||
|
||||
## Installation
|
||||
|
||||
**Node.js 16.0.0 or newer is required.**
|
||||
|
||||
```sh-session
|
||||
npm install @discordjs/collection
|
||||
yarn add @discordjs/collection
|
||||
pnpm add @discordjs/collection
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
|
||||
- [Documentation](https://discord.js.org/#/docs/collection)
|
||||
- [discord.js Discord server](https://discord.gg/djs)
|
||||
- [GitHub](https://github.com/discordjs/collection)
|
||||
- [npm](https://www.npmjs.com/package/@discordjs/collection)
|
||||
|
||||
## 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/collection).
|
||||
See [the contribution guide](https://github.com/discordjs/collection/blob/main/.github/CONTRIBUTING.md) if you'd like to submit a PR.
|
||||
|
||||
## Help
|
||||
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
|
||||
nudge in the right direction, please don't hesitate to join our official [discord.js Server](https://discord.gg/djs).
|
||||
462
packages/collection/__tests__/collection.test.ts
Normal file
462
packages/collection/__tests__/collection.test.ts
Normal file
@@ -0,0 +1,462 @@
|
||||
import Collection from '../src';
|
||||
|
||||
type TestCollection = Collection<string, number>;
|
||||
|
||||
test('do basic map operations', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
expect(coll.size).toEqual(1);
|
||||
expect(coll.has('a')).toBeTruthy();
|
||||
expect(coll.get('a')).toStrictEqual(1);
|
||||
coll.delete('a');
|
||||
expect(coll.has('a')).toBeFalsy();
|
||||
expect(coll.get('a')).toStrictEqual(undefined);
|
||||
coll.clear();
|
||||
expect(coll.size).toStrictEqual(0);
|
||||
});
|
||||
|
||||
test('get the first item of the collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
expect(coll.first()).toStrictEqual(1);
|
||||
});
|
||||
|
||||
test('get the first 3 items of the collection where size equals', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
expect(coll.first(3)).toStrictEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('get the first 3 items of the collection where size is less', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
expect(coll.first(3)).toStrictEqual([1, 2]);
|
||||
});
|
||||
|
||||
test('get the last item of the collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
expect(coll.last()).toStrictEqual(2);
|
||||
});
|
||||
|
||||
test('get the last 3 items of the collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
expect(coll.last(3)).toStrictEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('get the last 3 items of the collection where size is less', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
expect(coll.last(3)).toStrictEqual([1, 2]);
|
||||
});
|
||||
|
||||
test('find an item in the collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
expect(coll.find((x) => x === 1)).toStrictEqual(1);
|
||||
expect(coll.find((x) => x === 10)).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
test('sweep items from the collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
const n1 = coll.sweep((x) => x === 2);
|
||||
expect(n1).toStrictEqual(1);
|
||||
expect([...coll.values()]).toStrictEqual([1, 3]);
|
||||
const n2 = coll.sweep((x) => x === 4);
|
||||
expect(n2).toStrictEqual(0);
|
||||
expect([...coll.values()]).toStrictEqual([1, 3]);
|
||||
});
|
||||
|
||||
test('filter items from the collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
const filtered = coll.filter((x) => x % 2 === 1);
|
||||
expect(coll.size).toStrictEqual(3);
|
||||
expect(filtered.size).toStrictEqual(2);
|
||||
expect([...filtered.values()]).toStrictEqual([1, 3]);
|
||||
});
|
||||
|
||||
test('partition a collection into two collections', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
coll.set('d', 4);
|
||||
coll.set('e', 5);
|
||||
coll.set('f', 6);
|
||||
const [even, odd] = coll.partition((x) => x % 2 === 0);
|
||||
expect([...even.values()]).toStrictEqual([2, 4, 6]);
|
||||
expect([...odd.values()]).toStrictEqual([1, 3, 5]);
|
||||
});
|
||||
|
||||
test('map items in a collection into an array', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
const mapped = coll.map((x) => x + 1);
|
||||
expect(mapped).toStrictEqual([2, 3, 4]);
|
||||
});
|
||||
|
||||
test('map items in a collection into a collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
const mapped = coll.mapValues((x) => x + 1);
|
||||
expect([...mapped.values()]).toStrictEqual([2, 3, 4]);
|
||||
});
|
||||
|
||||
test('flatMap items in a collection into a single collection', () => {
|
||||
const coll = new Collection<string, { a: Collection<string, number> }>();
|
||||
const coll1 = new Collection<string, number>();
|
||||
const coll2 = new Collection<string, number>();
|
||||
coll1.set('z', 1);
|
||||
coll1.set('x', 2);
|
||||
coll2.set('c', 3);
|
||||
coll2.set('v', 4);
|
||||
coll.set('a', { a: coll1 });
|
||||
coll.set('b', { a: coll2 });
|
||||
const mapped = coll.flatMap((x) => x.a);
|
||||
expect([...mapped.values()]).toStrictEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test('check if some items pass a predicate', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
expect(coll.some((x) => x === 2)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('check if every items pass a predicate', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
expect(!coll.every((x) => x === 2)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('reduce collection into a single value with initial value', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
const sum = coll.reduce((a, x) => a + x, 0);
|
||||
expect(sum).toStrictEqual(6);
|
||||
});
|
||||
|
||||
test('reduce collection into a single value without initial value', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
const sum = coll.reduce((a, x) => a + x, 0);
|
||||
expect(sum).toStrictEqual(6);
|
||||
});
|
||||
|
||||
test('reduce empty collection without initial value', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
expect(() => coll.reduce((a: number, x) => a + x)).toThrowError(
|
||||
new TypeError('Reduce of empty collection with no initial value'),
|
||||
);
|
||||
});
|
||||
|
||||
test('iterate over each item', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
const a: [string, number][] = [];
|
||||
coll.each((v, k) => a.push([k, v]));
|
||||
expect(a).toStrictEqual([
|
||||
['a', 1],
|
||||
['b', 2],
|
||||
['c', 3],
|
||||
]);
|
||||
});
|
||||
|
||||
test('tap the collection', () => {
|
||||
const coll = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
coll.tap((c) => expect(c).toStrictEqual(coll));
|
||||
});
|
||||
|
||||
test('shallow clone the collection', () => {
|
||||
const coll = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
const clone = coll.clone();
|
||||
expect([...coll.values()]).toStrictEqual([...clone.values()]);
|
||||
});
|
||||
|
||||
test('merge multiple collections', () => {
|
||||
const coll1 = new Collection();
|
||||
coll1.set('a', 1);
|
||||
const coll2 = new Collection();
|
||||
coll2.set('b', 2);
|
||||
const coll3 = new Collection();
|
||||
coll3.set('c', 3);
|
||||
const merged = coll1.concat(coll2, coll3);
|
||||
expect([...merged.values()]).toStrictEqual([1, 2, 3]);
|
||||
expect(coll1 !== merged).toBeTruthy();
|
||||
});
|
||||
|
||||
test('check equality of two collections', () => {
|
||||
const coll1 = new Collection<string, number>();
|
||||
coll1.set('a', 1);
|
||||
const coll2 = new Collection<string, number>();
|
||||
coll2.set('a', 1);
|
||||
expect(coll1.equals(coll2)).toBeTruthy();
|
||||
coll2.set('b', 2);
|
||||
expect(!coll1.equals(coll2)).toBeTruthy();
|
||||
coll2.clear();
|
||||
expect(!coll1.equals(coll2)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('sort a collection in place', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 3);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 1);
|
||||
expect([...coll.values()]).toStrictEqual([3, 2, 1]);
|
||||
coll.sort((a, b) => a - b);
|
||||
expect([...coll.values()]).toStrictEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('random select from a collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyz';
|
||||
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26];
|
||||
|
||||
for (let i = 0; i < chars.length; i++) coll.set(chars[i], numbers[i]);
|
||||
|
||||
const random = coll.random(5);
|
||||
expect(random.length === 5).toBeTruthy();
|
||||
|
||||
const set = new Set(random);
|
||||
expect(set.size === random.length).toBeTruthy();
|
||||
});
|
||||
|
||||
test('when random param > collection size', () => {
|
||||
const coll = new Collection();
|
||||
coll.set('a', 3);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 1);
|
||||
|
||||
const random = coll.random(5);
|
||||
expect(random.length).toEqual(coll.size);
|
||||
});
|
||||
|
||||
test('sort a collection', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 3);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 1);
|
||||
expect([...coll.values()]).toStrictEqual([3, 2, 1]);
|
||||
const sorted = coll.sorted((a, b) => a - b);
|
||||
expect([...coll.values()]).toStrictEqual([3, 2, 1]);
|
||||
expect([...sorted.values()]).toStrictEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
describe('at() tests', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
|
||||
test('Positive index', () => {
|
||||
expect(coll.at(0)).toStrictEqual(1);
|
||||
});
|
||||
|
||||
test('Negative index', () => {
|
||||
expect(coll.at(-1)).toStrictEqual(2);
|
||||
});
|
||||
|
||||
test('Invalid positive index', () => {
|
||||
expect(coll.at(3)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Invalid negative index', () => {
|
||||
expect(coll.at(-3)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyAt() tests', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
|
||||
test('Positive index', () => {
|
||||
expect(coll.keyAt(0)).toStrictEqual('a');
|
||||
});
|
||||
|
||||
test('Negative index', () => {
|
||||
expect(coll.keyAt(-1)).toStrictEqual('b');
|
||||
});
|
||||
|
||||
test('Invalid positive index', () => {
|
||||
expect(coll.keyAt(3)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Invalid negative index', () => {
|
||||
expect(coll.keyAt(-3)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasAll() tests', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
|
||||
test('All keys exist in the collection', () => {
|
||||
expect(coll.hasAll('a', 'b')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Some keys exist in the collection', () => {
|
||||
expect(coll.hasAll('b', 'c')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('No keys exist in the collection', () => {
|
||||
expect(coll.hasAll('c', 'd')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasAny() tests', () => {
|
||||
const coll: TestCollection = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
|
||||
test('All keys exist in the collection', () => {
|
||||
expect(coll.hasAny('a', 'b')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Some keys exist in the collection', () => {
|
||||
expect(coll.hasAny('b', 'c')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('No keys exist in the collection', () => {
|
||||
expect(coll.hasAny('c', 'd')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('reverse() tests', () => {
|
||||
const coll = new Collection();
|
||||
coll.set('a', 1);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 3);
|
||||
|
||||
coll.reverse();
|
||||
|
||||
expect([...coll.values()]).toStrictEqual([3, 2, 1]);
|
||||
expect([...coll.keys()]).toStrictEqual(['c', 'b', 'a']);
|
||||
});
|
||||
|
||||
describe('random thisArg tests', () => {
|
||||
const coll = new Collection();
|
||||
coll.set('a', 3);
|
||||
coll.set('b', 2);
|
||||
coll.set('c', 1);
|
||||
|
||||
const object = {};
|
||||
const string = 'Hi';
|
||||
const boolean = false;
|
||||
const symbol = Symbol('testArg');
|
||||
const array = [1, 2, 3];
|
||||
|
||||
coll.set('d', object);
|
||||
coll.set('e', string);
|
||||
coll.set('f', boolean);
|
||||
coll.set('g', symbol);
|
||||
coll.set('h', array);
|
||||
|
||||
test('thisArg test: number', () => {
|
||||
coll.some(function thisArgTest1(value) {
|
||||
expect(this.valueOf()).toStrictEqual(1);
|
||||
expect(typeof this).toEqual('number');
|
||||
return value === this;
|
||||
}, 1);
|
||||
});
|
||||
|
||||
test('thisArg test: object', () => {
|
||||
coll.some(function thisArgTest2(value) {
|
||||
expect(this).toStrictEqual(object);
|
||||
expect(this.constructor === Object).toBeTruthy();
|
||||
return value === this;
|
||||
}, object);
|
||||
});
|
||||
|
||||
test('thisArg test: string', () => {
|
||||
coll.some(function thisArgTest3(value) {
|
||||
expect(this.valueOf()).toStrictEqual(string);
|
||||
expect(typeof this).toEqual('string');
|
||||
return value === this;
|
||||
}, string);
|
||||
});
|
||||
|
||||
test('thisArg test: boolean', () => {
|
||||
coll.some(function thisArgTest4(value) {
|
||||
expect(this.valueOf()).toStrictEqual(boolean);
|
||||
expect(typeof this).toEqual('boolean');
|
||||
return value === this;
|
||||
}, boolean);
|
||||
});
|
||||
|
||||
test('thisArg test: symbol', () => {
|
||||
coll.some(function thisArgTest5(value) {
|
||||
expect(this.valueOf()).toStrictEqual(symbol);
|
||||
expect(typeof this).toEqual('symbol');
|
||||
return value === this;
|
||||
}, symbol);
|
||||
});
|
||||
|
||||
test('thisArg test: array', () => {
|
||||
coll.some(function thisArgTest6(value) {
|
||||
expect(this).toStrictEqual(array);
|
||||
expect(Array.isArray(this)).toBeTruthy();
|
||||
return value === this;
|
||||
}, array);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensure() tests', () => {
|
||||
function createTestCollection() {
|
||||
return new Collection([
|
||||
['a', 1],
|
||||
['b', 2],
|
||||
]);
|
||||
}
|
||||
|
||||
test('set new value if key does not exist', () => {
|
||||
const coll = createTestCollection();
|
||||
coll.ensure('c', () => 3);
|
||||
expect(coll.size).toStrictEqual(3);
|
||||
expect(coll.get('c')).toStrictEqual(3);
|
||||
});
|
||||
|
||||
test('return existing value if key exists', () => {
|
||||
const coll = createTestCollection();
|
||||
const ensureB = coll.ensure('b', () => 3);
|
||||
const getB = coll.get('b');
|
||||
expect(ensureB).toStrictEqual(2);
|
||||
expect(getB).toStrictEqual(2);
|
||||
expect(coll.size).toStrictEqual(2);
|
||||
});
|
||||
});
|
||||
17
packages/collection/babel.config.js
Normal file
17
packages/collection/babel.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @type {import('@babel/core').TransformOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
parserOpts: { strictMode: true },
|
||||
sourceMaps: 'inline',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: { node: 'current' },
|
||||
modules: 'commonjs',
|
||||
},
|
||||
],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
||||
1
packages/collection/docs/README.md
Normal file
1
packages/collection/docs/README.md
Normal file
@@ -0,0 +1 @@
|
||||
## [View the documentation here.](https://discord.js.org/#/docs/collection)
|
||||
5
packages/collection/docs/index.yml
Normal file
5
packages/collection/docs/index.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
- name: General
|
||||
files:
|
||||
- name: Welcome
|
||||
id: welcome
|
||||
path: ../../README.md
|
||||
11
packages/collection/jest.config.js
Normal file
11
packages/collection/jest.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @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'],
|
||||
};
|
||||
75
packages/collection/package.json
Normal file
75
packages/collection/package.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "@discordjs/collection",
|
||||
"version": "0.4.0",
|
||||
"description": "Utility data structure used in discord.js",
|
||||
"scripts": {
|
||||
"test": "jest --pass-with-no-tests",
|
||||
"build": "tsup",
|
||||
"lint": "eslint src --ext mjs,js,ts",
|
||||
"lint:fix": "eslint src --ext mjs,js,ts --fix",
|
||||
"format": "prettier --write **/*.{ts,js,json,yml,yaml}",
|
||||
"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 './*'"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "src",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"contributors": [
|
||||
"Crawl <icrawltogo@gmail.com>",
|
||||
"Amish Shah <amishshah.2k@gmail.com>",
|
||||
"SpaceEEC <spaceeec@yahoo.com>",
|
||||
"Vlad Frangu <kingdgrizzle@gmail.com>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"map",
|
||||
"collection",
|
||||
"utility"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/discordjs/discord.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/discordjs/discord.js/issues"
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.5",
|
||||
"@babel/preset-env": "^7.16.5",
|
||||
"@babel/preset-typescript": "^7.16.5",
|
||||
"@discordjs/ts-docgen": "^0.3.4",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.8.0",
|
||||
"@typescript-eslint/parser": "^5.8.0",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-config-marine": "^9.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "^27.4.5",
|
||||
"prettier": "^2.5.1",
|
||||
"standard-version": "^9.3.2",
|
||||
"tsup": "^5.11.8",
|
||||
"typedoc": "^0.22.10",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
7
packages/collection/scripts/docs.mjs
Normal file
7
packages/collection/scripts/docs.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
import { runGenerator } from '@discordjs/ts-docgen';
|
||||
|
||||
runGenerator({
|
||||
existingOutput: 'docs/typedoc-out.json',
|
||||
custom: 'docs/index.yml',
|
||||
output: 'docs/docs.json',
|
||||
});
|
||||
687
packages/collection/src/index.ts
Normal file
687
packages/collection/src/index.ts
Normal file
@@ -0,0 +1,687 @@
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface CollectionConstructor {
|
||||
new (): Collection<unknown, unknown>;
|
||||
new <K, V>(entries?: ReadonlyArray<readonly [K, V]> | null): Collection<K, V>;
|
||||
new <K, V>(iterable: Iterable<readonly [K, V]>): Collection<K, V>;
|
||||
readonly prototype: Collection<unknown, unknown>;
|
||||
readonly [Symbol.species]: CollectionConstructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Separate interface for the constructor so that emitted js does not have a constructor that overwrites itself
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface Collection<K, V> extends Map<K, V> {
|
||||
constructor: CollectionConstructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Map with additional utility methods. This is used throughout discord.js rather than Arrays for anything that has
|
||||
* an ID, for significantly improved performance and ease-of-use.
|
||||
*/
|
||||
export class Collection<K, V> extends Map<K, V> {
|
||||
public static readonly default: typeof Collection = Collection;
|
||||
|
||||
/**
|
||||
* Obtains the value of the given key if it exists, otherwise sets and returns the value provided by the default value generator.
|
||||
*
|
||||
* @param key The key to get if it exists, or set otherwise
|
||||
* @param defaultValueGenerator A function that generates the default value
|
||||
*
|
||||
* @example
|
||||
* collection.ensure(guildId, () => defaultGuildConfig);
|
||||
*/
|
||||
public ensure(key: K, defaultValueGenerator: (key: K, collection: this) => V): V {
|
||||
if (this.has(key)) return this.get(key)!;
|
||||
const defaultValue = defaultValueGenerator(key, this);
|
||||
this.set(key, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all of the elements exist in the collection.
|
||||
*
|
||||
* @param keys - The keys of the elements to check for
|
||||
*
|
||||
* @returns `true` if all of the elements exist, `false` if at least one does not exist.
|
||||
*/
|
||||
public hasAll(...keys: K[]) {
|
||||
return keys.every((k) => super.has(k));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the elements exist in the collection.
|
||||
*
|
||||
* @param keys - The keys of the elements to check for
|
||||
*
|
||||
* @returns `true` if any of the elements exist, `false` if none exist.
|
||||
*/
|
||||
public hasAny(...keys: K[]) {
|
||||
return keys.some((k) => super.has(k));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the first value(s) in this collection.
|
||||
*
|
||||
* @param amount Amount of values to obtain from the beginning
|
||||
*
|
||||
* @returns A single value if no amount is provided or an array of values, starting from the end if amount is negative
|
||||
*/
|
||||
public first(): V | undefined;
|
||||
public first(amount: number): V[];
|
||||
public first(amount?: number): V | V[] | undefined {
|
||||
if (typeof amount === 'undefined') return this.values().next().value;
|
||||
if (amount < 0) return this.last(amount * -1);
|
||||
amount = Math.min(this.size, amount);
|
||||
const iter = this.values();
|
||||
return Array.from({ length: amount }, (): V => iter.next().value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the first key(s) in this collection.
|
||||
*
|
||||
* @param amount Amount of keys to obtain from the beginning
|
||||
*
|
||||
* @returns A single key if no amount is provided or an array of keys, starting from the end if
|
||||
* amount is negative
|
||||
*/
|
||||
public firstKey(): K | undefined;
|
||||
public firstKey(amount: number): K[];
|
||||
public firstKey(amount?: number): K | K[] | undefined {
|
||||
if (typeof amount === 'undefined') return this.keys().next().value;
|
||||
if (amount < 0) return this.lastKey(amount * -1);
|
||||
amount = Math.min(this.size, amount);
|
||||
const iter = this.keys();
|
||||
return Array.from({ length: amount }, (): K => iter.next().value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the last value(s) in this collection.
|
||||
*
|
||||
* @param amount Amount of values to obtain from the end
|
||||
*
|
||||
* @returns A single value if no amount is provided or an array of values, starting from the start if
|
||||
* amount is negative
|
||||
*/
|
||||
public last(): V | undefined;
|
||||
public last(amount: number): V[];
|
||||
public last(amount?: number): V | V[] | undefined {
|
||||
const arr = [...this.values()];
|
||||
if (typeof amount === 'undefined') return arr[arr.length - 1];
|
||||
if (amount < 0) return this.first(amount * -1);
|
||||
if (!amount) return [];
|
||||
return arr.slice(-amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the last key(s) in this collection.
|
||||
*
|
||||
* @param amount Amount of keys to obtain from the end
|
||||
*
|
||||
* @returns A single key if no amount is provided or an array of keys, starting from the start if
|
||||
* amount is negative
|
||||
*/
|
||||
public lastKey(): K | undefined;
|
||||
public lastKey(amount: number): K[];
|
||||
public lastKey(amount?: number): K | K[] | undefined {
|
||||
const arr = [...this.keys()];
|
||||
if (typeof amount === 'undefined') return arr[arr.length - 1];
|
||||
if (amount < 0) return this.firstKey(amount * -1);
|
||||
if (!amount) return [];
|
||||
return arr.slice(-amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to [Array.at()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at).
|
||||
* Returns the item at a given index, allowing for positive and negative integers.
|
||||
* Negative integers count back from the last item in the collection.
|
||||
*
|
||||
* @param index The index of the element to obtain
|
||||
*/
|
||||
public at(index: number) {
|
||||
index = Math.floor(index);
|
||||
const arr = [...this.values()];
|
||||
return arr.at(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to [Array.at()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at).
|
||||
* Returns the key at a given index, allowing for positive and negative integers.
|
||||
* Negative integers count back from the last item in the collection.
|
||||
*
|
||||
* @param index The index of the key to obtain
|
||||
*/
|
||||
public keyAt(index: number) {
|
||||
index = Math.floor(index);
|
||||
const arr = [...this.keys()];
|
||||
return arr.at(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains unique random value(s) from this collection.
|
||||
*
|
||||
* @param amount Amount of values to obtain randomly
|
||||
*
|
||||
* @returns A single value if no amount is provided or an array of values
|
||||
*/
|
||||
public random(): V | undefined;
|
||||
public random(amount: number): V[];
|
||||
public random(amount?: number): V | V[] | undefined {
|
||||
const arr = [...this.values()];
|
||||
if (typeof amount === 'undefined') return arr[Math.floor(Math.random() * arr.length)];
|
||||
if (!arr.length || !amount) return [];
|
||||
return Array.from(
|
||||
{ length: Math.min(amount, arr.length) },
|
||||
(): V => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains unique random key(s) from this collection.
|
||||
*
|
||||
* @param amount Amount of keys to obtain randomly
|
||||
*
|
||||
* @returns A single key if no amount is provided or an array
|
||||
*/
|
||||
public randomKey(): K | undefined;
|
||||
public randomKey(amount: number): K[];
|
||||
public randomKey(amount?: number): K | K[] | undefined {
|
||||
const arr = [...this.keys()];
|
||||
if (typeof amount === 'undefined') return arr[Math.floor(Math.random() * arr.length)];
|
||||
if (!arr.length || !amount) return [];
|
||||
return Array.from(
|
||||
{ length: Math.min(amount, arr.length) },
|
||||
(): K => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to [Array.reverse()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse)
|
||||
* but returns a Collection instead of an Array.
|
||||
*/
|
||||
public reverse() {
|
||||
const entries = [...this.entries()].reverse();
|
||||
this.clear();
|
||||
for (const [key, value] of entries) this.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a single item where the given function returns a truthy value. This behaves like
|
||||
* [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find).
|
||||
* <warn>All collections used in Discord.js are mapped using their `id` property, and if you want to find by id you
|
||||
* should use the `get` method. See
|
||||
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) for details.</warn>
|
||||
*
|
||||
* @param fn The function to test with (should return boolean)
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection.find(user => user.username === 'Bob');
|
||||
*/
|
||||
public find<V2 extends V>(fn: (value: V, key: K, collection: this) => value is V2): V2 | undefined;
|
||||
public find(fn: (value: V, key: K, collection: this) => boolean): V | undefined;
|
||||
public find<This, V2 extends V>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => value is V2,
|
||||
thisArg: This,
|
||||
): V2 | undefined;
|
||||
public find<This>(fn: (this: This, value: V, key: K, collection: this) => boolean, thisArg: This): V | undefined;
|
||||
public find(fn: (value: V, key: K, collection: this) => boolean, thisArg?: unknown): V | undefined {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
for (const [key, val] of this) {
|
||||
if (fn(val, key, this)) return val;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the key of a single item where the given function returns a truthy value. This behaves like
|
||||
* [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex),
|
||||
* but returns the key rather than the positional index.
|
||||
*
|
||||
* @param fn The function to test with (should return boolean)
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection.findKey(user => user.username === 'Bob');
|
||||
*/
|
||||
public findKey<K2 extends K>(fn: (value: V, key: K, collection: this) => key is K2): K2 | undefined;
|
||||
public findKey(fn: (value: V, key: K, collection: this) => boolean): K | undefined;
|
||||
public findKey<This, K2 extends K>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => key is K2,
|
||||
thisArg: This,
|
||||
): K2 | undefined;
|
||||
public findKey<This>(fn: (this: This, value: V, key: K, collection: this) => boolean, thisArg: This): K | undefined;
|
||||
public findKey(fn: (value: V, key: K, collection: this) => boolean, thisArg?: unknown): K | undefined {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
for (const [key, val] of this) {
|
||||
if (fn(val, key, this)) return key;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes items that satisfy the provided filter function.
|
||||
*
|
||||
* @param fn Function used to test (should return a boolean)
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @returns The number of removed entries
|
||||
*/
|
||||
public sweep(fn: (value: V, key: K, collection: this) => boolean): number;
|
||||
public sweep<T>(fn: (this: T, value: V, key: K, collection: this) => boolean, thisArg: T): number;
|
||||
public sweep(fn: (value: V, key: K, collection: this) => boolean, thisArg?: unknown): number {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
const previousSize = this.size;
|
||||
for (const [key, val] of this) {
|
||||
if (fn(val, key, this)) this.delete(key);
|
||||
}
|
||||
return previousSize - this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to
|
||||
* [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter),
|
||||
* but returns a Collection instead of an Array.
|
||||
*
|
||||
* @param fn The function to test with (should return boolean)
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection.filter(user => user.username === 'Bob');
|
||||
*/
|
||||
public filter<K2 extends K>(fn: (value: V, key: K, collection: this) => key is K2): Collection<K2, V>;
|
||||
public filter<V2 extends V>(fn: (value: V, key: K, collection: this) => value is V2): Collection<K, V2>;
|
||||
public filter(fn: (value: V, key: K, collection: this) => boolean): Collection<K, V>;
|
||||
public filter<This, K2 extends K>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => key is K2,
|
||||
thisArg: This,
|
||||
): Collection<K2, V>;
|
||||
public filter<This, V2 extends V>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => value is V2,
|
||||
thisArg: This,
|
||||
): Collection<K, V2>;
|
||||
public filter<This>(fn: (this: This, value: V, key: K, collection: this) => boolean, thisArg: This): Collection<K, V>;
|
||||
public filter(fn: (value: V, key: K, collection: this) => boolean, thisArg?: unknown): Collection<K, V> {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
const results = new this.constructor[Symbol.species]<K, V>();
|
||||
for (const [key, val] of this) {
|
||||
if (fn(val, key, this)) results.set(key, val);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Partitions the collection into two collections where the first collection
|
||||
* contains the items that passed and the second contains the items that failed.
|
||||
*
|
||||
* @param fn Function used to test (should return a boolean)
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* const [big, small] = collection.partition(guild => guild.memberCount > 250);
|
||||
*/
|
||||
public partition<K2 extends K>(
|
||||
fn: (value: V, key: K, collection: this) => key is K2,
|
||||
): [Collection<K2, V>, Collection<Exclude<K, K2>, V>];
|
||||
public partition<V2 extends V>(
|
||||
fn: (value: V, key: K, collection: this) => value is V2,
|
||||
): [Collection<K, V2>, Collection<K, Exclude<V, V2>>];
|
||||
public partition(fn: (value: V, key: K, collection: this) => boolean): [Collection<K, V>, Collection<K, V>];
|
||||
public partition<This, K2 extends K>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => key is K2,
|
||||
thisArg: This,
|
||||
): [Collection<K2, V>, Collection<Exclude<K, K2>, V>];
|
||||
public partition<This, V2 extends V>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => value is V2,
|
||||
thisArg: This,
|
||||
): [Collection<K, V2>, Collection<K, Exclude<V, V2>>];
|
||||
public partition<This>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => boolean,
|
||||
thisArg: This,
|
||||
): [Collection<K, V>, Collection<K, V>];
|
||||
public partition(
|
||||
fn: (value: V, key: K, collection: this) => boolean,
|
||||
thisArg?: unknown,
|
||||
): [Collection<K, V>, Collection<K, V>] {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
const results: [Collection<K, V>, Collection<K, V>] = [
|
||||
new this.constructor[Symbol.species]<K, V>(),
|
||||
new this.constructor[Symbol.species]<K, V>(),
|
||||
];
|
||||
for (const [key, val] of this) {
|
||||
if (fn(val, key, this)) {
|
||||
results[0].set(key, val);
|
||||
} else {
|
||||
results[1].set(key, val);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps each item into a Collection, then joins the results into a single Collection. Identical in behavior to
|
||||
* [Array.flatMap()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap).
|
||||
*
|
||||
* @param fn Function that produces a new Collection
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection.flatMap(guild => guild.members.cache);
|
||||
*/
|
||||
public flatMap<T>(fn: (value: V, key: K, collection: this) => Collection<K, T>): Collection<K, T>;
|
||||
public flatMap<T, This>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => Collection<K, T>,
|
||||
thisArg: This,
|
||||
): Collection<K, T>;
|
||||
public flatMap<T>(fn: (value: V, key: K, collection: this) => Collection<K, T>, thisArg?: unknown): Collection<K, T> {
|
||||
const collections = this.map(fn, thisArg);
|
||||
return new this.constructor[Symbol.species]<K, T>().concat(...collections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps each item to another value into an array. Identical in behavior to
|
||||
* [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
|
||||
*
|
||||
* @param fn Function that produces an element of the new array, taking three arguments
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection.map(user => user.tag);
|
||||
*/
|
||||
public map<T>(fn: (value: V, key: K, collection: this) => T): T[];
|
||||
public map<This, T>(fn: (this: This, value: V, key: K, collection: this) => T, thisArg: This): T[];
|
||||
public map<T>(fn: (value: V, key: K, collection: this) => T, thisArg?: unknown): T[] {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
const iter = this.entries();
|
||||
return Array.from({ length: this.size }, (): T => {
|
||||
const [key, value] = iter.next().value;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
return fn(value, key, this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps each item to another value into a collection. Identical in behavior to
|
||||
* [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
|
||||
*
|
||||
* @param fn Function that produces an element of the new collection, taking three arguments
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection.mapValues(user => user.tag);
|
||||
*/
|
||||
public mapValues<T>(fn: (value: V, key: K, collection: this) => T): Collection<K, T>;
|
||||
public mapValues<This, T>(fn: (this: This, value: V, key: K, collection: this) => T, thisArg: This): Collection<K, T>;
|
||||
public mapValues<T>(fn: (value: V, key: K, collection: this) => T, thisArg?: unknown): Collection<K, T> {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
const coll = new this.constructor[Symbol.species]<K, T>();
|
||||
for (const [key, val] of this) coll.set(key, fn(val, key, this));
|
||||
return coll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there exists an item that passes a test. Identical in behavior to
|
||||
* [Array.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some).
|
||||
*
|
||||
* @param fn Function used to test (should return a boolean)
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection.some(user => user.discriminator === '0000');
|
||||
*/
|
||||
public some(fn: (value: V, key: K, collection: this) => boolean): boolean;
|
||||
public some<T>(fn: (this: T, value: V, key: K, collection: this) => boolean, thisArg: T): boolean;
|
||||
public some(fn: (value: V, key: K, collection: this) => boolean, thisArg?: unknown): boolean {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
for (const [key, val] of this) {
|
||||
if (fn(val, key, this)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all items passes a test. Identical in behavior to
|
||||
* [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every).
|
||||
*
|
||||
* @param fn Function used to test (should return a boolean)
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection.every(user => !user.bot);
|
||||
*/
|
||||
public every<K2 extends K>(fn: (value: V, key: K, collection: this) => key is K2): this is Collection<K2, V>;
|
||||
public every<V2 extends V>(fn: (value: V, key: K, collection: this) => value is V2): this is Collection<K, V2>;
|
||||
public every(fn: (value: V, key: K, collection: this) => boolean): boolean;
|
||||
public every<This, K2 extends K>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => key is K2,
|
||||
thisArg: This,
|
||||
): this is Collection<K2, V>;
|
||||
public every<This, V2 extends V>(
|
||||
fn: (this: This, value: V, key: K, collection: this) => value is V2,
|
||||
thisArg: This,
|
||||
): this is Collection<K, V2>;
|
||||
public every<This>(fn: (this: This, value: V, key: K, collection: this) => boolean, thisArg: This): boolean;
|
||||
public every(fn: (value: V, key: K, collection: this) => boolean, thisArg?: unknown): boolean {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
for (const [key, val] of this) {
|
||||
if (!fn(val, key, this)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a function to produce a single value. Identical in behavior to
|
||||
* [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce).
|
||||
*
|
||||
* @param fn Function used to reduce, taking four arguments; `accumulator`, `currentValue`, `currentKey`,
|
||||
* and `collection`
|
||||
* @param initialValue Starting value for the accumulator
|
||||
*
|
||||
* @example
|
||||
* collection.reduce((acc, guild) => acc + guild.memberCount, 0);
|
||||
*/
|
||||
public reduce<T>(fn: (accumulator: T, value: V, key: K, collection: this) => T, initialValue?: T): T {
|
||||
let accumulator!: T;
|
||||
|
||||
if (typeof initialValue !== 'undefined') {
|
||||
accumulator = initialValue;
|
||||
for (const [key, val] of this) accumulator = fn(accumulator, val, key, this);
|
||||
return accumulator;
|
||||
}
|
||||
let first = true;
|
||||
for (const [key, val] of this) {
|
||||
if (first) {
|
||||
accumulator = val as unknown as T;
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
accumulator = fn(accumulator, val, key, this);
|
||||
}
|
||||
|
||||
// No items iterated.
|
||||
if (first) {
|
||||
throw new TypeError('Reduce of empty collection with no initial value');
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to
|
||||
* [Map.forEach()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach),
|
||||
* but returns the collection instead of undefined.
|
||||
*
|
||||
* @param fn Function to execute for each element
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection
|
||||
* .each(user => console.log(user.username))
|
||||
* .filter(user => user.bot)
|
||||
* .each(user => console.log(user.username));
|
||||
*/
|
||||
public each(fn: (value: V, key: K, collection: this) => void): this;
|
||||
public each<T>(fn: (this: T, value: V, key: K, collection: this) => void, thisArg: T): this;
|
||||
public each(fn: (value: V, key: K, collection: this) => void, thisArg?: unknown): this {
|
||||
this.forEach(fn as (value: V, key: K, map: Map<K, V>) => void, thisArg);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a function on the collection and returns the collection.
|
||||
*
|
||||
* @param fn Function to execute
|
||||
* @param thisArg Value to use as `this` when executing function
|
||||
*
|
||||
* @example
|
||||
* collection
|
||||
* .tap(coll => console.log(coll.size))
|
||||
* .filter(user => user.bot)
|
||||
* .tap(coll => console.log(coll.size))
|
||||
*/
|
||||
public tap(fn: (collection: this) => void): this;
|
||||
public tap<T>(fn: (this: T, collection: this) => void, thisArg: T): this;
|
||||
public tap(fn: (collection: this) => void, thisArg?: unknown): this {
|
||||
if (typeof thisArg !== 'undefined') fn = fn.bind(thisArg);
|
||||
fn(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an identical shallow copy of this collection.
|
||||
*
|
||||
* @example
|
||||
* const newColl = someColl.clone();
|
||||
*/
|
||||
public clone() {
|
||||
return new this.constructor[Symbol.species](this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines this collection with others into a new collection. None of the source collections are modified.
|
||||
*
|
||||
* @param collections Collections to merge
|
||||
*
|
||||
* @example
|
||||
* const newColl = someColl.concat(someOtherColl, anotherColl, ohBoyAColl);
|
||||
*/
|
||||
public concat(...collections: Collection<K, V>[]) {
|
||||
const newColl = this.clone();
|
||||
for (const coll of collections) {
|
||||
for (const [key, val] of coll) newColl.set(key, val);
|
||||
}
|
||||
return newColl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this collection shares identical items with another.
|
||||
* This is different to checking for equality using equal-signs, because
|
||||
* the collections may be different objects, but contain the same data.
|
||||
*
|
||||
* @param collection Collection to compare with
|
||||
*
|
||||
* @returns Whether the collections have identical contents
|
||||
*/
|
||||
public equals(collection: Collection<K, V>) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!collection) return false; // runtime check
|
||||
if (this === collection) return true;
|
||||
if (this.size !== collection.size) return false;
|
||||
for (const [key, value] of this) {
|
||||
if (!collection.has(key) || value !== collection.get(key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The sort method sorts the items of a collection in place and returns it.
|
||||
* The sort is not necessarily stable in Node 10 or older.
|
||||
* The default sort order is according to string Unicode code points.
|
||||
*
|
||||
* @param compareFunction Specifies a function that defines the sort order.
|
||||
* If omitted, the collection is sorted according to each character's Unicode code point value, according to the string conversion of each element.
|
||||
*
|
||||
* @example
|
||||
* collection.sort((userA, userB) => userA.createdTimestamp - userB.createdTimestamp);
|
||||
*/
|
||||
public sort(compareFunction: Comparator<K, V> = Collection.defaultSort) {
|
||||
const entries = [...this.entries()];
|
||||
entries.sort((a, b): number => compareFunction(a[1], b[1], a[0], b[0]));
|
||||
|
||||
// Perform clean-up
|
||||
super.clear();
|
||||
|
||||
// Set the new entries
|
||||
for (const [k, v] of entries) {
|
||||
super.set(k, v);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The intersect method returns a new structure containing items where the keys are present in both original structures.
|
||||
*
|
||||
* @param other The other Collection to filter against
|
||||
*/
|
||||
public intersect(other: Collection<K, V>) {
|
||||
const coll = new this.constructor[Symbol.species]<K, V>();
|
||||
for (const [k, v] of other) {
|
||||
if (this.has(k)) coll.set(k, v);
|
||||
}
|
||||
return coll;
|
||||
}
|
||||
|
||||
/**
|
||||
* The difference method returns a new structure containing items where the key is present in one of the original structures but not the other.
|
||||
*
|
||||
* @param other The other Collection to filter against
|
||||
*/
|
||||
public difference(other: Collection<K, V>) {
|
||||
const coll = new this.constructor[Symbol.species]<K, V>();
|
||||
for (const [k, v] of other) {
|
||||
if (!this.has(k)) coll.set(k, v);
|
||||
}
|
||||
for (const [k, v] of this) {
|
||||
if (!other.has(k)) coll.set(k, v);
|
||||
}
|
||||
return coll;
|
||||
}
|
||||
|
||||
/**
|
||||
* The sorted method sorts the items of a collection and returns it.
|
||||
* The sort is not necessarily stable in Node 10 or older.
|
||||
* The default sort order is according to string Unicode code points.
|
||||
*
|
||||
* @param compareFunction Specifies a function that defines the sort order.
|
||||
* If omitted, the collection is sorted according to each character's Unicode code point value,
|
||||
* according to the string conversion of each element.
|
||||
*
|
||||
* @example
|
||||
* collection.sorted((userA, userB) => userA.createdTimestamp - userB.createdTimestamp);
|
||||
*/
|
||||
public sorted(compareFunction: Comparator<K, V> = Collection.defaultSort) {
|
||||
return new this.constructor[Symbol.species](this).sort((av, bv, ak, bk) => compareFunction(av, bv, ak, bk));
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
// toJSON is called recursively by JSON.stringify.
|
||||
return [...this.values()];
|
||||
}
|
||||
|
||||
private static defaultSort<V>(firstValue: V, secondValue: V): number {
|
||||
return Number(firstValue > secondValue) || Number(firstValue === secondValue) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type Comparator<K, V> = (firstValue: V, secondValue: V, firstKey: K, secondKey: K) => number;
|
||||
|
||||
export default Collection;
|
||||
20
packages/collection/tsconfig.eslint.json
Normal file
20
packages/collection/tsconfig.eslint.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.mjs",
|
||||
"**/*.jsx",
|
||||
"**/*.test.ts",
|
||||
"**/*.test.js",
|
||||
"**/*.test.mjs",
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.mjs"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
4
packages/collection/tsconfig.json
Normal file
4
packages/collection/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
14
packages/collection/tsup.config.ts
Normal file
14
packages/collection/tsup.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Options } from 'tsup';
|
||||
|
||||
export const tsup: Options = {
|
||||
clean: true,
|
||||
dts: true,
|
||||
entryPoints: ['src/index.ts'],
|
||||
format: ['esm', 'cjs'],
|
||||
minify: true,
|
||||
// if false: causes Collection.constructor to be a minified value like: 'o'
|
||||
keepNames: true,
|
||||
skipNodeModulesBundle: true,
|
||||
sourcemap: true,
|
||||
target: 'es2021',
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
||||
"plugins": ["import"],
|
||||
"parserOptions": {
|
||||
25
packages/discord.js/.gitignore
vendored
Normal file
25
packages/discord.js/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Packages
|
||||
node_modules/
|
||||
|
||||
# Log files
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Env
|
||||
.env
|
||||
test/auth.json
|
||||
test/auth.js
|
||||
docs/deploy/deploy_key
|
||||
docs/deploy/deploy_key.pub
|
||||
deploy/deploy_key
|
||||
deploy/deploy_key.pub
|
||||
|
||||
# Dist
|
||||
dist/
|
||||
docs/docs.json
|
||||
7
packages/discord.js/.prettierrc.json
Normal file
7
packages/discord.js/.prettierrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
191
packages/discord.js/LICENSE
Normal file
191
packages/discord.js/LICENSE
Normal file
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2015 - 2021 Noel Buechler
|
||||
Copyright 2015 - 2021 Amish Shah
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
127
packages/discord.js/README.md
Normal file
127
packages/discord.js/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
<div align="center">
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
|
||||
<a href="https://www.npmjs.com/package/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/workflows/Testing/badge.svg" alt="Tests status" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## About
|
||||
|
||||
discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to easily interact with the
|
||||
[Discord API](https://discord.com/developers/docs/intro).
|
||||
|
||||
- Object-oriented
|
||||
- Predictable abstractions
|
||||
- Performant
|
||||
- 100% coverage of the Discord API
|
||||
|
||||
## Installation
|
||||
|
||||
**Node.js 16.6.0 or newer is required.**
|
||||
|
||||
```sh-session
|
||||
npm install discord.js
|
||||
yarn add discord.js
|
||||
pnpm add discord.js
|
||||
```
|
||||
|
||||
### Optional packages
|
||||
|
||||
- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for WebSocket data compression and inflation (`npm install zlib-sync`)
|
||||
- [erlpack](https://github.com/discord/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install discord/erlpack`)
|
||||
- [bufferutil](https://www.npmjs.com/package/bufferutil) for a much faster WebSocket connection (`npm install bufferutil`)
|
||||
- [utf-8-validate](https://www.npmjs.com/package/utf-8-validate) in combination with `bufferutil` for much faster WebSocket processing (`npm install utf-8-validate`)
|
||||
- [@discordjs/voice](https://github.com/discordjs/voice) for interacting with the Discord Voice API (`npm install @discordjs/voice`)
|
||||
|
||||
## Example usage
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Register a slash command against the Discord API:
|
||||
|
||||
```js
|
||||
const { REST } = require('@discordjs/rest');
|
||||
const { Routes } = require('discord-api-types/v9');
|
||||
|
||||
const commands = [
|
||||
{
|
||||
name: 'ping',
|
||||
description: 'Replies with Pong!',
|
||||
},
|
||||
];
|
||||
|
||||
const rest = new REST({ version: '9' }).setToken('token');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log('Started refreshing application (/) commands.');
|
||||
|
||||
await rest.put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), { body: commands });
|
||||
|
||||
console.log('Successfully reloaded application (/) commands.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
Afterwards we can create a quite simple example bot:
|
||||
|
||||
```js
|
||||
const { Client, Intents } = require('discord.js');
|
||||
const client = new Client({ intents: [Intents.FLAGS.GUILDS] });
|
||||
|
||||
client.on('ready', () => {
|
||||
console.log(`Logged in as ${client.user.tag}!`);
|
||||
});
|
||||
|
||||
client.on('interactionCreate', async interaction => {
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'ping') {
|
||||
await interaction.reply('Pong!');
|
||||
}
|
||||
});
|
||||
|
||||
client.login('token');
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/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)
|
||||
- [npm](https://www.npmjs.com/package/discord.js)
|
||||
- [Related libraries](https://discord.com/developers/docs/topics/community-resources#libraries)
|
||||
|
||||
### Extensions
|
||||
|
||||
- [RPC](https://www.npmjs.com/package/discord-rpc) ([source](https://github.com/discordjs/RPC))
|
||||
|
||||
## 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).
|
||||
See [the contribution guide](https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md) if you'd like to submit a PR.
|
||||
|
||||
## Help
|
||||
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
|
||||
nudge in the right direction, please don't hesitate to join our official [discord.js Server](https://discord.gg/djs).
|
||||
5
packages/discord.js/docs/index.yml
Normal file
5
packages/discord.js/docs/index.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
- name: General
|
||||
files:
|
||||
- name: Welcome
|
||||
id: welcome
|
||||
path: ../../README.md
|
||||
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
82
packages/discord.js/package.json
Normal file
82
packages/discord.js/package.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "discord.js",
|
||||
"version": "14.0.0-dev",
|
||||
"description": "A powerful library for interacting with the Discord API",
|
||||
"scripts": {
|
||||
"test": "yarn docs:test && yarn lint:typings && yarn test:typescript",
|
||||
"test:typescript": "tsc --noEmit && tsd",
|
||||
"lint": "eslint ./src",
|
||||
"lint:fix": "eslint ./src --fix",
|
||||
"lint:typings": "tslint ./typings/index.d.ts",
|
||||
"format": "prettier --write **/*.{ts,js,json,yml,yaml}",
|
||||
"docs": "docgen --source ./src --custom ./docs/index.yml --output ./docs/docs.json",
|
||||
"docs:test": "docgen --source ./src --custom ./docs/index.yml",
|
||||
"prepublishOnly": "yarn lint && yarn test",
|
||||
"changelog": "git cliff --prepend ./CHANGELOG.md -l -c ../../cliff.toml -r ../../ --include-path './*'"
|
||||
},
|
||||
"main": "./src/index.js",
|
||||
"types": "./typings/index.d.ts",
|
||||
"directories": {
|
||||
"lib": "src",
|
||||
"test": "test"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"typings"
|
||||
],
|
||||
"contributors": [
|
||||
"Crawl <icrawltogo@gmail.com>",
|
||||
"Amish Shah <amishshah.2k@gmail.com>",
|
||||
"Vlad Frangu <kingdgrizzle@gmail.com>",
|
||||
"SpaceEEC <spaceeec@yahoo.com>",
|
||||
"Antonio Roman <kyradiscord@gmail.com>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"discord",
|
||||
"api",
|
||||
"bot",
|
||||
"client",
|
||||
"node",
|
||||
"discordapp"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/discordjs/discord.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/discordjs/discord.js/issues"
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.11.0",
|
||||
"@discordjs/collection": "^0.4.0",
|
||||
"@sapphire/async-queue": "^1.1.9",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/ws": "^8.2.2",
|
||||
"discord-api-types": "^0.26.0",
|
||||
"form-data": "^4.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"ws": "^8.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@discordjs/docgen": "^0.11.0",
|
||||
"@types/node": "^16.11.12",
|
||||
"dtslint": "^4.2.1",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"husky": "^7.0.4",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest": "^27.4.5",
|
||||
"lint-staged": "^12.1.4",
|
||||
"prettier": "^2.5.1",
|
||||
"tsd": "^0.19.0",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.6.0"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user