This commit is contained in:
Skillz
2023-03-31 18:02:28 -05:00
112 changed files with 1447 additions and 14380 deletions

11
.github/CODEOWNERS vendored
View File

@@ -1 +1,10 @@
* @Skillz4Killz @itohatweb
* @Skillz4Killz @itohatweb
/.github/workflows/ @H01001000
/.github/sync.yml @H01001000
/.yarn/ @H01001000
/.yarnrc.yml @H01001000
/codecov.yml @H01001000
/packages/*/package.json @H01001000
/package.json @H01001000
/turbo.json @H01001000

82
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
version: v1
labels:
- label: 'website'
sync: true
matcher:
files: 'website/**/*'
- label: 'pkg-all'
matcher:
files: '*'
- label: 'pkg-bot'
sync: true
matcher:
files: 'packages/bot/**/*'
- label: 'pkg-client'
sync: true
matcher:
files: 'packages/client/**/*'
- label: 'pkg-gateway'
sync: true
matcher:
files: 'packages/gateway/**/*'
- label: 'pkg-rest'
sync: true
matcher:
files: 'packages/rest/**/*'
- label: 'pkg-types'
sync: true
matcher:
files: 'packages/types/**/*'
- label: 'pkg-utils'
sync: true
matcher:
files: 'packages/utils/**/*'
- label: 't-breaking'
matcher:
title: "^[a-z]+(\\(.+\\))?!: .*"
- label: 't-build'
matcher:
title: "^build(\\(.+\\))?!?: .*"
- label: 't-ci'
matcher:
files: '.github/workflows/*'
title: '^ci: .*'
- label: 't-docs'
matcher:
title: "^docs(\\(.+\\))?!?: .*"
- label: 't-feat'
matcher:
title: "^feat(\\(.+\\))?!?: .*"
- label: 't-fix'
matcher:
title: "^fix(\\(.+\\))?!?: .*"
- label: 't-perf'
matcher:
title: "^perf(\\(.+\\))?!?: .*"
- label: 't-refactor'
matcher:
title: "^refactor(\\(.+\\))?!?: .*"
- label: 't-style'
matcher:
title: "^refactor(\\(.+\\))?!?: .*"
- label: 't-test'
matcher:
title: "^test(\\(.+\\))?!?: .*"

51
.github/workflows/benchmark.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Benchmark
on:
workflow_call:
outputs:
cpuMatch:
value: ${{ jobs.benchmark.outputs.cpuMatch }}
jobs:
benchmark:
name: Benchmark
runs-on: ubuntu-latest
outputs:
cpuMatch: ${{ steps.cpuCheck.outputs.match }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Check cpu model
id: cpuCheck
run: node ./scripts/checkCpuModel.js
- name: Get yarn cache directory path
if: ${{ steps.cpuCheck.outputs.match == 'true' }}
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ steps.cpuCheck.outputs.match == 'true' }}
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
if: ${{ steps.cpuCheck.outputs.match == 'true' }}
- name: Build
if: ${{ steps.cpuCheck.outputs.match == 'true' }}
run: yarn build
#
- name: Download db from benchmark repo
if: ${{ steps.cpuCheck.outputs.match == 'true' }}
run: wget https://github.com/discordeno/benchmarks/raw/main/db.tar.gz
- name: Decompress db
if: ${{ steps.cpuCheck.outputs.match == 'true' }}
run: tar -xzvf db.tar.gz
- name: Benchmark
if: ${{ steps.cpuCheck.outputs.match == 'true' }}
run: node --expose-gc ./packages/benchmark/dist/index.js

View File

@@ -1,147 +0,0 @@
name: Bot Test
on:
pull_request:
push:
jobs:
build-type-and-test:
name: Build Type and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Build Type and Test
run: yarn build:type --cache-dir=".turbo" --filter=./packages/bot
build-dist:
name: Build Dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build-${{ github.sha }}
- name: Type Test
run: yarn build --cache-dir=".turbo" --filter=./packages/bot
format-unit-and-integration-test:
name: Format Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-lint-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Check Formatting
run: yarn lint --cache-dir=".turbo" --filter=./packages/bot
test-type-unit-and-integration-test:
name: Test Type Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-unit-and-integration-test:test-type-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Test Type Test
run: yarn test:test-type --cache-dir=".turbo" --filter=./packages/bot
# Not using matrix because test later on cant needs a specific job
bot-unit-test:
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: bot
bot-e2e-test:
needs: bot-unit-test
if: ${{ github.ref == 'refs/heads/main' }}
uses: ./.github/workflows/e2e-test.yml
secrets: inherit
with:
package: bot
bot-other-runtime-test:
needs: bot-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: bot

View File

@@ -1,142 +0,0 @@
name: Client Test
on:
pull_request:
push:
jobs:
build-type-and-test:
name: Build Type and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Build Type and Test
run: yarn build:type --cache-dir=".turbo" --filter=./packages/client
build-dist:
name: Build Dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build-${{ github.sha }}
- name: Type Test
run: yarn build --cache-dir=".turbo" --filter=./packages/client
format-unit-and-integration-test:
name: Format Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-lint-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Check Formatting
run: yarn lint --cache-dir=".turbo" --filter=./packages/client
test-type-unit-and-integration-test:
name: Test Type Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-unit-and-integration-test:test-type-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Test Type Test
run: yarn test:test-type --cache-dir=".turbo" --filter=./packages/client
# Not using matrix because test later on cant needs a specific job
client-unit-test:
name: Client
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: client
client-other-runtime-test:
name: Client
needs: client-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: client

View File

@@ -1,142 +0,0 @@
name: Discordeno Test
on:
pull_request:
push:
jobs:
build-type-and-test:
name: Build Type and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Build Type and Test
run: yarn build:type --cache-dir=".turbo" --filter=./packages/discordeno
build-dist:
name: Build Dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build-${{ github.sha }}
- name: Type Test
run: yarn build --cache-dir=".turbo" --filter=./packages/discordeno
format-unit-and-integration-test:
name: Format Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-lint-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Check Formatting
run: yarn lint --cache-dir=".turbo" --filter=./packages/discordeno
test-type-unit-and-integration-test:
name: Test Type Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-unit-and-integration-test:test-type-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Test Type Test
run: yarn test:test-type --cache-dir=".turbo" --filter=./packages/discordeno
# Not using matrix because test later on cant needs a specific job
discordeno-unit-test:
name: Discordeno
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: discordeno
discordeno-other-runtime-test:
name: Discordeno
needs: discordeno-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: discordeno

14
.github/workflows/labeler.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Labeler
on:
pull_request_target:
types: ['edited', 'opened', 'reopened', 'synchronize']
jobs:
labeler:
name: Labeler
runs-on: ubuntu-latest
steps:
- uses: fuxingloh/multi-labeler@v2
with:
github-token: ${{secrets.GITHUB_TOKEN}}

View File

@@ -1,8 +1,11 @@
name: Gateway Test
name: Library Checks
on:
pull_request:
push:
branches:
- main
merge_group:
jobs:
build-type-and-test:
@@ -30,7 +33,7 @@ jobs:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Build Type and Test
run: yarn build:type --cache-dir=".turbo" --filter=./packages/gateway
run: yarn build:type --cache-dir=".turbo"
build-dist:
name: Build Dist
@@ -57,7 +60,7 @@ jobs:
path: .turbo
key: ${{ runner.os }}-turbo-build-${{ github.sha }}
- name: Type Test
run: yarn build --cache-dir=".turbo" --filter=./packages/gateway
run: yarn build --cache-dir=".turbo"
format-unit-and-integration-test:
name: Format Test
@@ -91,7 +94,7 @@ jobs:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Check Formatting
run: yarn lint --cache-dir=".turbo" --filter=./packages/gateway
run: yarn lint --cache-dir=".turbo"
test-type-unit-and-integration-test:
name: Test Type Test
@@ -125,9 +128,56 @@ jobs:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Test Type Test
run: yarn test:test-type --cache-dir=".turbo" --filter=./packages/gateway
run: yarn test:test-type --cache-dir=".turbo"
# Not using matrix because test later on cant needs a specific job
bot-unit-test:
name: Bot
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: bot
bot-e2e-test:
name: Bot
needs: bot-unit-test
if: ${{ github.ref == 'refs/heads/main' || github.event_name == 'merge_group' }}
uses: ./.github/workflows/e2e-test.yml
secrets: inherit
with:
package: bot
bot-other-runtime-test:
name: Bot
needs: bot-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: bot
client-unit-test:
name: Client
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: client
client-other-runtime-test:
name: Client
needs: client-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: client
discordeno-unit-test:
name: Discordeno
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: discordeno
discordeno-other-runtime-test:
name: Discordeno
needs: discordeno-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: discordeno
gateway-unit-test:
name: Gateway
needs: build-dist
@@ -146,3 +196,50 @@ jobs:
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: gateway
rest-unit-test:
name: Rest
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: rest
rest-e2e-test:
name: Rest
needs: rest-unit-test
if: ${{ github.ref == 'refs/heads/main' || github.event_name == 'merge_group' }}
uses: ./.github/workflows/e2e-test.yml
secrets: inherit
with:
package: rest
rest-other-runtime-test:
name: Rest
needs: rest-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: rest
types-unit-test:
name: Types
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: types
types-other-runtime-test:
name: Types
needs: types-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: types
utils-unit-test:
name: Utils
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: utils
utils-other-runtime-test:
name: Utils
needs: utils-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: utils

View File

@@ -1,35 +0,0 @@
name: Lint
on:
push:
pull_request:
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Run linters
uses: wearerequired/lint-action@v2
with:
auto_fix: true
eslint: true
eslint_extensions: "js,ts"
prettier: true

View File

@@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
package: ["gateway", "rest", "types", "utils", "bot", "client"]
package: ["gateway", "rest", "types", "utils", "bot"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3

View File

@@ -1,147 +0,0 @@
name: Rest Test
on:
pull_request:
push:
jobs:
build-type-and-test:
name: Build Type and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Build Type and Test
run: yarn build:type --cache-dir=".turbo" --filter=./packages/rest
build-dist:
name: Build Dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build-${{ github.sha }}
- name: Type Test
run: yarn build --cache-dir=".turbo" --filter=./packages/rest
format-unit-and-integration-test:
name: Format Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-lint-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Check Formatting
run: yarn lint --cache-dir=".turbo" --filter=./packages/rest
test-type-unit-and-integration-test:
name: Test Type Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-unit-and-integration-test:test-type-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Test Type Test
run: yarn test:test-type --cache-dir=".turbo" --filter=./packages/rest
# Not using matrix because test later on cant needs a specific job
rest-unit-test:
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: rest
rest-e2e-test:
needs: rest-unit-test
if: ${{ github.ref == 'refs/heads/main' }}
uses: ./.github/workflows/e2e-test.yml
secrets: inherit
with:
package: rest
rest-other-runtime-test:
needs: rest-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: rest

56
.github/workflows/retryBenchmark.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: Benchmark with retry
on:
pull_request:
push:
branches:
- main
jobs:
benchmark-try-1:
uses: ./.github/workflows/benchmark.yml
benchmark-try-2:
needs: benchmark-try-1
if: ${{ needs.benchmark-try-1.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml
benchmark-try-3:
needs: benchmark-try-2
if: ${{ needs.benchmark-try-2.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml
benchmark-try-4:
needs: benchmark-try-3
if: ${{ needs.benchmark-try-3.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml
benchmark-try-5:
needs: benchmark-try-4
if: ${{ needs.benchmark-try-4.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml
benchmark-try-6:
needs: benchmark-try-5
if: ${{ needs.benchmark-try-5.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml
benchmark-try-7:
needs: benchmark-try-6
if: ${{ needs.benchmark-try-6.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml
benchmark-try-8:
needs: benchmark-try-7
if: ${{ needs.benchmark-try-7.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml
benchmark-try-9:
needs: benchmark-try-8
if: ${{ needs.benchmark-try-8.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml
benchmark-try-10:
needs: benchmark-try-9
if: ${{ needs.benchmark-try-9.outputs.cpuMatch == 'false' }}
uses: ./.github/workflows/benchmark.yml

View File

@@ -1,142 +0,0 @@
name: Types Test
on:
pull_request:
push:
jobs:
build-type-and-test:
name: Build Type and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Build Type and Test
run: yarn build:type --cache-dir=".turbo" --filter=./packages/types
build-dist:
name: Build Dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build-${{ github.sha }}
- name: Type Test
run: yarn build --cache-dir=".turbo" --filter=./packages/types
format-unit-and-integration-test:
name: Format Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-lint-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Check Formatting
run: yarn lint --cache-dir=".turbo" --filter=./packages/types
test-type-unit-and-integration-test:
name: Test Type Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-unit-and-integration-test:test-type-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Test Type Test
run: yarn test:test-type --cache-dir=".turbo" --filter=./packages/types
# Not using matrix because test later on cant needs a specific job
types-unit-test:
name: Types
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: types
types-other-runtime-test:
name: Types
needs: types-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: types

View File

@@ -1,142 +0,0 @@
name: Utils Test
on:
pull_request:
push:
jobs:
build-type-and-test:
name: Build Type and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Build Type and Test
run: yarn build:type --cache-dir=".turbo" --filter=./packages/utils
build-dist:
name: Build Dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build-${{ github.sha }}
- name: Type Test
run: yarn build --cache-dir=".turbo" --filter=./packages/utils
format-unit-and-integration-test:
name: Format Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-lint-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Check Formatting
run: yarn lint --cache-dir=".turbo" --filter=./packages/utils
test-type-unit-and-integration-test:
name: Test Type Test
runs-on: ubuntu-latest
needs: build-type-and-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-unit-and-integration-test:test-type-${{ github.sha }}
- name: Build type cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-build:type-${{ github.sha }}
- name: Test Type Test
run: yarn test:test-type --cache-dir=".turbo" --filter=./packages/utils
# Not using matrix because test later on cant needs a specific job
utils-unit-test:
name: Utils
needs: build-dist
uses: ./.github/workflows/unit-test.yml
with:
package: utils
utils-other-runtime-test:
name: Utils
needs: utils-unit-test
uses: ./.github/workflows/other-runtime-unit-test.yml
with:
package: utils

1
.gitignore vendored
View File

@@ -33,6 +33,7 @@ out
build
.docusaurus
.cache-loader
/db
# misc
.DS_Store

View File

@@ -6,22 +6,23 @@ Discord API library for [Node.JS](https://nodejs.org), [Deno](https://deno.land)
[![Discord](https://img.shields.io/discord/785384884197392384?color=7289da&logo=discord&logoColor=dark)](https://discord.com/invite/5vBgXk3UcZ)
[![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK)](https://codecov.io/gh/discordeno/discordeno)
![action status](https://github.com/discordeno/discordeno/actions/workflows/lib-check.yml/badge.svg?event=push)
## Tips
- If you are already convinced about using Discordeno, go to [Getting Started](https://discordeno.github.io/discordeno/)
- If you are already convinced about using Discordeno, go to [Getting Started](https://discordeno.js.org/)
- To learn if Discordeno is right for you, read everything below.
## Packages
| Package | npm | Tests | Coverage |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [discordeno](https://www.npmjs.com/package/discordeno) | ![npm (scoped)](https://img.shields.io/npm/v/discordeno) | ![action status](https://github.com/discordeno/discordeno/actions/workflows/discordeno-test.yml/badge.svg?event=push) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=discordeno)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/types](https://www.npmjs.com/package/@discordeno/types) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/types) | ![action status](https://github.com/discordeno/discordeno/actions/workflows/types-test.yml/badge.svg?event=push) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=types)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/utils](https://www.npmjs.com/package/@discordeno/utils) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/utils) | ![action status](https://github.com/discordeno/discordeno/actions/workflows/utils-test.yml/badge.svg?event=push) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=utils)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/rest](https://www.npmjs.com/package/@discordeno/rest) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/rest) | ![action status](https://github.com/discordeno/discordeno/actions/workflows/rest-test.yml/badge.svg?event=push) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=rest)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/gateway](https://www.npmjs.com/package/@discordeno/gateway) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/gateway) | ![action status](https://github.com/discordeno/discordeno/actions/workflows/gateway-test.yml/badge.svg?event=push) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=gateway)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/bot](https://www.npmjs.com/package/@discordeno/bot) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/bot) | ![action status](https://github.com/discordeno/discordeno/actions/workflows/bot-test.yml/badge.svg?event=push) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=bot)](https://codecov.io/gh/discordeno/discordeno) |
| Package | npm | Tests |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [discordeno](https://www.npmjs.com/package/discordeno) | ![npm (scoped)](https://img.shields.io/npm/v/discordeno) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=discordeno)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/types](https://www.npmjs.com/package/@discordeno/types) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/types) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=types)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/utils](https://www.npmjs.com/package/@discordeno/utils) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/utils) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=utils)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/rest](https://www.npmjs.com/package/@discordeno/rest) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/rest) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=rest)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/gateway](https://www.npmjs.com/package/@discordeno/gateway) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/gateway) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=gateway)](https://codecov.io/gh/discordeno/discordeno) |
| [@discordeno/bot](https://www.npmjs.com/package/@discordeno/bot) | ![npm (scoped)](https://img.shields.io/npm/v/@discordeno/bot) | [![codecov](https://codecov.io/gh/discordeno/discordeno/branch/main/graph/badge.svg?token=SQI9OYJ7AK&flag=bot)](https://codecov.io/gh/discordeno/discordeno) |
## Features
@@ -29,9 +30,8 @@ Discordeno is actively maintained to guarantee **excellent performance, latest f
- **Simple, Efficient, and Lightweight**: Discordeno is lightweight, simple to use, and adaptable.
- By default: No caching.
- **Functional & Class API**: Discordeno is flexible enough to provide both methods.
- **Functional API**:
- The functional API eliminates the challenges of extending built-in classes and inheritance while ensuring overall simple but performant code.
- The class based API, client package, provides a similar api as the [Eris](https://github.com/abalabahaha/eris) library to provide the best class based experience.
- **Cross Runtime**: Supports the Node.js, Deno, and Bun runtimes.
- **Standalone components**: Discordeno offers the option to have practically any component of a bot as a separate
piece, including standalone REST, gateways, custom caches, and more.
@@ -93,11 +93,69 @@ Have your cache setup in any way you like. Redis, PGSQL or any cache layer you w
## Getting Started
Interested? [Check the website](https://discordeno.github.io/discordeno/) for more details on getting started.
Interested? [Check the website](https://discordeno.js.org/) for more details on getting started.
## Links
- [Website](https://discordeno.github.io/discordeno/)
- [Website](https://discordeno.js.org/)
- [Discord](https://discord.com/invite/5vBgXk3UcZ)
Discordeno follows [semantic versioning](https://semver.org/)
## Contributing/Developing
We use yarn as package manager and workspace manager, and turborepo as monorepo manager.
To config the workspace run
```sh
# if you don't have yarn installed
npm install -g yarn
yarn install
```
Then you can build all the files and types across all packages using
(unless specified all commands below are run at root directory)
```sh
yarn release-build
```
You can run unit tests on all packages using
```sh
yarn test:unit
```
Other useful scripts
(if you run in the package's directory, you need build dist before for test and types before for lint/fmt. Running in root directory should automatically do it for you)
```sh
# check style
yarn lint
# format code
yarn fmt
# check type
yarn test:type
# check type for tests
yarn test:test-type
# unit test showing coverage
yarn test:unit-coverage
# unit test with Deno
yarn test:deno-unit
# integration test
yarn test:integration
# e2e test
yarn test:e2e
# build doc for website
yarn build:doc
```

View File

@@ -1,9 +1,4 @@
flags:
client:
carryforward: false
client-unit:
carryforward: false
discordeno:
carryforward: false
discordeno-unit:

View File

@@ -17,7 +17,6 @@
"@discordeno/types": "./packages/types/dist/index.js",
"@discordeno/rest": "./packages/rest/dist/index.js",
"@discordeno/gateway": "./packages/gateway/dist/index.js",
"@discordeno/bot": "./packages/bot/dist/index.js",
"@discordeno/client": "./packages/client/dist/index.js"
"@discordeno/bot": "./packages/bot/dist/index.js"
}
}

View File

@@ -5,6 +5,6 @@
This repo is meant as a template which you can use to create a Discord bot very easily using the
[Discordeno library](https://github.com/discordeno/discordeno).
[Website/Guide](https://discordeno.github.io/discordeno//)
[Website/Guide](https://discordeno.js.org//)
[Discord Server](https://discord.com/invite/5vBgXk3UcZ)

View File

@@ -0,0 +1,41 @@
{
"name": "benchmark",
"version": "0.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/discordeno/discordeno.git"
},
"scripts": {
"build:type": "tsc --declaration --emitDeclarationOnly --declarationDir dist",
"release-build": "yarn build && yarn build:type",
"fmt": "eslint --fix \"src/**/*.ts*\"",
"lint": "eslint \"src/**/*.ts*\"",
"build": "swc src --delete-dir-on-start --out-dir dist && node ../../scripts/fixBenchExtension.js",
"bench": "node dist/index.js"
},
"dependencies": {
"@discordeno/bot": "latest",
"@discordeno/gateway": "19.0.0-alpha.1",
"@discordeno/rest": "19.0.0-alpha.1",
"@discordeno/types": "19.0.0-alpha.1",
"@discordeno/utils": "19.0.0-alpha.1",
"benchmark": "^2.1.4",
"microtime": "^3.1.1",
"node-fetch": "^3.3.1"
},
"devDependencies": {
"@swc/cli": "^0.1.57",
"@swc/core": "^1.3.21",
"@types/benchmark": "^2",
"@types/node": "^18.11.9",
"eslint": "^8.0.1",
"eslint-config-discordeno": "*",
"ts-node": "^10.9.1",
"tsconfig": "*",
"typescript": "^4.9.3"
}
}

View File

@@ -0,0 +1,3 @@
import Benchmark from 'benchmark'
export const suite = new Benchmark.Suite()

View File

@@ -0,0 +1,17 @@
import { camelize, snakelize } from '@discordeno/utils'
import { suite } from '../benchmarkSuite.js'
import { events } from '../utils/db.js'
const camelizedEvents: any[] = []
events.forEach((event) => {
camelizedEvents.push(camelize(event))
})
suite.add(`Camelize 1 event`, () => {
snakelize(events[1])
})
suite.add(`Snakelize 1 event`, () => {
snakelize(camelizedEvents[1])
})

View File

@@ -0,0 +1,37 @@
import { createBot, type Bot } from '@discordeno/bot'
import { events as dbEvents } from '../utils/db.js'
import { memoryBenchmark } from '../utils/memoryBenchmark.js'
/* Example Usage
deno run --v8-flags="--expose-gc" -A .\index.ts
*/
/*
import { createBot } from "https://deno.land/x/discordeno@17.1.0/mod.ts";
import { enableCachePlugin } from "https://deno.land/x/discordeno@17.1.0/plugins/mod.ts";
memoryBenchmark(() => enableCachePlugin(createBot({
token: " ",
botId: 0n,
})))
*/
const enableCachePlugin = (bot: Bot): Bot => bot
await memoryBenchmark(
'[Cache Plugin]',
() =>
enableCachePlugin(
createBot({
token: ' ',
events: {},
}),
),
(bot, event) => {
// @ts-expect-error it works
bot.events[
event.payload.t!.toLowerCase().replace(/_([a-z])/g, (g) => {
return g[1].toUpperCase()
})
]?.(event.payload.d, {})
},
dbEvents.filter((event) => event.payload.t),
)

View File

@@ -0,0 +1,19 @@
import { createRestManager } from '@discordeno/rest'
import { suite } from '../benchmarkSuite.js'
const rest = createRestManager({ token: ' ' })
suite.add(`rest.simplifyUrl`, () => {
rest.simplifyUrl('/messages/555555555555555555', 'PUT')
rest.simplifyUrl('/users/555555555555555555', 'PUT')
rest.simplifyUrl('/webhooks/555555555555555555', 'PUT')
rest.simplifyUrl('/channel/555555555555555555', 'PUT')
rest.simplifyUrl('/guild/555555555555555555', 'PUT')
rest.simplifyUrl('/channels/555555555555555555', 'PUT')
rest.simplifyUrl('/guilds/555555555555555555', 'PUT')
rest.simplifyUrl('/channels/555555555555555555/reactions/555555555555555555/wdiubaibfwuabfobaowbfoibnion', 'PUT')
rest.simplifyUrl('/channels/555555555555555555/messages/555555555555555555', 'DELETE')
rest.simplifyUrl('/channels/555555555555555555/messages/555555555555555555', 'POST')
rest.simplifyUrl('/channels/555555555555555555/messages/555555555555555555', 'GET')
rest.simplifyUrl('/channels/555555555555555555/messages/555555555555555555', 'PUT')
})

View File

@@ -0,0 +1,12 @@
import fs from 'node:fs/promises'
import { suite } from './benchmarkSuite.js'
const benchmarks = await fs.readdir('packages/benchmark/dist/benchmarks').then((files) => files.filter((file) => file.endsWith('.js')))
await Promise.all(benchmarks.map(async (file) => await import(`./benchmarks/${file}`)))
suite
.on('cycle', function (event: any) {
console.log(String(event.target))
})
.run()

View File

@@ -0,0 +1,33 @@
import type { DiscordGatewayPayload } from '@discordeno/types'
import fetch from 'node-fetch'
import fs from 'node:fs/promises'
export const events: Array<{
shardId: number
payload: DiscordGatewayPayload
}> = []
try {
const files = await fs.readdir('db/events')
for await (const file of files) {
const eventsInFile: Array<
| {
shardId: number
payload: DiscordGatewayPayload
}
| string
> = Object.values(await fs.readFile(`db/events/${file}`, 'utf8').then((text) => JSON.parse(text)))
eventsInFile.forEach((eventInFile) => {
if (typeof eventInFile === 'string') return
events.push(eventInFile)
})
}
} catch {
const event = await fetch('https://raw.githubusercontent.com/discordeno/benchmarks/main/db/events/10.json')
.then(async (res) => await res.json())
.then((eventsInFile: any) => eventsInFile['0'])
for (let i = 0; i < 10; i++) {
events.push(event)
}
}

View File

@@ -0,0 +1,157 @@
export async function memoryBenchmark<O, E>(
name: string,
objectCreator: () => O,
objectFeeder: (object: O, event: E) => void,
events: E[],
options: { times: number; log: boolean; table: boolean } = {
times: 3,
log: false,
table: false,
},
): Promise<void> {
const garbageCollect = global.gc ?? (() => {})
const stages = ['start', 'loaded', 'end', 'cached'] as const
const typesOfMemUsages = ['rss', 'heapUsed', 'heapTotal'] as const
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
async function runTest(object: O) {
// Determine memory stats now before touching anything
const results: {
start: NodeJS.MemoryUsage
loaded?: NodeJS.MemoryUsage
end?: NodeJS.MemoryUsage
cached?: NodeJS.MemoryUsage
} = {
start: process.memoryUsage(),
}
garbageCollect()
results.start = process.memoryUsage()
if (options.log) console.log(`[INFO] Loading json files.`)
if (options.log) {
console.log(`[INFO] DB files loaded into memory.`, events.length)
}
// Set the memory stats for when files are loaded in.
results.loaded = process.memoryUsage()
events.forEach((event) => {
objectFeeder(object, event)
})
if (options.log) {
console.log(`[INFO] Processed ${events.length} events.`)
}
// Set results for data once all events are processed
results.end = process.memoryUsage()
// @ts-expect-error init the object
results.cached = {}
for (const typeOfMemUsage of typesOfMemUsages) {
results.cached![typeOfMemUsage] = results.end[typeOfMemUsage] - results.loaded[typeOfMemUsage]
}
return results
}
const allResults = {
start: {
rss: [] as number[],
heapUsed: [] as number[],
heapTotal: [] as number[],
},
loaded: {
rss: [] as number[],
heapUsed: [] as number[],
heapTotal: [] as number[],
},
end: {
rss: [] as number[],
heapUsed: [] as number[],
heapTotal: [] as number[],
},
cached: {
rss: [] as number[],
heapUsed: [] as number[],
heapTotal: [] as number[],
},
}
const BYTES = 1000000
for (let index = 0; index < options.times; index++) {
if (options.log) console.log('running the', index + 1, 'time')
const currentResult = await runTest(objectCreator())
for (const typeOfMemUsage of typesOfMemUsages) {
for (const stage of stages) {
allResults[stage][typeOfMemUsage].push(currentResult[stage]![typeOfMemUsage])
}
}
}
type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends ReadonlyArray<infer ElementType> ? ElementType : never
const tableRows = ['Starting', 'Loaded', 'End', 'Cached'] as const
const tableFields = ['RSS', 'Heap Used', 'Heap Total'] as const
const preprocessedResults: {
[K in ArrayElement<typeof tableRows>]?: {
[K in ArrayElement<typeof tableFields>]?: {
value: number
min: number
max: number
}
}
} = {}
for (const [index, tableRow] of tableRows.entries()) {
for (const [index2, tableField] of tableFields.entries()) {
if (index2 === 0) preprocessedResults[tableRow] = {}
preprocessedResults[tableRow]![tableField] = {
value:
Math.round(
(allResults[stages[index]][typesOfMemUsages[index2]].reduce((acc, c) => acc + c, 0) / allResults.start.rss.length / BYTES) * 100,
) / 100,
min: Math.round((Math.min(...allResults[stages[index]][typesOfMemUsages[index2]]) / BYTES) * 100) / 100,
max: Math.round((Math.max(...allResults[stages[index]][typesOfMemUsages[index2]]) / BYTES) * 100) / 100,
}
}
}
const processedResults = preprocessedResults as {
[K in ArrayElement<typeof tableRows>]: {
[K in ArrayElement<typeof tableFields>]: {
value: number
min: number
max: number
}
}
}
const humanReadable: {
[K in ArrayElement<typeof tableRows>]?: {
[K in ArrayElement<typeof tableFields>]?: string
}
} = {}
for (const tableRow of tableRows) {
for (const [index, tableField] of tableFields.entries()) {
if (index === 0) humanReadable[tableRow] = {}
humanReadable[tableRow]![
tableField
] = `${processedResults[tableRow][tableField].value} MB (${processedResults[tableRow][tableField].min} MB … ${processedResults[tableRow][tableField].max} MB)`
}
}
if (options.table) console.table(humanReadable)
for (const resultKey of Object.keys(processedResults.Cached) as Array<keyof typeof processedResults.Cached>) {
const range = Math.max(
Math.round((processedResults.Cached[resultKey].min / processedResults.Cached[resultKey].value) * 100) / 100,
Math.round((processedResults.Cached[resultKey].max / processedResults.Cached[resultKey].value) * 100) / 100,
)
console.log(`${name} ${resultKey.toString()} x ${processedResults.Cached[resultKey].value} MB ±${isFinite(range) ? range : 0}% (3 runs sampled)`)
}
}

View File

@@ -104,8 +104,8 @@ export interface BotInteractionCallbackData {
embeds?: Embed[]
/** Allowed mentions for the message */
allowedMentions?: AllowedMentions
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** The customId you want to use for this modal response. */
customId?: string
/** The title you want to use for this modal response. */

View File

@@ -1,7 +0,0 @@
{
"all": true,
"src": "src",
"reporter": ["text", "lcov"],
"include": ["src/**/*.ts"],
"exclude": ["tests"]
}

View File

@@ -1,10 +0,0 @@
{
"require": "ts-node/register",
"loader": "ts-node/esm",
"recursive": true,
"timeout": 2000,
"watch-extensions": "ts",
"watch-files": ["src", "tests"],
"enable-source-maps": true,
"parallel": false
}

View File

@@ -1,36 +0,0 @@
# @discordeno/client
This package is intended to provide a class based implementation on top of discordeno. It is based on Eris libraries API as it is just amazing! This also means since the API is near 1:1 the migration for Eris -> Discordeno is as simple as:
```ts
npm uninstall eris && npm install @discordeno/client
```
Then replace all `from 'eris';` with `from '@discordeno/client'` and enjoy!
## Credits
https://github.com/abalabahaha/eris
## Eris License
The MIT License (MIT)
Copyright (c) 2016-2021 abalabahaha
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,48 +0,0 @@
{
"name": "@discordeno/client",
"version": "19.0.0-alpha.1",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/discordeno/discordeno.git"
},
"scripts": {
"build": "swc --delete-dir-on-start src --out-dir dist",
"build:type": "tsc --declaration --emitDeclarationOnly --declarationDir dist",
"release-build": "yarn build && yarn build:type",
"fmt": "eslint --fix \"src/**/*.ts*\"",
"lint": "eslint \"src/**/*.ts*\"",
"test:unit-coverage": "c8 mocha --no-warnings 'tests/**/*.spec.ts'",
"test:unit": "c8 --r lcov mocha --no-warnings 'tests/**/*.spec.ts' && node ../../scripts/coveragePathFixing.js client",
"test:deno-unit": "swc tests --delete-dir-on-start --out-dir denoTestsDist && node ../../scripts/fixDenoTestExtension.js && deno test -A --import-map ../../denoImportMap.json denoTestsDist",
"test:unit:watch": "mocha --no-warnings --watch --parallel 'tests/**/*.spec.ts'",
"test:type": "tsc --noEmit",
"test:test-type": "tsc --project tsconfig.test.json"
},
"dependencies": {
"@discordeno/gateway": "19.0.0-alpha.1",
"@discordeno/rest": "19.0.0-alpha.1",
"@discordeno/types": "19.0.0-alpha.1",
"@discordeno/utils": "19.0.0-alpha.1"
},
"devDependencies": {
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.40",
"@types/chai": "^4.3.4",
"@types/mocha": "^10.0.1",
"@types/node": "^18.15.3",
"@types/sinon": "^10.0.13",
"c8": "^7.13.0",
"chai": "^4.3.7",
"eslint": "^8.36.0",
"eslint-config-discordeno": "*",
"mocha": "^10.2.0",
"sinon": "^15.0.2",
"ts-node": "^10.9.1",
"tsconfig": "*",
"typescript": "^4.9.5"
}
}

View File

@@ -1,77 +0,0 @@
import type { BigString } from './Client.js'
export class Base {
/** Internal storage of the id done in bigint to preserve memory */
_id: bigint
constructor(id: BigString) {
this._id = BigInt(id)
}
/** The snowflake id */
get id(): string {
return this._id.toString()
}
set id(value: BigString) {
this._id = BigInt(value)
}
get createdAt(): number {
return Number(this._id / 4194304n + 1420070400000n)
}
/**
* Calculates the timestamp in milliseconds associated with a Discord ID/snowflake.
* @deprecated Recommend using Object.createdAt or Client.snowflakeToTimestamp if you want to get a timestamp from a id. This is not desired but supported only to keep a similar api to eris.
*/
static getCreatedAt(id: string): number {
return Base.getDiscordEpoch(id) + 1420070400000
}
/**
* Gets the number of milliseconds since epoch represented by an ID/snowflake
* @deprecated Recommend using Object.createdAt or Client.snowflakeToTimestamp if you want to get a timestamp from a id. This is not desired but supported only to keep a similar api to eris.
*/
static getDiscordEpoch(id: string): number {
// @ts-expect-error some eris magic at play here
return Math.floor(id / 4194304)
}
toString(): string {
return `[${this.constructor.name} ${this.id}]`
}
toJSON(props: string[] = []): Record<string, any> {
const json = {
id: this.id,
createdAt: this.createdAt,
}
for (const prop of props) {
// @ts-expect-error some eris magic at play here
const value = this[prop]
const type = typeof value
if (value === undefined) {
continue
} else if ((type !== 'object' && type !== 'function' && type !== 'bigint') || value === null) {
// @ts-expect-error some eris magic at play here
json[prop] = value
} else if (value.toJSON !== undefined) {
// @ts-expect-error some eris magic at play here
json[prop] = value.toJSON()
} else if (value.values !== undefined) {
// @ts-expect-error some eris magic at play here
json[prop] = [...value.values()]
} else if (type === 'bigint') {
// @ts-expect-error some eris magic at play here
json[prop] = value.toString()
} else if (type === 'object') {
// @ts-expect-error some eris magic at play here
json[prop] = value
}
}
return json
}
}
export default Base

File diff suppressed because it is too large Load Diff

View File

@@ -1,140 +0,0 @@
export class Collection<K, V> extends Map<K, V> {
limit: number | undefined
set(key: K, value: V): this {
// When this collection is limitd make sure we can add first
if ((this.limit ?? this.limit === 0) && this.size >= this.limit) {
return this
}
return super.set(key, value)
}
forceSet(key: K, value: V): this {
return super.set(key, value)
}
array(): V[] {
return [...this.values()]
}
/** Retrieve the value of the first element in this collection */
first(): V | undefined {
return this.values().next().value
}
last(): V | undefined {
return [...this.values()][this.size - 1]
}
random(): V | undefined {
const array = [...this.values()]
return array[Math.floor(Math.random() * array.length)]
}
find(callback: (value: V, key: K) => boolean): V | undefined {
for (const key of this.keys()) {
const value = this.get(key)!
if (callback(value, key)) return value
}
}
filter(callback: (value: V, key: K) => boolean, returnArray?: true): V[]
filter(callback: (value: V, key: K) => boolean, returnArray: false): Collection<K, V>
filter(callback: (value: V, key: K) => boolean, returnArray = true): Collection<K, V> | V[] {
const relevant = new Collection<K, V>()
this.forEach((value, key) => {
if (callback(value, key)) relevant.set(key, value)
})
return returnArray ? relevant.array() : relevant
}
map<T>(callback: (value: V, key: K) => T): T[] {
const results = []
for (const key of this.keys()) {
const value = this.get(key)!
results.push(callback(value, key))
}
return results
}
some(callback: (value: V, key: K) => boolean): boolean {
for (const key of this.keys()) {
const value = this.get(key)!
if (callback(value, key)) return true
}
return false
}
every(callback: (value: V, key: K) => boolean): boolean {
for (const key of this.keys()) {
const value = this.get(key)!
if (!callback(value, key)) return false
}
return true
}
reduce<T>(callback: (accumulator: T, value: V, key: K) => T, initialValue?: T): T {
let accumulator: T = initialValue!
for (const key of this.keys()) {
const value = this.get(key)!
accumulator = callback(accumulator, value, key)
}
return accumulator
}
/**
* Adds a object to the collection.
* @deprecated Recommend using Collection.set(). Keeping for the sake of Eris API.
* @deprecated extra parameter. No longer used, keeping for sake of Eris API.
*/
add(obj: V & { id: K }, extra?: unknown, replace?: boolean): V {
if (this.limit === 0) return obj
const existing = this.get(obj.id)
if (existing && !replace) {
return existing
}
this.set(obj.id, obj)
return obj
}
remove(obj: { id: K }): V | undefined {
const item = this.get(obj.id)
if (!item) return
this.delete(obj.id)
return item
}
update(obj: V & { id: K }, extra?: unknown, replace?: boolean): V {
const item = this.get(obj.id)
if (!item) {
this.set(obj.id, obj)
return obj
}
// @ts-expect-error some eris magic at play here
item.update?.(obj, extra)
return item
}
toRecord(): Record<string, V> {
const record: Record<string, V> = {}
for (const [key, value] of this.entries()) {
// @ts-expect-error should work fine
const finalKey = typeof key === 'string' ? key : key.toString()
record[finalKey] = value
}
return record
}
}
export default Collection

View File

@@ -1,650 +0,0 @@
export const GATEWAY_VERSION = 10
export const REST_VERSION = 10
export const MessageFlags = {
CROSSPOSTED: 1 << 0,
IS_CROSSPOST: 1 << 1,
SUPPRESS_EMBEDS: 1 << 2,
SOURCE_MESSAGE_DELETED: 1 << 3,
URGENT: 1 << 4,
HAS_THREAD: 1 << 5,
EPHEMERAL: 1 << 6,
LOADING: 1 << 7,
}
export const ActivityTypes = {
GAME: 0,
STREAMING: 1,
LISTENING: 2,
WATCHING: 3,
CUSTOM: 4,
COMPETING: 5,
}
export const ApplicationCommandOptionTypes = {
SUB_COMMAND: 1,
SUB_COMMAND_GROUP: 2,
STRING: 3,
INTEGER: 4,
BOOLEAN: 5,
USER: 6,
CHANNEL: 7,
ROLE: 8,
MENTIONABLE: 9,
NUMBER: 10,
}
export const ApplicationCommandPermissionTypes = {
ROLE: 1,
USER: 2,
}
export const ApplicationCommandTypes = {
CHAT_INPUT: 1,
USER: 2,
MESSAGE: 3,
}
export const AuditLogActions = {
GUILD_UPDATE: 1,
CHANNEL_CREATE: 10,
CHANNEL_UPDATE: 11,
CHANNEL_DELETE: 12,
CHANNEL_OVERWRITE_CREATE: 13,
CHANNEL_OVERWRITE_UPDATE: 14,
CHANNEL_OVERWRITE_DELETE: 15,
MEMBER_KICK: 20,
MEMBER_PRUNE: 21,
MEMBER_BAN_ADD: 22,
MEMBER_BAN_REMOVE: 23,
MEMBER_UPDATE: 24,
MEMBER_ROLE_UPDATE: 25,
MEMBER_MOVE: 26,
MEMBER_DISCONNECT: 27,
BOT_ADD: 28,
ROLE_CREATE: 30,
ROLE_UPDATE: 31,
ROLE_DELETE: 32,
INVITE_CREATE: 40,
INVITE_UPDATE: 41,
INVITE_DELETE: 42,
WEBHOOK_CREATE: 50,
WEBHOOK_UPDATE: 51,
WEBHOOK_DELETE: 52,
EMOJI_CREATE: 60,
EMOJI_UPDATE: 61,
EMOJI_DELETE: 62,
MESSAGE_DELETE: 72,
MESSAGE_BULK_DELETE: 73,
MESSAGE_PIN: 74,
MESSAGE_UNPIN: 75,
INTEGRATION_CREATE: 80,
INTEGRATION_UPDATE: 81,
INTEGRATION_DELETE: 82,
STAGE_INSTANCE_CREATE: 83,
STAGE_INSTANCE_UPDATE: 84,
STAGE_INSTANCE_DELETE: 85,
STICKER_CREATE: 90,
STICKER_UPDATE: 91,
STICKER_DELETE: 92,
GUILD_SCHEDULED_EVENT_CREATE: 100,
GUILD_SCHEDULED_EVENT_UPDATE: 101,
GUILD_SCHEDULED_EVENT_DELETE: 102,
THREAD_CREATE: 110,
THREAD_UPDATE: 111,
THREAD_DELETE: 112,
APPLICATION_COMMAND_PERMISSION_UPDATE: 121,
}
export const ButtonStyles = {
PRIMARY: 1,
SECONDARY: 2,
SUCCESS: 3,
DANGER: 4,
LINK: 5,
}
export enum ChannelTypes {
GUILD_TEXT = 0,
DM = 1,
GUILD_VOICE = 2,
GROUP_DM = 3,
GUILD_CATEGORY = 4,
GUILD_NEWS = 5,
GUILD_STORE = 6,
GUILD_NEWS_THREAD = 10,
GUILD_PUBLIC_THREAD = 11,
GUILD_PRIVATE_THREAD = 12,
GUILD_STAGE_VOICE = 13,
GUILD_STAGE = 13, // [DEPRECATED]
}
export const ComponentTypes = {
ACTION_ROW: 1,
BUTTON: 2,
SELECT_MENU: 3,
}
export const ConnectionVisibilityTypes = {
NONE: 0,
EVERYONE: 1,
}
export const DefaultMessageNotificationLevels = {
ALL_MESSAGES: 0,
ONLY_MENTIONS: 1,
}
export const ExplicitContentFilterLevels = {
DISABLED: 0,
MEMBERS_WITHOUT_ROLES: 1,
ALL_MEMBERS: 2,
}
export const GatewayOPCodes = {
DISPATCH: 0,
EVENT: 0, // [DEPRECATED]
HEARTBEAT: 1,
IDENTIFY: 2,
PRESENCE_UPDATE: 3,
STATUS_UPDATE: 3, // [DEPRECATED]
VOICE_STATE_UPDATE: 4,
VOICE_SERVER_PING: 5,
RESUME: 6,
RECONNECT: 7,
REQUEST_GUILD_MEMBERS: 8,
GET_GUILD_MEMBERS: 8, // [DEPRECATED]
INVALID_SESSION: 9,
HELLO: 10,
HEARTBEAT_ACK: 11,
SYNC_GUILD: 12,
SYNC_CALL: 13,
}
export const GuildFeatures = [
'ANIMATED_ICON',
'BANNER',
'COMMERCE',
'COMMUNITY',
'CREATOR_MONETIZABLE_PROVISIONAL',
'CREATOR_STORE_PAGE',
'DISCOVERABLE',
'FEATURABLE',
'INVITE_SPLASH',
'MEMBER_VERIFICATION_GATE_ENABLED',
'MORE_STICKERS',
'NEWS',
'PARTNERED',
'PREVIEW_ENABLED',
'PRIVATE_THREADS',
'ROLE_ICONS',
'ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE',
'ROLE_SUBSCRIPTIONS_ENABLED',
'SEVEN_DAY_THREAD_ARCHIVE',
'THREE_DAY_THREAD_ARCHIVE',
'TICKETED_EVENTS_ENABLED',
'VANITY_URL',
'VERIFIED',
'VIP_REGIONS',
'WELCOME_SCREEN_ENABLED',
]
export const GuildIntegrationExpireBehavior = {
REMOVE_ROLE: 0,
KICK: 1,
}
export const GuildIntegrationTypes = ['twitch', 'youtube', 'discord']
export const GuildNSFWLevels = {
DEFAULT: 0,
EXPLICIT: 1,
SAFE: 2,
AGE_RESTRICTED: 3,
}
export const ImageFormats = ['jpg', 'jpeg', 'png', 'webp', 'gif']
export const ImageSizeBoundaries = {
MINIMUM: 16,
MAXIMUM: 4096,
}
export const Intents = {
guilds: 1 << 0,
guildMembers: 1 << 1,
guildBans: 1 << 2,
guildEmojisAndStickers: 1 << 3,
guildEmojis: 1 << 3, // [DEPRECATED]
guildIntegrations: 1 << 4,
guildWebhooks: 1 << 5,
guildInvites: 1 << 6,
guildVoiceStates: 1 << 7,
guildPresences: 1 << 8,
guildMessages: 1 << 9,
guildMessageReactions: 1 << 10,
guildMessageTyping: 1 << 11,
directMessages: 1 << 12,
directMessageReactions: 1 << 13,
directMessageTyping: 1 << 14,
guildScheduledEvents: 1 << 16,
// Override these below
allNonPrivileged: 0,
allPrivileged: 0,
all: 0,
}
Intents.allNonPrivileged =
Intents.guilds |
Intents.guildBans |
Intents.guildEmojisAndStickers |
Intents.guildIntegrations |
Intents.guildWebhooks |
Intents.guildInvites |
Intents.guildVoiceStates |
Intents.guildMessages |
Intents.guildMessageReactions |
Intents.guildMessageTyping |
Intents.directMessages |
Intents.directMessageReactions |
Intents.directMessageTyping |
Intents.guildScheduledEvents
Intents.allPrivileged = Intents.guildMembers | Intents.guildPresences
Intents.all = Intents.allNonPrivileged | Intents.allPrivileged
export const InteractionResponseTypes = {
PONG: 1,
CHANNEL_MESSAGE_WITH_SOURCE: 4,
DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE: 5,
DEFERRED_UPDATE_MESSAGE: 6,
UPDATE_MESSAGE: 7,
APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: 8,
}
export const InteractionTypes = {
PING: 1,
APPLICATION_COMMAND: 2,
MESSAGE_COMPONENT: 3,
APPLICATION_COMMAND_AUTOCOMPLETE: 4,
}
export const MFALevels = {
NONE: 0,
ELEVATED: 1,
}
export const MessageActivityFlags = {
INSTANCE: 1 << 0,
JOIN: 1 << 1,
SPECTATE: 1 << 2,
JOIN_REQUEST: 1 << 3,
SYNC: 1 << 4,
PLAY: 1 << 5,
PARTY_PRIVACY_FRIENDS: 1 << 6,
PARTY_PRIVACY_VOICE_CHANNEL: 1 << 7,
EMBEDDED: 1 << 8,
}
export const MessageActivityTypes = {
JOIN: 1,
SPECTATE: 2,
LISTEN: 3,
JOIN_REQUEST: 5,
}
export const MessageTypes = {
DEFAULT: 0,
RECIPIENT_ADD: 1,
RECIPIENT_REMOVE: 2,
CALL: 3,
CHANNEL_NAME_CHANGE: 4,
CHANNEL_ICON_CHANGE: 5,
CHANNEL_PINNED_MESSAGE: 6,
GUILD_MEMBER_JOIN: 7,
USER_PREMIUM_GUILD_SUBSCRIPTION: 8,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1: 9,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2: 10,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3: 11,
CHANNEL_FOLLOW_ADD: 12,
GUILD_DISCOVERY_DISQUALIFIED: 14,
GUILD_DISCOVERY_REQUALIFIED: 15,
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING: 16,
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING: 17,
THREAD_CREATED: 18,
REPLY: 19,
CHAT_INPUT_COMMAND: 20,
THREAD_STARTER_MESSAGE: 21,
GUILD_INVITE_REMINDER: 22,
CONTEXT_MENU_COMMAND: 23,
AUTO_MODERATION_ACTION: 24,
ROLE_SUBSCRIPTION_PURCHASE: 25,
INTERACTION_PREMIUM_UPSELL: 26,
STAGE_START: 27,
STAGE_END: 28,
STAGE_SPEAKER: 29,
STAGE_TOPIC: 31,
GUILD_APPLICATION_PREMIUM_SUBSCRIPTION: 32,
}
export const PermissionOverwriteTypes = {
ROLE: 0,
USER: 1,
}
export const Permissions = {
createInstantInvite: 1n << 0n,
kickMembers: 1n << 1n,
banMembers: 1n << 2n,
administrator: 1n << 3n,
manageChannels: 1n << 4n,
manageGuild: 1n << 5n,
addReactions: 1n << 6n,
viewAuditLog: 1n << 7n,
viewAuditLogs: 1n << 7n, // [DEPRECATED]
voicePrioritySpeaker: 1n << 8n,
voiceStream: 1n << 9n,
stream: 1n << 9n, // [DEPRECATED]
viewChannel: 1n << 10n,
readMessages: 1n << 10n, // [DEPRECATED]
sendMessages: 1n << 11n,
sendTTSMessages: 1n << 12n,
manageMessages: 1n << 13n,
embedLinks: 1n << 14n,
attachFiles: 1n << 15n,
readMessageHistory: 1n << 16n,
mentionEveryone: 1n << 17n,
useExternalEmojis: 1n << 18n,
externalEmojis: 1n << 18n, // [DEPRECATED]
viewGuildInsights: 1n << 19n,
voiceConnect: 1n << 20n,
voiceSpeak: 1n << 21n,
voiceMuteMembers: 1n << 22n,
voiceDeafenMembers: 1n << 23n,
voiceMoveMembers: 1n << 24n,
voiceUseVAD: 1n << 25n,
changeNickname: 1n << 26n,
manageNicknames: 1n << 27n,
manageRoles: 1n << 28n,
manageWebhooks: 1n << 29n,
manageEmojisAndStickers: 1n << 30n,
manageEmojis: 1n << 30n, // [DEPRECATED]
useApplicationCommands: 1n << 31n,
useSlashCommands: 1n << 31n, // [DEPRECATED]
voiceRequestToSpeak: 1n << 32n,
manageEvents: 1n << 33n,
manageThreads: 1n << 34n,
createPublicThreads: 1n << 35n,
createPrivateThreads: 1n << 36n,
useExternalStickers: 1n << 37n,
sendMessagesInThreads: 1n << 38n,
startEmbeddedActivities: 1n << 39n,
moderateMembers: 1n << 40n,
// Override these below
all: 0n,
allText: 0n,
allVoice: 0n,
allGuild: 0n,
}
Permissions.allGuild =
Permissions.kickMembers |
Permissions.banMembers |
Permissions.administrator |
Permissions.manageChannels |
Permissions.manageGuild |
Permissions.viewAuditLog |
Permissions.viewGuildInsights |
Permissions.changeNickname |
Permissions.manageNicknames |
Permissions.manageRoles |
Permissions.manageWebhooks |
Permissions.manageEmojisAndStickers |
Permissions.manageEvents |
Permissions.moderateMembers
Permissions.allText =
Permissions.createInstantInvite |
Permissions.manageChannels |
Permissions.addReactions |
Permissions.viewChannel |
Permissions.sendMessages |
Permissions.sendTTSMessages |
Permissions.manageMessages |
Permissions.embedLinks |
Permissions.attachFiles |
Permissions.readMessageHistory |
Permissions.mentionEveryone |
Permissions.useExternalEmojis |
Permissions.manageRoles |
Permissions.manageWebhooks |
Permissions.useApplicationCommands |
Permissions.manageThreads |
Permissions.createPublicThreads |
Permissions.createPrivateThreads |
Permissions.useExternalStickers |
Permissions.sendMessagesInThreads
Permissions.allVoice =
Permissions.createInstantInvite |
Permissions.manageChannels |
Permissions.voicePrioritySpeaker |
Permissions.voiceStream |
Permissions.viewChannel |
Permissions.voiceConnect |
Permissions.voiceSpeak |
Permissions.voiceMuteMembers |
Permissions.voiceDeafenMembers |
Permissions.voiceMoveMembers |
Permissions.voiceUseVAD |
Permissions.manageRoles |
Permissions.voiceRequestToSpeak |
Permissions.startEmbeddedActivities
Permissions.all = Permissions.allGuild | Permissions.allText | Permissions.allVoice
export const PremiumTiers = {
NONE: 0,
TIER_1: 1,
TIER_2: 2,
TIER_3: 3,
}
export const GuildScheduledEventStatus = {
SCHEDULED: 1,
ACTIVE: 2,
COMPLETED: 3,
CANCELED: 4,
}
export const GuildScheduledEventEntityTypes = {
STAGE_INSTANCE: 1,
VOICE: 2,
EXTERNAL: 3,
}
export const GuildScheduledEventPrivacyLevel = {
PUBLIC: 1,
GUILD_ONLY: 2,
}
export const PremiumTypes = {
NONE: 0,
NITRO_CLASSIC: 1,
NITRO: 2,
}
export const StageInstancePrivacyLevel = {
PUBLIC: 1,
GUILD_ONLY: 2,
}
export const StickerFormats = {
PNG: 1,
APNG: 2,
LOTTIE: 3,
}
export const StickerTypes = {
STANDARD: 1,
GUILD: 2,
}
export const SystemChannelFlags = {
SUPPRESS_JOIN_NOTIFICATIONS: 1 << 0,
SUPPRESS_PREMIUM_SUBSCRIPTIONS: 1 << 1,
SUPPRESS_GUILD_REMINDER_NOTIFICATIONS: 1 << 2,
SUPPRESS_JOIN_NOTIFICATION_REPLIES: 1 << 3,
}
export const SystemJoinMessages = [
'%user% joined the party.',
'%user% is here.',
'Welcome, %user%. We hope you brought pizza.',
'A wild %user% appeared.',
'%user% just landed.',
'%user% just slid into the server.',
'%user% just showed up!',
'Welcome %user%. Say hi!',
'%user% hopped into the server.',
'Everyone welcome %user%!',
"Glad you're here, %user%.",
'Good to see you, %user%.',
'Yay you made it, %user%!',
]
export const ThreadMemberFlags = {
HAS_INTERACTED: 1 << 0,
ALL_MESSAGES: 1 << 1,
ONLY_MENTIONS: 1 << 2,
NO_MESSAGES: 1 << 3,
}
export const UserFlags = {
NONE: 0,
DISCORD_STAFF: 1 << 0,
DISCORD_EMPLOYEE: 1 << 0,
PARTNER: 1 << 1,
PARTNERED_SERVER_OWNER: 1 << 1,
DISCORD_PARTNER: 1 << 1, // [DEPRECATED]
HYPESQUAD: 1 << 2,
HYPESQUAD_EVENTS: 1 << 2,
BUG_HUNTER_LEVEL_1: 1 << 3,
HOUSE_BRAVERY: 1 << 6,
HYPESQUAD_ONLINE_HOUSE_1: 1 << 6,
HOUSE_BRILLIANCE: 1 << 7,
HYPESQUAD_ONLINE_HOUSE_2: 1 << 7,
HOUSE_BALANCE: 1 << 8,
HYPESQUAD_ONLINE_HOUSE_3: 1 << 8,
PREMIUM_EARLY_SUPPORTER: 1 << 9,
EARLY_SUPPORTER: 1 << 9,
TEAM_PSEUDO_USER: 1 << 10,
TEAM_USER: 1 << 10,
SYSTEM: 1 << 12,
BUG_HUNTER_LEVEL_2: 1 << 14,
VERIFIED_BOT: 1 << 16,
VERIFIED_DEVELOPER: 1 << 17,
EARLY_VERIFIED_BOT_DEVELOPER: 1 << 17,
VERIFIED_BOT_DEVELOPER: 1 << 17,
CERTIFIED_MODERATOR: 1 << 18,
DISCORD_CERTIFIED_MODERATOR: 1 << 18,
BOT_HTTP_INTERACTIONS: 1 << 19,
}
export const VerificationLevels = {
NONE: 0,
LOW: 1,
MEDIUM: 2,
HIGH: 3,
VERY_HIGH: 4,
}
export const VideoQualityModes = {
AUTO: 1,
FULL: 2,
}
export const VoiceOPCodes = {
IDENTIFY: 0,
SELECT_PROTOCOL: 1,
READY: 2,
HEARTBEAT: 3,
SESSION_DESCRIPTION: 4,
SPEAKING: 5,
HEARTBEAT_ACK: 6,
RESUME: 7,
HELLO: 8,
RESUMED: 9,
CLIENT_DISCONNECT: 13,
DISCONNECT: 13, // [DEPRECATED]
}
export const WebhookTypes = {
INCOMING: 1,
CHANNEL_FOLLOWER: 2,
APPLICATION: 3,
}
export type IntentStrings = keyof typeof Intents
export type PermissionClientStrings = keyof typeof Permissions
export const Constants = {
GATEWAY_VERSION,
REST_VERSION,
MessageFlags,
ActivityTypes,
ApplicationCommandOptionTypes,
ApplicationCommandPermissionTypes,
ApplicationCommandTypes,
AuditLogActions,
ButtonStyles,
ComponentTypes,
ConnectionVisibilityTypes,
DefaultMessageNotificationLevels,
ExplicitContentFilterLevels,
GatewayOPCodes,
GuildFeatures,
GuildIntegrationExpireBehavior,
GuildIntegrationTypes,
GuildNSFWLevels,
ImageFormats,
ImageSizeBoundaries,
Intents,
InteractionResponseTypes,
InteractionTypes,
MFALevels,
MessageActivityFlags,
MessageActivityTypes,
MessageTypes,
PermissionOverwriteTypes,
Permissions,
PremiumTiers,
GuildScheduledEventStatus,
GuildScheduledEventEntityTypes,
GuildScheduledEventPrivacyLevel,
PremiumTypes,
StageInstancePrivacyLevel,
StickerFormats,
StickerTypes,
SystemChannelFlags,
SystemJoinMessages,
ThreadMemberFlags,
UserFlags,
VerificationLevels,
VideoQualityModes,
VoiceOPCodes,
WebhookTypes,
}
// eslint-disable-next-line @typescript-eslint/no-redeclare
export type Constants = typeof Constants

View File

@@ -1,136 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { BigString } from './Client.js'
export const ORIGINAL_INTERACTION_RESPONSE = (appID: BigString, interactToken: string) => `/webhooks/${appID}/${interactToken}`
export const COMMAND = (applicationID: BigString, commandID: BigString) => `/applications/${applicationID}/commands/${commandID}`
export const COMMANDS = (applicationID: BigString) => `/applications/${applicationID}/commands`
export const COMMAND_PERMISSIONS = (applicationID: BigString, guildID: BigString, commandID: BigString) =>
`/applications/${applicationID}/guilds/${guildID}/commands/${commandID}/permissions`
export const CHANNEL = (chanID: BigString) => `/channels/${chanID}`
export const CHANNEL_BULK_DELETE = (chanID: BigString) => `/channels/${chanID}/messages/bulk-delete`
export const CHANNEL_CALL_RING = (chanID: BigString) => `/channels/${chanID}/call/ring`
export const CHANNEL_CROSSPOST = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}/crosspost`
export const CHANNEL_FOLLOW = (chanID: BigString) => `/channels/${chanID}/followers`
export const CHANNEL_INVITES = (chanID: BigString) => `/channels/${chanID}/invites`
export const CHANNEL_MESSAGE_REACTION = (chanID: BigString, msgID: BigString, reaction: string) =>
`/channels/${chanID}/messages/${msgID}/reactions/${reaction}`
export const CHANNEL_MESSAGE_REACTION_USER = (chanID: BigString, msgID: BigString, reaction: string, userID: BigString) =>
`/channels/${chanID}/messages/${msgID}/reactions/${reaction}/${userID}`
export const CHANNEL_MESSAGE_REACTIONS = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}/reactions`
export const CHANNEL_MESSAGE = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}`
export const CHANNEL_MESSAGES = (chanID: BigString) => `/channels/${chanID}/messages`
export const CHANNEL_MESSAGES_SEARCH = (chanID: BigString) => `/channels/${chanID}/messages/search`
export const CHANNEL_PERMISSION = (chanID: BigString, overID: BigString) => `/channels/${chanID}/permissions/${overID}`
export const CHANNEL_PERMISSIONS = (chanID: BigString) => `/channels/${chanID}/permissions`
export const CHANNEL_PIN = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/pins/${msgID}`
export const CHANNEL_PINS = (chanID: BigString) => `/channels/${chanID}/pins`
export const CHANNEL_RECIPIENT = (groupID: BigString, userID: BigString) => `/channels/${groupID}/recipients/${userID}`
export const CHANNEL_TYPING = (chanID: BigString) => `/channels/${chanID}/typing`
export const CHANNEL_WEBHOOKS = (chanID: BigString) => `/channels/${chanID}/webhooks`
export const CHANNELS = '/channels'
export const CUSTOM_EMOJI_GUILD = (emojiID: BigString) => `/emojis/${emojiID}/guild`
export const DISCOVERY_CATEGORIES = '/discovery/categories'
export const DISCOVERY_VALIDATION = '/discovery/valid-term'
export const GATEWAY = '/gateway'
export const GATEWAY_BOT = '/gateway/bot'
export const GUILD = (guildID: BigString) => `/guilds/${guildID}`
export const GUILD_AUDIT_LOGS = (guildID: BigString) => `/guilds/${guildID}/audit-logs`
export const GUILD_BAN = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/bans/${memberID}`
export const GUILD_BANS = (guildID: BigString) => `/guilds/${guildID}/bans`
export const GUILD_CHANNELS = (guildID: BigString) => `/guilds/${guildID}/channels`
export const GUILD_COMMAND = (applicationID: BigString, guildID: BigString, commandID: BigString) =>
`/applications/${applicationID}/guilds/${guildID}/commands/${commandID}`
export const GUILD_COMMAND_PERMISSIONS = (applicationID: BigString, guildID: BigString) =>
`/applications/${applicationID}/guilds/${guildID}/commands/permissions`
export const GUILD_COMMANDS = (applicationID: BigString, guildID: BigString) => `/applications/${applicationID}/guilds/${guildID}/commands`
export const GUILD_DISCOVERY = (guildID: BigString) => `/guilds/${guildID}/discovery-metadata`
export const GUILD_DISCOVERY_CATEGORY = (guildID: BigString, categoryID: BigString) => `/guilds/${guildID}/discovery-categories/${categoryID}`
export const GUILD_EMOJI = (guildID: BigString, emojiID: BigString) => `/guilds/${guildID}/emojis/${emojiID}`
export const GUILD_EMOJIS = (guildID: BigString) => `/guilds/${guildID}/emojis`
export const GUILD_INTEGRATION = (guildID: BigString, inteID: BigString) => `/guilds/${guildID}/integrations/${inteID}`
export const GUILD_INTEGRATION_SYNC = (guildID: BigString, inteID: BigString) => `/guilds/${guildID}/integrations/${inteID}/sync`
export const GUILD_INTEGRATIONS = (guildID: BigString) => `/guilds/${guildID}/integrations`
export const GUILD_INVITES = (guildID: BigString) => `/guilds/${guildID}/invites`
export const GUILD_VANITY_URL = (guildID: BigString) => `/guilds/${guildID}/vanity-url`
export const GUILD_MEMBER = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/members/${memberID}`
export const GUILD_MEMBER_NICK = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/members/${memberID}/nick`
export const GUILD_MEMBER_ROLE = (guildID: BigString, memberID: BigString, roleID: BigString) =>
`/guilds/${guildID}/members/${memberID}/roles/${roleID}`
export const GUILD_MEMBERS = (guildID: BigString) => `/guilds/${guildID}/members`
export const GUILD_MEMBERS_SEARCH = (guildID: BigString) => `/guilds/${guildID}/members/search`
export const GUILD_MESSAGES_SEARCH = (guildID: BigString) => `/guilds/${guildID}/messages/search`
export const GUILD_PREVIEW = (guildID: BigString) => `/guilds/${guildID}/preview`
export const GUILD_PRUNE = (guildID: BigString) => `/guilds/${guildID}/prune`
export const GUILD_ROLE = (guildID: BigString, roleID: BigString) => `/guilds/${guildID}/roles/${roleID}`
export const GUILD_ROLES = (guildID: BigString) => `/guilds/${guildID}/roles`
export const GUILD_STICKER = (guildID: BigString, stickerID: BigString) => `/guilds/${guildID}/stickers/${stickerID}`
export const GUILD_STICKERS = (guildID: BigString) => `/guilds/${guildID}/stickers`
export const GUILD_TEMPLATE = (code: string) => `/guilds/templates/${code}`
export const GUILD_TEMPLATES = (guildID: BigString) => `/guilds/${guildID}/templates`
export const GUILD_TEMPLATE_GUILD = (guildID: BigString, code: string) => `/guilds/${guildID}/templates/${code}`
export const GUILD_VOICE_REGIONS = (guildID: BigString) => `/guilds/${guildID}/regions`
export const GUILD_WEBHOOKS = (guildID: BigString) => `/guilds/${guildID}/webhooks`
export const GUILD_WELCOME_SCREEN = (guildID: BigString) => `/guilds/${guildID}/welcome-screen`
export const GUILD_WIDGET = (guildID: BigString) => `/guilds/${guildID}/widget.json`
export const GUILD_WIDGET_SETTINGS = (guildID: BigString) => `/guilds/${guildID}/widget`
export const GUILD_VOICE_STATE = (guildID: BigString, user: BigString) => `/guilds/${guildID}/voice-states/${user}`
export const GUILDS = '/guilds'
export const INTERACTION_RESPOND = (interactID: BigString, interactToken: string) => `/interactions/${interactID}/${interactToken}/callback`
export const INVITE = (inviteID: string) => `/invites/${inviteID}`
export const OAUTH2_APPLICATION = (appID: BigString) => `/oauth2/applications/${appID}`
export const STAGE_INSTANCE = (channelID: BigString) => `/stage-instances/${channelID}`
export const STAGE_INSTANCES = '/stage-instances'
export const STICKER = (stickerID: BigString) => `/stickers/${stickerID}`
export const STICKER_PACKS = '/sticker-packs'
export const THREAD_MEMBER = (channelID: BigString, userID: BigString) => `/channels/${channelID}/thread-members/${userID}`
export const THREAD_MEMBERS = (channelID: BigString) => `/channels/${channelID}/thread-members`
export const THREAD_WITH_MESSAGE = (channelID: BigString, msgID: BigString) => `/channels/${channelID}/messages/${msgID}/threads`
export const THREAD_WITHOUT_MESSAGE = (channelID: BigString) => `/channels/${channelID}/threads`
export const THREADS_ACTIVE = (channelID: BigString) => `/channels/${channelID}/threads/active`
export const THREADS_ARCHIVED = (channelID: BigString, type: string) => `/channels/${channelID}/threads/archived/${type}`
export const THREADS_ARCHIVED_JOINED = (channelID: BigString) => `/channels/${channelID}/users/@me/threads/archived/private`
export const THREADS_GUILD_ACTIVE = (guildID: BigString) => `/guilds/${guildID}/threads/active`
export const USER = (userID: BigString) => `/users/${userID}`
export const USER_BILLING = (userID: BigString) => `/users/${userID}/billing`
export const USER_BILLING_PAYMENTS = (userID: BigString) => `/users/${userID}/billing/payments`
export const USER_BILLING_PREMIUM_SUBSCRIPTION = (userID: BigString) => `/users/${userID}/billing/premium-subscription`
export const USER_CHANNELS = (userID: BigString) => `/users/${userID}/channels`
export const USER_CONNECTIONS = (userID: BigString) => `/users/${userID}/connections`
export const USER_CONNECTION_PLATFORM = (userID: BigString, platform: string, id: string) => `/users/${userID}/connections/${platform}/${id}`
export const USER_GUILD = (userID: BigString, guildID: BigString) => `/users/${userID}/guilds/${guildID}`
export const USER_GUILDS = (userID: BigString) => `/users/${userID}/guilds`
export const USER_MFA_CODES = (userID: BigString) => `/users/${userID}/mfa/codes`
export const USER_MFA_TOTP_DISABLE = (userID: BigString) => `/users/${userID}/mfa/totp/disable`
export const USER_MFA_TOTP_ENABLE = (userID: BigString) => `/users/${userID}/mfa/totp/enable`
export const USER_NOTE = (userID: BigString, targetID: BigString) => `/users/${userID}/note/${targetID}`
export const USER_PROFILE = (userID: BigString) => `/users/${userID}/profile`
export const USER_RELATIONSHIP = (userID: BigString, relID: BigString) => `/users/${userID}/relationships/${relID}`
export const USER_SETTINGS = (userID: BigString) => `/users/${userID}/settings`
export const USERS = '/users'
export const VOICE_REGIONS = '/voice/regions'
export const WEBHOOK = (hookID: BigString) => `/webhooks/${hookID}`
export const WEBHOOK_MESSAGE = (hookID: BigString, token: string, msgID: BigString) => `/webhooks/${hookID}/${token}/messages/${msgID}`
export const WEBHOOK_SLACK = (hookID: BigString) => `/webhooks/${hookID}/slack`
export const WEBHOOK_TOKEN = (hookID: BigString, token: string) => `/webhooks/${hookID}/${token}`
export const WEBHOOK_TOKEN_SLACK = (hookID: BigString, token: string) => `/webhooks/${hookID}/${token}/slack`
// CDN Endpoints
export const ACHIEVEMENT_ICON = (applicationID: BigString, achievementID: BigString, icon: string) =>
`/app-assets/${applicationID}/achievements/${achievementID}/icons/${icon}`
export const APPLICATION_ASSET = (applicationID: BigString, asset: string) => `/app-assets/${applicationID}/${asset}`
export const APPLICATION_ICON = (applicationID: BigString, icon: string) => `/app-icons/${applicationID}/${icon}`
export const BANNER = (guildOrUserID: BigString, hash: string) => `/banners/${guildOrUserID}/${hash}`
export const CHANNEL_ICON = (chanID: BigString, chanIcon: string) => `/channel-icons/${chanID}/${chanIcon}`
export const CUSTOM_EMOJI = (emojiID: BigString) => `/emojis/${emojiID}`
export const DEFAULT_USER_AVATAR = (userDiscriminator: string) => `/embed/avatars/${userDiscriminator}`
export const GUILD_AVATAR = (guildID: BigString, userID: BigString, guildAvatar: string) =>
`/guilds/${guildID}/users/${userID}/avatars/${guildAvatar}`
export const GUILD_DISCOVERY_SPLASH = (guildID: BigString, guildDiscoverySplash: string) => `/discovery-splashes/${guildID}/${guildDiscoverySplash}`
export const GUILD_ICON = (guildID: BigString, guildIcon: string) => `/icons/${guildID}/${guildIcon}`
export const GUILD_SPLASH = (guildID: BigString, guildSplash: string) => `/splashes/${guildID}/${guildSplash}`
export const ROLE_ICON = (roleID: BigString, roleIcon: string) => `/role-icons/${roleID}/${roleIcon}`
export const TEAM_ICON = (teamID: BigString, teamIcon: string) => `/team-icons/${teamID}/${teamIcon}`
export const USER_AVATAR = (userID: BigString, userAvatar: string) => `/avatars/${userID}/${userAvatar}`
// Client Endpoints
export const MESSAGE_LINK = (guildID: BigString, channelID: BigString, messageID: BigString) => `/channels/${guildID}/${channelID}/${messageID}`

View File

@@ -1,116 +0,0 @@
import type { RequestMethods, RestManager } from '@discordeno/rest'
import { createRestManager } from '@discordeno/rest'
import Base from './Base.js'
import type Client from './Client.js'
import type { FileContent, RequestHandlerOptions } from './typings.js'
// TODO: make dynamic based on package.json file
const version = '19.0.0-alpha.1'
export class RequestHandler {
/** The client manager. */
client: Client
/** The options this manager was configured with. */
options: RequestHandlerOptions
/** The user agent used to make requests. */
userAgent: string
/** The rate limits currently in cache. */
ratelimits: Record<string, unknown>
/** The latency information for this manager. */
latencyRef: {
latency: number
raw: number[]
timeOffset: number
timeOffsets: number[]
lastTimeOffsetCheck: number
}
/** Whether or not the manager is globally blocked. */
globalBlock: boolean
/** The ready queue */
readyQueue: unknown[]
/** The internal rest manager from dd. */
discordeno: RestManager
constructor(client: Client, options: RequestHandlerOptions) {
this.options = options = Object.assign(
{
// agent: client.options.agent || null,
agent: null,
baseURL: 'https://discord.com/api',
decodeReasons: true,
disableLatencyCompensation: false,
domain: 'discord.com',
// latencyThreshold: client.options.latencyThreshold || 30000,
latencyThreshold: 30000,
// ratelimiterOffset: client.options.ratelimiterOffset || 0,
ratelimiterOffset: 0,
// requestTimeout: client.options.requestTimeout || 15000,
requestTimeout: 15000,
},
options,
)
this.client = client
this.discordeno = createRestManager({
token: this.client.token,
proxy:
options.baseURL ?? this.client.options.proxyURL
? {
baseUrl: options.baseURL ?? this.client.options.proxyURL!,
authorization: this.client.token,
}
: undefined,
})
this.userAgent = `DiscordBot (https://github.com/discordeno/discordeno, ${version})`
this.ratelimits = {}
this.latencyRef = {
latency: this.options.ratelimiterOffset ?? 0,
raw: new Array(10).fill(this.options.ratelimiterOffset),
timeOffset: 0,
timeOffsets: new Array(10).fill(0),
lastTimeOffsetCheck: 0,
}
this.globalBlock = false
this.readyQueue = []
}
/**
* @deprecated Use `.client` instead
*/
get _client(): Client {
return this.client
}
/**
* @deprecated Useless, handled by discordeno itself. Kept for Eris api compatibility.
*/
globalUnblock(): void {}
warnUser(): void {}
/**
* Make an API request
* @deprecated Use a proxy rest instead.
*/
async request(method: RequestMethods, url: string, auth?: boolean, body?: any, file?: FileContent): Promise<unknown> {
if (file) body.file = file
return await this.discordeno.makeRequest(method, url, body)
}
routefy(url: string, method: RequestMethods): string {
return this.discordeno.simplifyUrl(url, method)
}
toString(): string {
return '[RequestHandler]'
}
toJSON(props: string[] = []): Record<string, any> {
return Base.prototype.toJSON.call(this, ['globalBlock', 'latencyRef', 'options', 'ratelimits', 'readyQueue', 'userAgent', ...props])
}
}
export default RequestHandler

View File

@@ -1,149 +0,0 @@
/* eslint-disable no-useless-call */
import type { DiscordInvite, DiscordInviteCreate, DiscordMemberWithUser, TargetTypes } from '@discordeno/types'
import Base from '../Base.js'
import type Client from '../Client.js'
import type Channel from './channels/Channel.js'
import Guild from './guilds/Guild.js'
import Member from './guilds/Member.js'
import User from './users/User.js'
export class Invite {
/** The client object. */
client: Client
/** The invite code (unique Id) */
code: string
/** The channel this invite is for */
channel?: Channel
/** The guild this invite is for. */
guild?: Guild
/** The user who created this invite. */
inviter?: User
/** The amount of times this invite has been used. */
uses: number | null = null
/** The amount of times this invite can be used. */
maxUses: number | null = null
/** How long the invite is valid for (in seconds) */
maxAge: number | null = null
/** Whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) */
temporary: boolean = false
/** The time at which the invite was created */
createdAt?: number
presenceCount?: number | null
memberCount?: number | null
stageInstance?: {
members: Member[]
participantCount: number
speakerCount: number
topic: string
} | null
targetApplicationID?: string | null
targetType?: TargetTypes | null
targetUser?: User | null
constructor(data: DiscordInvite | DiscordInviteCreate, client: Client) {
// super();
this.client = client
this.code = data.code
// @ts-expect-error js hacks
this.channel = data.channel
if (data.inviter) {
this.inviter = new User(data.inviter, client)
client.users.set(this.inviter.id, this.inviter)
}
if (this.isInviteCreate(data)) {
this.uses = data.uses !== undefined ? data.uses : null
this.maxUses = data.max_uses !== undefined ? data.max_uses : null
this.maxAge = data.max_age !== undefined ? data.max_age : null
this.temporary = data.temporary !== undefined ? data.temporary : false
this.createdAt = Date.parse(data.created_at)
} else {
if (data.guild) {
if (client.guilds.has(data.guild.id!)) {
if (data.channel) {
// @ts-expect-error should work i think dumb partials
const channel = new GuildChannel(data.channel, client)
client.guilds.get(data.guild.id!)?.channels.set(channel.id, channel)
}
} else {
// @ts-expect-error js hacks
this.guild = new Guild(data.guild, client)
}
}
this.presenceCount = data.approximate_presence_count !== undefined ? data.approximate_presence_count : null
this.memberCount = data.approximate_member_count !== undefined ? data.approximate_member_count : null
if (data.stage_instance !== undefined) {
this.stageInstance = {
members: data.stage_instance.members.map((m) => {
// @ts-expect-error js hacks
const member = new Member(m as DiscordMemberWithUser, this.guild, client)
this.guild?.members.set(member.id, member)
return member
}),
participantCount: data.stage_instance.participant_count,
speakerCount: data.stage_instance.speaker_count,
topic: data.stage_instance.topic,
}
} else {
this.stageInstance = null
}
}
this.targetApplicationID = data.target_application !== undefined ? data.target_application.id : null
this.targetType = data.target_type !== undefined ? data.target_type : null
this.targetUser = data.target_user !== undefined ? new User(data.target_user, client) : null
if (this.targetUser) client.users.set(this.targetUser.id, this.targetUser)
}
/**
* @deprecated Use .client
*/
get _client(): Client {
return this.client
}
/**
* @deprecated Use .createdAt
*/
get _createdAt(): number | undefined {
return this.createdAt
}
/** Delete the invite */
async delete(reason?: string): Promise<void> {
return await this.client.deleteInvite.call(this.client, this.code, reason)
}
toString(): string {
return `[Invite ${this.code}]`
}
toJSON(props = []): Record<string, any> {
return Base.prototype.toJSON([
'channel',
'code',
'createdAt',
'guild',
'maxAge',
'maxUses',
'memberCount',
'presenceCount',
'revoked',
'temporary',
'uses',
...props,
])
}
isInviteCreate(data: DiscordInvite | DiscordInviteCreate): data is DiscordInviteCreate {
return Reflect.has(data, 'created_at')
}
}
export default Invite

View File

@@ -1,444 +0,0 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import {
MessageTypes,
type DiscordApplication,
type DiscordAttachment,
type DiscordEmbed,
type DiscordMemberWithUser,
type DiscordMessage,
type DiscordMessageActivity,
type DiscordMessageComponents,
type DiscordStickerItem,
type InteractionTypes,
} from '@discordeno/types'
import Base from '../Base.js'
import type Client from '../Client.js'
import { MessageFlags } from '../Constants.js'
import { MESSAGE_LINK } from '../Endpoints.js'
import type { GetMessageReactionOptions, MessageContentEdit, MessageWebhookContent } from '../typings.js'
import type NewsChannel from './channels/News.js'
import type PrivateChannel from './channels/Private.js'
import type TextChannel from './channels/Text.js'
import type TextVoiceChannel from './channels/TextVoice.js'
import type NewsThreadChannel from './channels/threads/NewsThread.js'
import type PrivateThreadChannel from './channels/threads/PrivateThread.js'
import type PublicThreadChannel from './channels/threads/PublicThread.js'
import type Guild from './guilds/Guild.js'
import Member from './guilds/Member.js'
import User from './users/User.js'
export class Message extends Base {
/** The client manager. */
client: Client
/** Timestamp of message creation */
timestamp: number
/** The type of the message */
type: MessageTypes
/** The channel the message is in. Can be partial with only the id if the channel is not cached. */
channel: PrivateChannel | TextChannel | NewsChannel | NewsThreadChannel | PublicThreadChannel | PrivateThreadChannel | TextVoiceChannel
/** The message content. */
content: string
/** An object containing the reactions on the message. Each key is a reaction emoji and each value is an object with properties `me` (Boolean) and `count` (Number) for that specific reaction emoji. */
reactions: Record<string, { me: boolean; count: number }>
/** The ID of the guild this message is in (undefined if in DMs) */
guildID?: string
/** ID of the webhook that sent the message */
webhookID?: string
/** An object containing the reference to the original message if it is a crossposted message or reply */
messageReference?: {
/** The id of the original message this message was crossposted from */
messageID?: string
/** The id of the channel this message was crossposted from */
channelID?: string
/** The id of the guild this message was crossposted from */
guildID?: string
} | null
/** The flags that are enabled on this message. */
flags: number
/** The message author */
author: User
/** The message author with server-specific data */
member?: Member
/** The message that was replied to. If undefined, message data was not received. If null, the message was deleted. */
referencedMessage?: Message | null
/** An object containing info about the interaction the message is responding to, if applicable */
interaction?: {
/** The id of the interaction */
id: string
/** The type of interaction */
type: InteractionTypes
/** The name of the command */
name: string
/** The user who invoked the interaction */
user: User
/** The member who invoked the interaction */
member?: Member
}
/** Array of mentioned users */
mentions: User[] = []
/** Array of mentioned roles' ids */
roleMentions: string[] = []
/** Array of attachments */
attachments: DiscordAttachment[] = []
/** Array of embeds */
embeds: DiscordEmbed[] = []
/** The stickers sent with the message */
stickerItems: DiscordStickerItem[] = []
/** An array of component objects */
components: DiscordMessageComponents = []
/** The activity specified in the message */
activity?: DiscordMessageActivity
/** The application of the activity in the message */
application?: Partial<DiscordApplication>
/** The ID of the interaction's application */
applicationID?: string
/** Timestamp of latest message edit */
editedTimestamp?: number
/** Whether the message mentions everyone/here or not */
mentionEveryone: boolean = false
/** Whether the message is pinned or not */
pinned: boolean = false
/** Whether to play the message using TTS or not */
tts: boolean = false
constructor(data: DiscordMessage, client: Client) {
super(data.id)
this.client = client
this.timestamp = Date.parse(data.timestamp)
this.type = data.type || MessageTypes.Default
this.timestamp = Date.parse(data.timestamp)
// @ts-expect-error eris js hack
this.channel = this.client.getChannel(data.channel_id) ?? {
id: data.channel_id,
}
this.content = ''
this.reactions = {}
this.guildID = data.guild_id
this.webhookID = data.webhook_id
if (data.message_reference) {
this.messageReference = {
messageID: data.message_reference.message_id,
channelID: data.message_reference.channel_id,
guildID: data.message_reference.guild_id,
}
} else {
this.messageReference = null
}
this.flags = data.flags ?? 0
this.author = new User(data.author, client)
if (!data.webhook_id) {
this.client.users.set(this.author.id, this.author)
}
if (data.referenced_message) {
const channel = this.client.getChannel(data.referenced_message.channel_id) as TextChannel
this.referencedMessage = new Message(data.referenced_message, this.client)
if (channel) {
channel.messages.set(this.referencedMessage.id, this.referencedMessage)
}
} else {
this.referencedMessage = data.referenced_message
}
if (data.interaction) {
this.interaction = {
id: data.interaction.id,
type: data.interaction.type,
name: data.interaction.name,
user: new User(data.interaction.user, client),
}
if (data.interaction.member) {
data.interaction.member.user = data.interaction.user
if (this.guild) {
this.interaction.member = new Member(
// @ts-expect-error some eris magic at play here
data.interaction.member,
this.guild,
client,
)
this.guild.members.set(this.interaction.member.id, this.interaction.member)
} else {
// @ts-expect-error some eris magic at play here
interactionMember = data.interaction.member
}
} else if (this.guild?.members.has(data.interaction.user.id)) {
this.interaction.member = this.guild.members.get(data.interaction.user.id)
}
}
if (this.guild) {
if (data.member) {
data.member.user = data.author
this.member = new Member(data.member as DiscordMemberWithUser, this.guild, client)
this.guild.members.set(this.member.id, this.member)
} else if (this.guild.members.has(this.author.id)) {
this.member = this.guild.members.get(this.author.id)
}
}
this.update(data)
}
/**
* @deprecated Use `.client` instead.
*/
get _client(): Client {
return this.client
}
get guild(): Guild | undefined {
return this.guildID ? this.client.guilds.get(this.guildID) : undefined
}
update(data: DiscordMessage) {
if (data.pinned !== undefined) this.pinned = !!data.pinned
if (data.tts !== undefined) this.tts = data.tts
if (data.attachments !== undefined) this.attachments = data.attachments
if (data.embeds !== undefined) this.embeds = data.embeds
if (data.flags !== undefined) this.flags = data.flags
if (data.activity !== undefined) this.activity = data.activity
if (data.components !== undefined) this.components = data.components
if (data.application !== undefined) this.application = data.application
if (data.edited_timestamp) this.editedTimestamp = Date.parse(data.edited_timestamp)
if (data.application_id !== undefined) this.applicationID = data.application_id
if (data.sticker_items !== undefined) this.stickerItems = data.sticker_items
if (data.content !== undefined) {
this.content = data.content || ''
this.mentionEveryone = !!data.mention_everyone
this.mentions = []
for (const mention of data.mentions ?? []) {
const user = new User(mention, this.client)
this.client.users.set(user.id, user)
if (mention.member && this.guild) {
mention.member.user = mention
this.guild.members.set(mention.id, new Member(mention.member as DiscordMemberWithUser, this.guild, this.client))
}
}
if (data.mention_roles) this.roleMentions = data.mention_roles
}
if (data.reactions) {
for (const reaction of data.reactions ?? []) {
this.reactions[reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name!] = {
count: reaction.count,
me: reaction.me,
}
}
}
}
get channelMentions(): string[] {
return (this.content.match(/<#[0-9]+>/g) ?? []).map((mention) => mention.substring(2, mention.length - 1))
}
get cleanContent() {
let cleanContent = this.content?.replace(/<a?(:\w+:)[0-9]+>/g, '$1') || ''
let authorName = this.author.username
if (this.guild) {
const member = this.guild.members.get(this.author.id)
if (member?.nick) {
authorName = member.nick
}
}
cleanContent = cleanContent.replace(new RegExp(`<@!?${this.author.id}>`, 'g'), '@\u200b' + authorName)
if (this.mentions) {
this.mentions.forEach((mention) => {
if (this.guild) {
const member = this.guild.members.get(mention.id)
if (member?.nick) {
cleanContent = cleanContent.replace(new RegExp(`<@!?${mention.id}>`, 'g'), '@\u200b' + member.nick)
}
}
cleanContent = cleanContent.replace(new RegExp(`<@!?${mention.id}>`, 'g'), '@\u200b' + mention.username)
})
}
if (this.guild && this.roleMentions) {
for (const roleID of this.roleMentions) {
const role = this.guild.roles.get(roleID)
const roleName = role ? role.name : 'deleted-role'
cleanContent = cleanContent.replace(new RegExp(`<@&${roleID}>`, 'g'), '@\u200b' + roleName)
}
}
this.channelMentions.forEach((id) => {
const channel = this.client.getChannel(id) as TextChannel
if (channel?.name && channel.mention) {
cleanContent = cleanContent.replace(channel.mention, '#' + channel.name)
}
})
return cleanContent.replace(/@everyone/g, '@\u200beveryone').replace(/@here/g, '@\u200bhere')
}
get jumpLink() {
return `${this.client.CLIENT_URL}${MESSAGE_LINK(this.guildID ?? '@me', this.channel.id, this.id)}`
}
/** Add a reaction to a message */
async addReaction(reaction: string): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.addMessageReaction.call(this.client, this.channel.id, this.id, reaction)
}
/** Create a thread with this message */
async createThreadWithMessage(options: {
name: string
autoArchiveDuration: 60 | 1440 | 4320 | 10080
}): Promise<NewsThreadChannel | PublicThreadChannel> {
return await this.client.createThreadWithMessage.call(this.client, this.channel.id, this.id, options)
}
/** Crosspost (publish) a message to subscribed channels (NewsChannel only) */
async crosspost(): Promise<Message> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be crossposted')
}
return await this.client.crosspostMessage.call(this.client, this.channel.id, this.id)
}
/** Delete the message */
async delete(reason?: string): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be deleted')
}
return await this.client.deleteMessage.call(this.client, this.channel.id, this.id, reason)
}
/** Delete the message as a webhook */
async deleteWebhook(token: string): Promise<void> {
if (!this.webhookID) throw new Error('Message is not a webhook')
if (this.flags & MessageFlags.EPHEMERAL) throw new Error('Ephemeral messages cannot be deleted')
return await this.client.deleteWebhookMessage.call(this.client, this.webhookID, token, this.id)
}
/** Edit the message */
async edit(content: MessageContentEdit): Promise<Message> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be edited via this method')
}
return await this.client.editMessage.call(this.client, this.channel.id, this.id, content)
}
/** Edit the message as a webhook */
async editWebhook(token: string, options: MessageWebhookContent): Promise<Message> {
if (!this.webhookID) {
throw new Error('Message is not a webhook')
}
return await this.client.editWebhookMessage.call(this.client, this.webhookID, token, this.id, options)
}
/** Get a list of users who reacted with a specific reaction */
async getReaction(reaction: string, options?: GetMessageReactionOptions): Promise<User[]> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.getMessageReaction.call(this.client, this.channel.id, this.id, reaction, options)
}
/** Pin the message */
async pin(): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be pinned')
}
return await this.client.pinMessage.call(this.client, this.channel.id, this.id)
}
/** Remove a reaction from a message */
async removeReaction(reaction: string): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.removeMessageReaction.call(this.client, this.channel.id, this.id, reaction)
}
/** Remove all reactions from a message for a single emoji */
async removeReactionEmoji(reaction: string): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.removeMessageReactionEmoji.call(this.client, this.channel.id, this.id, reaction)
}
/** Remove all reactions from a message */
async removeReactions(): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.removeMessageReactions.call(this.client, this.channel.id, this.id)
}
/** Unpin the message */
async unpin(): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be pinned')
}
return await this.client.unpinMessage.call(this.client, this.channel.id, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'activity',
'application',
'attachments',
'author',
'content',
'editedTimestamp',
'embeds',
'flags',
'guildID',
'hit',
'member',
'mentionEveryone',
'mentions',
'messageReference',
'pinned',
'reactions',
'referencedMesssage',
'roleMentions',
'stickers',
'stickerItems',
'timestamp',
'tts',
'type',
'webhookID',
...props,
])
}
}
export default Message

View File

@@ -1,59 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { BitwisePermissionFlags } from '@discordeno/types'
import { Base } from '../Base.js'
import type { BigString } from '../Client.js'
import type { PermissionClientStrings } from '../Constants.js'
import { Permissions } from '../Constants.js'
export class Permission {
allow: bigint
deny: bigint
_json?: Record<string, boolean>
constructor(allow: BigString | number = 0, deny: BigString | number = 0) {
this.allow = BigInt(allow)
this.deny = BigInt(deny)
}
get isAdmin(): boolean {
return !!(this.allow & BigInt(BitwisePermissionFlags.ADMINISTRATOR))
}
get json() {
if (!this._json) {
this._json = {}
for (const key of Object.keys(BitwisePermissionFlags)) {
if (typeof key === 'number') continue
const perm = key as keyof typeof BitwisePermissionFlags
if (this.allow & BigInt(BitwisePermissionFlags[perm])) {
this._json[perm] = true
} else if (this.deny & BigInt(BitwisePermissionFlags[perm])) {
this._json[perm] = false
}
}
}
return this._json
}
/** Check if this permission allows a specific permission */
has(permission: bigint | PermissionClientStrings): boolean {
if (this.isAdmin) return true
if (typeof permission === 'bigint') {
return (this.allow & permission) === permission
}
return !!(this.allow & Permissions[permission])
}
toString() {
return `[${this.constructor.name} +${this.allow} -${this.deny}]`
}
toJSON(props: string[] = []): Record<string, any> {
return Base.prototype.toJSON.call(['allow', 'deny', ...props])
}
}
export default Permission

View File

@@ -1,21 +0,0 @@
import type { DiscordOverwrite, OverwriteTypes } from '@discordeno/types'
import { Base } from '../Base.js'
import Permission from './Permission.js'
export class PermissionOverwrite extends Permission {
id: string
type: OverwriteTypes
constructor(data: DiscordOverwrite) {
super(data.allow, data.deny)
this.id = data.id
this.type = data.type
}
toJSON(props: string[] = []): Record<string, any> {
return Base.prototype.toJSON.call(['id', 'type', ...props])
}
}
export default PermissionOverwrite

View File

@@ -1,12 +0,0 @@
import type { BigString } from '@discordeno/types'
import type Collection from '../../Collection.js'
import type { AnyGuildChannel } from '../../typings.js'
import GuildChannel from './Guild.js'
export class CategoryChannel extends GuildChannel {
get channels(): Collection<BigString, Exclude<AnyGuildChannel, CategoryChannel>> {
return this.guild?.channels.filter((c) => c.parentID === this.id) as unknown as Collection<BigString, Exclude<AnyGuildChannel, CategoryChannel>>
}
}
export default CategoryChannel

View File

@@ -1,32 +0,0 @@
import type { ChannelTypes, DiscordChannel } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
export class Channel extends Base {
type: ChannelTypes
client: Client
constructor(data: DiscordChannel | Pick<DiscordChannel, 'id' | 'permissions' | 'name' | 'type'>, client: Client) {
super(data.id)
this.type = data.type
this.client = client
}
get mention(): string {
return `<#${this.id}>`
}
/**
* @deprecated Removed this circular dependency hack for better alternative.
*/
static from(_data: DiscordChannel, client: Client): void {
console.error('Usage of channel.from is deprecated, please use generateChannelFrom(data, client)')
client.emit('warn', new Error(`Usage of "Channel.from" is deprecated. Use "generateChanneFrom()"`))
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['type', ...props])
}
}
export default Channel

View File

@@ -1,117 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import type { BigString, DiscordChannel, OverwriteTypes } from '@discordeno/types'
import { BitwisePermissionFlags, ChannelTypes } from '@discordeno/types'
import { Collection } from '@discordeno/utils'
import type Client from '../../Client.js'
import type { EditChannelOptions, EditChannelPositionOptions } from '../../typings.js'
import type Guild from '../guilds/Guild.js'
import type Member from '../guilds/Member.js'
import Permission from '../Permission.js'
import PermissionOverwrite from '../PermissionOverwrite.js'
import Channel from './Channel.js'
export class GuildChannel extends Channel {
position: number
name: string
parentID?: string | null
guild: Guild
nsfw: boolean
permissionOverwrites = new Collection<BigString, PermissionOverwrite>()
/** The RTC region ID of the channel (automatic if `null`) (guild voice channels only) */
rtcRegion: string | null = null
constructor(data: DiscordChannel, client: Client) {
super(data, client)
this.position = data.position ?? 0
this.guild = client.guilds.get(data.guild_id!)!
this.name = data.name ?? ''
this.parentID = data.parent_id
this.nsfw = !!data.nsfw
}
update(data: DiscordChannel): void {
if (data.type !== undefined) {
this.type = data.type
}
if (data.name !== undefined) {
this.name = data.name
}
if (data.position !== undefined) {
this.position = data.position
}
if (data.parent_id !== undefined) {
this.parentID = data.parent_id
}
this.nsfw = !!data.nsfw
if (data.permission_overwrites) {
data.permission_overwrites.forEach((overwrite) => {
const perms = new PermissionOverwrite(overwrite)
this.permissionOverwrites.set(perms.id, perms)
})
}
}
/** Delete the channel */
async delete(reason?: string): Promise<void> {
return await this.client.deleteChannel.call(this.client, this.id, reason)
}
/** Delete a channel permission overwrite */
async deletePermission(overwriteID: BigString, reason?: string): Promise<void> {
return await this.client.deleteChannelPermission.call(this.client, this.id, overwriteID, reason)
}
/** Edit the channel's properties */
async edit(options: EditChannelOptions, reason?: string) {
return await this.client.editChannel.call(this.client, this.id, options, reason)
}
/** Create a channel permission overwrite */
async editPermission(overwriteID: BigString, allow: bigint | number, deny: bigint | number, type: OverwriteTypes, reason?: string): Promise<void> {
return await this.client.editChannelPermission.call(this.client, this.id, overwriteID, allow, deny, type, reason)
}
/** Edit the channel's position. Note that channel position numbers are lowest on top and highest at the bottom. */
async editPosition(position: number, options?: EditChannelPositionOptions): Promise<void> {
return await this.client.editChannelPosition.call(this.client, this.id, position, options)
}
/** Get the channel-specific permissions of a member */
permissionsOf(memberID: BigString | Member): Permission {
const member = ['string', 'bigint'].includes(typeof memberID) ? this.guild.members.get(memberID as BigString)! : (memberID as Member)
let permission = this.guild.permissionsOf(member).allow
if (permission & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) {
return new Permission(BitwisePermissionFlags.ADMINISTRATOR)
}
const channel =
[ChannelTypes.PublicThread, ChannelTypes.PrivateThread, ChannelTypes.AnnouncementThread].includes(this.type) && this.parentID
? this.guild.channels.get(this.parentID)
: this
let overwrite = channel?.permissionOverwrites.get(this.guild.id)
if (overwrite) {
permission = (permission & ~overwrite.deny) | overwrite.allow
}
let deny = 0n
let allow = 0n
for (const roleID of member.roles) {
if ((overwrite = channel?.permissionOverwrites.get(roleID))) {
deny |= overwrite.deny
allow |= overwrite.allow
}
}
permission = (permission & ~deny) | allow
overwrite = channel?.permissionOverwrites.get(member.id)
if (overwrite) {
permission = (permission & ~overwrite.deny) | overwrite.allow
}
return new Permission(permission)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['name', 'nsfw', 'parentID', 'permissionOverwrites', 'position', ...props])
}
}
export default GuildChannel

View File

@@ -1,28 +0,0 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { BigString, DiscordChannel } from '@discordeno/types'
import type Client from '../../Client.js'
import type { ChannelFollow } from '../../typings.js'
import type Message from '../Message.js'
import TextChannel from './Text.js'
export class NewsChannel extends TextChannel {
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client, messageLimit)
this.rateLimitPerUser = 0
this.update(data)
}
/** Crosspost (publish) a message to subscribed channels */
async crosspostMessage(messageID: BigString): Promise<Message> {
return await this.client.crosspostMessage.call(this.client, this.id, messageID)
}
/** Follow this channel in another channel. This creates a webhook in the target channel */
async follow(webhookChannelID: BigString): Promise<ChannelFollow> {
return await this.client.followChannel.call(this.client, this.id, webhookChannelID)
}
}
export default NewsChannel

View File

@@ -1,104 +0,0 @@
/* eslint-disable no-useless-call */
import { ChannelTypes, type BigString, type DiscordChannel, type GetMessagesOptions } from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type { FileContent, GetMessageReactionOptions, MessageContent, MessageContentEdit } from '../../typings.js'
import type Message from '../Message.js'
import User from '../users/User.js'
import Channel from './Channel.js'
export class PrivateChannel extends Channel {
/** The ID of the last message in this channel */
lastMessageID = ""
// TODO: THIS A THING IN DMS????
/** The rate limit per user. */
rateLimitPerUser?: number
/** Collection of Messages in this channel */
messages: Collection<string, Message>
/** The recipient in this private channel */
recipient?: User
constructor(data: DiscordChannel, client: Client) {
super(data, client)
this.lastMessageID = data.last_message_id ?? ""
this.rateLimitPerUser = data.rate_limit_per_user
if (this.type === ChannelTypes.DM || this.type === undefined) {
if (data.recipients?.[0]) this.recipient = new User(data.recipients[0], client)
}
this.messages = new Collection()
this.messages.limit = client.options.messageLimit
}
/** Add a reaction to a message */
async addMessageReaction(messageID: BigString, reaction: string): Promise<void> {
return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction)
}
/** Create a message in a text channel */
async createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise<Message> {
return await this.client.createMessage.call(this.client, this.id, content, file)
}
// TODO: REASONS ARE A THING FOR AUDIT LOGS IN DMS???
/** Delete a message */
async deleteMessage(messageID: BigString, reason?: string): Promise<void> {
return await this.client.deleteMessage.call(this.client, this.id, messageID, reason)
}
/** Edit a message */
async editMessage(messageID: BigString, content: MessageContentEdit): Promise<Message> {
return await this.client.editMessage.call(this.client, this.id, messageID, content)
}
/** Get a previous message in a text channel */
async getMessage(messageID: BigString): Promise<Message> {
return await this.client.getMessage.call(this.client, this.id, messageID)
}
/** Get a list of users who reacted with a specific reaction */
async getMessageReaction(messageID: BigString, reaction: string, options: GetMessageReactionOptions): Promise<User[]> {
return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options)
}
/** Get a previous message in a text channel */
async getMessages(options: GetMessagesOptions): Promise<Message[]> {
return await this.client.getMessages.call(this.client, this.id, options)
}
/** Get all the pins in a text channel */
async getPins(): Promise<Message[]> {
return await this.client.getPins.call(this.client, this.id)
}
/** Leave the channel */
async leave(): Promise<void> {
return await this.client.deleteChannel.call(this.client, this.id)
}
/** Pin a message */
async pinMessage(messageID: BigString): Promise<void> {
return await this.client.pinMessage.call(this.client, this.id, messageID)
}
/** Remove a reaction from a message */
async removeMessageReaction(messageID: BigString, reaction: string): Promise<void> {
return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction)
}
/** Send typing status in a text channel */
async sendTyping(): Promise<void> {
return await this.client.sendChannelTyping.call(this.client, this.id)
}
/** Unpin a message */
async unpinMessage(messageID: BigString): Promise<void> {
return await this.client.unpinMessage.call(this.client, this.id, messageID)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['call', 'lastCall', 'lastMessageID', 'messages', 'recipient', ...props])
}
}
export default PrivateChannel

View File

@@ -1,45 +0,0 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { DiscordChannel } from '@discordeno/types'
import type { StageInstanceOptions } from '../../typings.js'
import type StageInstance from '../guilds/StageInstance.js'
import VoiceChannel from './Voice.js'
export class StageChannel extends VoiceChannel {
/** The topic of the channel */
topic?: string | null
update(data: DiscordChannel): void {
super.update(data)
if (data.topic !== undefined) {
this.topic = data.topic
}
}
/** Create a stage instance */
async createInstance(options: StageInstanceOptions): Promise<StageInstance> {
return await this.client.createStageInstance.call(this.client, this.id, options)
}
/** Delete the stage instance for this channel */
async deleteInstance(): Promise<void> {
return await this.client.deleteStageInstance.call(this.client, this.id)
}
/** Update the stage instance for this channel */
async editInstance(options: StageInstanceOptions): Promise<StageInstance> {
return await this.client.editStageInstance.call(this.client, this.id, options)
}
/** Get the stage instance for this channel */
async getInstance(): Promise<StageInstance> {
return await this.client.getStageInstance.call(this.client, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['topic', ...props])
}
}
export default StageChannel

View File

@@ -1,376 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { BigString, DiscordChannel, GetMessagesOptions } from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type {
CreateChannelInviteOptions,
CreateThreadOptions,
CreateThreadWithoutMessageOptions,
FileContent,
GetArchivedThreadsOptions,
GetMessageReactionOptions,
ListedChannelThreads,
MessageContent,
MessageContentEdit,
PurgeChannelOptions,
} from '../../typings.js'
import type Invite from '../Invite.js'
import type Message from '../Message.js'
import GuildChannel from './Guild.js'
import type PrivateThreadChannel from './threads/PrivateThread.js'
import type PublicThreadChannel from './threads/PublicThread.js'
export class TextChannel extends GuildChannel {
/** Collection of Messages in this channel */
messages: Collection<string, Message>
/** The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled */
rateLimitPerUser: number | null
/** The ID of the last message in this channel */
lastMessageID = ""
/** The timestamp of the last pinned message */
lastPinTimestamp?: number | null
/** Default duration for newly created threads, in minutes, to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 */
defaultAutoArchiveDuration?: number
/** The channel topic (0-4096 characters for GUILD_FORUM channels, 0-1024 characters for all others) */
topic?: string | null
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client)
this.messages = new Collection()
if (messageLimit == null) this.messages.limit = client.options.messageLimit
else this.messages.limit = messageLimit
this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user
this.lastMessageID = data.last_message_id ?? ""
this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null
this.update(data)
}
update(data: DiscordChannel): void {
super.update(data)
if (data.rate_limit_per_user !== undefined) this.rateLimitPerUser = data.rate_limit_per_user
if (data.topic !== undefined) this.topic = data.topic
if (data.default_auto_archive_duration !== undefined) this.defaultAutoArchiveDuration = data.default_auto_archive_duration
}
/** Add a reaction to a message */
async addMessageReaction(messageID: BigString, reaction: string): Promise<void> {
return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction)
}
/** Create an invite for the channel */
async createInvite(options?: CreateChannelInviteOptions, reason?: string): Promise<Invite> {
return await this.client.createChannelInvite.call(this.client, this.id, options, reason)
}
/**
* Create a message in the channel
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object} [content.messageReference] The message reference, used when replying to messages
* @arg {String} [content.messageReference.channelID] The channel ID of the referenced message
* @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message
* @arg {String} [content.messageReference.guildID] The guild ID of the referenced message
* @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message
* @arg {String} [content.messageReferenceID] [DEPRECATED] The ID of the message should be replied to. Use `messageReference` instead
* @arg {Array<String>} [content.stickerIDs] An array of IDs corresponding to stickers to send
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async createMessage(content: MessageContent, file?: FileContent | FileContent[]) {
return this.client.createMessage.call(this.client, this.id, content, file)
}
/**
* Create a thread with an existing message
* @arg {String} messageID The ID of the message to create the thread from
* @arg {Object} options The thread options
* @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @arg {String} options.name The thread channel name
* @returns {Promise<NewsThreadChannel | PublicThreadChannel>}
*/
async createThreadWithMessage(messageID: string, options: CreateThreadOptions) {
return this.client.createThreadWithMessage.call(this.client, this.id, messageID, options)
}
/**
* Create a thread without an existing message
* @arg {Object} options The thread options
* @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @arg {boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only)
* @arg {String} options.name The thread channel name
* @arg {Number} [options.type] The channel type of the thread to create. It is recommended to explicitly set this property as this will be a required property in API v10
* @returns {Promise<PrivateThreadChannel>}
*/
async createThreadWithoutMessage(options: CreateThreadWithoutMessageOptions) {
return this.client.createThreadWithoutMessage.call(this.client, this.id, options)
}
/**
* Create a channel webhook
* @arg {Object} options Webhook options
* @arg {String} [options.avatar] The default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings
* @arg {String} options.name The default name
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Object>} Resolves with a webhook object
*/
async createWebhook(options: { name: string; avatar?: string | null }, reason: string) {
return this.client.createChannelWebhook.call(this.client, this.id, options, reason)
}
/**
* Delete a message
* @arg {String} messageID The ID of the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
async deleteMessage(messageID: string, reason: string) {
return this.client.deleteMessage.call(this.client, this.id, messageID, reason)
}
/**
* Bulk delete messages (bot accounts only)
* @arg {Array<String>} messageIDs Array of message IDs to delete
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
async deleteMessages(messageIDs: string[], reason: string) {
return this.client.deleteMessages.call(this.client, this.id, messageIDs, reason)
}
/**
* Edit a message
* @arg {String} messageID The ID of the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [content.file] A file object (or an Array of them)
* @arg {Buffer} content.file[].file A buffer containing file data
* @arg {String} content.file[].name What to name the file
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
async editMessage(messageID: string, content: MessageContentEdit) {
return this.client.editMessage.call(this.client, this.id, messageID, content)
}
/** Get all archived threads in this channel */
async getArchivedThreads(type: 'private', options?: GetArchivedThreadsOptions): Promise<ListedChannelThreads<PrivateThreadChannel>>
async getArchivedThreads(type: 'public', options?: GetArchivedThreadsOptions): Promise<ListedChannelThreads<PublicThreadChannel>>
async getArchivedThreads(
type: 'public' | 'private',
options?: GetArchivedThreadsOptions,
): Promise<ListedChannelThreads<PrivateThreadChannel | PublicThreadChannel>> {
return await this.client.getArchivedThreads.call(this.client, this.id, type as 'public', options)
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
async getInvites() {
return this.client.getChannelInvites.call(this.client, this.id)
}
/**
* Get joined private archived threads in this channel
* @arg {Object} [options] Additional options when requesting archived threads
* @arg {Date} [options.before] List of threads to return before the timestamp
* @arg {Number} [options.limit] Maximum number of threads to return
* @returns {Promise<Object>} An object containing an array of `threads`, an array of `members` and whether the response `hasMore` threads that could be returned in a subsequent call
*/
async getJoinedPrivateArchivedThreads(options: GetArchivedThreadsOptions) {
return this.client.getJoinedPrivateArchivedThreads.call(this.client, this.id, options)
}
/**
* Get a previous message in the channel
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
async getMessage(messageID: string) {
return this.client.getMessage.call(this.client, this.id, messageID)
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
async getMessageReaction(messageID: string, reaction: string, options: GetMessageReactionOptions) {
return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options)
}
/**
* Get previous messages in the channel
* @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100)
* @arg {String} [options.before] Get messages before this message ID
* @arg {Number} [options.limit=50] The max number of messages to get
* @returns {Promise<Array<Message>>}
*/
async getMessages(options: GetMessagesOptions) {
return this.client.getMessages.call(this.client, this.id, options)
}
/**
* Get all the pins in the channel
* @returns {Promise<Array<Message>>}
*/
async getPins() {
return this.client.getPins.call(this.client, this.id)
}
/**
* Get all the webhooks in the channel
* @returns {Promise<Array<Object>>} Resolves with an array of webhook objects
*/
async getWebhooks() {
return this.client.getChannelWebhooks.call(this.client, this.id)
}
/**
* Pin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async pinMessage(messageID: string) {
return this.client.pinMessage.call(this.client, this.id, messageID)
}
/**
* Purge previous messages in the channel with an optional filter (bot accounts only)
* @arg {Object} options Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.before] Get messages before this message ID
* @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object
* @arg {Number} options.limit The max number of messages to search through, -1 for no limit
* @arg {String} [options.reason] The reason to be displayed in audit logs
* @returns {Promise<Number>} Resolves with the number of messages deleted
*/
async purge(limit: PurgeChannelOptions) {
return this.client.purgeChannel.call(this.client, this.id, limit)
}
/**
* Remove a reaction from a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for
* @returns {Promise}
*/
async removeMessageReaction(messageID: string, reaction: string, userID: string) {
return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID)
}
/**
* Remove all reactions from a message for a single emoji
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
async removeMessageReactionEmoji(messageID: string, reaction: string) {
return this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction)
}
/**
* Remove all reactions from a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async removeMessageReactions(messageID: string) {
return this.client.removeMessageReactions.call(this.client, this.id, messageID)
}
/**
* Send typing status in the channel
* @returns {Promise}
*/
async sendTyping() {
return this.client.sendChannelTyping.call(this.client, this.id)
}
/**
* Unpin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async unpinMessage(messageID: string) {
return this.client.unpinMessage.call(this.client, this.id, messageID)
}
/**
* Un-send a message. You're welcome Programmix
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async unsendMessage(messageID: string) {
return this.client.deleteMessage.call(this.client, this.id, messageID)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['lastMessageID', 'lastPinTimestamp', 'messages', 'rateLimitPerUser', 'topic', ...props])
}
}
export default TextChannel

View File

@@ -1,271 +0,0 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordChannel, GetMessagesOptions } from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type {
CreateInviteOptions,
FileContent,
GetMessageReactionOptions,
MessageContent,
MessageContentEdit,
PurgeChannelOptions,
} from '../../typings.js'
import type Message from '../Message.js'
import VoiceChannel from './Voice.js'
/**
* Represents a Text-in-Voice channel. See VoiceChannel for more properties and methods.
* @extends VoiceChannel
* @prop {String} lastMessageID The ID of the last message in this channel
* @prop {Collection<Message>} messages Collection of Messages in this channel
* @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled
*/
export class TextVoiceChannel extends VoiceChannel {
lastMessageID = ""
messages: Collection<string, Message>
rateLimitPerUser: number | null
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client)
this.messages = new Collection()
if (messageLimit == null) this.messages.limit = client.options.messageLimit
else this.messages.limit = messageLimit
this.lastMessageID = data.last_message_id ?? ""
this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user
}
update(data: DiscordChannel) {
super.update(data)
// "not yet, possibly TBD"
if (data.rate_limit_per_user !== undefined) {
this.rateLimitPerUser = data.rate_limit_per_user
}
}
/**
* Add a reaction to a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
async addMessageReaction(messageID: string, reaction: string) {
return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction)
}
/**
* Create an invite for the channel
* @arg {Object} [options] Invite generation options
* @arg {Number} [options.maxAge] How long the invite should last in seconds
* @arg {Number} [options.maxUses] How many uses the invite should last for
* @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not
* @arg {Boolean} [options.unique] Whether the invite is unique or not
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Invite>}
*/
async createInvite(options: CreateInviteOptions, reason: string) {
return await this.client.createChannelInvite.call(this.client, this.id, options, reason)
}
/**
* Create a message in the channel
* Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel ID for a user
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} content.content A content string
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object} [content.messageReference] The message reference, used when replying to messages
* @arg {String} [content.messageReference.channelID] The channel ID of the referenced message
* @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message
* @arg {String} [content.messageReference.guildID] The guild ID of the referenced message
* @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message
* @arg {Array<String>} [content.stickerIDs] An array of IDs corresponding to the stickers to send
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object} [file] A file object
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async createMessage(content: MessageContent, file?: FileContent | FileContent[]) {
return await this.client.createMessage.call(this.client, this.id, content, file)
}
/**
* Delete a message
* @arg {String} messageID The ID of the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
async deleteMessage(messageID: string, reason: string) {
return await this.client.deleteMessage.call(this.client, this.id, messageID, reason)
}
/**
* Bulk delete messages (bot accounts only)
* @arg {Array<String>} messageIDs Array of message IDs to delete
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
async deleteMessages(messageIDs: string[], reason: string) {
return await this.client.deleteMessages.call(this.client, this.id, messageIDs, reason)
}
/**
* Edit a message
* @arg {String} messageID The ID of the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} content.content A content string
* @arg {Boolean} [content.disableEveryone] Whether to filter @everyone/@here or not (overrides default)
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
async editMessage(messageID: string, content: MessageContentEdit) {
return await this.client.editMessage.call(this.client, this.id, messageID, content)
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
async getInvites() {
return await this.client.getChannelInvites.call(this.client, this.id)
}
/**
* Get a previous message in the channel
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
async getMessage(messageID: string) {
return await this.client.getMessage.call(this.client, this.id, messageID)
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
async getMessageReaction(messageID: string, reaction: string, options: GetMessageReactionOptions) {
return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options)
}
/**
* Get previous messages in the channel
* @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100)
* @arg {String} [options.before] Get messages before this message ID
* @arg {Number} [options.limit=50] The max number of messages to get
* @returns {Promise<Array<Message>>}
*/
async getMessages(options: GetMessagesOptions) {
return await this.client.getMessages.call(this.client, this.id, options)
}
/**
* Purge previous messages in the channel with an optional filter (bot accounts only)
* @arg {Object} options Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.before] Get messages before this message ID
* @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object
* @arg {Number} options.limit The max number of messages to search through, -1 for no limit
* @arg {String} [options.reason] The reason to be displayed in audit logs
* @returns {Promise<Number>} Resolves with the number of messages deleted
*/
async purge(options: PurgeChannelOptions) {
return await this.client.purgeChannel.call(this.client, this.id, options)
}
/**
* Remove a reaction from a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for
* @returns {Promise}
*/
async removeMessageReaction(messageID: string, reaction: string, userID: string) {
return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID)
}
/**
* Remove all reactions from a message for a single emoji
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
async removeMessageReactionEmoji(messageID: string, reaction: string) {
return await this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction)
}
/**
* Remove all reactions from a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async removeMessageReactions(messageID: string) {
return await this.client.removeMessageReactions.call(this.client, this.id, messageID)
}
/**
* Send typing status in the channel
* @returns {Promise}
*/
async sendTyping() {
return await this.client.sendChannelTyping.call(this.client, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['lastMessageID', 'messages', 'rateLimitPerUser', ...props])
}
}
export default TextVoiceChannel

View File

@@ -1,91 +0,0 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordChannel } from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type { CreateInviteOptions, TextVoiceChannelTypes, VideoQualityMode } from '../../typings.js'
import type Member from '../guilds/Member.js'
import GuildChannel from './Guild.js'
export class VoiceChannel extends GuildChannel {
bitrate: number = 0
rtcRegion: string | null = null
type: TextVoiceChannelTypes = 0
userLimit: number = 0
videoQualityMode: VideoQualityMode = 0
voiceMembers: Collection<string, Member>
constructor(data: DiscordChannel, client: Client) {
super(data, client)
this.voiceMembers = new Collection()
this.update(data)
}
update(data: DiscordChannel): void {
super.update(data)
if (data.bitrate !== undefined) {
this.bitrate = data.bitrate
}
if (data.rtc_region !== undefined) {
this.rtcRegion = data.rtc_region
}
if (data.user_limit !== undefined) {
this.userLimit = data.user_limit
}
if (data.video_quality_mode !== undefined) {
this.videoQualityMode = data.video_quality_mode
}
}
/**
* Create an invite for the channel
* @arg {Object} [options] Invite generation options
* @arg {Number} [options.maxAge] How long the invite should last in seconds
* @arg {Number} [options.maxUses] How many uses the invite should last for
* @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not
* @arg {Boolean} [options.unique] Whether the invite is unique or not
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Invite>}
*/
async createInvite(options: CreateInviteOptions, reason: string) {
return await this.client.createChannelInvite.call(this.client, this.id, options, reason)
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
async getInvites() {
return await this.client.getChannelInvites.call(this.client, this.id)
}
// TODO: gateway
// /**
// * Joins the channel.
// * @arg {Object} [options] VoiceConnection constructor options
// * @arg {Object} [options.opusOnly] Skip opus encoder initialization. You should not enable this unless you know what you are doing
// * @arg {Object} [options.shared] Whether the VoiceConnection will be part of a SharedStream or not
// * @arg {Boolean} [options.selfMute] Whether the bot joins the channel muted or not
// * @arg {Boolean} [options.selfDeaf] Whether the bot joins the channel deafened or not
// * @returns {Promise<VoiceConnection>} Resolves with a VoiceConnection
// */
// join(options: JoinVoiceChannelOptions) {
// return this.client.joinVoiceChannel.call(this.client, this.id, options);
// }
// TODO: gateway
// /**
// * Leaves the channel.
// */
// leave() {
// return this.client.leaveVoiceChannel.call(this.client, this.id);
// }
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['bitrate', 'rtcRegion', 'userLimit', 'videoQualityMode', 'voiceMembers', ...props])
}
}
export default VoiceChannel

View File

@@ -1,41 +0,0 @@
/* eslint-disable no-useless-call */
import type { DiscordThreadMember } from '@discordeno/types'
import Base from '../../../Base.js'
import type Client from '../../../Client.js'
import type Member from '../../guilds/Member.js'
export class ThreadMember extends Base {
client: Client
/** The user-thread settings of this member */
flags: number
/** The ID of the thread this member is a part of */
threadID: string
/** Timestamp of when the member joined the thread */
joinTimestamp: number
/** The guild member that this thread member belongs to. This will never be present when fetching over REST */
guildMember?: Member
constructor(data: DiscordThreadMember, client: Client) {
super(data.user_id)
this.client = client
this.flags = data.flags
this.threadID = data.id
this.joinTimestamp = Date.parse(data.join_timestamp)
}
get _client(): Client {
return this.client
}
/** Remove the member from the thread */
async leave(): Promise<void> {
return await this._client.leaveThread.call(this._client, this.threadID, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['threadID', 'joinTimestamp', ...props])
}
}
export default ThreadMember

View File

@@ -1,5 +0,0 @@
import ThreadChannel from './Thread.js'
export class NewsThreadChannel extends ThreadChannel {}
export default NewsThreadChannel

View File

@@ -1,25 +0,0 @@
import type { DiscordChannel } from '@discordeno/types'
import type Client from '../../../Client.js'
import ThreadChannel from './Thread.js'
export class PrivateThreadChannel extends ThreadChannel {
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client, messageLimit)
this.update(data)
}
update(data: DiscordChannel): void {
if (data.thread_metadata !== undefined) {
this.threadMetadata = {
archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp),
archived: data.thread_metadata.archived,
autoArchiveDuration: data.thread_metadata.auto_archive_duration,
invitable: data.thread_metadata.invitable,
locked: data.thread_metadata.locked,
}
}
}
}
export default PrivateThreadChannel

View File

@@ -1,5 +0,0 @@
import ThreadChannel from './Thread.js'
export class PublicThreadChannel extends ThreadChannel {}
export default PublicThreadChannel

View File

@@ -1,180 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import type { BigString, DiscordChannel, GetMessagesOptions } from '@discordeno/types'
import type Client from '../../../Client.js'
import Collection from '../../../Collection.js'
import type { FileContent, GetMessageReactionOptions, MessageContent, MessageContentEdit, PurgeChannelOptions } from '../../../typings.js'
import type Message from '../../Message.js'
import type User from '../../users/User.js'
import GuildChannel from '../Guild.js'
import ThreadMember from './Member.js'
export class ThreadChannel extends GuildChannel {
/** The cached messages that were sent in this channel. */
messages: Collection<BigString, Message>
/** The cached thread members that are in this channel. */
members: Collection<BigString, ThreadMember>
/** The id of the last message in this channel. */
lastMessageID: string
/** The id of the user who created this thread. */
ownerID: string
/** The approximate amount of members that have joined this thread. */
memberCount?: number
/** The approximate amount of messages in this channel. */
messageCount?: number
/** The rate limit that users can send messages in this channel. 0 means no rate limit has been enabled. */
rateLimitPerUser?: number
/** The data relevant to this thread. */
threadMetadata?: {
/** Timestamp when the thread's archive status was last changed, used for calculating recent activity */
archiveTimestamp: number
/** Whether the thread is archived. */
archived: boolean
/** Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 */
autoArchiveDuration: number
/** Whether the thread is locked. */
locked: boolean
/** Whether or not the thread is inviteable. */
invitable?: boolean
}
/** The bot's thread member object if it has joined the thread. */
member?: ThreadMember
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client)
this.members = new Collection()
this.messages = new Collection()
this.messages.limit = messageLimit ?? client.options.messageLimit
this.lastMessageID = data.last_message_id ?? ""
this.ownerID = data.owner_id!
this.update(data)
}
update(data: DiscordChannel): void {
super.update(data)
if (data.member_count !== undefined) {
this.memberCount = data.member_count
}
if (data.message_count !== undefined) {
this.messageCount = data.message_count
}
if (data.rate_limit_per_user !== undefined) {
this.rateLimitPerUser = data.rate_limit_per_user
}
if (data.thread_metadata !== undefined) {
this.threadMetadata = {
archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp),
archived: data.thread_metadata.archived,
autoArchiveDuration: data.thread_metadata.auto_archive_duration,
locked: data.thread_metadata.locked,
}
}
if (data.member !== undefined) {
this.member = new ThreadMember(data.member, this.client)
}
}
async addMessageReaction(messageID: BigString, reaction: string): Promise<void> {
return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction)
}
async createMessage(content: MessageContent, file?: FileContent | FileContent[]) {
return await this.client.createMessage.call(this.client, this.id, content, file)
}
async deleteMessage(messageID: BigString, reason?: string): Promise<void> {
return await this.client.deleteMessage.call(this.client, this.id, messageID, reason)
}
async deleteMessages(messageIDs: BigString[], reason?: string): Promise<void> {
return await this.client.deleteMessages.call(this.client, this.id, messageIDs, reason)
}
async editMessage(messageID: BigString, content: MessageContentEdit) {
return await this.client.editMessage.call(this.client, this.id, messageID, content)
}
async getMembers(): Promise<ThreadMember[]> {
return await this.client.getThreadMembers.call(this.client, this.id)
}
async getMessage(messageID: BigString): Promise<Message> {
return await this.client.getMessage.call(this.client, this.id, messageID)
}
async getMessageReaction(messageID: BigString, reaction: string, options?: GetMessageReactionOptions): Promise<User[]> {
return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options)
}
async getMessages(options: GetMessagesOptions) {
return await this.client.getMessages.call(this.client, this.id, options)
}
async getPins(): Promise<Message[]> {
return await this.client.getPins.call(this.client, this.id)
}
async join(userID: BigString = '@me'): Promise<void> {
return await this.client.joinThread.call(this.client, this.id, userID)
}
async leave(userID: BigString): Promise<void> {
return await this.client.leaveThread.call(this.client, this.id, userID)
}
async pinMessage(messageID: BigString): Promise<void> {
return await this.client.pinMessage.call(this.client, this.id, messageID)
}
async purge(options: PurgeChannelOptions): Promise<number> {
return await this.client.purgeChannel.call(this.client, this.id, options)
}
async removeMessageReaction(messageID: BigString, reaction: string, userID: BigString = '@me') {
return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID)
}
async removeMessageReactionEmoji(messageID: BigString, reaction: string): Promise<void> {
return await this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction)
}
async removeMessageReactions(messageID: BigString): Promise<void> {
return await this.client.removeMessageReactions.call(this.client, this.id, messageID)
}
async sendTyping(): Promise<void> {
return await this.client.sendChannelTyping.call(this.client, this.id)
}
async unpinMessage(messageID: BigString): Promise<void> {
return await this.client.unpinMessage.call(this.client, this.id, messageID)
}
/**
* @deprecated Use deleteMessage instead
*/
async unsendMessage(messageID: BigString): Promise<void> {
return await this.client.deleteMessage.call(this.client, this.id, messageID)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'lastMessageID',
'memberCount',
'messageCount',
'messages',
'ownerID',
'rateLimitPerUser',
'threadMetadata',
'member',
...props,
])
}
}
export default ThreadChannel

View File

@@ -1,181 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordAuditLogChange, DiscordAuditLogEntry } from '@discordeno/types'
import { AuditLogEvents } from '@discordeno/types'
import Base from '../../Base.js'
import type GuildChannel from '../channels/Guild.js'
import type TextChannel from '../channels/Text.js'
import Invite from '../Invite.js'
import type Message from '../Message.js'
import type User from '../users/User.js'
import type Guild from './Guild.js'
import type Member from './Member.js'
import type Role from './Role.js'
export class GuildAuditLogEntry extends Base {
/** The guild to which this entry belongs. */
guild: Guild
/** The action type of the entry. */
actionType: AuditLogEvents
/** The reason for the action. */
reason: string | null
/** The user that performed the action. */
user?: User
/** The properties of the targeted object before the action was taken. For example, if a channel was renamed from #general to #potato, this would be `{name: "general"}`` */
before: Record<DiscordAuditLogChange['key'], DiscordAuditLogChange['old_value']>
/** The properties of the targeted object after the action was taken. For example, if a channel was renamed from #general to #potato, this would be `{name: "potato"}`` */
after: Record<DiscordAuditLogChange['key'], DiscordAuditLogChange['new_value']>
/** The ID of the action target */
targetID?: string
/** The number of entities targeted. For example, for action type 26 (MEMBER_MOVE), this is the number of members that were moved/disconnected from the voice channel */
count?: number
/** The channel targeted in the entry, action types 26 (MEMBER_MOVE), 72/74/75 (MESSAGE_DELETE/PIN/UNPIN) and 83/84/85 (STAGE_INSTANCE_CREATE/UPDATE/DELETE) only */
channel?: GuildChannel
/** The message that was (un)pinned, action types 74/75 (MESSAGE_PIN/UNPIN) only. If the message is not cached, this will be an object with an `id` key. No other property is guaranteed. */
message?: Message | { id: string }
/** The number of days of inactivity to prune for, action type 21 (MEMBER_PRUNE) only */
deleteMemberDays?: number
/** The number of members pruned from the server, action type 21 (MEMBER_PRUNE) only */
membersRemoved?: number
/** The member described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the member is not cached, this could be {id: String} */
member?: Member | { id: string }
/** The role described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the role is not cached, this could be {id: String, name: String} */
role?: Role | { id: string; name: string }
constructor(data: DiscordAuditLogEntry, guild: Guild) {
super(data.id)
this.guild = guild
this.actionType = data.action_type
this.reason = data.reason ?? null
this.user = data.user_id ? guild.client.users.get(data.user_id) : undefined
this.before = {} as any
this.after = {} as any
if (data.changes) {
data.changes.forEach((change) => {
if (change.old_value !== undefined) {
this.before[change.key] = change.old_value
}
if (change.new_value !== undefined) {
this.after[change.key] = change.new_value
}
})
}
if (data.target_id) {
this.targetID = data.target_id
}
if (data.options) {
if (data.options.count) {
this.count = +data.options.count
}
if (data.options.channel_id) {
if (this.actionType >= 83) {
this.channel = guild.threads.get(data.options.channel_id)
} else {
this.channel = guild.channels.get(data.options.channel_id)
}
if (data.options.message_id) {
this.message = (this.channel && (this.channel as TextChannel).messages.get(data.options.message_id)) ?? {
id: data.options.message_id,
}
}
}
if (data.options.delete_member_days) {
this.deleteMemberDays = +data.options.delete_member_days
this.membersRemoved = +data.options.members_removed
}
if (data.options.type) {
if (data.options.type === '1') {
this.member = guild.members.get(data.options.id) ?? {
id: data.options.id,
}
} else if (data.options.type === '0') {
this.role = guild.roles.get(data.options.id) ?? {
id: data.options.id,
name: data.options.role_name,
}
}
}
}
}
get target() {
// pay more, get less
if (this.actionType < 10) {
// Guild
return this.guild
} else if (this.actionType < 20) {
// Channel
return this.guild?.channels.get(this.targetID!)
} else if (this.actionType < 30) {
// Member
if (this.actionType === AuditLogEvents.MemberMove || this.actionType === AuditLogEvents.MemberDisconnect) {
// MEMBER_MOVE / MEMBER_DISCONNECT
return null
}
return this.guild?.members.get(this.targetID!)
} else if (this.actionType < 40) {
// Role
return this.guild?.roles.get(this.targetID!)
} else if (this.actionType < 50) {
// Invite
const changes = this.actionType === 42 ? this.before : this.after // Apparently the meaning of life is a deleted invite
return new Invite(
{
code: changes.code as string,
// @ts-expect-error idk why this is happening
channel: changes.channel,
guild: this.guild.toJSON(),
uses: changes.uses as number,
max_uses: changes.max_uses as number,
max_age: changes.max_age as number,
temporary: changes.temporary as boolean,
},
this.guild?.client,
)
} else if (this.actionType < 60) {
// Webhook
return null // Go get the webhook yourself
} else if (this.actionType < 70) {
// Emoji
return this.guild?.emojis?.find((emoji) => emoji.id === this.targetID)
} else if (this.actionType < 80) {
// Message
return this.guild?.client.users.get(this.targetID!)
} else if (this.actionType < 83) {
// Integrations
return null
} else if (this.actionType < 90) {
// Stage Instances
return this.guild?.threads.get(this.targetID!)
} else if (this.actionType < 100) {
// Sticker
return this.guild?.stickers?.find((sticker) => sticker.id === this.targetID)
} else {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
throw new Error('Unrecognized action type: ' + this.actionType)
}
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'actionType',
'after',
'before',
'channel',
'count',
'deleteMemberDays',
'member',
'membersRemoved',
'reason',
'role',
'targetID',
'user',
...props,
])
}
}
export default GuildAuditLogEntry

View File

@@ -1,952 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import {
BitwisePermissionFlags,
ChannelTypes,
type ApplicationCommandTypes,
type BigString,
type DefaultMessageNotificationLevels,
type DiscordEmoji,
type DiscordGuild,
type DiscordMemberWithUser,
type DiscordSticker,
type ExplicitContentFilterLevels,
type GuildFeatures,
type GuildNsfwLevel,
type MfaLevels,
type PremiumTiers,
type RequestGuildMembers,
type SystemChannelFlags,
type VerificationLevels,
} from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { ImageFormat, ImageSize } from '../../Client.js'
import Collection from '../../Collection.js'
import { BANNER, GUILD_DISCOVERY_SPLASH, GUILD_ICON, GUILD_SPLASH } from '../../Endpoints.js'
import type Shard from '../../gateway/Shard.js'
import type {
AnyGuildChannel,
AnyThreadChannel,
ApplicationCommand,
ApplicationCommandPermissions,
ApplicationCommandStructure,
ChannelPosition,
CreateChannelOptions,
CreateStickerOptions,
DiscoveryMetadata,
DiscoveryOptions,
DiscoverySubcategoryResponse,
EditStickerOptions,
Emoji,
EmojiOptions,
GetGuildAuditLogOptions,
GetGuildBansOptions,
GetPruneOptions,
GetRESTGuildMembersOptions,
GuildApplicationCommandPermissions,
GuildAuditLog,
GuildBan,
GuildOptions,
GuildTemplateOptions,
GuildVanity,
IntegrationOptions,
ListedGuildThreads,
MemberOptions,
PruneMemberOptions,
RoleOptions,
Sticker,
VoiceRegion,
VoiceStateOptions,
Webhook,
WelcomeScreen,
WelcomeScreenOptions,
Widget,
WidgetData,
} from '../../typings.js'
import { generateChannelFrom } from '../../utils/generate.js'
import type CategoryChannel from '../channels/Category.js'
import type GuildChannel from '../channels/Guild.js'
import type StageChannel from '../channels/Stage.js'
import type TextChannel from '../channels/Text.js'
import type TextVoiceChannel from '../channels/TextVoice.js'
import type ThreadChannel from '../channels/threads/Thread.js'
import type VoiceChannel from '../channels/Voice.js'
import type Invite from '../Invite.js'
import Permission from '../Permission.js'
import User from '../users/User.js'
import type GuildIntegration from './Integration.js'
import Member from './Member.js'
import Role from './Role.js'
import StageInstance from './StageInstance.js'
import type GuildTemplate from './Template.js'
import type { VoiceState } from './VoiceState.js'
export class Guild extends Base {
/** The client object */
client: Client
/** The id of the guild owner. */
ownerID: string
/** The id of the application. */
applicationID?: string | null
/** The id of the widget channel. */
widgetChannelID?: string | null
/** The afk channel id if one is set. */
afkChannelID?: string | null
/** The system channel id if one is set. */
systemChannelID?: string | null
/** The public updates channel id if one is set. */
publicUpdatesChannelID?: string | null
/** The rules channel id if one is set. */
rulesChannelID?: string | null
/** The name of the guild. */
name?: string
/** The description of the guild. */
description?: string | null
/** The vanity url if one is set. */
vanityURL?: string | null
/** The preferred locale of the server. */
preferredLocale?: string
/** The system channel flags. */
systemChannelFlags?: SystemChannelFlags
/** The verification level of the guild. */
verificationLevel?: VerificationLevels
/** The default notification level. */
defaultNotifications?: DefaultMessageNotificationLevels
/** The explicit content filter setting for this guild. */
explicitContentFilter?: ExplicitContentFilterLevels
/** Array of guild features */
features: GuildFeatures[] = []
/** The premium tier of the guild. */
premiumTier?: PremiumTiers
/** The MFA level of the guild. */
mfaLevel?: MfaLevels
/** The NSFW level of the guild. */
nsfwLevel?: GuildNsfwLevel
/** The compressed form of the guild splash image. */
_splash?: bigint
/** The compressed form of the guild's discovery splash image. */
_discoverySplash?: bigint
/** The compressed form of the guild's banner image. */
_banner?: bigint
/** The compressed form of the guild's icon image. */
_icon?: bigint
/** The cached emojis in the guild. */
emojis?: DiscordEmoji[]
/** The cached stickers in the guild. */
stickers?: DiscordSticker[]
/** The afk timeout in seconds. */
afkTimeout?: number
/** When this guild was joined at. */
joinedAt: number
/** The amount of members in the guild. */
memberCount: number
/** The approximate member count in the guild. */
approximateMemberCount?: number
/** The approximate presence count in the guild. */
approximatePresenceCount?: number
/** The amount of subscribers to the server. */
premiumSubscriptionCount?: number
/** The maximum amount of presences that can be in a guild. */
maxPresences?: number | null
/** The maximum amount of members that can be in the guild. */
maxMembers?: number
/** The maximum amount of members that can be in a video channel. */
maxVideoChannelUsers?: number | null
/** Whether or not this guild is unavailable. */
unavailable: boolean
/** Whether or not the widget is enabled in this guild. */
widgetEnabled: boolean
/** Whether or not this guild is considered large. */
large?: boolean
/** Whether or not the premium progress bar is enabled. */
premiumProgressBarEnabled?: boolean
/** Whether or not this server is nsfw. */
nsfw?: boolean
/** The welcome screen settings. */
welcomeScreen?: {
description: string | null
welcomeChannels?: Array<{
channelID: string
description: string
emojiID: string | null
emojiName: string | null
}>
}
/** The cached members in this guild. */
members = new Collection<BigString, Member>()
/** The cached roles in this guild. */
roles = new Collection<BigString, Role>()
/** The cached channels in this guild. */
channels = new Collection<BigString, GuildChannel>()
/** The cached threads in this guild. */
threads = new Collection<BigString, ThreadChannel>()
/** The cached voice states in this guild. */
voiceStates = new Collection<BigString, VoiceState>()
/** The cached stage instances in this guild. */
stageInstances = new Collection<BigString, StageInstance>()
/** The shard that manages this guild. */
shard: Shard;
constructor(data: DiscordGuild, client: Client) {
super(data.id)
this.client = client
this.shard = client.shards.get(client.guildShardMap[this.id] || (Base.getDiscordEpoch(data.id) % (client.options.maxShards as number)) || 0)!;
this.ownerID = data.owner_id
this.unavailable = !!data.unavailable
this.joinedAt = Date.parse(data.joined_at!)
this.memberCount = data.member_count ?? 0
this.applicationID = data.application_id
this.widgetEnabled = !!data.widget_enabled
if (data.widget_channel_id !== undefined) {
this.widgetChannelID = data.widget_channel_id
}
if (data.approximate_member_count !== undefined) {
this.approximateMemberCount = data.approximate_member_count
}
if (data.approximate_presence_count !== undefined) {
this.approximatePresenceCount = data.approximate_presence_count
}
if (data.roles) {
for (const r of data.roles) {
const role = new Role(r, this)
this.roles.set(role.id, role)
}
}
if (data.channels) {
for (const channelData of data.channels) {
channelData.guild_id = this.id.toString()
const channel = generateChannelFrom(channelData, client) as GuildChannel
this.channels.set(channel.id, channel)
client._channelGuildMap.set(channel.id, this.id)
}
}
if (data.threads) {
for (const threadData of data.threads) {
threadData.guild_id = this.id.toString()
const thread = generateChannelFrom(threadData, client) as unknown as ThreadChannel
this.threads.set(thread.id, thread)
client._threadGuildMap.set(thread.id, this.id)
}
}
if (data.members) {
for (const m of data.members) {
const member = new Member(m as DiscordMemberWithUser, this, client)
this.members.set(member.id, member)
}
}
if (data.stage_instances) {
for (const stageInstance of data.stage_instances) {
stageInstance.guild_id = this.id
const instance = new StageInstance(stageInstance, client)
this.stageInstances.set(instance.id, instance)
}
}
if (data.presences) {
for (const presence of data.presences) {
if (presence.user?.id) {
const cached = this.client.users.get(presence.user.id)
if (cached) cached.update(presence.user)
else {
const user = new User(presence.user, this.client)
this.client.users.set(user.id, user)
}
}
}
}
if (data.voice_states) {
for (const voiceState of data.voice_states) {
if (!this.members.get(voiceState.user_id)) continue
if (voiceState.member) {
const member = new Member(voiceState.member, this, client)
this.members.set(member.id, member)
const user = new User(voiceState.member.user, client)
this.client.users.set(user.id, user)
// TODO: check channel type maybe voice channel?
;(this.channels.get(voiceState.channel_id!) as VoiceChannel)?.voiceMembers.set(member.id, member)
}
// TODO: voice support
// if (
// client.options.seedVoiceConnections &&
// voiceState.user_id === client.id &&
// !client.voiceConnections.get(this.id)
// ) {
// process.nextTick(() =>
// this.client.joinVoiceChannel(voiceState.channel_id)
// );
// }
}
}
this.update(data)
}
/**
* @deprecated - please use .client
*/
get _client() {
return this.client
}
update(data: DiscordGuild) {
if (data.name !== undefined) {
this.name = data.name
}
if (data.verification_level !== undefined) {
this.verificationLevel = data.verification_level
}
if (data.splash !== undefined) {
this._splash = data.splash ? this.client.iconHashToBigInt(data.splash) : undefined
}
if (data.discovery_splash !== undefined) {
this._discoverySplash = data.discovery_splash ? this.client.iconHashToBigInt(data.discovery_splash) : undefined
}
if (data.banner !== undefined) {
this._banner = data.banner ? this.client.iconHashToBigInt(data.banner) : undefined
}
if (data.owner_id !== undefined) {
this.ownerID = data.owner_id
}
if (data.icon !== undefined) {
this._icon = data.icon ? this.client.iconHashToBigInt(data.icon) : undefined
}
// TODO: compress features.
if (data.features !== undefined) {
this.features = data.features
}
if (data.emojis !== undefined) {
this.emojis = data.emojis
}
if (data.stickers !== undefined) {
this.stickers = data.stickers
}
if (data.afk_channel_id !== undefined) {
this.afkChannelID = data.afk_channel_id
}
if (data.afk_timeout !== undefined) {
this.afkTimeout = data.afk_timeout
}
if (data.default_message_notifications !== undefined) {
this.defaultNotifications = data.default_message_notifications
}
if (data.mfa_level !== undefined) {
this.mfaLevel = data.mfa_level
}
if (data.large !== undefined) {
this.large = data.large
}
if (data.max_presences !== undefined) {
this.maxPresences = data.max_presences
}
if (data.explicit_content_filter !== undefined) {
this.explicitContentFilter = data.explicit_content_filter
}
if (data.system_channel_id !== undefined) {
this.systemChannelID = data.system_channel_id
}
if (data.system_channel_flags !== undefined) {
this.systemChannelFlags = data.system_channel_flags
}
if (data.premium_progress_bar_enabled !== undefined) {
this.premiumProgressBarEnabled = data.premium_progress_bar_enabled
}
if (data.premium_tier !== undefined) {
this.premiumTier = data.premium_tier
}
if (data.premium_subscription_count !== undefined) {
this.premiumSubscriptionCount = data.premium_subscription_count
}
if (data.vanity_url_code !== undefined) {
this.vanityURL = data.vanity_url_code
}
if (data.preferred_locale !== undefined) {
this.preferredLocale = data.preferred_locale
}
if (data.description !== undefined) {
this.description = data.description
}
if (data.max_members !== undefined) {
this.maxMembers = data.max_members
}
if (data.public_updates_channel_id !== undefined) {
this.publicUpdatesChannelID = data.public_updates_channel_id
}
if (data.rules_channel_id !== undefined) {
this.rulesChannelID = data.rules_channel_id
}
if (data.max_video_channel_users !== undefined) {
this.maxVideoChannelUsers = data.max_video_channel_users
}
if (data.welcome_screen !== undefined) {
this.welcomeScreen = {
description: data.welcome_screen.description,
welcomeChannels: data.welcome_screen.welcome_channels?.map((c) => {
return {
channelID: c.channel_id,
description: c.description,
emojiID: c.emoji_id,
emojiName: c.emoji_name,
}
}),
}
}
// if (data.nsfw !== undefined) {
// this.nsfw = data.nsfw;
// }
if (data.nsfw_level !== undefined) {
this.nsfwLevel = data.nsfw_level
}
}
get banner(): string | undefined {
return this._banner ? this.client.iconBigintToHash(this._banner) : undefined
}
get bannerURL(): string | null {
return this.banner ? this.client._formatImage(BANNER(this.id, this.banner)) : null
}
get icon(): string | undefined {
return this._icon ? this.client.iconBigintToHash(this._icon) : undefined
}
get iconURL(): string | null {
return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon)) : null
}
get splash(): string | undefined {
return this._splash ? this.client.iconBigintToHash(this._splash) : undefined
}
get splashURL(): string | null {
return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash)) : null
}
get discoverySplash(): string | undefined {
return this._discoverySplash ? this.client.iconBigintToHash(this._discoverySplash) : undefined
}
get discoverySplashURL(): string | null {
return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash)) : null
}
/** Add a discovery subcategory */
async addDiscoverySubcategory(categoryID: BigString, reason?: string): Promise<DiscoverySubcategoryResponse> {
return await this.client.addGuildDiscoverySubcategory.call(this.client, this.id, categoryID, reason)
}
/** Add a role to a guild member */
async addMemberRole(memberID: BigString, roleID: BigString, reason?: string): Promise<void> {
return await this.client.addGuildMemberRole.call(this.client, this.id, memberID, roleID, reason)
}
/** Ban a user from the guild */
async banMember(userID: BigString, deleteMessageDays = 0, reason?: string): Promise<void> {
return await this.client.banGuildMember.call(this.client, this.id, userID, deleteMessageDays, reason)
}
/** Bulk create/edit guild application commands */
async bulkEditCommands(commands: Array<ApplicationCommand<ApplicationCommandTypes>>): Promise<Array<ApplicationCommand<ApplicationCommandTypes>>> {
return await this.client.bulkEditGuildCommands.call(this.client, this.id, commands)
}
/** Create a channel in the guild */
async createChannel(
name: string,
type = ChannelTypes.GuildText,
options: CreateChannelOptions,
): Promise<CategoryChannel | TextChannel | TextVoiceChannel | StageChannel> {
return await this.client.createChannel.call(this.client, this.id, name, type as number, options)
}
/** Create a guild application command */
async createCommand(command: ApplicationCommandStructure): Promise<ApplicationCommand<ApplicationCommandTypes>> {
return await this.client.createGuildCommand.call(this.client, this.id, command)
}
/** Create a emoji in the guild */
async createEmoji(options: EmojiOptions, reason?: string): Promise<Emoji> {
return await this.client.createGuildEmoji.call(this.client, this.id, options, reason)
}
/** Create a guild role */
async createRole(options: Role | RoleOptions, reason?: string): Promise<Role> {
return await this.client.createRole.call(this.client, this.id, options, reason)
}
/** Create a guild sticker */
async createSticker(options: CreateStickerOptions, reason?: string): Promise<Sticker> {
return await this.client.createGuildSticker.call(this.client, this.id, options, reason)
}
/** Create a template for this guild */
async createTemplate(name: string, description?: string): Promise<GuildTemplate> {
return await this.client.createGuildTemplate.call(this.client, this.id, name, description)
}
/** Delete the guild (bot user must be owner) */
async delete(): Promise<void> {
if (this.ownerID !== this.client.id) throw new Error('To delete a guild, the bot must be the owner of the guild.')
return await this.client.deleteGuild.call(this.client, this.id)
}
/** Delete a guild application command */
async deleteCommand(commandID: BigString): Promise<void> {
return await this.client.deleteGuildCommand.call(this.client, this.id, commandID)
}
/** Delete a discovery subcategory */
async deleteDiscoverySubcategory(categoryID: BigString, reason?: string): Promise<void> {
return await this.client.deleteGuildDiscoverySubcategory.call(this.client, this.id, categoryID, reason)
}
/** Delete a emoji in the guild */
async deleteEmoji(emojiID: BigString, reason?: string): Promise<void> {
return await this.client.deleteGuildEmoji.call(this.client, this.id, emojiID, reason)
}
/** Delete a guild integration */
async deleteIntegration(integrationID: BigString): Promise<void> {
return await this.client.deleteGuildIntegration.call(this.client, this.id, integrationID)
}
/** Delete a role */
async deleteRole(roleID: BigString, reason?: string): Promise<void> {
return await this.client.deleteRole.call(this.client, this.id, roleID, reason)
}
/** Delete a guild sticker */
async deleteSticker(stickerID: BigString, reason?: string): Promise<void> {
return await this.client.deleteGuildSticker.call(this.client, this.id, stickerID, reason)
}
/** Delete a guild template */
async deleteTemplate(code: string): Promise<void> {
return await this.client.deleteGuildTemplate.call(this.client, this.id, code)
}
/** Get the guild's banner with the given format and size */
dynamicBannerURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.banner ? this.client._formatImage(BANNER(this.id, this.banner), format, size) : null
}
/** Get the guild's discovery splash with the given format and size */
dynamicDiscoverySplashURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), format, size) : null
}
/** Get the guild's icon with the given format and size */
dynamicIconURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon), format, size) : null
}
/** Get the guild's splash with the given format and size */
dynamicSplashURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash), format, size) : null
}
/** Edit the guild */
async edit(options: GuildOptions, reason?: string): Promise<Guild> {
return await this.client.editGuild.call(this.client, this.id, options, reason)
}
/** Edit multiple channels' positions. Note that channel position numbers are grouped by type (category, text, voice), then sorted in ascending order (lowest number is on top). */
async editChannelPositions(channelPositions: ChannelPosition[]): Promise<void> {
return await this.client.editChannelPositions.call(this.client, this.id, channelPositions)
}
/** Edit a guild application command */
async editCommand(commandID: BigString, commands: ApplicationCommandStructure): Promise<ApplicationCommand<ApplicationCommandTypes>> {
return await this.client.editGuildCommand.call(this.client, this.id, commandID, commands)
}
/**
* Edits command permissions for a specific command in a guild.
* Note: You can only add up to 10 permission overwrites for a command.
*/
async editCommandPermissions(commandID: BigString, permissions: ApplicationCommandPermissions[]): Promise<GuildApplicationCommandPermissions> {
return await this.client.editCommandPermissions.call(this.client, this.id, commandID, permissions)
}
/** Edit the guild's discovery data */
async editDiscovery(options: DiscoveryOptions): Promise<DiscoveryMetadata> {
return await this.client.editGuildDiscovery.call(this.client, this.id, options)
}
/**
* Edit a emoji in the guild
* @arg {String} emojiID The ID of the emoji you want to modify
* @arg {Object} options Emoji options
* @arg {String} [options.name] The name of emoji
* @arg {Array} [options.roles] An array containing authorized role IDs
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Object>} A guild emoji object
*/
async editEmoji(
emojiID: BigString,
options: {
name?: string | undefined
roles?: string[] | undefined
},
reason?: string,
): Promise<Emoji> {
return await this.client.editGuildEmoji.call(this.client, this.id, emojiID, options, reason)
}
/** Edit a guild integration */
async editIntegration(integrationID: BigString, options: IntegrationOptions): Promise<void> {
return await this.client.editGuildIntegration.call(this.client, this.id, integrationID, options)
}
/** Edit a guild member */
async editMember(memberID: BigString, options: MemberOptions, reason?: string): Promise<Member> {
return await this.client.editGuildMember.call(this.client, this.id, memberID, options, reason)
}
/** Edit the guild role */
async editRole(roleID: BigString, options: RoleOptions, reason?: string): Promise<Role> {
return await this.client.editRole.call(this.client, this.id, roleID, options, reason)
}
/** Edit a guild sticker */
async editSticker(stickerID: BigString, options: EditStickerOptions, reason?: string): Promise<Sticker> {
return await this.client.editGuildSticker.call(this.client, this.id, stickerID, options, reason)
}
/** Edit a guild template */
async editTemplate(code: string, options: GuildTemplateOptions): Promise<GuildTemplate> {
return await this.client.editGuildTemplate.call(this.client, this.id, code, options)
}
/** Modify the guild's vanity code */
async editVanity(code: string | null): Promise<unknown> {
return await this.client.editGuildVanity.call(this.client, this.id, code)
}
/** Update a user's voice state - See [caveats](https://discord.com/developers/docs/resources/guild#modify-user-voice-state-caveats) */
async editVoiceState(options: VoiceStateOptions, userID: BigString = '@me'): Promise<void> {
return await this.client.editGuildVoiceState.call(this.client, this.id, options, userID)
}
/** Edit the guild welcome screen */
async editWelcomeScreen(options: WelcomeScreenOptions): Promise<WelcomeScreen> {
return await this.client.editGuildWelcomeScreen.call(this.client, this.id, options)
}
/** Modify a guild's widget */
async editWidget(options: Widget): Promise<Widget> {
return await this.client.editGuildWidget.call(this.client, this.id, options)
}
/** Request all guild members from Discord */
async fetchAllMembers(timeout?: number): Promise<number> {
return await this.fetchMembers({
guildId: this.id,
limit: 0,
}).then((m: any[]) => m.length)
}
/** Request specific guild members through the gateway connection */
async fetchMembers(options: RequestGuildMembers): Promise<Member[]> {
// TODO: Use gateway fetch
return await this.client.getRESTGuildMembers(this.id, options)
}
/** Get all active threads in this guild */
async getActiveThreads(): Promise<ListedGuildThreads<AnyThreadChannel>> {
return await this.client.getActiveGuildThreads.call(this.client, this.id)
}
/** Get the audit log for the guild */
async getAuditLog(options: GetGuildAuditLogOptions): Promise<GuildAuditLog> {
return await this.client.getGuildAuditLog.call(this.client, this.id, options)
}
/** Get a ban from the ban list of a guild */
async getBan(userID: BigString): Promise<GuildBan> {
return await this.client.getGuildBan.call(this.client, this.id, userID)
}
/** Get the ban list of the guild */
async getBans(options?: GetGuildBansOptions): Promise<GuildBan[]> {
return await this.client.getGuildBans.call(this.client, this.id, options)
}
/** Get a guild application command */
async getCommand(commandID: BigString): Promise<ApplicationCommand<ApplicationCommandTypes>> {
return await this.client.getGuildCommand.call(this.client, this.id, commandID)
}
/** Get the a guild's application command permissions */
async getCommandPermissions(commandID: BigString): Promise<GuildApplicationCommandPermissions> {
return await this.client.getCommandPermissions.call(this.client, this.id, commandID)
}
/** Get the guild's application commands */
async getCommands(): Promise<ApplicationCommand<ApplicationCommandTypes>> {
return await this.client.getGuildCommands.call(this.client, this.id)
}
/** Get the guild's discovery object */
async getDiscovery(): Promise<DiscoveryMetadata> {
return await this.client.getGuildDiscovery.call(this.client, this.id)
}
/** Get the all of a guild's application command permissions */
async getGuildCommandPermissions(): Promise<GuildApplicationCommandPermissions[]> {
return await this.client.getGuildCommandPermissions.call(this.client, this.id)
}
/** Get a list of integrations for the guild */
async getIntegrations(): Promise<GuildIntegration[]> {
return await this.client.getGuildIntegrations.call(this.client, this.id)
}
/** Get all invites in the guild */
async getInvites(): Promise<Invite[]> {
return await this.client.getGuildInvites.call(this.client, this.id)
}
/** Get the prune count for the guild */
async getPruneCount(options: GetPruneOptions): Promise<number> {
return await this.client.getPruneCount.call(this.client, this.id, options)
}
/** Get a guild's channels via the REST API. REST mode is required to use this endpoint. */
async getRESTChannels(): Promise<AnyGuildChannel[]> {
return await this.client.getRESTGuildChannels.call(this.client, this.id)
}
/** Get a guild emoji via the REST API. REST mode is required to use this endpoint. */
async getRESTEmoji(emojiID: BigString): Promise<Emoji> {
return await this.client.getRESTGuildEmoji.call(this.client, this.id, emojiID)
}
/** Get a guild's emojis via the REST API. REST mode is required to use this endpoint. */
async getRESTEmojis(): Promise<Emoji[]> {
return await this.client.getRESTGuildEmojis.call(this.client, this.id)
}
/** Get a guild's members via the REST API. REST mode is required to use this endpoint. */
async getRESTMember(memberID: BigString): Promise<Member> {
return await this.client.getRESTGuildMember.call(this.client, this.id, memberID)
}
/** Get a guild's members via the REST API. REST mode is required to use this endpoint. */
async getRESTMembers(options?: GetRESTGuildMembersOptions): Promise<Member[]> {
return await this.client.getRESTGuildMembers.call(this.client, this.id, options)
}
/** Get a guild's roles via the REST API. REST mode is required to use this endpoint. */
async getRESTRoles(): Promise<Role[]> {
return await this.client.getRESTGuildRoles.call(this.client, this.id)
}
/** Get a guild sticker via the REST API. REST mode is required to use this endpoint. */
async getRESTSticker(stickerID: BigString): Promise<Sticker> {
return await this.client.getRESTGuildSticker.call(this.client, this.id, stickerID)
}
/** Get a guild's stickers via the REST API. REST mode is required to use this endpoint. */
async getRESTStickers(): Promise<Sticker[]> {
return await this.client.getRESTGuildStickers.call(this.client, this.id)
}
/** Get the guild's templates */
async getTemplates(): Promise<GuildTemplate[]> {
return await this.client.getGuildTemplates.call(this.client, this.id)
}
/** Returns the vanity url of the guild */
async getVanity(): Promise<GuildVanity> {
return await this.client.getGuildVanity.call(this.client, this.id)
}
/** Get possible voice regions for a guild */
async getVoiceRegions(): Promise<VoiceRegion[]> {
return await this.client.getVoiceRegions.call(this.client, this.id)
}
/** Get all the webhooks in the guild */
async getWebhooks(): Promise<Webhook[]> {
return await this.client.getGuildWebhooks.call(this.client, this.id)
}
/** Get the welcome screen of the Community guild, shown to new members */
async getWelcomeScreen(): Promise<WelcomeScreen> {
return await this.client.getGuildWelcomeScreen.call(this.client, this.id)
}
/** Get a guild's widget object */
async getWidget(): Promise<WidgetData> {
return await this.client.getGuildWidget.call(this.client, this.id)
}
/** Get a guild's widget settings object */
async getWidgetSettings(): Promise<Widget> {
return await this.client.getGuildWidgetSettings.call(this.client, this.id)
}
/** Kick a member from the guild */
async kickMember(userID: BigString, reason?: string): Promise<void> {
return await this.client.kickGuildMember.call(this.client, this.id, userID, reason)
}
/** Leave the guild */
async leave(): Promise<void> {
return await this.client.leaveGuild.call(this.client, this.id)
}
// TODO: gateway voice
// /** Leaves the voice channel in this guild */
// async leaveVoiceChannel(): Promise<void> {
// return await this.client.closeVoiceConnection.call(this.client, this.id);
// }
/** Get the guild permissions of a member */
permissionsOf(memberID: BigString | Member): Permission {
const member = ['string', 'bigint'].includes(typeof memberID) ? this.members.get(memberID as BigString)! : (memberID as Member)
if (member.id === this.ownerID) {
return new Permission(BitwisePermissionFlags.ADMINISTRATOR)
} else {
let permissions = this.roles.get(this.id)!.permissions.allow
if (permissions & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) {
return new Permission(BitwisePermissionFlags.ADMINISTRATOR)
}
for (const id of member.roles) {
const role = this.roles.get(id)
if (!role) {
continue
}
const { allow: perm } = role.permissions
if (perm & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) {
permissions = BigInt(BitwisePermissionFlags.ADMINISTRATOR)
break
} else {
permissions |= perm
}
}
return new Permission(permissions)
}
}
/** Begin pruning the guild */
async pruneMembers(options?: PruneMemberOptions): Promise<number> {
return await this.client.pruneMembers.call(this.client, this.id, options)
}
/** Remove a role from a guild member */
async removeMemberRole(memberID: BigString, roleID: BigString, reason?: string): Promise<void> {
return await this.client.removeGuildMemberRole.call(this.client, this.id, memberID, roleID, reason)
}
/** Search for guild members by partial nickname/username */
async searchMembers(query: string, limit = 1): Promise<Member[]> {
return await this.client.searchGuildMembers.call(this.client, this.id, query, limit)
}
/** Force a guild integration to sync */
async syncIntegration(integrationID: BigString): Promise<void> {
return await this.client.syncGuildIntegration.call(this.client, this.id, integrationID)
}
/** Force a guild template to sync */
async syncTemplate(code: string): Promise<GuildTemplate> {
return await this.client.syncGuildTemplate.call(this.client, this.id, code)
}
/** Unban a user from the guild */
async unbanMember(userID: BigString, reason?: string): Promise<void> {
return await this.client.unbanGuildMember.call(this.client, this.id, userID, reason)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'afkChannelID',
'afkTimeout',
'applicationID',
'approximateMemberCount',
'approximatePresenceCount',
'autoRemoved',
'banner',
'categories',
'channels',
'defaultNotifications',
'description',
'discoverySplash',
'emojiCount',
'emojis',
'explicitContentFilter',
'features',
'icon',
'joinedAt',
'keywords',
'large',
'maxMembers',
'maxPresences',
'maxVideoChannelUsers',
'memberCount',
'members',
'mfaLevel',
'name',
'ownerID',
'pendingVoiceStates',
'preferredLocale',
'premiumProgressBarEnabled',
'premiumSubscriptionCount',
'premiumTier',
'primaryCategory',
'primaryCategoryID',
'publicUpdatesChannelID',
'roles',
'rulesChannelID',
'splash',
'stickers',
'systemChannelFlags',
'systemChannelID',
'unavailable',
'vanityURL',
'verificationLevel',
'voiceStates',
'welcomeScreen',
'widgetChannelID',
'widgetEnabled',
...props,
])
}
}
export default Guild

View File

@@ -1,131 +0,0 @@
/* eslint-disable no-useless-call */
import type { DiscordIntegration, DiscordIntegrationApplication } from '@discordeno/types'
import Base from '../../Base.js'
import type { IntegrationOptions } from '../../typings.js'
import User from '../users/User.js'
import type Guild from './Guild.js'
export class GuildIntegration extends Base {
/** The guild where this integration exists. */
guild: Guild
/** The name of the integration. */
name: string
/** The type of integration. */
type: string
/** The user connected to the integration. */
user?: User
/** Whether the integration is syncing or not. */
syncing?: boolean
/** THe Unix timestamp of last integration sync. */
syncedAt?: number
/** The number of subscribers. */
subscriberCount?: number
/** The role id of the role connected to the integration. */
roleID?: string
/** WHether or not the application was revoked. */
revoked?: boolean
/** Whether integration emoticons are enabled or not. */
enableEmoticons?: boolean
/** Behavior of expired subscriptions */
expireBehavior?: number
/** Grace period for expired subscriptions. */
expireGracePeriod?: number
/** Whether the integration is enabled or not. */
enabled?: boolean
/** The bot/oauth2 application for integration. */
application?: DiscordIntegrationApplication
/** Info on the integration account */
account: {
/** The id of the integration account. */
id: string
/** The name of the integration account. */
name: string
}
constructor(data: DiscordIntegration, guild: Guild) {
super(data.id)
this.guild = guild
this.name = data.name
this.type = data.type
if (data.role_id !== undefined) {
this.roleID = data.role_id
}
if (data.user) {
this.user = new User(data.user, guild.client)
guild.client.users.set(this.user.id, this.user)
}
this.account = data.account // not worth making a class for
this.update(data)
}
update(data: DiscordIntegration): void {
this.enabled = data.enabled
if (data.syncing !== undefined) {
this.syncing = data.syncing
}
if (data.expire_behavior !== undefined) {
this.expireBehavior = data.expire_behavior
}
if (data.expire_behavior !== undefined) {
this.expireGracePeriod = data.expire_grace_period
}
if (data.enable_emoticons) {
this.enableEmoticons = data.enable_emoticons
}
if (data.subscriber_count !== undefined) {
this.subscriberCount = data.subscriber_count
}
if (data.synced_at !== undefined) {
this.syncedAt = Date.parse(data.synced_at)
}
if (data.revoked !== undefined) {
this.revoked = data.revoked
}
if (data.application !== undefined) {
this.application = data.application
}
}
/** Delete the guild integration */
async delete(): Promise<void> {
return await this.guild.client.deleteGuildIntegration.call(this.guild.client, this.guild.id, this.id)
}
/** Edit the guild integration */
async edit(options: IntegrationOptions): Promise<void> {
return await this.guild.client.editGuildIntegration.call(this.guild.client, this.guild.id, this.id, options)
}
/** Force the guild integration to sync */
async sync(): Promise<void> {
return await this.guild.client.syncGuildIntegration.call(this.guild.client, this.guild.id, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'account',
'application',
'enabled',
'enableEmoticons',
'expireBehavior',
'expireGracePeriod',
'name',
'revoked',
'roleID',
'subscriberCount',
'syncedAt',
'syncing',
'type',
'user',
...props,
])
}
}
export default GuildIntegration

View File

@@ -1,222 +0,0 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { BigString, DiscordMember, DiscordMemberWithUser } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { ImageFormat, ImageSize } from '../../Client.js'
import { GUILD_AVATAR } from '../../Endpoints.js'
import type { MemberOptions, Status } from '../../typings.js'
import User from '../users/User.js'
import type Guild from './Guild.js'
export class Member extends Base {
/** The client manager */
client: Client
/** An array of role IDs this member is a part of */
roles: string[]
/** The guild the member is in */
guild: Guild
/** The user object of the member */
user: User
/** The server nickname of the member. */
nick: string | null
/** The timestamp when this member joined the server. */
joinedAt?: number
/** Timestamp of when the member boosted the guild */
premiumSince?: number
/** Whether the user has not yet passed the guild's Membership Screening requirements */
pending?: boolean
/** Timestamp of timeout expiry. If `null`, the member is not timed out */
communicationDisabledUntil?: number | null
/** The members current status */
status?: Status;
/** The compressed form of the members avatar. */
_avatar?: bigint
constructor(data: (DiscordMember & { id: BigString }) | DiscordMemberWithUser, guild: Guild, client: Client) {
super(client.isDiscordMemberWithUser(data) ? data.user.id : data.id)
this.client = client
this.guild = guild
this.nick = null
this.roles = data.roles ?? []
const userID = client.isDiscordMemberWithUser(data) ? data.user.id : data.id
this.user = client.users.get(userID)!
if (data.user) {
this.user = new User(data.user, client)
client.users.set(this.user.id, this.user)
}
if (!this.user) {
throw new Error('User associated with Member not found: ' + userID)
}
this.update(data)
}
update(data: (DiscordMember & { id: BigString }) | DiscordMemberWithUser) {
if (data.joined_at !== undefined) {
this.joinedAt = data.joined_at ? Date.parse(data.joined_at) : undefined
}
if (data.premium_since !== undefined) {
this.premiumSince = data.premium_since === null ? undefined : Date.parse(data.premium_since)
}
// eslint-disable-next-line no-prototype-builtins
if (data.hasOwnProperty('mute') && this.guild) {
// TODO: voice stuff
// const state = this.guild.voiceStates.get(this.id);
// if (
// data.channel_id === null &&
// !data.mute &&
// !data.deaf &&
// !data.suppress
// ) {
// this.guild.voiceStates.delete(this.id);
// } else if (state) {
// state.update(data);
// } else if (data.channel_id || data.mute || data.deaf || data.suppress) {
// this.guild.voiceStates.update(data);
// }
}
if (data.nick !== undefined) this.nick = data.nick
if (data.roles !== undefined) this.roles = data.roles
if (data.pending !== undefined) this.pending = data.pending
if (data.avatar !== undefined) this._avatar = data.avatar ? this.client.iconHashToBigInt(data.avatar) : undefined
if (data.communication_disabled_until !== undefined) {
if (data.communication_disabled_until !== null) {
this.communicationDisabledUntil = Date.parse(data.communication_disabled_until)
} else {
this.communicationDisabledUntil = data.communication_disabled_until
}
}
}
get avatar(): string | undefined {
return this._avatar ? this.client.iconBigintToHash(this._avatar) : undefined
}
get accentColor() {
return this.user.accentColor
}
get avatarURL() {
return this.avatar ? this.client._formatImage(GUILD_AVATAR(this.guild.id, this.id, this.avatar)) : this.user.avatarURL
}
get banner() {
return this.user.banner
}
get bannerURL() {
return this.user.bannerURL
}
get bot() {
return this.user.bot
}
get createdAt() {
return this.user.createdAt
}
get defaultAvatar() {
return this.user.defaultAvatar
}
get defaultAvatarURL() {
return this.user.defaultAvatarURL
}
get discriminator() {
return this.user.discriminator
}
get mention() {
return `<@!${this.id}>`
}
get permissions() {
return this.guild.permissionsOf(this)
}
get staticAvatarURL() {
return this.user.staticAvatarURL
}
get username() {
return this.user.username
}
get voiceState() {
if (this.guild?.voiceStates.has(this.id)) {
return this.guild.voiceStates.get(this.id)
} else {
// @ts-expect-error some eris magic at play here
return new VoiceState({ id: this.id })
}
}
/** Add a role to the guild member */
async addRole(roleID: BigString, reason?: string): Promise<void> {
return await this.client.addGuildMemberRole.call(this.client, this.guild.id, this.id, roleID, reason)
}
/** Ban the user from the guild */
async ban(deleteMessageDays = 0, reason?: string): Promise<void> {
return await this.client.banGuildMember.call(this.client, this.guild.id, this.id, deleteMessageDays, reason)
}
/** Edit the guild member */
async edit(options: MemberOptions, reason?: string): Promise<Member> {
return await this.client.editGuildMember.call(this.client, this.guild.id, this.id, options, reason)
}
/** Get the member's avatar with the given format and size */
dynamicAvatarURL(format?: ImageFormat, size?: ImageSize): string {
return this.avatar
? this.client._formatImage(GUILD_AVATAR(this.guild.id, this.id, this.avatar), format, size)
: this.user.dynamicAvatarURL(format, size)
}
/** Kick the member from the guild */
async kick(reason?: string): Promise<void> {
return await this.client.kickGuildMember.call(this.client, this.guild.id, this.id, reason)
}
/** Remove a role from the guild member */
async removeRole(roleID: BigString, reason?: string): Promise<void> {
return await this.client.removeGuildMemberRole.call(this.client, this.guild.id, this.id, roleID, reason)
}
/** Unban the user from the guild */
async unban(reason?: string): Promise<void> {
return await this.client.unbanGuildMember.call(this.client, this.guild.id, this.id, reason)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'activities',
'communicationDisabledUntil',
'joinedAt',
'nick',
'pending',
'premiumSince',
'roles',
'status',
'user',
'voiceState',
...props,
])
}
}
export default Member

View File

@@ -1,114 +0,0 @@
import type { DiscordEmoji, DiscordGuildPreview } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { ImageFormat, ImageSize } from '../../Client.js'
import { GUILD_DISCOVERY_SPLASH, GUILD_ICON, GUILD_SPLASH } from '../../Endpoints.js'
import { GuildToggles } from '../toggles/Guild.js'
export class GuildPreview extends Base {
/** The client object */
client: Client
/** The name of the guild. */
name: string
/** The description of the guild. */
description: string | null
/** An array of guild emojis. */
emojis: DiscordEmoji[]
/** The approximate number of members in the guild. */
approximateMemberCount: number
/** The approximate number of presences in the guild. */
approximatePresenceCount: number
/** The guild's icon image url. */
_icon: bigint | null
/** The guild's splash image url. */
_splash: bigint | null
/** The guild's discovery splash image url. */
_discoverySplash: bigint | null
/** The guild's features. */
_features: GuildToggles
constructor(data: DiscordGuildPreview, client: Client) {
super(data.id)
this.client = client
this.name = data.name
this.description = data.description
this._icon = data.icon ? client.iconHashToBigInt(data.icon) : null
this._splash = data.splash ? client.iconHashToBigInt(data.splash) : null
this._discoverySplash = data.discovery_splash ? client.iconHashToBigInt(data.discovery_splash) : null
this.approximateMemberCount = data.approximate_member_count
this.approximatePresenceCount = data.approximate_presence_count
this.emojis = data.emojis
// TODO: make dd version accept a specific subset of discord guild here
// @ts-expect-error this should not cause an issue
this._features = new GuildToggles(data)
}
/**
* @deprecated Use .client
*/
get _client(): Client {
return this.client
}
get icon(): string | undefined {
return this._icon ? this.client.iconBigintToHash(this._icon) : undefined
}
get iconURL(): string | null {
return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon)) : null
}
get splash(): string | undefined {
return this._splash ? this.client.iconBigintToHash(this._splash) : undefined
}
get splashURL(): string | null {
return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash)) : null
}
get discoverySplash(): string | undefined {
return this._discoverySplash ? this.client.iconBigintToHash(this._discoverySplash) : undefined
}
get discoverySplashURL(): string | null {
return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash)) : null
}
get features(): string[] {
return this._features.features.map((feature) => feature.replace(/([a-z])([A-Z])/, '$1_$2').toUpperCase())
}
/** Get the guild's splash with the given format and size */
dynamicDiscoverySplashURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), format, size) : null
}
/** Get the guild's icon with the given format and size */
dynamicIconURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon), format, size) : null
}
/** Get the guild's splash with the given format and size */
dynamicSplashURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash), format, size) : null
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'approximateMemberCount',
'approximatePresenceCount',
'description',
'discoverySplash',
'emojis',
'features',
'icon',
'name',
'splash',
...props,
])
}
}
export default GuildPreview

View File

@@ -1,125 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { DiscordRole, DiscordRoleTags } from '@discordeno/types'
import Base from '../../Base.js'
import { ROLE_ICON } from '../../Endpoints.js'
import type { RoleOptions } from '../../typings.js'
import Permission from '../Permission.js'
import type Guild from './Guild.js'
export class Role extends Base {
permissions: Permission
name: string
color: number
hoist: boolean
mentionable: boolean
managed: boolean
icon?: string
unicodeEmoji?: string
position: number
guild: Guild
tags?: Omit<DiscordRoleTags, 'premium_subscriber' | 'available_for_purchase' | 'guild_connections'> & {
premium_subscriber?: boolean
available_for_purchase?: boolean
guild_connections?: boolean
}
constructor(data: DiscordRole, guild: Guild) {
super(data.id)
this.guild = guild
this.name = data.name
this.permissions = new Permission(data.permissions)
this.color = data.color
this.hoist = !!data.hoist
this.mentionable = !!data.mentionable
this.managed = !!data.managed
this.icon = data.icon
this.unicodeEmoji = data.unicode_emoji
this.position = data.position
this.tags = data.tags
? {
bot_id: data.tags.bot_id,
integration_id: data.tags.integration_id,
premium_subscriber: data.tags.premium_subscriber === null,
subscription_listing_id: data.tags.subscription_listing_id,
available_for_purchase: data.tags.available_for_purchase === null,
guild_connections: data.tags.guild_connections === null,
}
: undefined
}
update(data: DiscordRole) {
if (data.name !== undefined) {
this.name = data.name
}
if (data.mentionable !== undefined) {
this.mentionable = data.mentionable
}
if (data.managed !== undefined) {
this.managed = data.managed
}
if (data.hoist !== undefined) {
this.hoist = data.hoist
}
if (data.color !== undefined) {
this.color = data.color
}
if (data.position !== undefined) {
this.position = data.position
}
if (data.permissions !== undefined) {
this.permissions = new Permission(data.permissions)
}
if (data.tags !== undefined) {
this.tags = {
bot_id: data.tags.bot_id,
integration_id: data.tags.integration_id,
subscription_listing_id: data.tags.subscription_listing_id,
available_for_purchase: data.tags.available_for_purchase === null,
premium_subscriber: data.tags.premium_subscriber === null,
}
}
if (data.icon !== undefined) {
this.icon = data.icon
}
if (data.unicode_emoji !== undefined) {
this.unicodeEmoji = data.unicode_emoji
}
}
get iconURL() {
return this.icon ? this.guild.client._formatImage(ROLE_ICON(this.id, this.icon)) : null
}
get json() {
return this.permissions.json
}
get mention() {
return `<@&${this.id}>`
}
/** Delete the role */
async delete(reason: string): Promise<void> {
return await this.guild.client.deleteRole.call(this.guild.client, this.guild.id, this.id, reason)
}
/** Edit the guild role */
async edit(options: RoleOptions, reason?: string): Promise<Role> {
return await this.guild.client.editRole.call(this.guild.client, this.guild.id, this.id, options, reason)
}
/** Edit the role's position. Note that role position numbers are highest on top and lowest at the bottom. */
async editPosition(position: number): Promise<void> {
return await this.guild.client.editRolePosition.call(this.guild.client, this.guild.id, this.id, position)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['color', 'hoist', 'icon', 'managed', 'mentionable', 'name', 'permissions', 'position', 'tags', 'unicodeEmoji', ...props])
}
}
export default Role

View File

@@ -1,56 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import Base from '../../Base.js'
import type { DiscordStageInstance } from '@discordeno/types'
import type Client from '../../Client.js'
import type { StageInstanceOptions } from '../../typings.js'
import type StageChannel from '../channels/Stage.js'
import type Guild from './Guild.js'
export class StageInstance extends Base {
/** The client manager. */
client: Client
/** The associated stage channel */
channel: StageChannel | { id: string }
/** The guild of the associated stage channel */
guild: Guild | { id: string }
/** The stage channel topic */
topic?: string | null
constructor(data: DiscordStageInstance, client: Client) {
super(data.id)
this.client = client
this.channel = client.getChannel(data.channel_id) ?? {
id: data.channel_id,
}
this.guild = client.guilds.get(data.guild_id) ?? { id: data.guild_id }
this.update(data)
}
/**
* @deprecated Use `.client` instead.
*/
get _client(): Client {
return this.client
}
update(data: DiscordStageInstance) {
if (data.topic !== undefined) {
this.topic = data.topic
}
}
/** Delete this stage instance */
async delete(): Promise<void> {
return await this.client.deleteStageInstance.call(this.client, this.channel.id)
}
/** Update this stage instance */
async edit(options: StageInstanceOptions): Promise<StageInstance> {
return await this.client.editStageInstance.call(this.client, this.channel.id, options)
}
}
export default StageInstance

View File

@@ -1,95 +0,0 @@
/* eslint-disable no-useless-call */
import type { DiscordTemplate } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { GuildTemplateOptions } from '../../typings.js'
import User from '../users/User.js'
import Guild from './Guild.js'
export class GuildTemplate {
/** The client class itself. */
client: Client
/** The template code (unique Id) */
code: string
/** Template name */
name: string
/** The description for the template */
description: string | null
/** Number of times this template has been used */
usageCount: number
/** The user who created the template */
creator: User
/** When this template was created */
createdAt: number
/** When this template was last synced to the source guild */
updatedAt: number
/** The guild snapshot this template contains */
serializedSourceGuild: Guild
/** The guild this template is based on. If the guild is not cached, this will be an object with `id` key. No other property is guaranteed */
sourceGuild: Guild | { id: string }
/** Whether the template has un-synced changes */
isDirty: boolean | null
constructor(data: DiscordTemplate, client: Client) {
this.client = client
this.code = data.code
this.createdAt = Date.parse(data.created_at)
this.creator = new User(data.creator, client)
this.client.users.set(this.creator.id, this.creator)
this.description = data.description
this.isDirty = data.is_dirty
this.name = data.name
this.sourceGuild = client.guilds.get(data.source_guild_id) ?? { id: data.source_guild_id }
this.updatedAt = Date.parse(data.updated_at)
this.usageCount = data.usage_count
data.serialized_source_guild.features = []
// @ts-expect-error Hacks to get this to not error
this.serializedSourceGuild = new Guild(data.serialized_source_guild, client)
}
/**
* @deprecated Use .client instead.
*/
get _client(): Client {
return this.client
}
/** Create a guild based on this template. Only for bots in less than 10 guilds */
async createGuild(name: string, icon?: string): Promise<Guild> {
return await this.client.createGuildFromTemplate.call(this.client, this.code, name, icon)
}
/** Delete this template */
async delete(): Promise<void> {
return await this.client.deleteGuildTemplate.call(this.client, this.sourceGuild.id, this.code)
}
/** Edit this template */
async edit(options: GuildTemplateOptions): Promise<GuildTemplate> {
return await this.client.editGuildTemplate.call(this.client, this.sourceGuild.id, this.code, options)
}
/** Force this template to sync to the guild's current state */
async sync(): Promise<GuildTemplate> {
return await this.client.syncGuildTemplate.call(this.client, this.sourceGuild.id, this.code)
}
toJSON(props: string[] = []): Record<string, any> {
return Base.prototype.toJSON.call(this, [
'code',
'createdAt',
'creator',
'description',
'isDirty',
'name',
'serializedSourceGuild',
'sourceGuild',
'updatedAt',
'usageCount',
...props,
])
}
}
export default GuildTemplate

View File

@@ -1,22 +0,0 @@
import type { DiscordUnavailableGuild } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
export class UnavailableGuild extends Base {
/** Whether or not the guild is unavailable. */
unavailable: boolean
constructor(data: DiscordUnavailableGuild, client: Client) {
super(data.id)
// TODO: gateway
// this.shard = client.shards.get(client.guildShardMap[this.id]);
this.unavailable = !!data.unavailable
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['unavailable', ...props])
}
}
export default UnavailableGuild

View File

@@ -1,132 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordVoiceState } from '@discordeno/types'
import Base from '../../Base.js'
import { VoiceStateToggle, VoiceStateToggles } from '../toggles/Voice.js'
export class VoiceState extends Base {
channelID: string | null = null
requestToSpeakTimestamp: number | null
sessionID!: string | null
bitfield: VoiceStateToggles
constructor(data: DiscordVoiceState & { id: string }) {
super(data.id)
this.requestToSpeakTimestamp = null
this.bitfield = new VoiceStateToggles(data)
this.update(data)
}
/** Whether or not the user is deafened by the server. */
get deaf(): boolean {
return this.bitfield.deaf
}
/** Set whether or not the user is deafened by the server. */
set deaf(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.deaf)
else this.bitfield.remove(VoiceStateToggle.deaf)
}
/** Whether or not the user is muted by the server. */
get mute(): boolean {
return this.bitfield.mute
}
/** Set whether or not the user is muted by the server. */
set mute(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.mute)
else this.bitfield.remove(VoiceStateToggle.mute)
}
/** Whether or not the user has deafened themself. */
get selfDeaf(): boolean {
return this.bitfield.selfDeaf
}
/** Set whether or not the user has deafened themself. */
set selfDeaf(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.selfDeaf)
else this.bitfield.remove(VoiceStateToggle.selfDeaf)
}
/** Whether or not the user has muted themself. */
get selfMute(): boolean {
return this.bitfield.selfMute
}
/** Set whether or not the user has muted themself. */
set selfMute(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.selfMute)
else this.bitfield.remove(VoiceStateToggle.selfMute)
}
/** Whether or not the user is streaming. */
get selfStream(): boolean {
return this.bitfield.selfStream
}
/** Set whether or not the user is streaming. */
set selfStream(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.selfStream)
else this.bitfield.remove(VoiceStateToggle.selfStream)
}
/** Whether or not the user is video calling. */
get selfVideo(): boolean {
return this.bitfield.selfVideo
}
/** Set whether or not the user is video calling. */
set selfVideo(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.selfVideo)
else this.bitfield.remove(VoiceStateToggle.selfVideo)
}
/** Whether or not the user is suppressed from speaking. */
get suppress(): boolean {
return this.bitfield.suppress
}
/** Set whether or not the user is suppressed from speaking. */
set suppress(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.suppress)
else this.bitfield.remove(VoiceStateToggle.suppress)
}
update(data: DiscordVoiceState) {
if (data.channel_id !== undefined) {
this.channelID = data.channel_id
this.sessionID = data.channel_id === null ? null : data.session_id
} else if (this.channelID === undefined) {
this.channelID = this.sessionID = null
}
if (data.mute !== undefined) this.mute = data.mute
if (data.deaf !== undefined) this.deaf = data.deaf
if (data.request_to_speak_timestamp) this.requestToSpeakTimestamp = Date.parse(data.request_to_speak_timestamp)
if (data.self_mute !== undefined) this.selfMute = data.self_mute
if (data.self_deaf !== undefined) this.selfDeaf = data.self_deaf
if (data.self_video !== undefined) this.selfVideo = data.self_video
if (data.self_stream !== undefined) this.selfStream = data.self_stream
if (data.suppress !== undefined) this.suppress = data.suppress
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'channelID',
'deaf',
'mute',
'requestToSpeakTimestamp',
'selfDeaf',
'selfMute',
'selfStream',
'selfVideo',
'sessionID',
'suppress',
...props,
])
}
}

View File

@@ -1,80 +0,0 @@
/* eslint-disable @typescript-eslint/return-await */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordInteraction, DiscordInteractionData } from '@discordeno/types'
import { InteractionResponseTypes } from '@discordeno/types'
import type Client from '../../Client.js'
import type { ApplicationCommandOptionChoice } from '../../typings.js'
import type NewsChannel from '../channels/News.js'
import type PrivateChannel from '../channels/Private.js'
import type TextChannel from '../channels/Text.js'
import type Guild from '../guilds/Guild.js'
import Member from '../guilds/Member.js'
import Permission from '../Permission.js'
import User from '../users/User.js'
import Interaction from './Interaction.js'
export class AutocompleteInteraction extends Interaction {
/** The guild id if this interaction occurred in a guild. */
guildID?: string
/** The permissions the app or bot has within the channel, the interaction was sent from. */
appPermissions?: Permission
/** The channel id where this interaction was created in. */
channelID: string
/** The user who triggered the interaction. */
user: User
/** The data attached to this interaction. */
data?: DiscordInteractionData
/** The member who triggered the interaction. Sent when used in a guild. */
member?: Member
constructor(data: DiscordInteraction, client: Client) {
super(data, client)
this.channelID = data.channel_id!
this.data = data.data
if (data.guild_id !== undefined) {
this.guildID = data.guild_id
}
if (data.member !== undefined && this.guild) {
this.member = new Member(data.member, this.guild, this.client)
this.guild.members.set(this.member.id, this.member)
}
this.user = new User(data.user ?? data.member!.user, this.client)
this.client.users.set(this.user.id, this.user)
if (data.app_permissions !== undefined) {
this.appPermissions = new Permission(data.app_permissions)
}
}
/** The channel the interaction was created in. */
get channel(): PrivateChannel | TextChannel | NewsChannel {
return this.client.getChannel(this.channelID) as PrivateChannel | TextChannel | NewsChannel
}
/** The guild the interaction was created in. */
get guild(): Guild | undefined {
return this.guildID ? this.client.guilds.get(this.guildID) : undefined
}
async acknowledge(choices: ApplicationCommandOptionChoice[]) {
return await this.result(choices)
}
async result(choices: ApplicationCommandOptionChoice[]) {
if (this.acknowledged) throw new Error('You have already acknowledged this interaction.')
return this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
data: { choices },
})
.then(() => this.update())
}
}
export default AutocompleteInteraction

View File

@@ -1,321 +0,0 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import { ApplicationCommandTypes, InteractionResponseTypes } from '@discordeno/types'
import Collection from '../../Collection.js'
import Channel from '../channels/Channel.js'
import Member from '../guilds/Member.js'
import Role from '../guilds/Role.js'
import Message from '../Message.js'
import User from '../users/User.js'
import Interaction from './Interaction.js'
import type {
BigString,
DiscordAttachment,
DiscordInteraction,
DiscordInteractionDataOption,
DiscordMessageComponents,
MessageComponentTypes,
} from '@discordeno/types'
import type Client from '../../Client.js'
import type { AnyChannel, FileContent, InteractionContent, InteractionContentEdit } from '../../typings.js'
export class CommandInteraction extends Interaction {
name: string = "";
channel: AnyChannel
/** The type of component */
componentType?: MessageComponentTypes
/** The custom id provided for this component. */
customId?: string
/** The components if its a Modal Submit interaction. */
components?: DiscordMessageComponents
/** The values chosen by the user. */
values?: string[]
/** the type of the invoked command */
commandType: ApplicationCommandTypes = ApplicationCommandTypes.ChatInput
/** The Ids and Message objects */
messages = new Collection<BigString, Message>()
/** The Ids and User objects */
users = new Collection<BigString, User>()
/** The Ids and partial Member objects */
members = new Collection<BigString, Member>()
/** The Ids and Role objects */
roles = new Collection<BigString, Role>()
/** The Ids and partial Channel objects */
channels = new Collection<BigString, Channel>()
/** The ids and attachment objects */
attachments = new Collection<BigString, DiscordAttachment>()
/** The params + values from the user */
options?: DiscordInteractionDataOption[]
/** The target id if this is a context menu command. */
targetID?: string
/** the id of the guild the command is registered to */
guildID?: string
member?: Member
user: User
constructor(info: DiscordInteraction, client: Client) {
super(info, client)
this.channel = this.client.getChannel(info.channel_id!) ?? {
id: info.channel_id!,
}
// this.data = info.data!;
const guild = this.client.guilds.get(info.guild_id!)
if (info.data?.resolved !== undefined) {
// Users
if (info.data.resolved.users !== undefined) {
for (const u of Object.values(info.data.resolved.users)) {
const user = new User(u, this.client)
this.users.set(user.id, user)
}
}
// Members
if (info.data.resolved.members !== undefined) {
for (const [, m] of Object.entries(info.data.resolved.members)) {
// @ts-expect-error some eris magic at play here
m.id = m
// @ts-expect-error some eris magic at play here
const member = new Member(m, guild, this.client)
this.members.set(member.id, member)
}
}
// Roles
if (info.data.resolved.roles !== undefined) {
for (const r of Object.values(info.data.resolved.roles)) {
// @ts-expect-error some eris magic at play here
const role = new Role(r, guild)
this.roles.set(role.id, role)
}
}
// Channels
if (info.data.resolved.channels !== undefined) {
for (const c of Object.values(info.data.resolved.channels)) {
const channel = new Channel(c, this.client)
this.channels.set(channel.id, channel)
}
}
// Messages
if (info.data.resolved.messages !== undefined) {
for (const m of Object.values(info.data.resolved.messages)) {
const message = new Message(m, this.client)
this.messages.set(message.id, message)
}
}
// Attachments
if (info.data.resolved.attachments !== undefined) {
for (const attachment of Object.values(info.data.resolved.attachments)) {
this.attachments.set(attachment.id, attachment)
}
}
}
this.guildID = info.guild_id
if (info.member !== undefined) {
// @ts-expect-error some eris magic at play here
this.member = new Member(info.member, guild, this.client)
guild?.members.set(this.member.id, this.member)
}
this.user = new User(info.user ?? info.member!.user, this.client)
this.client.users.set(this.user.id, this.user)
if (info.data) {
this.name = info.data.name;
this.componentType = info.data.component_type
this.customId = info.data.custom_id
this.components = info.data.components
this.values = info.data.values
this.commandType = info.data.type
this.options = info.data.options
this.targetID = info.data.target_id
}
}
get data() {
return {
name: this.name,
component_type: this.componentType,
custom_id: this.customId,
components: this.components,
values: this.values,
type: this.commandType,
resolved: {
messages: this.messages.toRecord(),
users: this.users.toRecord(),
members: this.members.toRecord(),
roles: this.roles.toRecord(),
channels: this.channels.toRecord(),
attachments: this.attachments.toRecord(),
},
options: this.options,
target_id: this.targetID,
guild_id: this.guildID,
}
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async acknowledge(flags?: number): Promise<void> {
return this.defer(flags)
}
/** Respond to the interaction with a followup message */
async createFollowup(content: string | InteractionContent, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error('createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, {
...content,
wait: true,
file,
})
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
*/
async createMessage(content: string | InteractionContent, file?: FileContent | FileContent[]): Promise<void> {
if (this.acknowledged) {
// @ts-expect-error some eris magic at play here
return await this.createFollowup(content, file)
}
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
return this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.ChannelMessageWithSource,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async defer(flags?: number): Promise<void> {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredChannelMessageWithSource,
data: {
flags: flags ?? 0,
},
})
.then(() => this.update())
}
/** Delete a message */
async deleteMessage(messageID: BigString): Promise<void> {
if (!this.acknowledged) {
throw new Error('deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
return this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID)
}
/**
* Delete the Original message
* Warning: Will error with ephemeral messages.
*/
async deleteOriginalMessage(): Promise<void> {
if (!this.acknowledged) {
throw new Error('deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
return this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
/** Edit a message */
async editMessage(messageID: BigString, content: string | InteractionContentEdit, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error('editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, {
...content,
file,
})
}
/** Edit the Original response message */
async editOriginalMessage(content: string | InteractionContentEdit, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error('editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', {
...content,
file,
})
}
/**
* Get the Original response message
* Warning: Will error with ephemeral messages.
*/
async getOriginalMessage(): Promise<Message> {
if (!this.acknowledged) {
throw new Error('getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
return this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
}
export default CommandInteraction

View File

@@ -1,296 +0,0 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { BigString, DiscordInteraction, MessageComponentTypes } from '@discordeno/types'
import { InteractionResponseTypes } from '@discordeno/types'
import type Client from '../../Client.js'
import type { AnyChannel, FileContent, InteractionApplicationCommandCallbackData, MessageWebhookContent, WebhookPayload } from '../../typings.js'
import type Guild from '../guilds/Guild.js'
import Member from '../guilds/Member.js'
import Message from '../Message.js'
import Permission from '../Permission.js'
import User from '../users/User.js'
import Interaction from './Interaction.js'
export class ComponentInteraction extends Interaction {
/** The channel id where this interaction occurred in. */
channelID: string
/** The guild id where this interaction occurred in. */
guildID?: string
/** The member object if this interaction occurred in a guild. */
member?: Member
/** The user object for the user that created this interaction. */
user: User
/** The permissions the app or bot has within the channel, the interaction was sent from. */
appPermissions?: Permission
/** The message object, if this interaction occurred on a message. */
message?: Message
/** The custom id of the component. */
customID: string
/** The type of component. */
componentType: MessageComponentTypes
/** The values from a selector component. */
values?: string[]
constructor(data: DiscordInteraction, client: Client) {
super(data, client)
this.channelID = data.channel_id!
// this.data = data.data;
this.guildID = data.guild_id
// Required to make a component
this.customID = data.data!.custom_id!
this.componentType = data.data!.component_type!
this.values = data.data!.values
if (data.member !== undefined && this.guild) {
this.member = new Member(data.member, this.guild, this.client)
this.guild.members.set(this.member.id, this.member)
}
if (data.message !== undefined) {
this.message = new Message(data.message, this.client)
}
this.user = new User(data.user ?? data.member!.user, this.client)
this.client.users.set(this.user.id, this.user)
if (data.app_permissions !== undefined) {
this.appPermissions = new Permission(data.app_permissions)
}
}
/** The channel if cached, where this interaction occurred. */
get channel(): AnyChannel | undefined {
return this.channelID ? this.client.getChannel(this.channelID) : undefined
}
/** The guild if cached, where this interaction occurred. */
get guild(): Guild | undefined {
return this.guildID ? this.client.guilds.get(this.guildID) : undefined
}
/** Acknowledges the interaction with a defer message update response */
async acknowledge(): Promise<void> {
return await this.deferUpdate()
}
/** Respond to the interaction with a followup message. */
async createFollowup(content: WebhookPayload, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error(
'createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, { ...content, wait: true })
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
*/
async createMessage(content: InteractionApplicationCommandCallbackData, file?: FileContent | FileContent[]): Promise<Message | undefined> {
if (this.acknowledged) {
return await this.createFollowup(content, file)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
await this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.ChannelMessageWithSource,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async defer(flags: number): Promise<void> {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredChannelMessageWithSource,
data: {
flags: flags || 0,
},
})
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer message update response
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async deferUpdate(): Promise<void> {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredUpdateMessage,
})
.then(() => this.update())
}
/** Delete a message */
async deleteMessage(messageID: BigString): Promise<void> {
if (!this.acknowledged) {
throw new Error(
'deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID)
}
/**
* Delete the parent message
* Warning: Will error with ephemeral messages.
*/
async deleteOriginalMessage(): Promise<void> {
if (!this.acknowledged) {
throw new Error(
'deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
/** Edit a message */
async editMessage(messageID: BigString, content: MessageWebhookContent, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error(
'editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, content)
}
/** Edit the parent message */
async editOriginalMessage(content: MessageWebhookContent, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error(
'editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', content)
}
/**
* Acknowledges the interaction by editing the parent message. If already acknowledged runs editOriginalMessage
* Note: You can **not** use more than 1 initial interaction response per interaction, use edit if you have already responded with a different interaction response.
* Warning: Will error with ephemeral messages.
*/
async editParent(content: InteractionApplicationCommandCallbackData, file?: FileContent | FileContent[]): Promise<Message | undefined> {
if (this.acknowledged) {
return this.editOriginalMessage(content)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
await this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.UpdateMessage,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Get the parent message
* Warning: Will error with ephemeral messages.
*/
async getOriginalMessage(): Promise<Message> {
if (!this.acknowledged) {
throw new Error(
'getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
return this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
}
export default ComponentInteraction

View File

@@ -1,46 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordInteraction, InteractionTypes } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
export class Interaction extends Base {
client: Client
applicationID: string
token: string
type: InteractionTypes
version: 1
acknowledged: boolean
constructor(data: DiscordInteraction, client: Client) {
super(data.id)
this.client = client
this.applicationID = data.application_id
this.token = data.token
this.type = data.type
this.version = data.version
this.acknowledged = false
}
/**
* @deprecated Use `.client`
*/
get _client(): Client {
return this.client
}
update() {
this.acknowledged = true
}
/**
* @deprecated Use generateInteractionFrom(data, client) instead.
*/
static from(data: DiscordInteraction, client: Client) {
// Remove js hack of circular deps
console.error('Usage of Interaction.from() is deprecated. Use generateInteractionFrom(data, client) instead.')
client.emit('warn', new Error(`Usage of Interaction.from() is deprecated. Use generateInteractionFrom(data, client) instead.`))
}
}
export default Interaction

View File

@@ -1,33 +0,0 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import { InteractionResponseTypes } from '@discordeno/types'
import Interaction from './Interaction.js'
export class PingInteraction extends Interaction {
/**
* Acknowledges the ping interaction with a pong response.
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async acknowledge(): Promise<void> {
return this.pong()
}
/**
* Acknowledges the ping interaction with a pong response.
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async pong(): Promise<void> {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.Pong,
})
.then(() => this.update())
}
}
export default PingInteraction

View File

@@ -1,493 +0,0 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import type { DiscordInteraction } from '@discordeno/types'
import { InteractionResponseTypes } from '@discordeno/types'
import type Client from '../../Client.js'
import type {
ApplicationCommandOptionChoice,
FileContent,
InteractionContent,
InteractionContentEdit,
InteractionResponse,
PossiblyUncachedTextable,
TextableChannel,
} from '../../typings.js'
import Member from '../guilds/Member.js'
import Message from '../Message.js'
import Permission from '../Permission.js'
import User from '../users/User.js'
import Interaction from './Interaction.js'
export class UnknownInteraction<T extends PossiblyUncachedTextable = TextableChannel> extends Interaction {
channel?: T
data?: unknown
guildID?: string
member?: Member
message?: Message
type: number = 0
user?: User
appPermissions?: Permission
constructor(data: DiscordInteraction, client: Client) {
super(data, client)
if (data.channel_id !== undefined) {
this.channel = (this.client.getChannel(data.channel_id) as unknown as T) || {
id: data.channel_id,
}
}
if (data.data !== undefined) {
this.data = data.data
}
if (data.guild_id !== undefined) {
this.guildID = data.guild_id
}
if (data.member !== undefined) {
const guild = this.client.guilds.get(data.guild_id ?? '')!
this.member = new Member(data.member, guild, this.client)
guild.members.set(this.member.id, this.member)
}
if (data.message !== undefined) {
this.message = new Message(data.message, this.client)
}
if (data.user !== undefined) {
this.user = new User(data.user, this.client)
this.client.users.set(this.user.id, this.user)
}
if (data.app_permissions !== undefined) {
this.appPermissions = new Permission(data.app_permissions)
}
}
/**
* Acknowledges the autocomplete interaction with a result of choices.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Object} data The data object
* @arg {Number} data.type The type of [interaction response](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type) to send
* @arg {Object} data.data The data to return to discord
* @returns {Promise}
*/
async acknowledge(data: InteractionResponse) {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse.call(this.client, this.id, this.token, data).then(() => this.update())
}
/**
* Respond to the interaction with a followup message
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [options.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Number} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message?>}
*/
async createFollowup(content: string | InteractionContent, file?: FileContent | FileContent[]) {
if (!this.acknowledged) {
throw new Error(
'createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, Object.assign({ wait: true as true }, content))
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async createMessage(content: string | InteractionContent, file?: FileContent | FileContent[]) {
if (this.acknowledged) {
return await this.createFollowup(content, file)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return await this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.ChannelMessageWithSource,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Number} [flags] 64 for Ephemeral
* @returns {Promise}
*/
async defer(flags: number) {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredChannelMessageWithSource,
data: {
flags: flags || 0,
},
})
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer message update response (Message Component only)
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async deferUpdate() {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredUpdateMessage,
})
.then(() => this.update())
}
/**
* Delete a message
* @arg {String} messageID the id of the message to delete, or "@original" for the original response.
* @returns {Promise}
*/
async deleteMessage(messageID: string) {
if (!this.acknowledged) {
throw new Error(
'deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.',
)
}
return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID)
}
/**
* Delete the Original message (or the parent message for components)
* Warning: Will error with ephemeral messages.
* @returns {Promise}
*/
async deleteOriginalMessage() {
if (!this.acknowledged) {
throw new Error(
'deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.',
)
}
return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
/**
* Edit a message
* @arg {String} messageID the id of the message to edit, or "@original" for the original response.
* @arg {Object} content Interaction message edit options
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editMessage(messageID: string, content: string | InteractionContentEdit, file?: FileContent | FileContent[]) {
if (!this.acknowledged) {
throw new Error(
'editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, content)
}
/**
* Edit the Original response message
* @arg {Object} content Interaction message edit options (or the parent message for components)
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editOriginalMessage(content: string | InteractionContentEdit, file?: FileContent | FileContent[]) {
if (!this.acknowledged) {
throw new Error(
'editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', content)
}
/**
* Acknowledges the interaction by editing the parent message. If already acknowledged runs editOriginalMessage (Message Component only)
* Note: You can **not** use more than 1 initial interaction response per interaction, use edit if you have already responded with a different interaction response.
* Warning: Will error with ephemeral messages.
* @arg {String | Object} content What to edit the message with
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async editParent(content: InteractionContentEdit, file?: FileContent | FileContent[]) {
if (this.acknowledged) {
return await this.editOriginalMessage(content)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return await this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.UpdateMessage,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Get the Original response message (or the parent message for components)
* Warning: Will error with ephemeral messages.
* @returns {Promise<Message>}
*/
async getOriginalMessage() {
if (!this.acknowledged) {
throw new Error(
'getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.',
)
}
return await this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
/**
* Acknowledges the ping interaction with a pong response (Ping Only)
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async pong() {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.Pong,
})
.then(() => this.update())
}
/**
* Acknowledges the autocomplete interaction with a result of choices.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Array<Object>} choices The autocomplete choices to return to the user
* @arg {String | Number} choices[].name The choice display name
* @arg {String} choices[].value The choice value to return to the bot
* @returns {Promise}
*/
async result(choices: ApplicationCommandOptionChoice[]) {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
data: { choices },
})
.then(() => this.update())
}
}
export default UnknownInteraction

View File

@@ -1,304 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { GuildFeatures, type DiscordGuild } from '@discordeno/types'
import { ToggleBitfieldBigint } from './Toggle.js'
const featureNames = [
'inviteSplash',
'vipRegions',
'vanityUrl',
'verified',
'partnered',
'community',
'developerSupportServer',
'news',
'discoverable',
'featurable',
'animatedIcon',
'banner',
'welcomeScreenEnabled',
'memberVerificationGateEnabled',
'previewEnabled',
'ticketedEventsEnabled',
'monetizationEnabled',
'moreStickers',
'privateThreads',
'roleIcons',
'autoModeration',
'invitesDisabled',
'animatedBanner',
]
export const GuildToggle = {
/** Whether the bot is the owner of the guild */
owner: 1n << 0n,
/** Whether the server widget is enabled */
widgetEnabled: 1n << 1n,
/** Whether this is considered a large guild */
large: 1n << 2n,
/** Whether this guild is unavailable due to an outage */
unavailable: 1n << 3n,
/** Whether the guild has the boost progress bar enabled */
premiumProgressBarEnabled: 1n << 4n,
// GUILD FEATURES ARE BELOW THIS
/** Whether the guild has access to set an invite splash background */
inviteSplash: 1n << 5n,
/** Whether the guild has access to set 384 kbps bitrate in voice (previously VIP voice servers) */
vipRegions: 1n << 6n,
/** Whether the guild has access to set a vanity URL */
vanityUrl: 1n << 7n,
/** Whether the guild is verified */
verified: 1n << 8n,
/** Whether the guild is partnered */
partnered: 1n << 9n,
/** Whether the guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates */
community: 1n << 10n,
/** Whether the guild has access to set an animated guild banner image */
animatedBanner: 1n << 11n,
/** Whether the guild has access to create news channels */
news: 1n << 12n,
/** Whether the guild is able to be discovered in the directory */
discoverable: 1n << 13n,
/** Whether the guild is able to be featured in the directory */
featurable: 1n << 15n,
/** Whether the guild has access to set an animated guild icon */
animatedIcon: 1n << 16n,
/** Whether the guild has access to set a guild banner image */
banner: 1n << 17n,
/** Whether the guild has enabled the welcome screen */
welcomeScreenEnabled: 1n << 18n,
/** Whether the guild has enabled [Membership Screening](https://discord.com/developers/docs/resources/guild#membership-screening-object) */
memberVerificationGateEnabled: 1n << 19n,
/** Whether the guild can be previewed before joining via Membership Screening or the directory */
previewEnabled: 1n << 20n,
/** Whether the guild has enabled ticketed events */
ticketedEventsEnabled: 1n << 21n,
/** Whether the guild has enabled monetization */
monetizationEnabled: 1n << 22n,
/** Whether the guild has increased custom sticker slots */
moreStickers: 1n << 23n,
/** Whether the guild has access to create private threads */
privateThreads: 1n << 26n,
/** Whether the guild is able to set role icons */
roleIcons: 1n << 27n,
/** Whether the guild has set up auto moderation rules */
autoModeration: 1n << 28n,
/** Whether the guild has paused invites, preventing new users from joining */
invitesDisabled: 1n << 29n,
/** Whether the guild has been set as a support server on the App Directory */
developerSupportServer: 1n << 30n,
}
export class GuildToggles extends ToggleBitfieldBigint {
constructor(guildOrTogglesBigint: DiscordGuild | bigint) {
super()
if (typeof guildOrTogglesBigint === 'bigint') this.bitfield = guildOrTogglesBigint
else {
const guild = guildOrTogglesBigint
if (guild.owner) this.add(GuildToggle.owner)
if (guild.widget_enabled) this.add(GuildToggle.widgetEnabled)
if (guild.large) this.add(GuildToggle.large)
if (guild.unavailable) this.add(GuildToggle.unavailable)
if (guild.premium_progress_bar_enabled) this.add(GuildToggle.premiumProgressBarEnabled)
if (guild.features.includes(GuildFeatures.InviteSplash)) this.add(GuildToggle.inviteSplash)
if (guild.features.includes(GuildFeatures.VipRegions)) this.add(GuildToggle.vipRegions)
if (guild.features.includes(GuildFeatures.VanityUrl)) this.add(GuildToggle.vanityUrl)
if (guild.features.includes(GuildFeatures.Verified)) this.add(GuildToggle.verified)
if (guild.features.includes(GuildFeatures.Partnered)) this.add(GuildToggle.partnered)
if (guild.features.includes(GuildFeatures.Community)) this.add(GuildToggle.community)
if (guild.features.includes(GuildFeatures.DeveloperSupportServer)) this.add(GuildToggle.developerSupportServer)
if (guild.features.includes(GuildFeatures.AnimatedBanner)) this.add(GuildToggle.animatedBanner)
if (guild.features.includes(GuildFeatures.News)) this.add(GuildToggle.news)
if (guild.features.includes(GuildFeatures.Discoverable)) this.add(GuildToggle.discoverable)
if (guild.features.includes(GuildFeatures.Featurable)) this.add(GuildToggle.featurable)
if (guild.features.includes(GuildFeatures.AnimatedIcon)) this.add(GuildToggle.animatedIcon)
if (guild.features.includes(GuildFeatures.Banner)) this.add(GuildToggle.banner)
if (guild.features.includes(GuildFeatures.WelcomeScreenEnabled)) this.add(GuildToggle.welcomeScreenEnabled)
if (guild.features.includes(GuildFeatures.MemberVerificationGateEnabled)) {
this.add(GuildToggle.memberVerificationGateEnabled)
}
if (guild.features.includes(GuildFeatures.PreviewEnabled)) this.add(GuildToggle.previewEnabled)
if (guild.features.includes(GuildFeatures.TicketedEventsEnabled)) this.add(GuildToggle.ticketedEventsEnabled)
if (guild.features.includes(GuildFeatures.MoreStickers)) this.add(GuildToggle.moreStickers)
if (guild.features.includes(GuildFeatures.PrivateThreads)) this.add(GuildToggle.privateThreads)
if (guild.features.includes(GuildFeatures.RoleIcons)) this.add(GuildToggle.roleIcons)
if (guild.features.includes(GuildFeatures.AutoModeration)) this.add(GuildToggle.autoModeration)
if (guild.features.includes(GuildFeatures.InvitesDisabled)) this.add(GuildToggle.invitesDisabled)
}
}
get features() {
const features: GuildToggleKeys[] = []
for (const key of Object.keys(GuildToggle)) {
if (!featureNames.includes(key)) continue
if (!super.contains(GuildToggle[key as GuildToggleKeys])) continue
features.push(key as GuildToggleKeys)
}
return features
}
/** Whether the bot is the owner of the guild */
get owner() {
return this.has('owner')
}
/** Whether the server widget is enabled */
get widgetEnabled() {
return this.has('widgetEnabled')
}
/** Whether this is considered a large guild */
get large() {
return this.has('large')
}
/** Whether this guild is unavailable due to an outage */
get unavailable() {
return this.has('unavailable')
}
/** Whether the guild has the boost progress bar enabled */
get premiumProgressBarEnabled() {
return this.has('premiumProgressBarEnabled')
}
/** Whether the guild has access to set an invite splash background */
get inviteSplash() {
return this.has('inviteSplash')
}
/** Whether the guild has access to set 384 kbps bitrate in voice (previously VIP voice servers) */
get vipRegions() {
return this.has('vipRegions')
}
/** Whether the guild has access to set a vanity URL */
get vanityUrl() {
return this.has('vanityUrl')
}
/** Whether the guild is verified */
get verified() {
return this.has('verified')
}
/** Whether the guild is partnered */
get partnered() {
return this.has('partnered')
}
/** Whether the guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates */
get community() {
return this.has('community')
}
/** Whether the Guild has been set as a support server on the App Directory */
get developerSupportServer() {
return this.has('developerSupportServer')
}
/** Whether the guild has access to set an animated guild banner image */
get animatedBanner() {
return this.has('animatedBanner')
}
/** Whether the guild has access to create news channels */
get news() {
return this.has('news')
}
/** Whether the guild is able to be discovered in the directory */
get discoverable() {
return this.has('discoverable')
}
/** Whether the guild is able to be featured in the directory */
get featurable() {
return this.has('featurable')
}
/** Whether the guild has access to set an animated guild icon */
get animatedIcon() {
return this.has('animatedIcon')
}
/** Whether the guild has access to set a guild banner image */
get banner() {
return this.has('banner')
}
/** Whether the guild has enabled the welcome screen */
get welcomeScreenEnabled() {
return this.has('welcomeScreenEnabled')
}
/** Whether the guild has enabled [Membership Screening](https://discord.com/developers/docs/resources/guild#membership-screening-object) */
get memberVerificationGateEnabled() {
return this.has('memberVerificationGateEnabled')
}
/** Whether the guild can be previewed before joining via Membership Screening or the directory */
get previewEnabled() {
return this.has('previewEnabled')
}
/** Whether the guild has enabled ticketed events */
get ticketedEventsEnabled() {
return this.has('ticketedEventsEnabled')
}
/** Whether the guild has enabled monetization */
get monetizationEnabled() {
return this.has('monetizationEnabled')
}
/** Whether the guild has increased custom sticker slots */
get moreStickers() {
return this.has('moreStickers')
}
/** Whether the guild has access to create private threads */
get privateThreads() {
return this.has('privateThreads')
}
/** Whether the guild is able to set role icons */
get roleIcons() {
return this.has('roleIcons')
}
/** Whether the guild has set up auto moderation rules */
get autoModeration() {
return this.has('autoModeration')
}
/** Whether the guild has paused invites, preventing new users from joining */
get invitesDisabled() {
return this.has('invitesDisabled')
}
/** Checks whether or not the permissions exist in this */
has(permissions: GuildToggleKeys | GuildToggleKeys[]) {
if (!Array.isArray(permissions)) return super.contains(GuildToggle[permissions])
return super.contains(permissions.reduce((a, b) => (a |= GuildToggle[b]), 0n))
}
/** Lists all the toggles for the role and whether or not each is true or false. */
list() {
const json: Record<string, boolean> = {}
for (const [key, value] of Object.entries(GuildToggle)) {
json[key] = super.contains(value)
}
return json as Record<GuildToggleKeys, boolean>
}
}
export type GuildToggleKeys = keyof typeof GuildToggle

View File

@@ -1,50 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
export class ToggleBitfield {
bitfield = 0
constructor(bitfield?: number) {
if (bitfield) this.bitfield = bitfield
}
/** Tests whether or not this bitfield has the permission requested. */
contains(bits: number) {
return Boolean(this.bitfield & bits)
}
/** Adds some bits to the bitfield. */
add(bits: number) {
this.bitfield |= bits
return this
}
/** Removes some bits from the bitfield. */
remove(bits: number) {
this.bitfield &= ~bits
return this
}
}
export class ToggleBitfieldBigint {
bitfield = 0n
constructor(bitfield?: bigint) {
if (bitfield) this.bitfield = bitfield
}
/** Tests whether or not this bitfield has the permission requested. */
contains(bits: bigint) {
return Boolean(this.bitfield & bits)
}
/** Adds some bits to the bitfield. */
add(bits: bigint) {
this.bitfield |= bits
return this
}
/** Removes some bits from the bitfield. */
remove(bits: bigint) {
this.bitfield &= ~bits
return this
}
}

View File

@@ -1,93 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordVoiceState } from '@discordeno/types'
import { ToggleBitfield } from './Toggle.js'
export const VoiceStateToggle = {
/** Whether this user is deafened by the server */
deaf: 1 << 0,
/** Whether this user is muted by the server */
mute: 1 << 1,
/** Whether this user is locally deafened */
selfDeaf: 1 << 2,
/** Whether this user is locally muted */
selfMute: 1 << 3,
/** Whether this user is streaming using "Go Live" */
selfStream: 1 << 4,
/** Whether this user's camera is enabled */
selfVideo: 1 << 5,
/** Whether this user is muted by the current user */
suppress: 1 << 6,
}
export class VoiceStateToggles extends ToggleBitfield {
constructor(voiceOrTogglesInt: DiscordVoiceState | number) {
super()
if (typeof voiceOrTogglesInt === 'number') this.bitfield = voiceOrTogglesInt
else {
const voice = voiceOrTogglesInt
if (voice.deaf) this.add(VoiceStateToggle.deaf)
if (voice.mute) this.add(VoiceStateToggle.mute)
if (voice.self_deaf) this.add(VoiceStateToggle.selfDeaf)
if (voice.self_mute) this.add(VoiceStateToggle.selfMute)
if (voice.self_stream) this.add(VoiceStateToggle.selfStream)
if (voice.self_video) this.add(VoiceStateToggle.selfVideo)
if (voice.suppress) this.add(VoiceStateToggle.suppress)
}
}
/** Whether this user is deafened by the server */
get deaf() {
return this.has('deaf')
}
/** Whether this user is muted by the server */
get mute() {
return this.has('mute')
}
/** Whether this user is locally deafened */
get selfDeaf() {
return this.has('selfDeaf')
}
/** Whether this user is locally muted */
get selfMute() {
return this.has('selfMute')
}
/** Whether this user is streaming using "Go Live" */
get selfStream() {
return this.has('selfStream')
}
/** Whether this user's camera is enabled */
get selfVideo() {
return this.has('selfVideo')
}
/** Whether this user is muted by the current user */
get suppress() {
return this.has('suppress')
}
/** Checks whether or not the permissions exist in this */
has(permissions: VoiceStateToggleKeys | VoiceStateToggleKeys[]) {
if (!Array.isArray(permissions)) return super.contains(VoiceStateToggle[permissions])
return super.contains(permissions.reduce((a, b) => (a |= VoiceStateToggle[b]), 0))
}
/** Lists all the toggles for the role and whether or not each is true or false. */
list() {
const json: Record<string, boolean> = {}
for (const [key, value] of Object.entries(VoiceStateToggle)) {
json[key] = super.contains(value)
}
return json as Record<VoiceStateToggleKeys, boolean>
}
}
export type VoiceStateToggleKeys = keyof typeof VoiceStateToggle

View File

@@ -1,37 +0,0 @@
import type { PremiumTypes, DiscordUser } from '@discordeno/types'
import type Client from '../../Client.js'
import User from './User.js'
export class ExtendedUser extends User {
email?: string | null
verified?: boolean
mfaEnabled?: boolean
premiumType?: PremiumTypes
constructor(data: DiscordUser, client: Client) {
super(data, client)
this.update(data)
}
update(data: DiscordUser): void {
super.update(data)
if (data.email !== undefined) {
this.email = data.email
}
if (data.verified !== undefined) {
this.verified = data.verified
}
if (data.mfa_enabled !== undefined) {
this.mfaEnabled = data.mfa_enabled
}
if (data.premium_type !== undefined) {
this.premiumType = data.premium_type
}
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['email', 'mfaEnabled', 'premium', 'verified', ...props])
}
}
export default ExtendedUser

View File

@@ -1,128 +0,0 @@
/* eslint-disable no-useless-call */
import type { BigString, DiscordUser, UserFlags } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { ImageFormat, ImageSize } from '../../Client.js'
import { BANNER, DEFAULT_USER_AVATAR, USER_AVATAR } from '../../Endpoints.js'
import type PrivateChannel from '../channels/Private.js'
export class User extends Base {
client: Client
bot: boolean
system: boolean
_avatar!: bigint | null
username!: string
discriminator!: string
publicFlags?: UserFlags
_banner!: bigint | null
accentColor?: number
constructor(data: DiscordUser, client: Client) {
super(data.id)
this.client = client
this.bot = !!data.bot
this.system = !!data.system
this.update(data)
}
/** @deprecated Use User.client Supported for Eris api compatibility. */
get _client(): Client {
return this.client
}
get avatar(): string | null {
if (!this._avatar) return null
return this.client.iconBigintToHash(this._avatar)
}
set avatar(value: BigString | null) {
this._avatar = typeof value === 'string' ? this.client.iconHashToBigInt(value) : value
}
get banner(): string | null {
if (!this._banner) return null
return this.client.iconBigintToHash(this._banner)
}
set banner(value: BigString | null) {
this._banner = typeof value === 'string' ? this.client.iconHashToBigInt(value) : value
}
get avatarURL(): string | null {
return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar)) : this.defaultAvatarURL
}
get bannerURL(): string | null {
if (!this.banner) {
return null
}
return this.client._formatImage(BANNER(this.id, this.banner))
}
get defaultAvatar(): string {
// @ts-expect-error some eris magic at play here
return (this.discriminator % 5).toString()
}
get defaultAvatarURL(): string {
return `${this.client.CDN_URL}${DEFAULT_USER_AVATAR(this.defaultAvatar)}.png`
}
get mention(): string {
return `<@${this.id}>`
}
get staticAvatarURL(): string {
return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar), 'jpg') : this.defaultAvatarURL
}
update(data: DiscordUser): void {
if (data.avatar !== undefined) {
this.avatar = data.avatar
}
if (data.username !== undefined) {
this.username = data.username
}
if (data.discriminator !== undefined) {
this.discriminator = data.discriminator
}
if (data.public_flags !== undefined) {
this.publicFlags = data.public_flags
}
if (data.banner !== undefined) {
this.banner = data.banner
}
if (data.accent_color !== undefined) {
this.accentColor = data.accent_color
}
}
/** Get the user's avatar with the given format and size */
dynamicAvatarURL(format?: ImageFormat, size?: ImageSize): string {
return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar), format, size) : this.defaultAvatarURL
}
/** Get the user's banner with the given format and size */
dynamicBannerURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.banner ? this.client._formatImage(BANNER(this.id, this.banner), format, size) : null
}
/**
* Get a DM channel with the user, or create one if it does not exist
* @returns {Promise<PrivateChannel>}
*/
async getDMChannel(): Promise<PrivateChannel> {
return await this.client.getDMChannel.call(this.client, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['accentColor', 'avatar', 'banner', 'bot', 'discriminator', 'publicFlags', 'system', 'username', ...props])
}
}
export default User

File diff suppressed because it is too large Load Diff

View File

@@ -1,151 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import Base from '../Base.js'
import type Client from '../Client.js'
import Collection from '../Collection.js'
import type { ShardManagerOptions } from '../typings.js'
import Shard from './Shard.js'
export class ShardManager extends Collection<number, Shard> {
/** The client manager */
client: Client
/** The options that were used to configure this manager. */
options: ShardManagerOptions
/** The buckets that this manager is handling. */
buckets: Map<number, number>
/** The queue in which to connect a shard. */
connectQueue: Shard[]
/** The timeout to use for connecting a shard. */
connectTimeout: NodeJS.Timeout | null
constructor(client: Client, options: ShardManagerOptions = {}) {
super()
this.client = client
this.options = Object.assign({ concurrency: 1 }, options)
this.buckets = new Map()
this.connectQueue = []
this.connectTimeout = null
}
/**
* @deprecated Use `.client` instead.
*/
get _client(): Client {
return this.client
}
connect(shard: Shard) {
this.connectQueue.push(shard)
this.tryConnect()
}
get concurrency(): number {
return this.options.concurrency as number
}
setConcurrency(concurrency: number) {
this.options.concurrency = concurrency
}
spawn(id: number) {
let shard = this.get(id)
if (!shard) {
shard = new Shard(id, this.client)
this.set(id, shard)
shard
.on('ready', () => {
this.client.emit('shardReady', shard!.id)
if (this.client.ready) return
for (const other of this.values()) if (!other.ready) return
this.client.ready = true
this.client.startTime = Date.now()
this.client.emit('ready')
})
.on('resume', () => {
this.client.emit('shardResume', shard!.id)
if (this.client.ready) return
for (const other of this.values()) if (!other.ready) return
this.client.ready = true
this.client.startTime = Date.now()
this.client.emit('ready')
})
.on('disconnect', (error) => {
this.client.emit('shardDisconnect', error, shard!.id)
for (const other of this.values()) if (other.ready) return
this.client.ready = false
this.client.startTime = 0
this.client.emit('disconnect')
})
}
if (shard.status === 'disconnected') {
return this.connect(shard)
}
}
tryConnect() {
// nothing in queue
if (this.connectQueue.length === 0) {
return
}
// loop over the connectQueue
for (const shard of this.connectQueue) {
// find the bucket for our shard
const rateLimitKey = shard.id % this.concurrency || 0
const lastConnect = this.buckets.get(rateLimitKey) ?? 0
// has enough time passed since the last connect for this bucket (5s/bucket)?
// alternatively if we have a sessionID, we can skip this check
if (!shard.sessionID && Date.now() - lastConnect < 5000) {
continue
}
// Are there any connecting shards in the same bucket we should wait on?
if (this.some((s) => s.connecting && (s.id % this.concurrency || 0) === rateLimitKey)) {
continue
}
// connect the shard
shard.identify()
this.buckets.set(rateLimitKey, Date.now())
// remove the shard from the queue
const index = this.connectQueue.findIndex((s) => s.id === shard.id)
this.connectQueue.splice(index, 1)
}
// set the next timeout if we have more shards to connect
if (!this.connectTimeout && this.connectQueue.length > 0) {
this.connectTimeout = setTimeout(() => {
this.connectTimeout = null
this.tryConnect()
}, 500)
}
}
_readyPacketCB(shardID: number) {
const rateLimitKey = shardID % this.concurrency || 0
this.buckets.set(rateLimitKey, Date.now())
this.tryConnect()
}
toString() {
return `[ShardManager ${this.size}]`
}
toJSON(props = []) {
return Base.prototype.toJSON.call(this, ['buckets', 'connectQueue', 'connectTimeout', 'options', ...props])
}
}
export default ShardManager

View File

@@ -1,145 +0,0 @@
import Base from './Base.js'
import type { ClientOptions } from './Client.js'
import Client from './Client.js'
import Collection from './Collection.js'
import * as Constants from './Constants.js'
import Shard from './gateway/Shard.js'
import RequestHandler from './RequestHandler.js'
import { CategoryChannel } from './Structures/channels/Category.js'
import Channel from './Structures/channels/Channel.js'
import Guild, { GuildChannel } from './Structures/channels/Guild.js'
import { NewsChannel } from './Structures/channels/News.js'
import { PrivateChannel } from './Structures/channels/Private.js'
import { StageChannel } from './Structures/channels/Stage.js'
import { TextChannel } from './Structures/channels/Text.js'
import { TextVoiceChannel } from './Structures/channels/TextVoice.js'
import Member, { ThreadMember } from './Structures/channels/threads/Member.js'
import { NewsThreadChannel } from './Structures/channels/threads/NewsThread.js'
import { PrivateThreadChannel } from './Structures/channels/threads/PrivateThread.js'
import { PublicThreadChannel } from './Structures/channels/threads/PublicThread.js'
import { ThreadChannel } from './Structures/channels/threads/Thread.js'
import { VoiceChannel } from './Structures/channels/Voice.js'
import { GuildIntegration } from './Structures/guilds/Integration.js'
import { GuildPreview } from './Structures/guilds/Preview.js'
import Role from './Structures/guilds/Role.js'
import StageInstance from './Structures/guilds/StageInstance.js'
import { GuildTemplate } from './Structures/guilds/Template.js'
import { UnavailableGuild } from './Structures/guilds/Unavailable.js'
import { VoiceState } from './Structures/guilds/VoiceState.js'
import { AutocompleteInteraction } from './Structures/interactions/Autocomplete.js'
import Command, { CommandInteraction } from './Structures/interactions/Command.js'
import { ComponentInteraction } from './Structures/interactions/Component.js'
import Interaction from './Structures/interactions/Interaction.js'
import { PingInteraction } from './Structures/interactions/Ping.js'
import { UnknownInteraction } from './Structures/interactions/Unknown.js'
import Invite from './Structures/Invite.js'
import Message from './Structures/Message.js'
import Permission from './Structures/Permission.js'
import PermissionOverwrite from './Structures/PermissionOverwrite.js'
import { ExtendedUser } from './Structures/users/Extended.js'
import User from './Structures/users/User.js'
import Bucket from './utils/Bucket.js'
import DiscordRESTError from './utils/DiscordRESTError.js'
// TODO: MAKE THIS DYNAMIC FROM PACKAGE.JSON
export const VERSION = "19.0.0";
export function DiscordenoClient(token: string, options: ClientOptions): Client {
return new Client(token, options)
}
DiscordenoClient.AutocompleteInteraction = AutocompleteInteraction
DiscordenoClient.Base = Base
DiscordenoClient.Bucket = Bucket
DiscordenoClient.CategoryChannel = CategoryChannel
DiscordenoClient.Channel = Channel
DiscordenoClient.CommandInteraction = CommandInteraction
DiscordenoClient.ComponentInteraction = ComponentInteraction
DiscordenoClient.Client = Client
DiscordenoClient.Collection = Collection
DiscordenoClient.Command = Command
// DiscordenoClient.CommandClient = CommandClient
DiscordenoClient.Constants = Constants
// DiscordenoClient.DiscordHTTPError = DiscordHTTPError
DiscordenoClient.DiscordRESTError = DiscordRESTError
DiscordenoClient.ExtendedUser = ExtendedUser
DiscordenoClient.Guild = Guild
DiscordenoClient.GuildChannel = GuildChannel
DiscordenoClient.GuildIntegration = GuildIntegration
DiscordenoClient.GuildPreview = GuildPreview
DiscordenoClient.GuildTemplate = GuildTemplate
DiscordenoClient.Interaction = Interaction
DiscordenoClient.Invite = Invite
DiscordenoClient.Member = Member
DiscordenoClient.Message = Message
DiscordenoClient.NewsChannel = NewsChannel
DiscordenoClient.NewsThreadChannel = NewsThreadChannel
DiscordenoClient.Permission = Permission
DiscordenoClient.PermissionOverwrite = PermissionOverwrite
DiscordenoClient.PingInteraction = PingInteraction
DiscordenoClient.PrivateChannel = PrivateChannel
DiscordenoClient.PrivateThreadChannel = PrivateThreadChannel
DiscordenoClient.PublicThreadChannel = PublicThreadChannel
DiscordenoClient.RequestHandler = RequestHandler
DiscordenoClient.Role = Role
DiscordenoClient.Shard = Shard
DiscordenoClient.StageChannel = StageChannel
DiscordenoClient.StageInstance = StageInstance
DiscordenoClient.TextChannel = TextChannel
DiscordenoClient.TextVoiceChannel = TextVoiceChannel
DiscordenoClient.ThreadChannel = ThreadChannel
DiscordenoClient.ThreadMember = ThreadMember
DiscordenoClient.UnavailableGuild = UnavailableGuild
DiscordenoClient.UnknownInteraction = UnknownInteraction
DiscordenoClient.User = User
DiscordenoClient.VERSION = VERSION
DiscordenoClient.VoiceChannel = VoiceChannel
DiscordenoClient.VoiceState = VoiceState
export * from './Base.js'
export * from './Client.js'
export * from './Collection.js'
export * from './Constants.js'
export * from './Endpoints.js'
export * from './gateway/Shard.js'
export * from './gateway/ShardManager.js'
export * from './Structures/channels/Category.js'
export * from './Structures/channels/Channel.js'
export * from './Structures/channels/Guild.js'
export * from './Structures/channels/News.js'
export * from './Structures/channels/Private.js'
export * from './Structures/channels/Stage.js'
export * from './Structures/channels/Text.js'
export * from './Structures/channels/TextVoice.js'
export * from './Structures/channels/threads/Member.js'
export * from './Structures/channels/threads/NewsThread.js'
export * from './Structures/channels/threads/PrivateThread.js'
export * from './Structures/channels/threads/PublicThread.js'
export * from './Structures/channels/threads/Thread.js'
export * from './Structures/channels/Voice.js'
export * from './Structures/guilds/AuditLogEntry.js'
export * from './Structures/guilds/Guild.js'
export * from './Structures/guilds/Integration.js'
export * from './Structures/guilds/Member.js'
export * from './Structures/guilds/Preview.js'
export * from './Structures/guilds/Role.js'
export * from './Structures/guilds/StageInstance.js'
export * from './Structures/guilds/Template.js'
export * from './Structures/guilds/Unavailable.js'
export * from './Structures/guilds/VoiceState.js'
export * from './Structures/interactions/Autocomplete.js'
export * from './Structures/interactions/Command.js'
export * from './Structures/interactions/Component.js'
export * from './Structures/interactions/Interaction.js'
export * from './Structures/interactions/Ping.js'
export * from './Structures/interactions/Unknown.js'
export * from './Structures/Invite.js'
export * from './Structures/Message.js'
export * from './Structures/Permission.js'
export * from './Structures/PermissionOverwrite.js'
export * from './Structures/users/Extended.js'
export * from './Structures/users/User.js'
export * from './typings.js'
export * from './utils/BrowserWebSocket.js'
export * from './utils/Bucket.js'
export * from './utils/DiscordRESTError.js'
export * from './utils/generate.js'

File diff suppressed because it is too large Load Diff

View File

@@ -1,105 +0,0 @@
/* eslint-disable @typescript-eslint/class-literal-property-style */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/no-var-requires */
import { EventEmitter } from 'node:events'
class BrowserWebSocketError extends Error {
static CONNECTING: 0 = 0
static OPEN: 1
static CLOSING: 2
static CLOSED: 3
readyState: number = 0
event: Event
constructor(message: string | undefined, event: Event) {
super(message)
this.event = event
}
}
/**
* Represents a browser's websocket usable by Eris
* @extends EventEmitter
* @prop {String} url The URL to connect to
*/
class BrowserWebSocket extends EventEmitter {
_ws: WebSocket
constructor(url: string) {
super()
if (typeof window === 'undefined') {
throw new Error('BrowserWebSocket cannot be used outside of a browser environment')
}
this._ws = new window.WebSocket(url)
this._ws.onopen = () => this.emit('open')
this._ws.onmessage = this._onMessage.bind(this)
this._ws.onerror = (event) => this.emit('error', new BrowserWebSocketError('Unknown error', event))
this._ws.onclose = (event) => this.emit('close', event.code, event.reason)
}
get readyState() {
return this._ws.readyState
}
static get CONNECTING() {
return 0
}
static set CONNECTING(state: number) {
BrowserWebSocket.CONNECTING = state
}
static get OPEN() {
return 1
}
static set OPEN(state: number) {
BrowserWebSocket.OPEN = state
}
static get CLOSING() {
return 2
}
static set CLOSING(state: number) {
BrowserWebSocket.CLOSING = state
}
static get CLOSED() {
return 3
}
static set CLOSED(state: number) {
BrowserWebSocket.CLOSED = state
}
close(code?: number, reason?: string) {
return this._ws.close(code, reason)
}
removeEventListener(type: string | symbol, listener: (...args: any[]) => void): this {
return this.removeListener(type, listener)
}
send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
return this._ws.send(data)
}
terminate() {
return this._ws.close()
}
async _onMessage(event: MessageEvent<any>) {
if (event.data instanceof window.Blob) {
this.emit('message', await event.data.arrayBuffer())
} else {
this.emit('message', event.data)
}
}
}
export default BrowserWebSocket

View File

@@ -1,102 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
interface BucketOptions {
latencyRef?: { latency: number }
reservedTokens?: number
}
/**
* Handle ratelimiting something
* @prop {Number} interval How long (in ms) to wait between clearing used tokens
* @prop {Number} lastReset Timestamp of last token clearing
* @prop {Number} lastSend Timestamp of last token consumption
* @prop {Number} tokenLimit The max number tokens the bucket can consume per interval
* @prop {Number} tokens How many tokens the bucket has consumed in this interval
*/
class Bucket {
interval: number
latencyRef: { latency: number }
lastReset: number
lastSend: number
tokenLimit: number
tokens: number
reservedTokens: number
// eslint-disable-next-line @typescript-eslint/ban-types
_queue: Array<{ func: Function; priority: boolean }>
timeout: NodeJS.Timeout | null = null
/**
* Construct a Bucket
* @arg {Number} tokenLimit The max number of tokens the bucket can consume per interval
* @arg {Number} interval How long (in ms) to wait between clearing used tokens
* @arg {Object} [options] Optional parameters
* @arg {Object} options.latencyRef A latency reference object
* @arg {Number} options.latencyRef.latency Interval between consuming tokens
* @arg {Number} options.reservedTokens How many tokens to reserve for priority operations
*/
constructor(tokenLimit: number, interval: number, options: BucketOptions = {} as BucketOptions) {
this.tokenLimit = tokenLimit
this.interval = interval
this.latencyRef = options.latencyRef ?? { latency: 0 }
this.lastReset = this.tokens = this.lastSend = 0
this.reservedTokens = options.reservedTokens ?? 0
this._queue = []
}
check() {
if (this.timeout ?? this._queue.length === 0) {
return
}
if (this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency < Date.now()) {
this.lastReset = Date.now()
this.tokens = Math.max(0, this.tokens - this.tokenLimit)
}
let val
let tokensAvailable = this.tokens < this.tokenLimit
let unreservedTokensAvailable = this.tokens < this.tokenLimit - this.reservedTokens
while (this._queue.length > 0 && (unreservedTokensAvailable || (tokensAvailable && this._queue[0].priority))) {
this.tokens++
tokensAvailable = this.tokens < this.tokenLimit
unreservedTokensAvailable = this.tokens < this.tokenLimit - this.reservedTokens
const item = this._queue.shift()
val = this.latencyRef.latency - Date.now() + this.lastSend
if (this.latencyRef.latency === 0 || val <= 0) {
item!.func()
this.lastSend = Date.now()
} else {
setTimeout(() => {
item!.func()
}, val)
this.lastSend = Date.now() + val
}
}
if (this._queue.length > 0 && !this.timeout) {
this.timeout = setTimeout(
() => {
this.timeout = null
this.check()
},
this.tokens < this.tokenLimit
? this.latencyRef.latency
: Math.max(0, this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency - Date.now()),
)
}
}
/**
* Queue something in the Bucket
* @arg {Function} func A callback to call when a token can be consumed
* @arg {Boolean} [priority=false] Whether or not the callback should use reserved tokens
*/
queue(func: () => void, priority = false) {
if (priority) {
this._queue.unshift({ func, priority })
} else {
this._queue.push({ func, priority })
}
this.check()
}
}
export default Bucket

View File

@@ -1,86 +0,0 @@
import type { ClientRequest, IncomingHttpHeaders, IncomingMessage } from 'http'
import type { HTTPResponse } from '../typings.js'
export class DiscordRESTError extends Error {
code: number = -1
req!: ClientRequest
res!: IncomingMessage
response!: HTTPResponse
constructor(req: ClientRequest, res: IncomingMessage, response: HTTPResponse, stack: string) {
super()
Object.defineProperty(this, 'req', {
enumerable: false,
value: req,
})
Object.defineProperty(this, 'res', {
enumerable: false,
value: res,
})
Object.defineProperty(this, 'response', {
enumerable: false,
value: response,
})
Object.defineProperty(this, 'code', {
enumerable: false,
value: +response.code || -1,
})
let message = response.message || 'Unknown error'
if (response.errors) {
message += '\n ' + this.flattenErrors(response.errors).join('\n ')
} else {
const errors = this.flattenErrors(response)
if (errors.length > 0) {
message += '\n ' + errors.join('\n ')
}
}
Object.defineProperty(this, 'message', {
enumerable: false,
value: message,
})
if (stack) {
this.stack = this.name + ': ' + this.message + '\n' + stack
} else {
Error.captureStackTrace(this, DiscordRESTError)
}
}
get headers(): IncomingHttpHeaders {
return this.response.headers
}
get name(): string {
return `${this.constructor.name} [${this.code}]`
}
flattenErrors(errors: HTTPResponse, keyPrefix?: string): string[] {
let messages: string[] = []
for (const fieldName of Object.keys(errors)) {
if (fieldName === 'message' || fieldName === 'code') {
continue
}
const prefix = `${keyPrefix ?? ""}${fieldName}`;
// @ts-expect-error js hack from eris
if (errors[fieldName]._errors) {
// @ts-expect-error js hack from eris
messages = messages.concat(errors[fieldName]._errors.map((obj: any) => `${prefix}: ${obj.message as string}`))
// @ts-expect-error js hack from eris
} else if (Array.isArray(errors[fieldName])) {
// @ts-expect-error js hack from eris
messages = messages.concat(errors[fieldName].map((str: string) => `${prefix}: ${str}`))
// @ts-expect-error js hack from eris
} else if (typeof errors[fieldName] === 'object') {
// @ts-expect-error js hack from eris
messages = messages.concat(this.flattenErrors(errors[fieldName], `${prefix}.`))
}
}
return messages
}
}
export default DiscordRESTError

View File

@@ -1,85 +0,0 @@
import type { DiscordChannel, DiscordInteraction } from '@discordeno/types'
import { ChannelTypes, InteractionTypes } from '@discordeno/types'
import type Client from '../Client.js'
import type { TextableChannel } from '../index.js'
import CategoryChannel from '../Structures/channels/Category.js'
import Channel from '../Structures/channels/Channel.js'
import GuildChannel from '../Structures/channels/Guild.js'
import NewsChannel from '../Structures/channels/News.js'
import PrivateChannel from '../Structures/channels/Private.js'
import StageChannel from '../Structures/channels/Stage.js'
import TextChannel from '../Structures/channels/Text.js'
import TextVoiceChannel from '../Structures/channels/TextVoice.js'
import NewsThreadChannel from '../Structures/channels/threads/NewsThread.js'
import PrivateThreadChannel from '../Structures/channels/threads/PrivateThread.js'
import PublicThreadChannel from '../Structures/channels/threads/PublicThread.js'
import AutocompleteInteraction from '../Structures/interactions/Autocomplete.js'
import CommandInteraction from '../Structures/interactions/Command.js'
import ComponentInteraction from '../Structures/interactions/Component.js'
import PingInteraction from '../Structures/interactions/Ping.js'
import UnknownInteraction from '../Structures/interactions/Unknown.js'
export function generateChannelFrom(data: DiscordChannel, client: Client): Channel {
switch (data.type) {
case ChannelTypes.GuildText: {
return new TextChannel(data, client)
}
case ChannelTypes.DM: {
return new PrivateChannel(data, client)
}
case ChannelTypes.GuildVoice: {
return new TextVoiceChannel(data, client)
}
case ChannelTypes.GuildCategory: {
return new CategoryChannel(data, client)
}
case ChannelTypes.GuildAnnouncement: {
return new NewsChannel(data, client)
}
case ChannelTypes.AnnouncementThread: {
return new NewsThreadChannel(data, client)
}
case ChannelTypes.PublicThread: {
return new PublicThreadChannel(data, client)
}
case ChannelTypes.PrivateThread: {
return new PrivateThreadChannel(data, client)
}
case ChannelTypes.GuildStageVoice: {
return new StageChannel(data, client)
}
}
if (data.guild_id) {
if (data.last_message_id !== undefined) {
client.emit('warn', new Error(`Unknown guild text channel type: ${data.type}\n${JSON.stringify(data)}`))
return new TextChannel(data, client)
}
client.emit('warn', new Error(`Unknown guild channel type: ${data.type}\n${JSON.stringify(data)}`))
return new GuildChannel(data, client)
}
client.emit('warn', new Error(`Unknown channel type: ${data.type}\n${JSON.stringify(data)}`))
return new Channel(data, client)
}
export function generateInteractionFrom(
data: DiscordInteraction,
client: Client,
): UnknownInteraction<TextableChannel> | PingInteraction | CommandInteraction | ComponentInteraction | AutocompleteInteraction {
switch (data.type) {
case InteractionTypes.Ping: {
return new PingInteraction(data, client)
}
case InteractionTypes.ApplicationCommand: {
return new CommandInteraction(data, client)
}
case InteractionTypes.MessageComponent: {
return new ComponentInteraction(data, client)
}
case InteractionTypes.ApplicationCommandAutocomplete: {
return new AutocompleteInteraction(data, client)
}
}
client.emit('warn', new Error(`Unknown interaction type: ${data.type}\n${JSON.stringify(data)}`))
return new UnknownInteraction(data, client)
}

View File

@@ -1,7 +0,0 @@
import { describe, it } from 'mocha'
describe('index.ts', () => {
it('will import without error', async () => {
await import('../src/index.js')
})
})

View File

@@ -1,11 +0,0 @@
{
"extends": "tsconfig/test.json",
"include": [
"tests",
],
"exclude": [
"node_modules",
"dist",
"src"
]
}

View File

@@ -7,7 +7,6 @@ import {
camelToSnakeCase,
delay,
encode,
findFiles,
getBotIdFromToken,
isGetMessagesAfter,
isGetMessagesAround,
@@ -33,7 +32,6 @@ import type {
DiscordBan,
DiscordChannel,
DiscordEmoji,
DiscordFollowAnnouncementChannel,
DiscordFollowedChannel,
DiscordGetGatewayBot,
DiscordGuild,
@@ -67,7 +65,7 @@ import type {
MfaLevels,
ModifyGuildTemplate,
} from '@discordeno/types'
import type { CreateRestManagerOptions, RestManager, SendRequestOptions } from './types.js'
import type { CreateRequestBodyOptions, CreateRestManagerOptions, RestManager, SendRequestOptions } from './types.js'
// TODO: make dynamic based on package.json file
const version = '19.0.0-alpha.1'
@@ -693,62 +691,54 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
return obj
},
createRequest(options) {
createRequestBody(method, options) {
const headers: Record<string, string> = {
'user-agent': `DiscordBot (https://github.com/discordeno/discordeno, v${version})`,
}
if (!options.unauthorized) headers.authorization = `Bot ${rest.token}`
// SOMETIMES SPECIAL HEADERS (E.G. CUSTOM AUTHORIZATION) NEED TO BE USED
if (options.headers) {
for (const key in options.headers) {
headers[key.toLowerCase()] = options.headers[key]
}
}
// GET METHODS SHOULD NOT HAVE A BODY
if (options.method === 'GET') {
options.body = undefined
}
if (!options?.unauthorized) headers.authorization = `Bot ${rest.token}`
// IF A REASON IS PROVIDED ENCODE IT IN HEADERS
if (options.body?.reason) {
headers['X-Audit-Log-Reason'] = encodeURIComponent(options.body.reason as string)
options.body.reason = undefined
if (options?.reason !== undefined) {
headers['x-audit-log-reason'] = encodeURIComponent(options?.reason)
}
if (options.body) {
const { file } = options.body
if (file) {
const files = findFiles(file)
const form = new FormData()
let body: string | FormData | undefined
// WHEN CREATING A STICKER, DISCORD WANTS FORM DATA ONLY
if (options.url?.endsWith('/stickers') && options.method === 'POST') {
form.append('file', files[0].blob, files[0].name)
form.append('name', options.body.name as string)
form.append('description', options.body.description as string)
form.append('tags', options.body.tags as string)
} else {
for (let i = 0; i < files.length; i++) {
form.append(`file${i}`, files[i].blob, files[i].name)
}
// TODO: check if we need to add specific check for GET method
// Since GET does not allow bodies
if (file) options.body.file = undefined
form.append('payload_json', JSON.stringify(rest.changeToDiscordFormat(options.body)))
}
options.body.file = form
} else if (options.body && !['GET', 'DELETE'].includes(options.method)) {
headers['Content-Type'] = 'application/json'
// Have to check for attachments first, since body then has to be send in a different way.
if (options?.files !== undefined) {
const form = new FormData()
for (let i = 0; i < options.files.length; ++i) {
form.append(`file${i}`, options.files[i].blob, options.files[i].name)
}
form.append('payload_json', JSON.stringify({ ...options.body, files: undefined }))
body = form
// No need to set the `content-type` header since `fetch` does that automatically for us when we use a `FormData` object.
} else if (options?.body !== undefined) {
if (options.body instanceof FormData) {
body = options.body
// No need to set the `content-type` header since `fetch` does that automatically for us when we use a `FormData` object.
} else {
body = JSON.stringify(rest.changeToDiscordFormat(options.body))
headers['content-type'] = `application/json`
}
}
// SOMETIMES SPECIAL HEADERS (E.G. CUSTOM AUTHORIZATION) NEED TO BE USED
if (options?.headers) {
Object.assign(headers, options.headers)
}
return {
body,
headers,
body: (options.body?.file ?? JSON.stringify(rest.changeToDiscordFormat(options.body))) as FormData | string,
method: options.method,
method,
}
},
@@ -860,7 +850,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
async sendRequest(options) {
const url = options.url.startsWith('https://') ? options.url : `${rest.baseUrl}/v${rest.version}${options.url}`
const payload = rest.createRequest({ method: options.method, url: options.url, body: options.body, ...options.options })
const payload = rest.createRequestBody(options.method, options.requestBodyOptions)
logger.debug(`sending request to ${url}`, 'with payload:', { ...payload, headers: { ...payload.headers, authorization: 'Bot tokenhere' } })
const response = await fetch(url, payload)
@@ -909,26 +899,31 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
options.resolve({ ok: true, status: response.status, body: JSON.stringify(json) })
},
// Credits: github.com/abalabahaha/eris lib/rest/RequestHandler.js#L397
// Modified for our use-case
simplifyUrl(url, method) {
let route = url
.replace(/\/([a-z-]+)\/(?:[0-9]{17,19})/g, function (match, p: string) {
return ['channels', 'guilds'].includes(p) ? match : `/${p}/x`
})
.replace(/\/reactions\/[^/]+/g, '/reactions/x')
const parts = url.split('/')
const secondLastPart = parts[parts.length - 2]
// GENERAL /reactions and /reactions/emoji/@me share the buckets
if (route.includes('/reactions')) {
route = route.substring(0, route.indexOf('/reactions') + '/reactions'.length)
if (secondLastPart === 'channels' || secondLastPart === 'guilds') {
return url
}
// Delete Message endpoint has its own rate limit
if (method === 'DELETE' && route.endsWith('/messages/x')) {
route = method + route
if (secondLastPart === 'reactions' || parts[parts.length - 1] === '@me') {
parts.splice(-2)
parts.push('reactions')
} else {
parts.splice(-1)
parts.push('x')
}
return route
if (parts[parts.length - 3] === 'reactions') {
parts.splice(-2)
}
if (method === 'DELETE' && secondLastPart === 'messages') {
return `D${parts.join('/')}`
}
return parts.join('/')
},
processRequest(request: SendRequestOptions) {
@@ -957,16 +952,17 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
}
},
async makeRequest(method, url, body, options) {
async makeRequest(method, url, options) {
if (!rest.baseUrl.startsWith('https://discord.com') && url[0] === '/') {
// Special handling for sending blobs across http to proxy
if (body?.file) {
if (!Array.isArray(body.file)) {
body.file = [body.file]
// TODO: fix this hacky handling
if (!(options?.body instanceof FormData) && !Array.isArray(options?.body) && options?.body?.file) {
if (!Array.isArray(options.body.file)) {
options.body.file = [options.body.file]
}
// convert blobs to string before sending to proxy
body.file = await Promise.all(
body.file.map(async (f: FileContent) => {
options.body.file = await Promise.all(
(options.body.file as FileContent[]).map(async (f: FileContent) => {
const url = encode(await f.blob.arrayBuffer())
return { name: f.name, blob: `data:${f.blob.type};base64,${url}` }
@@ -977,17 +973,18 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
const headers: HeadersInit = {
Authorization: rest.authorization ?? '',
}
if (body) {
if (options?.body) {
headers['Content-Type'] = 'application/json'
}
const result = await fetch(`${rest.baseUrl}${url}`, {
body: body ? JSON.stringify(body) : undefined,
body: options?.body ? JSON.stringify(options.body) : undefined,
headers,
method,
})
if (!result.ok) {
const err = (await result.json().catch(() => {})) as Record<string, any>
const err = (await result.json().catch(() => { })) as Record<string, any>
// Legacy Handling to not break old code or when body is missing
if (!err?.body) throw new Error(`Error: ${err.message ?? result.statusText}`)
throw new Error(JSON.stringify(err))
@@ -1000,40 +997,39 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
const payload: SendRequestOptions = {
url,
method,
body,
requestBodyOptions: options,
retryCount: 0,
retryRequest: async function (options: SendRequestOptions) {
retryRequest: async function(payload: SendRequestOptions) {
rest.processRequest(payload)
},
resolve: (data) => {
resolve(data.status !== 204 ? JSON.parse(data.body ?? '{}') : undefined)
},
reject,
options,
}
rest.processRequest(payload)
})
},
async get<T = Record<string, unknown>>(url: string) {
return camelize(await rest.makeRequest('GET', url)) as Camelize<T>
async get<T = Record<string, unknown>>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
return camelize(await rest.makeRequest('GET', url, options)) as Camelize<T>
},
async post<T = Record<string, unknown>>(url: string, body?: Record<string, any>) {
return camelize(await rest.makeRequest('POST', url, body)) as Camelize<T>
async post<T = Record<string, unknown>>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
return camelize(await rest.makeRequest('POST', url, options)) as Camelize<T>
},
async delete(url: string, body?: Record<string, any>) {
camelize(await rest.makeRequest('DELETE', url, body))
async delete(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
camelize(await rest.makeRequest('DELETE', url, options))
},
async patch<T = Record<string, unknown>>(url: string, body?: Record<string, any>) {
return camelize(await rest.makeRequest('PATCH', url, body)) as Camelize<T>
async patch<T = Record<string, unknown>>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
return camelize(await rest.makeRequest('PATCH', url, options)) as Camelize<T>
},
async put<T = void>(url: string, body?: Record<string, any>, options?: Record<string, any>) {
return camelize(await rest.makeRequest('PUT', url, body, options)) as Camelize<T>
async put<T = void>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
return camelize(await rest.makeRequest('PUT', url, options)) as Camelize<T>
},
async addReaction(channelId, messageId, reaction) {
@@ -1065,74 +1061,79 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
await rest.put(rest.routes.channels.threads.user(channelId, userId))
},
async createAutomodRule(guildId, options) {
return await rest.post<DiscordAutoModerationRule>(rest.routes.guilds.automod.rules(guildId), options)
async createAutomodRule(guildId, body) {
return await rest.post<DiscordAutoModerationRule>(rest.routes.guilds.automod.rules(guildId), { body })
},
async createChannel(guildId, options) {
return await rest.post<DiscordChannel>(rest.routes.guilds.channels(guildId), options)
async createChannel(guildId, body) {
return await rest.post<DiscordChannel>(rest.routes.guilds.channels(guildId), { body })
},
async createEmoji(guildId, options) {
return await rest.post<DiscordEmoji>(rest.routes.guilds.emojis(guildId), options)
async createEmoji(guildId, body) {
return await rest.post<DiscordEmoji>(rest.routes.guilds.emojis(guildId), { body })
},
async createGlobalApplicationCommand(command) {
return await rest.post<DiscordApplicationCommand>(rest.routes.interactions.commands.commands(rest.applicationId), command)
async createGlobalApplicationCommand(body) {
return await rest.post<DiscordApplicationCommand>(rest.routes.interactions.commands.commands(rest.applicationId), { body })
},
async createGuild(options) {
return await rest.post<DiscordGuild>(rest.routes.guilds.all(), options)
async createGuild(body) {
return await rest.post<DiscordGuild>(rest.routes.guilds.all(), { body })
},
async createGuildApplicationCommand(command, guildId) {
return await rest.post<DiscordApplicationCommand>(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), command)
async createGuildApplicationCommand(body, guildId) {
return await rest.post<DiscordApplicationCommand>(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), { body })
},
async createGuildFromTemplate(templateCode, options) {
if (options.icon) {
options.icon = await urlToBase64(options.icon)
async createGuildFromTemplate(templateCode, body) {
if (body.icon) {
body.icon = await urlToBase64(body.icon)
}
return await rest.post<DiscordGuild>(rest.routes.guilds.templates.code(templateCode), options)
return await rest.post<DiscordGuild>(rest.routes.guilds.templates.code(templateCode), { body })
},
async createGuildSticker(guildId, options) {
return await rest.post<DiscordSticker>(rest.routes.guilds.stickers(guildId), options)
const form = new FormData()
form.append('file', options.file.blob, options.file.name)
form.append('name', options.name)
form.append('description', options.description)
form.append('tags', options.tags)
return await rest.post<DiscordSticker>(rest.routes.guilds.stickers(guildId), { body: form })
},
async createGuildTemplate(guildId, options) {
return await rest.post<DiscordTemplate>(rest.routes.guilds.templates.all(guildId), options)
async createGuildTemplate(guildId, body) {
return await rest.post<DiscordTemplate>(rest.routes.guilds.templates.all(guildId), { body })
},
async createForumThread(channelId, options) {
return await rest.post<DiscordChannel>(rest.routes.channels.forum(channelId), options)
async createForumThread(channelId, body) {
return await rest.post<DiscordChannel>(rest.routes.channels.forum(channelId), { body, files: body.files })
},
async createInvite(channelId, options = {}) {
return await rest.post<DiscordInvite>(rest.routes.channels.invites(channelId), options)
async createInvite(channelId, body = {}) {
return await rest.post<DiscordInvite>(rest.routes.channels.invites(channelId), { body })
},
async createRole(guildId, options, reason) {
return await rest.post<DiscordRole>(rest.routes.guilds.roles.all(guildId), {
...options,
reason,
})
async createRole(guildId, body, reason) {
return await rest.post<DiscordRole>(rest.routes.guilds.roles.all(guildId), { body, reason })
},
async createScheduledEvent(guildId, options) {
return await rest.post<DiscordScheduledEvent>(rest.routes.guilds.events.events(guildId), options)
async createScheduledEvent(guildId, body) {
return await rest.post<DiscordScheduledEvent>(rest.routes.guilds.events.events(guildId), { body })
},
async createStageInstance(options) {
return await rest.post<DiscordStageInstance>(rest.routes.channels.stages(), options)
async createStageInstance(body) {
return await rest.post<DiscordStageInstance>(rest.routes.channels.stages(), { body })
},
async createWebhook(channelId, options) {
async createWebhook(channelId, options, reason) {
return await rest.post<DiscordWebhook>(rest.routes.channels.webhooks(channelId), {
name: options.name,
avatar: options.avatar ? await urlToBase64(options.avatar) : undefined,
reason: options.reason,
body: {
name: options.name,
avatar: options.avatar ? await urlToBase64(options.avatar) : undefined,
},
reason,
})
},
@@ -1147,7 +1148,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async deleteChannelPermissionOverride(channelId, overwriteId, reason) {
await rest.delete(rest.routes.channels.overwrite(channelId, overwriteId), reason ? { reason } : undefined)
await rest.delete(rest.routes.channels.overwrite(channelId, overwriteId), { reason })
},
async deleteEmoji(guildId, id, reason) {
@@ -1171,7 +1172,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async deleteGuildSticker(guildId, stickerId, reason) {
await rest.delete(rest.routes.guilds.sticker(guildId, stickerId), reason ? { reason } : undefined)
await rest.delete(rest.routes.guilds.sticker(guildId, stickerId), { reason })
},
async deleteGuildTemplate(guildId, templateCode) {
@@ -1183,7 +1184,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async deleteInvite(inviteCode, reason) {
await rest.delete(rest.routes.guilds.invite(inviteCode), reason ? { reason } : undefined)
await rest.delete(rest.routes.guilds.invite(inviteCode), { reason })
},
async deleteMessage(channelId, messageId, reason) {
@@ -1192,7 +1193,9 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
async deleteMessages(channelId, messageIds, reason) {
await rest.post(rest.routes.channels.bulk(channelId), {
messages: messageIds.slice(0, 100).map((id) => id.toString()),
body: {
messages: messageIds.slice(0, 100).map((id) => id.toString()),
},
reason,
})
},
@@ -1226,7 +1229,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async deleteStageInstance(channelId, reason) {
await rest.delete(rest.routes.channels.stage(channelId), reason ? { reason } : undefined)
await rest.delete(rest.routes.channels.stage(channelId), { reason })
},
async deleteUserReaction(channelId, messageId, userId, reaction) {
@@ -1247,155 +1250,167 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
await rest.delete(rest.routes.webhooks.webhook(webhookId, token))
},
async editApplicationCommandPermissions(guildId, commandId, bearerToken, options) {
async editApplicationCommandPermissions(guildId, commandId, bearerToken, permissions) {
return await rest.put<DiscordApplicationCommandPermissions>(
rest.routes.interactions.commands.permission(rest.applicationId, guildId, commandId),
{
permissions: options,
},
{
body: {
permissions,
},
headers: { authorization: `Bearer ${bearerToken}` },
},
)
},
async editAutomodRule(guildId, ruleId, options) {
return await rest.patch<DiscordAutoModerationRule>(rest.routes.guilds.automod.rule(guildId, ruleId), options)
async editAutomodRule(guildId, ruleId, body) {
return await rest.patch<DiscordAutoModerationRule>(rest.routes.guilds.automod.rule(guildId, ruleId), { body })
},
async editBotProfile(options) {
const avatar = options?.botAvatarURL ? await urlToBase64(options?.botAvatarURL) : options?.botAvatarURL
return await rest.patch<DiscordUser>(rest.routes.userBot(), {
username: options.username?.trim(),
avatar,
body: {
username: options.username?.trim(),
avatar,
},
})
},
async editChannel(channelId, options) {
return await rest.patch<DiscordChannel>(rest.routes.channels.channel(channelId), options)
async editChannel(channelId, body) {
return await rest.patch<DiscordChannel>(rest.routes.channels.channel(channelId), { body })
},
async editChannelPermissionOverrides(channelId, options) {
await rest.put(rest.routes.channels.overwrite(channelId, options.id), options)
async editChannelPermissionOverrides(channelId, body) {
await rest.put(rest.routes.channels.overwrite(channelId, body.id), { body })
},
async editChannelPositions(guildId, channelPositions) {
await rest.patch(rest.routes.guilds.channels(guildId), channelPositions)
async editChannelPositions(guildId, body) {
await rest.patch(rest.routes.guilds.channels(guildId), { body })
},
async editEmoji(guildId, id, options) {
return await rest.patch<DiscordEmoji>(rest.routes.guilds.emoji(guildId, id), options)
async editEmoji(guildId, id, body) {
return await rest.patch<DiscordEmoji>(rest.routes.guilds.emoji(guildId, id), { body })
},
async editFollowupMessage(token, messageId, options) {
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.message(rest.applicationId, token, messageId), options)
async editFollowupMessage(token, messageId, body) {
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.message(rest.applicationId, token, messageId), {
body,
files: body.files,
})
},
async editGlobalApplicationCommand(commandId, options) {
return await rest.patch<DiscordApplicationCommand>(rest.routes.interactions.commands.command(rest.applicationId, commandId), options)
async editGlobalApplicationCommand(commandId, body) {
return await rest.patch<DiscordApplicationCommand>(rest.routes.interactions.commands.command(rest.applicationId, commandId), { body })
},
async editGuild(guildId, options) {
return await rest.patch<DiscordGuild>(rest.routes.guilds.guild(guildId), options)
async editGuild(guildId, body) {
return await rest.patch<DiscordGuild>(rest.routes.guilds.guild(guildId), { body })
},
async editGuildApplicationCommand(commandId, guildId, options) {
return await rest.patch<DiscordApplicationCommand>(
rest.routes.interactions.commands.guilds.one(rest.applicationId, guildId, commandId),
options,
)
async editGuildApplicationCommand(commandId, guildId, body) {
return await rest.patch<DiscordApplicationCommand>(rest.routes.interactions.commands.guilds.one(rest.applicationId, guildId, commandId), {
body,
})
},
async editGuildMfaLevel(guildId: BigString, mfaLevel: MfaLevels, reason?: string): Promise<void> {
await rest.post(rest.routes.guilds.mfa(guildId), { level: mfaLevel, reason })
await rest.post(rest.routes.guilds.mfa(guildId), { body: { level: mfaLevel }, reason })
},
async editGuildSticker(guildId, stickerId, options) {
return await rest.patch<DiscordSticker>(rest.routes.guilds.sticker(guildId, stickerId), options)
async editGuildSticker(guildId, stickerId, body) {
return await rest.patch<DiscordSticker>(rest.routes.guilds.sticker(guildId, stickerId), { body })
},
async editGuildTemplate(guildId, templateCode: string, options: ModifyGuildTemplate): Promise<Camelize<DiscordTemplate>> {
return await rest.patch<DiscordTemplate>(rest.routes.guilds.templates.guild(guildId, templateCode), options)
async editGuildTemplate(guildId, templateCode: string, body: ModifyGuildTemplate): Promise<Camelize<DiscordTemplate>> {
return await rest.patch<DiscordTemplate>(rest.routes.guilds.templates.guild(guildId, templateCode), { body })
},
async editMessage(channelId, messageId, options) {
return await rest.patch<DiscordMessage>(rest.routes.channels.message(channelId, messageId), options)
async editMessage(channelId, messageId, body) {
return await rest.patch<DiscordMessage>(rest.routes.channels.message(channelId, messageId), { body })
},
async editOriginalInteractionResponse(token, options) {
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.original(rest.applicationId, token), options)
async editOriginalInteractionResponse(token, body) {
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.original(rest.applicationId, token), {
body,
files: body.files,
})
},
async editOriginalWebhookMessage(webhookId, token, options) {
return await rest.patch<DiscordMessage>(rest.routes.webhooks.original(webhookId, token, options), {
type: InteractionResponseTypes.UpdateMessage,
data: options,
body: {
type: InteractionResponseTypes.UpdateMessage,
data: options,
},
files: options.files,
})
},
async editOwnVoiceState(guildId, options) {
await rest.patch(rest.routes.guilds.voice(guildId), {
channel_id: options.channelId,
suppress: options.suppress,
request_to_speak_timestamp: options.requestToSpeakTimestamp
? new Date(options.requestToSpeakTimestamp).toISOString()
: options.requestToSpeakTimestamp,
body: {
...options,
request_to_speak_timestamp: options.requestToSpeakTimestamp
? new Date(options.requestToSpeakTimestamp).toISOString()
: options.requestToSpeakTimestamp,
},
})
},
async editScheduledEvent(guildId, eventId, options) {
return await rest.patch<DiscordScheduledEvent>(rest.routes.guilds.events.event(guildId, eventId), options)
async editScheduledEvent(guildId, eventId, body) {
return await rest.patch<DiscordScheduledEvent>(rest.routes.guilds.events.event(guildId, eventId), { body })
},
async editRole(guildId, roleId, options) {
return await rest.patch<DiscordRole>(rest.routes.guilds.roles.one(guildId, roleId), options)
async editRole(guildId, roleId, body) {
return await rest.patch<DiscordRole>(rest.routes.guilds.roles.one(guildId, roleId), { body })
},
async editRolePositions(guildId, options) {
return await rest.patch<DiscordRole[]>(rest.routes.guilds.roles.all(guildId), options)
async editRolePositions(guildId, body) {
return await rest.patch<DiscordRole[]>(rest.routes.guilds.roles.all(guildId), { body })
},
async editStageInstance(channelId, data) {
return await rest.patch<DiscordStageInstance>(rest.routes.channels.stage(channelId), { topic: data.topic })
async editStageInstance(channelId, topic, reason?: string) {
return await rest.patch<DiscordStageInstance>(rest.routes.channels.stage(channelId), { body: { topic }, reason })
},
async editUserVoiceState(guildId, options) {
await rest.patch(rest.routes.guilds.voice(guildId, options.userId), {
channel_id: options.channelId,
suppress: options.suppress,
user_id: options.userId,
})
await rest.patch(rest.routes.guilds.voice(guildId, options.userId), { body: options })
},
async editWebhook(webhookId, options) {
return await rest.patch<DiscordWebhook>(rest.routes.webhooks.id(webhookId), options)
async editWebhook(webhookId, body) {
return await rest.patch<DiscordWebhook>(rest.routes.webhooks.id(webhookId), { body })
},
async editWebhookMessage(webhookId, token, messageId, options) {
return await rest.patch<DiscordMessage>(rest.routes.webhooks.message(webhookId, token, messageId, options), options)
return await rest.patch<DiscordMessage>(rest.routes.webhooks.message(webhookId, token, messageId, options), {
body: options,
files: options.files,
})
},
async editWebhookWithToken(webhookId, token, options) {
return await rest.patch<DiscordWebhook>(rest.routes.webhooks.webhook(webhookId, token), options)
async editWebhookWithToken(webhookId, token, body) {
return await rest.patch<DiscordWebhook>(rest.routes.webhooks.webhook(webhookId, token), { body })
},
async editWelcomeScreen(guildId, options) {
return await rest.patch<DiscordWelcomeScreen>(rest.routes.guilds.welcome(guildId), options)
async editWelcomeScreen(guildId, body) {
return await rest.patch<DiscordWelcomeScreen>(rest.routes.guilds.welcome(guildId), { body })
},
async editWidgetSettings(guildId, options) {
return await rest.patch<DiscordGuildWidgetSettings>(rest.routes.guilds.widget(guildId), options)
async editWidgetSettings(guildId, body) {
return await rest.patch<DiscordGuildWidgetSettings>(rest.routes.guilds.widget(guildId), { body })
},
async executeWebhook(webhookId, token, options) {
return await rest.post<DiscordMessage>(rest.routes.webhooks.webhook(webhookId, token, options), options)
return await rest.post<DiscordMessage>(rest.routes.webhooks.webhook(webhookId, token, options), { body: options })
},
async followAnnouncement(sourceChannelId, targetChannelId) {
return await rest.post<DiscordFollowedChannel>(rest.routes.channels.follow(sourceChannelId), {
webhook_channel_id: targetChannelId,
} as DiscordFollowAnnouncementChannel)
body: {
webhook_channel_id: targetChannelId,
},
})
},
async getActiveThreads(guildId) {
@@ -1458,7 +1473,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
async getDmChannel(userId) {
return await rest.post<DiscordChannel>(rest.routes.channels.dm(), {
recipient_id: userId.toString(),
body: { recipient_id: userId },
})
},
@@ -1670,14 +1685,15 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
await rest.delete(rest.routes.channels.threads.user(channelId, userId))
},
// TODO: why that
async sendFollowupMessage(token, options) {
return await new Promise((resolve, reject) => {
rest.sendRequest({
url: rest.routes.webhooks.webhook(rest.applicationId, token),
method: 'POST',
body: options,
requestBodyOptions: { body: options, files: options.files },
retryCount: 0,
retryRequest: async function (options: SendRequestOptions) {
retryRequest: async function(options: SendRequestOptions) {
// TODO: should change to reprocess queue item
await rest.sendRequest(options)
},
@@ -1689,14 +1705,15 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
})
},
// TODO: why that
async sendInteractionResponse(interactionId, token, options) {
await new Promise((resolve, reject) => {
rest.sendRequest({
url: rest.routes.interactions.responses.callback(interactionId, token),
method: 'POST',
body: options,
requestBodyOptions: { body: options },
retryCount: 0,
retryRequest: async function (options: SendRequestOptions) {
retryRequest: async function(options: SendRequestOptions) {
// TODO: should change to reprocess queue item
await rest.sendRequest(options)
},
@@ -1708,32 +1725,32 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
})
},
async sendMessage(channelId, options) {
return await rest.post<DiscordMessage>(rest.routes.channels.messages(channelId), options)
async sendMessage(channelId, body) {
return await rest.post<DiscordMessage>(rest.routes.channels.messages(channelId), { body, files: body.files })
},
async startThreadWithMessage(channelId, messageId, options) {
return await rest.post<DiscordChannel>(rest.routes.channels.threads.message(channelId, messageId), options)
async startThreadWithMessage(channelId, messageId, body) {
return await rest.post<DiscordChannel>(rest.routes.channels.threads.message(channelId, messageId), { body })
},
async startThreadWithoutMessage(channelId, options) {
return await rest.post<DiscordChannel>(rest.routes.channels.threads.all(channelId), options)
async startThreadWithoutMessage(channelId, body) {
return await rest.post<DiscordChannel>(rest.routes.channels.threads.all(channelId), { body })
},
async syncGuildTemplate(guildId) {
return await rest.put<DiscordTemplate>(rest.routes.guilds.templates.all(guildId))
},
async banMember(guildId, userId, options) {
await rest.put<void>(rest.routes.guilds.members.ban(guildId, userId), options)
async banMember(guildId, userId, body) {
await rest.put<void>(rest.routes.guilds.members.ban(guildId, userId), { body })
},
async editBotMember(guildId, options) {
return await rest.patch<DiscordMember>(rest.routes.guilds.members.bot(guildId), options)
async editBotMember(guildId, body) {
return await rest.patch<DiscordMember>(rest.routes.guilds.members.bot(guildId), { body })
},
async editMember(guildId, userId, options) {
return await rest.patch<DiscordMemberWithUser>(rest.routes.guilds.members.member(guildId, userId), options)
async editMember(guildId, userId, body) {
return await rest.patch<DiscordMemberWithUser>(rest.routes.guilds.members.member(guildId, userId), { body })
},
async getMember(guildId, userId) {
@@ -1751,11 +1768,11 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async pinMessage(channelId, messageId, reason) {
await rest.put(rest.routes.channels.pin(channelId, messageId), reason ? { reason } : undefined)
await rest.put(rest.routes.channels.pin(channelId, messageId), { reason })
},
async pruneMembers(guildId, options) {
return await rest.post<{ pruned: number | null }>(rest.routes.guilds.members.prune(guildId), options)
async pruneMembers(guildId, body) {
return await rest.post<{ pruned: number | null }>(rest.routes.guilds.members.prune(guildId), { body })
},
async searchMembers(guildId, query, options) {
@@ -1767,19 +1784,19 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async unpinMessage(channelId, messageId, reason) {
await rest.delete(rest.routes.channels.pin(channelId, messageId), reason ? { reason } : undefined)
await rest.delete(rest.routes.channels.pin(channelId, messageId), { reason })
},
async triggerTypingIndicator(channelId) {
await rest.post(rest.routes.channels.typing(channelId))
},
async upsertGlobalApplicationCommands(commands) {
return await rest.put<DiscordApplicationCommand[]>(rest.routes.interactions.commands.commands(rest.applicationId), commands)
async upsertGlobalApplicationCommands(body) {
return await rest.put<DiscordApplicationCommand[]>(rest.routes.interactions.commands.commands(rest.applicationId), { body })
},
async upsertGuildApplicationCommands(guildId, commands) {
return await rest.put<DiscordApplicationCommand[]>(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), commands)
async upsertGuildApplicationCommands(guildId, body) {
return await rest.put<DiscordApplicationCommand[]>(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), { body })
},
}

View File

@@ -4,22 +4,6 @@ import type {
BeginGuildPrune,
BigString,
Camelize,
CreateApplicationCommand,
CreateAutoModerationRuleOptions,
CreateChannelInvite,
CreateForumPostWithMessage,
CreateGuild,
CreateGuildBan,
CreateGuildChannel,
CreateGuildEmoji,
CreateGuildFromTemplate,
CreateGuildRole,
CreateGuildStickerOptions,
CreateMessageOptions,
CreateScheduledEvent,
CreateStageInstance,
CreateTemplate,
DeleteWebhookMessageOptions,
CamelizedDiscordActiveThreads,
CamelizedDiscordApplication,
CamelizedDiscordApplicationCommand,
@@ -56,6 +40,22 @@ import type {
CamelizedDiscordVoiceRegion,
CamelizedDiscordWebhook,
CamelizedDiscordWelcomeScreen,
CreateApplicationCommand,
CreateAutoModerationRuleOptions,
CreateChannelInvite,
CreateForumPostWithMessage,
CreateGuild,
CreateGuildBan,
CreateGuildChannel,
CreateGuildEmoji,
CreateGuildFromTemplate,
CreateGuildRole,
CreateGuildStickerOptions,
CreateMessageOptions,
CreateScheduledEvent,
CreateStageInstance,
CreateTemplate,
DeleteWebhookMessageOptions,
EditAutoModerationRuleOptions,
EditBotMemberOptions,
EditChannelPermissionOverridesOptions,
@@ -64,9 +64,9 @@ import type {
EditMessage,
EditOwnVoiceState,
EditScheduledEvent,
EditStageInstanceOptions,
EditUserVoiceState,
ExecuteWebhook,
FileContent,
GetBans,
GetGuildAuditLog,
GetGuildPruneCountQuery,
@@ -92,7 +92,6 @@ import type {
SearchMembers,
StartThreadWithMessage,
StartThreadWithoutMessage,
WithReason,
} from '@discordeno/types'
import type { InvalidRequestBucket } from './invalidBucket.js'
import type { Queue } from './queue.js'
@@ -160,7 +159,7 @@ export interface RestManager {
/** Reshapes and modifies the obj as needed to make it ready for discords api. */
changeToDiscordFormat: (obj: any) => any
/** Creates the request body and headers that are necessary to send a request. Will handle different types of methods and everything necessary for discord. */
createRequest: (options: CreateRequestBodyOptions) => RequestBody
createRequestBody: (method: RequestMethods, options?: CreateRequestBodyOptions) => RequestBody
/** This will create a infinite loop running in 1 seconds using tail recursion to keep rate limits clean. When a rate limit resets, this will remove it so the queue can proceed. */
processRateLimitedPaths: () => void
/** Processes the rate limit headers and determines if it needs to be rate limited and returns the bucket id if available */
@@ -170,19 +169,19 @@ export interface RestManager {
/** Split a url to separate rate limit buckets based on major/minor parameters. */
simplifyUrl: (url: string, method: RequestMethods) => string
/** Make a request to be sent to the api. */
makeRequest: <T = unknown>(method: RequestMethods, url: string, body?: Record<string, any>, options?: Record<string, any>) => Promise<T>
makeRequest: <T = unknown>(method: RequestMethods, url: string, options?: Omit<CreateRequestBodyOptions, 'method'>) => Promise<T>
/** Takes a request and processes it into a queue. */
processRequest: (request: SendRequestOptions) => void
/** Make a get request to the api */
get: <T = void>(url: string) => Promise<Camelize<T>>
get: <T = void>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) => Promise<Camelize<T>>
/** Make a post request to the api. */
post: <T = void>(url: string, body?: Record<string, any>) => Promise<Camelize<T>>
post: <T = void>(url: string, options?: Omit<CreateRequestBodyOptions, 'method'>) => Promise<Camelize<T>>
/** Make a put request to the api. */
put: <T = void>(url: string, body?: Record<string, any>, options?: Record<string, any>) => Promise<Camelize<T>>
put: <T = void>(url: string, options?: Omit<CreateRequestBodyOptions, 'method'>) => Promise<Camelize<T>>
/** Make a delete request to the api. */
delete: (url: string, body?: Record<string, any>) => Promise<void>
delete: (url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) => Promise<void>
/** Make a patch request to the api. */
patch: <T = void>(url: string, body?: Record<string, any>) => Promise<Camelize<T>>
patch: <T = void>(url: string, options?: Omit<CreateRequestBodyOptions, 'method'>) => Promise<Camelize<T>>
/**
* Adds a reaction to a message.
*
@@ -487,7 +486,7 @@ export interface RestManager {
*
* @see {@link https://discord.com/developers/docs/resources/webhook#create-webhook}
*/
createWebhook: (channelId: BigString, options: CreateWebhook) => Promise<CamelizedDiscordWebhook>
createWebhook: (channelId: BigString, options: CreateWebhook, reason?: string) => Promise<CamelizedDiscordWebhook>
/**
* Deletes an automod rule.
*
@@ -1187,6 +1186,7 @@ export interface RestManager {
* Edits a stage instance.
*
* @param channelId - The ID of the stage channel the stage instance is associated with.
* @param topic - Topic of the Stage instance (1-120 characters).
* @returns An instance of the updated {@link CamelizedDiscordStageInstance}.
*
* @remarks
@@ -1196,7 +1196,7 @@ export interface RestManager {
*
* @see {@link https://discord.com/developers/docs/resources/stage-instance#modify-stage-instance}
*/
editStageInstance: (channelId: BigString, data: EditStageInstanceOptions) => Promise<CamelizedDiscordStageInstance>
editStageInstance: (channelId: BigString, topic: string, reason?: string) => Promise<CamelizedDiscordStageInstance>
/**
* Edits the voice state of another user.
*
@@ -2445,7 +2445,7 @@ export interface RestManager {
export type RequestMethods = 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT'
export type ApiVersions = 9 | 10
export interface CreateWebhook extends WithReason {
export interface CreateWebhook {
/** Name of the webhook (1-80 characters) */
name: string
/** Image url for the default webhook avatar */
@@ -2454,25 +2454,23 @@ export interface CreateWebhook extends WithReason {
export interface CreateRequestBodyOptions {
headers?: Record<string, string>
method: RequestMethods
body?: Record<string, unknown>
body?: any
unauthorized?: boolean
url?: string
reason?: string
files?: FileContent[]
}
export interface RequestBody {
headers: Record<string, string>
body: string | FormData
body?: string | FormData
method: RequestMethods
}
export interface SendRequestOptions {
/** The url to send the request to. */
url: string
/** The method to use when sending the request. */
/** The method to use for sending the request. */
method: RequestMethods
/** The body to be sent in the request. */
body?: Record<string, any>
/** The amount of times this request has been retried. */
retryCount: number
/** Handler to retry a request should it be rate limited. */
@@ -2484,7 +2482,7 @@ export interface SendRequestOptions {
/** If this request has a bucket id which it falls under for rate limit */
bucketId?: string
/** Additional request options, used for things like overriding authorization header. */
options?: Record<string, any>
requestBodyOptions?: CreateRequestBodyOptions
}
export interface RestRateLimitedPath {
@@ -2508,9 +2506,12 @@ export interface WebhookMessageEditor {
*
* @see {@link https://discord.com/developers/docs/resources/webhook#edit-webhook-message}
*/
(webhookId: BigString, token: string, messageId: BigString, options: InteractionCallbackData & { threadId?: BigString }): Promise<
CamelizedDiscordMessage
>
(
webhookId: BigString,
token: string,
messageId: BigString,
options: InteractionCallbackData & { threadId?: BigString },
): Promise<CamelizedDiscordMessage>
/**
* Edits the original webhook message.
*

View File

@@ -57,7 +57,7 @@ describe('Member tests', () => {
it('Send a direct message', async () => {
// DM test only on dd unit testing bot
if (rest.applicationId.toString() !== "770381961553510451") return;
if (rest.applicationId.toString() !== '770381961553510451') return
// Itoh Alt ID
const channel = await rest.getDmChannel(750661528360845322n)
expect(channel?.id).to.exist

View File

@@ -21,7 +21,7 @@ after(async () => {
describe('Send a message', () => {
it('With content', async () => {
const message = await rest.sendMessage('1041029705790402611', { content: 'testing rate limit manager' })
const message = await rest.sendMessage(e2ecache.channel.id, { content: 'testing rate limit manager' })
expect(message.content).to.be.equal('testing rate limit manager')
const edited = await rest.editMessage(message.channelId, message.id, { content: 'testing rate limit manager edited' })
@@ -37,7 +37,7 @@ describe('Send a message', () => {
expect(image).to.not.be.undefined
if (!image) throw new Error('Was not able to fetch the image.')
const message = await rest.sendMessage('1041029705790402611', { file: { blob: image, name: 'gamer' } })
const message = await rest.sendMessage(e2ecache.channel.id, { files: [{ blob: image, name: 'gamer' }] })
expect(message.attachments.length).to.be.greaterThan(0)
const [attachment] = message.attachments

View File

@@ -11,8 +11,12 @@ export const rest = createRestManager({
})
rest.deleteQueueDelay = 10000
const guild = await rest.createGuild({ name: 'ddenotester' });
const channel = await rest.createChannel(guild.id, { name: "ddenotestchannel" });
export const e2ecache = {
guild: await rest.createGuild({ name: 'ddenotester' }),
guild,
channel,
deletedGuild: false,
communityGuildId: E2E_TEST_GUILD_ID,
}

View File

@@ -24,8 +24,8 @@ describe('[rest] manager', () => {
version: 9,
proxy: {
baseUrl: 'https://localhost:8000',
authorization: token
}
authorization: token,
},
} as const
const rest = createRestManager(options)
@@ -80,7 +80,7 @@ describe('[rest] manager', () => {
it('Will add method in front route if method is DELETE', () => {
const rest = createRestManager({ token })
expect(rest.simplifyUrl('/channels/555555555555555555/messages/555555555555555555', 'DELETE')).to.be.equal(
'DELETE/channels/555555555555555555/messages/x',
'D/channels/555555555555555555/messages/x',
)
})

View File

@@ -149,6 +149,6 @@ and unofficial templates:
## Links
- [Website](https://discordeno.github.io/discordeno/)
- [Website](https://discordeno.js.org/)
- [Documentation](https://doc.deno.land/https/deno.land/x/discordeno/mod.ts)
- [Discord](https://discord.com/invite/5vBgXk3UcZ)

View File

@@ -1,4 +1,3 @@
import type { FileContent } from './discordeno.js'
import type {
ActivityTypes,
AllowedMentionsTypes,
@@ -2564,8 +2563,6 @@ export interface DiscordCreateForumPostWithMessage {
components?: DiscordMessageComponents[]
/** IDs of up to 3 stickers in the server to send in the message */
sticker_ids?: string[]
/** Contents of the file being sent. See {@link https://discord.com/developers/docs/reference#uploading-files Uploading Files} */
file: FileContent | FileContent[] | undefined
/** JSON-encoded body of non-file params, only for multipart/form-data requests. See {@link https://discord.com/developers/docs/reference#uploading-files Uploading Files} */
payload_json?: string
/** Attachment objects with filename and description. See {@link https://discord.com/developers/docs/reference#uploading-files Uploading Files} */

View File

@@ -62,8 +62,8 @@ export interface CreateMessageOptions {
/** When sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true */
failIfNotExists: boolean
}
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** The components you would like to have sent in this message */
components?: MessageComponents
/** IDs of up to 3 stickers in the server to send in the message */
@@ -431,8 +431,8 @@ export interface InteractionCallbackData {
embeds?: Array<Camelize<DiscordEmbed>>
/** Allowed mentions for the message */
allowedMentions?: AllowedMentions
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** The customId you want to use for this modal response. */
customId?: string
/** The title you want to use for this modal response. */
@@ -673,8 +673,8 @@ export interface ExecuteWebhook {
avatarUrl?: string
/** True if this is a TTS message */
tts?: boolean
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** Embedded `rich` content */
embeds?: Array<Camelize<DiscordEmbed>>
/** Allowed mentions for the message */
@@ -706,8 +706,8 @@ export interface CreateForumPostWithMessage extends WithReason {
embeds?: Array<Camelize<DiscordEmbed>>
/** Allowed mentions for the message */
allowedMentions?: AllowedMentions
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** The components you would like to have sent in this message */
components?: MessageComponents
}
@@ -890,8 +890,8 @@ export interface EditMessage {
embeds?: Array<Camelize<DiscordEmbed>> | null
/** Edit the flags of the message (only `SUPPRESS_EMBEDS` can currently be set/unset) */
flags?: 4 | null
/** The contents of the file being sent/edited */
file?: FileContent | FileContent[] | null
/** The contents of the files being sent/edited */
files?: FileContent[] | null
/** Allowed mentions for the message */
allowedMentions?: AllowedMentions
/** When specified (adding new attachments), attachments which are not provided in this list will be removed. */

View File

@@ -57,6 +57,8 @@ export enum TeamMembershipStates {
/** https://discord.com/developers/docs/topics/oauth2#application-application-flags */
export enum ApplicationFlags {
/** Indicates if an app uses the Auto Moderation API. */
ApplicationAutoModerationRuleCreateBadge = 1 << 6,
/** Intent required for bots in **100 or more servers** to receive [`presence_update` events](#DOCS_TOPICS_GATEWAY/presence-update) */
GatewayPresence = 1 << 12,
/** Intent required for bots in under 100 servers to receive [`presence_update` events](#DOCS_TOPICS_GATEWAY/presence-update), found in Bot Settings */

View File

@@ -149,6 +149,6 @@ and unofficial templates:
## Links
- [Website](https://discordeno.github.io/discordeno/)
- [Website](https://discordeno.js.org/)
- [Documentation](https://doc.deno.land/https/deno.land/x/discordeno/mod.ts)
- [Discord](https://discord.com/invite/5vBgXk3UcZ)

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