Seyfert 2.0 (#208)

* feat: permissible handlers

Co-authored-by: MARCROCK22 <MARCROCK22@users.noreply.github.com>

* feat: init handle command

* feat: unifique interaction/message (not full tested)

* fix: await

* fix: components handler

* fix: console.log

* feat: init transformers

* fix: xd

* fix: check

* chore: apply formatting

* chore: frozen-lockfile

* fix: use pnpm v9

* fix: use pnpm v9

* fix: guildCreate emits when bot has more than 1 shard

* feat: update cache adapter

* fix: types

* fix: limitedAdapter messages and bans support

* fix: yes

* feat: transformers (huge update)

* fix: pnpm

* feat: transformers & handleCommand methods

* feat(resolveCommandFromContent): for handle content of getCommandFrom Content and argsContent

* fix: use raw

* fix: consistency

* fix: return await

* chore: export transformers

* fix: socram code

* fix: handleCommand & types

* fix: events

---------

Co-authored-by: MARCROCK22 <MARCROCK22@users.noreply.github.com>
Co-authored-by: MARCROCK22 <marcos22dev@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: douglas546899 <douglas546899@gmail.com>
Co-authored-by: Aarón Rafael <69669283+Chewawi@users.noreply.github.com>
Co-authored-by: MARCROCK22 <57925328+MARCROCK22@users.noreply.github.com>
This commit is contained in:
Marcos Susaña 2024-06-20 20:59:55 -04:00 committed by GitHub
parent 017ccfc3bf
commit 026805bcb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 2340 additions and 1904 deletions

View File

@ -41,3 +41,4 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HUSKY: 0 HUSKY: 0

View File

@ -1,37 +1,37 @@
name: Publish name: Publish
on: on:
push: push:
branches: branches:
- build - build
jobs: jobs:
build: build:
name: Publish name: Publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: check out code - name: check out code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Node - name: Install Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20.x' node-version: '20.x'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v4
with: with:
version: 8 version: 9
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install --frozen-lockfile
- name: Create Release Pull Request - name: Create Release Pull Request
uses: changesets/action@v1 uses: changesets/action@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
with: with:
commit: "chore: release packages" commit: "chore: release packages"
publish: npm publish publish: npm publish
title: "chore: release packages" title: "chore: release packages"

View File

@ -1,33 +1,33 @@
name: Transpile code name: Transpile code
on: on:
push: push:
branches: branches:
- main - main
pull_request: pull_request:
branches: branches:
- main - main
jobs: jobs:
build: build:
name: Transpile code name: Transpile code
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: check out code - name: check out code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Node - name: Install Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20.x' node-version: '20.x'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v4
with: with:
version: 8 version: 9
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install --frozen-lockfile
- name: Build - name: Build
run: npx tsc run: npx tsc

View File

@ -21,10 +21,10 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"discord-api-types": "^0.37.88", "discord-api-types": "^0.37.90",
"magic-bytes.js": "^1.10.0", "magic-bytes.js": "^1.10.0",
"ts-mixer": "^6.0.4", "ts-mixer": "^6.0.4",
"ws": "^8.17.0" "ws": "^8.17.1"
}, },
"lint-staged": { "lint-staged": {
"*.ts": [ "*.ts": [
@ -35,11 +35,11 @@
"@biomejs/biome": "1.8.1", "@biomejs/biome": "1.8.1",
"@commitlint/cli": "^19.3.0", "@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2", "@commitlint/config-conventional": "^19.2.2",
"@types/node": "^20.14.2", "@types/node": "^20.14.6",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"husky": "^9.0.11", "husky": "^9.0.11",
"lint-staged": "^15.2.6", "lint-staged": "^15.2.7",
"typescript": "^5.4.5" "typescript": "^5.5.2"
}, },
"optionalDependencies": { "optionalDependencies": {
"ioredis": "^5.4.1", "ioredis": "^5.4.1",

94
pnpm-lock.yaml generated
View File

@ -12,8 +12,8 @@ importers:
specifier: ^3.6.0 specifier: ^3.6.0
version: 3.6.0 version: 3.6.0
discord-api-types: discord-api-types:
specifier: ^0.37.88 specifier: ^0.37.90
version: 0.37.88 version: 0.37.90
magic-bytes.js: magic-bytes.js:
specifier: ^1.10.0 specifier: ^1.10.0
version: 1.10.0 version: 1.10.0
@ -21,8 +21,8 @@ importers:
specifier: ^6.0.4 specifier: ^6.0.4
version: 6.0.4 version: 6.0.4
ws: ws:
specifier: ^8.17.0 specifier: ^8.17.1
version: 8.17.0 version: 8.17.1
optionalDependencies: optionalDependencies:
ioredis: ioredis:
specifier: ^5.4.1 specifier: ^5.4.1
@ -39,13 +39,13 @@ importers:
version: 1.8.1 version: 1.8.1
'@commitlint/cli': '@commitlint/cli':
specifier: ^19.3.0 specifier: ^19.3.0
version: 19.3.0(@types/node@20.14.2)(typescript@5.4.5) version: 19.3.0(@types/node@20.14.6)(typescript@5.5.2)
'@commitlint/config-conventional': '@commitlint/config-conventional':
specifier: ^19.2.2 specifier: ^19.2.2
version: 19.2.2 version: 19.2.2
'@types/node': '@types/node':
specifier: ^20.14.2 specifier: ^20.14.6
version: 20.14.2 version: 20.14.6
'@types/ws': '@types/ws':
specifier: ^8.5.10 specifier: ^8.5.10
version: 8.5.10 version: 8.5.10
@ -53,11 +53,11 @@ importers:
specifier: ^9.0.11 specifier: ^9.0.11
version: 9.0.11 version: 9.0.11
lint-staged: lint-staged:
specifier: ^15.2.6 specifier: ^15.2.7
version: 15.2.6 version: 15.2.7
typescript: typescript:
specifier: ^5.4.5 specifier: ^5.5.2
version: 5.4.5 version: 5.5.2
packages: packages:
@ -201,8 +201,8 @@ packages:
'@types/conventional-commits-parser@5.0.0': '@types/conventional-commits-parser@5.0.0':
resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==}
'@types/node@20.14.2': '@types/node@20.14.6':
resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} resolution: {integrity: sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw==}
'@types/ws@8.5.10': '@types/ws@8.5.10':
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
@ -362,8 +362,8 @@ packages:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
discord-api-types@0.37.88: discord-api-types@0.37.90:
resolution: {integrity: sha512-Yrj5S3JXzouPc6WLA8svgXCw3Q7IkNdW/Y/IfkJF+CsgUBsgECQ8qVoOaseJJ8Atj2TEvabut4rGmzq6PRi1/Q==} resolution: {integrity: sha512-lpOJSGrqHuXoM4FV/2HtjoaJpCClGFHpRNIdZEW8zPINlsCHNSfIwA2EQ8dxeE6k1QhhTuM9ZlOGVYXoU7FLgA==}
dot-prop@5.3.0: dot-prop@5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
@ -540,13 +540,13 @@ packages:
lines-and-columns@1.2.4: lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
lint-staged@15.2.6: lint-staged@15.2.7:
resolution: {integrity: sha512-M/3PdijFXT/A5lnbSK3EQNLbIIrkE00JZaD39r7t4kfFOqT1Ly9LgSZSMMtvQ3p2/C8Nyj/ou0vkNHmEwqoB8g==} resolution: {integrity: sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==}
engines: {node: '>=18.12.0'} engines: {node: '>=18.12.0'}
hasBin: true hasBin: true
listr2@8.2.1: listr2@8.2.2:
resolution: {integrity: sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==} resolution: {integrity: sha512-sy0dq+JPS+RAFiFk2K8Nbub7khNmeeoFALNUJ4Wzk34wZKAzaOhEXqGWs4RA5aui0RaM6Hgn7VEKhCj0mlKNLA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
locate-path@7.2.0: locate-path@7.2.0:
@ -710,8 +710,8 @@ packages:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
rfdc@1.3.1: rfdc@1.4.1:
resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
semver@7.6.2: semver@7.6.2:
resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==}
@ -793,8 +793,8 @@ packages:
tweetnacl@1.0.3: tweetnacl@1.0.3:
resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
typescript@5.4.5: typescript@5.5.2:
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
@ -825,8 +825,8 @@ packages:
resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
engines: {node: '>=18'} engines: {node: '>=18'}
ws@8.17.0: ws@8.17.1:
resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
peerDependencies: peerDependencies:
bufferutil: ^4.0.1 bufferutil: ^4.0.1
@ -909,11 +909,11 @@ snapshots:
'@biomejs/cli-win32-x64@1.8.1': '@biomejs/cli-win32-x64@1.8.1':
optional: true optional: true
'@commitlint/cli@19.3.0(@types/node@20.14.2)(typescript@5.4.5)': '@commitlint/cli@19.3.0(@types/node@20.14.6)(typescript@5.5.2)':
dependencies: dependencies:
'@commitlint/format': 19.3.0 '@commitlint/format': 19.3.0
'@commitlint/lint': 19.2.2 '@commitlint/lint': 19.2.2
'@commitlint/load': 19.2.0(@types/node@20.14.2)(typescript@5.4.5) '@commitlint/load': 19.2.0(@types/node@20.14.6)(typescript@5.5.2)
'@commitlint/read': 19.2.1 '@commitlint/read': 19.2.1
'@commitlint/types': 19.0.3 '@commitlint/types': 19.0.3
execa: 8.0.1 execa: 8.0.1
@ -960,15 +960,15 @@ snapshots:
'@commitlint/rules': 19.0.3 '@commitlint/rules': 19.0.3
'@commitlint/types': 19.0.3 '@commitlint/types': 19.0.3
'@commitlint/load@19.2.0(@types/node@20.14.2)(typescript@5.4.5)': '@commitlint/load@19.2.0(@types/node@20.14.6)(typescript@5.5.2)':
dependencies: dependencies:
'@commitlint/config-validator': 19.0.3 '@commitlint/config-validator': 19.0.3
'@commitlint/execute-rule': 19.0.0 '@commitlint/execute-rule': 19.0.0
'@commitlint/resolve-extends': 19.1.0 '@commitlint/resolve-extends': 19.1.0
'@commitlint/types': 19.0.3 '@commitlint/types': 19.0.3
chalk: 5.3.0 chalk: 5.3.0
cosmiconfig: 9.0.0(typescript@5.4.5) cosmiconfig: 9.0.0(typescript@5.5.2)
cosmiconfig-typescript-loader: 5.0.0(@types/node@20.14.2)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5) cosmiconfig-typescript-loader: 5.0.0(@types/node@20.14.6)(cosmiconfig@9.0.0(typescript@5.5.2))(typescript@5.5.2)
lodash.isplainobject: 4.0.6 lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2 lodash.merge: 4.6.2
lodash.uniq: 4.5.0 lodash.uniq: 4.5.0
@ -1025,15 +1025,15 @@ snapshots:
'@types/conventional-commits-parser@5.0.0': '@types/conventional-commits-parser@5.0.0':
dependencies: dependencies:
'@types/node': 20.14.2 '@types/node': 20.14.6
'@types/node@20.14.2': '@types/node@20.14.6':
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
'@types/ws@8.5.10': '@types/ws@8.5.10':
dependencies: dependencies:
'@types/node': 20.14.2 '@types/node': 20.14.6
JSONStream@1.3.5: JSONStream@1.3.5:
dependencies: dependencies:
@ -1154,21 +1154,21 @@ snapshots:
meow: 12.1.1 meow: 12.1.1
split2: 4.2.0 split2: 4.2.0
cosmiconfig-typescript-loader@5.0.0(@types/node@20.14.2)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5): cosmiconfig-typescript-loader@5.0.0(@types/node@20.14.6)(cosmiconfig@9.0.0(typescript@5.5.2))(typescript@5.5.2):
dependencies: dependencies:
'@types/node': 20.14.2 '@types/node': 20.14.6
cosmiconfig: 9.0.0(typescript@5.4.5) cosmiconfig: 9.0.0(typescript@5.5.2)
jiti: 1.21.6 jiti: 1.21.6
typescript: 5.4.5 typescript: 5.5.2
cosmiconfig@9.0.0(typescript@5.4.5): cosmiconfig@9.0.0(typescript@5.5.2):
dependencies: dependencies:
env-paths: 2.2.1 env-paths: 2.2.1
import-fresh: 3.3.0 import-fresh: 3.3.0
js-yaml: 4.1.0 js-yaml: 4.1.0
parse-json: 5.2.0 parse-json: 5.2.0
optionalDependencies: optionalDependencies:
typescript: 5.4.5 typescript: 5.5.2
cross-spawn@7.0.3: cross-spawn@7.0.3:
dependencies: dependencies:
@ -1185,7 +1185,7 @@ snapshots:
denque@2.1.0: denque@2.1.0:
optional: true optional: true
discord-api-types@0.37.88: {} discord-api-types@0.37.90: {}
dot-prop@5.3.0: dot-prop@5.3.0:
dependencies: dependencies:
@ -1334,14 +1334,14 @@ snapshots:
lines-and-columns@1.2.4: {} lines-and-columns@1.2.4: {}
lint-staged@15.2.6: lint-staged@15.2.7:
dependencies: dependencies:
chalk: 5.3.0 chalk: 5.3.0
commander: 12.1.0 commander: 12.1.0
debug: 4.3.5 debug: 4.3.5
execa: 8.0.1 execa: 8.0.1
lilconfig: 3.1.2 lilconfig: 3.1.2
listr2: 8.2.1 listr2: 8.2.2
micromatch: 4.0.7 micromatch: 4.0.7
pidtree: 0.6.0 pidtree: 0.6.0
string-argv: 0.3.2 string-argv: 0.3.2
@ -1349,13 +1349,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
listr2@8.2.1: listr2@8.2.2:
dependencies: dependencies:
cli-truncate: 4.0.0 cli-truncate: 4.0.0
colorette: 2.0.20 colorette: 2.0.20
eventemitter3: 5.0.1 eventemitter3: 5.0.1
log-update: 6.0.0 log-update: 6.0.0
rfdc: 1.3.1 rfdc: 1.4.1
wrap-ansi: 9.0.0 wrap-ansi: 9.0.0
locate-path@7.2.0: locate-path@7.2.0:
@ -1485,7 +1485,7 @@ snapshots:
onetime: 5.1.2 onetime: 5.1.2
signal-exit: 3.0.7 signal-exit: 3.0.7
rfdc@1.3.1: {} rfdc@1.4.1: {}
semver@7.6.2: {} semver@7.6.2: {}
@ -1555,7 +1555,7 @@ snapshots:
tweetnacl@1.0.3: tweetnacl@1.0.3:
optional: true optional: true
typescript@5.4.5: {} typescript@5.5.2: {}
uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/f40213ec0a97d0d8721d9d32d92d6eb6ddcd22e7: uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/f40213ec0a97d0d8721d9d32d92d6eb6ddcd22e7:
optional: true optional: true
@ -1584,7 +1584,7 @@ snapshots:
string-width: 7.1.0 string-width: 7.1.0
strip-ansi: 7.1.0 strip-ansi: 7.1.0
ws@8.17.0: {} ws@8.17.1: {}
y18n@5.0.8: {} y18n@5.0.8: {}

View File

@ -1,26 +1,22 @@
import { import {
type APIButtonComponentWithCustomId,
type APIButtonComponentWithURL,
type APIMessageComponentEmoji, type APIMessageComponentEmoji,
type ButtonStyle, type ButtonStyle,
ComponentType, ComponentType,
type APIButtonComponent,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { throwError } from '..'; import type { EmojiResolvable } from '../common';
import type { EmojiResolvable, When } from '../common';
import { resolvePartialEmoji } from '../structures/extra/functions'; import { resolvePartialEmoji } from '../structures/extra/functions';
export type ButtonStylesForID = Exclude<ButtonStyle, ButtonStyle.Link>;
/** /**
* Represents a button component. * Represents a button component.
* @template Type - The type of the button component. * @template Type - The type of the button component.
*/ */
export class Button<Type extends boolean = boolean> { export class Button {
/** /**
* Creates a new Button instance. * Creates a new Button instance.
* @param data - The initial data for the button. * @param data - The initial data for the button.
*/ */
constructor(public data: Partial<When<Type, APIButtonComponentWithCustomId, APIButtonComponentWithURL>> = {}) { constructor(public data: Partial<APIButtonComponent> = {}) {
this.data.type = ComponentType.Button; this.data.type = ComponentType.Button;
} }
@ -30,8 +26,7 @@ export class Button<Type extends boolean = boolean> {
* @returns The modified Button instance. * @returns The modified Button instance.
*/ */
setCustomId(id: string) { setCustomId(id: string) {
// @ts-expect-error (this.data as Extract<APIButtonComponent, { custom_id?: string }>).custom_id = id;
this.data.custom_id = id;
return this; return this;
} }
@ -41,8 +36,7 @@ export class Button<Type extends boolean = boolean> {
* @returns The modified Button instance. * @returns The modified Button instance.
*/ */
setURL(url: string) { setURL(url: string) {
// @ts-expect-error (this.data as Extract<APIButtonComponent, { url?: string }>).url = url;
this.data.url = url;
return this; return this;
} }
@ -52,7 +46,7 @@ export class Button<Type extends boolean = boolean> {
* @returns The modified Button instance. * @returns The modified Button instance.
*/ */
setLabel(label: string) { setLabel(label: string) {
this.data.label = label; (this.data as Extract<APIButtonComponent, { label?: string }>).label = label;
return this; return this;
} }
@ -63,8 +57,9 @@ export class Button<Type extends boolean = boolean> {
*/ */
setEmoji(emoji: EmojiResolvable) { setEmoji(emoji: EmojiResolvable) {
const resolve = resolvePartialEmoji(emoji); const resolve = resolvePartialEmoji(emoji);
if (!resolve) return throwError('Invalid Emoji'); if (!resolve) throw new Error('Invalid Emoji');
this.data.emoji = resolve as APIMessageComponentEmoji; (this.data as Extract<APIButtonComponent, { emoji?: APIMessageComponentEmoji }>).emoji =
resolve as APIMessageComponentEmoji;
return this; return this;
} }
@ -83,11 +78,16 @@ export class Button<Type extends boolean = boolean> {
return this; return this;
} }
setSKUId(skuId: string) {
(this.data as Extract<APIButtonComponent, { sku_id?: string }>).sku_id = skuId;
return this;
}
/** /**
* Converts the Button instance to its JSON representation. * Converts the Button instance to its JSON representation.
* @returns The JSON representation of the Button instance. * @returns The JSON representation of the Button instance.
*/ */
toJSON() { toJSON() {
return { ...this.data } as When<Type, APIButtonComponentWithCustomId, APIButtonComponentWithURL>; return { ...this.data } as Partial<APIButtonComponent>;
} }
} }

View File

@ -20,13 +20,7 @@ export class MemoryAdapter implements Adapter {
return values; return values;
} }
get(keys: string): any; bulkGet(keys: string[]) {
get(keys: string[]): any[];
get(keys: string | string[]) {
if (!Array.isArray(keys)) {
const data = this.storage.get(keys);
return data ? JSON.parse(data) : null;
}
return keys return keys
.map(x => { .map(x => {
const data = this.storage.get(x); const data = this.storage.get(x);
@ -35,44 +29,45 @@ export class MemoryAdapter implements Adapter {
.filter(x => x); .filter(x => x);
} }
set(keys: string, data: any): void; get(keys: string) {
set(keys: [string, any][]): void; const data = this.storage.get(keys);
set(keys: string | [string, any][], data?: any): void { return data ? JSON.parse(data) : null;
if (Array.isArray(keys)) { }
for (const [key, value] of keys) {
this.storage.set(key, JSON.stringify(value)); bulkSet(keys: [string, any][]) {
} for (const [key, value] of keys) {
} else { this.storage.set(key, JSON.stringify(value));
this.storage.set(keys, JSON.stringify(data));
} }
} }
patch(updateOnly: boolean, keys: string, data: any): void; set(key: string, data: any) {
patch(updateOnly: boolean, keys: [string, any][]): void; this.storage.set(key, JSON.stringify(data));
patch(updateOnly: boolean, keys: string | [string, any][], data?: any): void { }
if (Array.isArray(keys)) {
for (const [key, value] of keys) { bulkPatch(updateOnly: boolean, keys: [string, any][]) {
const oldData = this.get(key); for (const [key, value] of keys) {
if (updateOnly && !oldData) { const oldData = this.get(key);
continue;
}
this.storage.set(
key,
Array.isArray(value) ? JSON.stringify(value) : JSON.stringify({ ...(oldData ?? {}), ...value }),
);
}
} else {
const oldData = this.get(keys);
if (updateOnly && !oldData) { if (updateOnly && !oldData) {
return; continue;
} }
this.storage.set( this.storage.set(
keys, key,
Array.isArray(data) ? JSON.stringify(data) : JSON.stringify({ ...(oldData ?? {}), ...data }), Array.isArray(value) ? JSON.stringify(value) : JSON.stringify({ ...(oldData ?? {}), ...value }),
); );
} }
} }
patch(updateOnly: boolean, keys: string, data: any) {
const oldData = this.get(keys);
if (updateOnly && !oldData) {
return;
}
this.storage.set(
keys,
Array.isArray(data) ? JSON.stringify(data) : JSON.stringify({ ...(oldData ?? {}), ...data }),
);
}
values(to: string) { values(to: string) {
const array: any[] = []; const array: any[] = [];
const data = this.keys(to); const data = this.keys(to);
@ -96,14 +91,16 @@ export class MemoryAdapter implements Adapter {
return this.getToRelationship(to).length; return this.getToRelationship(to).length;
} }
remove(keys: string): void; bulkRemove(keys: string[]) {
remove(keys: string[]): void; for (const i of keys) {
remove(keys: string | string[]) {
for (const i of Array.isArray(keys) ? keys : [keys]) {
this.storage.delete(i); this.storage.delete(i);
} }
} }
remove(key: string) {
this.storage.delete(key);
}
flush(): void { flush(): void {
this.storage.clear(); this.storage.clear();
this.relationships.clear(); this.relationships.clear();

View File

@ -9,18 +9,23 @@ export interface ResourceLimitedMemoryAdapter {
export interface LimitedMemoryAdapterOptions { export interface LimitedMemoryAdapterOptions {
default?: ResourceLimitedMemoryAdapter; default?: ResourceLimitedMemoryAdapter;
guild?: ResourceLimitedMemoryAdapter; guild?: ResourceLimitedMemoryAdapter;
user?: ResourceLimitedMemoryAdapter; user?: ResourceLimitedMemoryAdapter;
ban?: ResourceLimitedMemoryAdapter;
member?: ResourceLimitedMemoryAdapter; member?: ResourceLimitedMemoryAdapter;
voice_state?: ResourceLimitedMemoryAdapter; voice_state?: ResourceLimitedMemoryAdapter;
channel?: ResourceLimitedMemoryAdapter; channel?: ResourceLimitedMemoryAdapter;
emoji?: ResourceLimitedMemoryAdapter; emoji?: ResourceLimitedMemoryAdapter;
overwrite?: ResourceLimitedMemoryAdapter;
presence?: ResourceLimitedMemoryAdapter; presence?: ResourceLimitedMemoryAdapter;
role?: ResourceLimitedMemoryAdapter; role?: ResourceLimitedMemoryAdapter;
stage_instance?: ResourceLimitedMemoryAdapter; stage_instance?: ResourceLimitedMemoryAdapter;
sticker?: ResourceLimitedMemoryAdapter; sticker?: ResourceLimitedMemoryAdapter;
thread?: ResourceLimitedMemoryAdapter; thread?: ResourceLimitedMemoryAdapter;
overwrite?: ResourceLimitedMemoryAdapter;
message?: ResourceLimitedMemoryAdapter;
} }
export class LimitedMemoryAdapter implements Adapter { export class LimitedMemoryAdapter implements Adapter {
@ -58,13 +63,7 @@ export class LimitedMemoryAdapter implements Adapter {
return values; return values;
} }
get(keys: string): any; bulkGet(keys: string[]) {
get(keys: string[]): any[];
get(keys: string | string[]) {
if (!Array.isArray(keys)) {
const data = [...this.storage.values()].find(x => x.has(keys))?.get(keys);
return data ? JSON.parse(data) : null;
}
const iterator = [...this.storage.values()]; const iterator = [...this.storage.values()];
return keys return keys
.map(key => { .map(key => {
@ -74,6 +73,11 @@ export class LimitedMemoryAdapter implements Adapter {
.filter(x => x); .filter(x => x);
} }
get(keys: string) {
const data = [...this.storage.values()].find(x => x.has(keys))?.get(keys);
return data ? JSON.parse(data) : null;
}
private __set(key: string, data: any) { private __set(key: string, data: any) {
const __guildId = Array.isArray(data) ? data[0].guild_id : data.guild_id; const __guildId = Array.isArray(data) ? data[0].guild_id : data.guild_id;
const namespace = `${key.split('.')[0]}${__guildId ? `.${__guildId}` : ''}`; const namespace = `${key.split('.')[0]}${__guildId ? `.${__guildId}` : ''}`;
@ -96,6 +100,7 @@ export class LimitedMemoryAdapter implements Adapter {
case 'user': case 'user':
self.removeToRelationship(namespace, k.split('.')[1]); self.removeToRelationship(namespace, k.split('.')[1]);
break; break;
case 'ban':
case 'member': case 'member':
case 'voice_state': case 'voice_state':
{ {
@ -124,38 +129,34 @@ export class LimitedMemoryAdapter implements Adapter {
this.storage.get(namespace)!.set(key, JSON.stringify(data)); this.storage.get(namespace)!.set(key, JSON.stringify(data));
} }
set(keys: string, data: any): void; bulkSet(keys: [string, any][]) {
set(keys: [string, any][]): void; for (const [key, value] of keys) {
set(keys: string | [string, any][], data?: any): void { this.__set(key, value);
if (Array.isArray(keys)) {
for (const [key, value] of keys) {
this.__set(key, value);
}
} else {
this.__set(keys, data);
} }
} }
patch(updateOnly: boolean, keys: string, data: any): void; set(keys: string, data: any) {
patch(updateOnly: boolean, keys: [string, any][]): void; this.__set(keys, data);
patch(updateOnly: boolean, keys: string | [string, any][], data?: any): void { }
if (Array.isArray(keys)) {
for (const [key, value] of keys) { bulkPatch(updateOnly: boolean, keys: [string, any][]) {
const oldData = this.get(key); for (const [key, value] of keys) {
if (updateOnly && !oldData) { const oldData = this.get(key);
continue;
}
this.__set(key, Array.isArray(value) ? value : { ...(oldData ?? {}), ...value });
}
} else {
const oldData = this.get(keys);
if (updateOnly && !oldData) { if (updateOnly && !oldData) {
return; continue;
} }
this.__set(keys, Array.isArray(data) ? data : { ...(oldData ?? {}), ...data }); this.__set(key, Array.isArray(value) ? value : { ...(oldData ?? {}), ...value });
} }
} }
patch(updateOnly: boolean, keys: string, data: any) {
const oldData = this.get(keys);
if (updateOnly && !oldData) {
return;
}
this.__set(keys, Array.isArray(data) ? data : { ...(oldData ?? {}), ...data });
}
values(to: string) { values(to: string) {
const array: any[] = []; const array: any[] = [];
const data = this.keys(to); const data = this.keys(to);
@ -179,14 +180,16 @@ export class LimitedMemoryAdapter implements Adapter {
return this.getToRelationship(to).length; return this.getToRelationship(to).length;
} }
remove(keys: string): void; bulkRemove(keys: string[]) {
remove(keys: string[]): void; for (const i of keys) {
remove(keys: string | string[]) {
for (const i of Array.isArray(keys) ? keys : [keys]) {
this.storage.get(i.split('.')[0])?.delete(i); this.storage.get(i.split('.')[0])?.delete(i);
} }
} }
remove(key: string) {
this.storage.get(key.split('.')[0])?.delete(key);
}
flush(): void { flush(): void {
this.storage.clear(); this.storage.clear();
this.relationships.clear(); this.relationships.clear();

View File

@ -31,7 +31,7 @@ export class RedisAdapter implements Adapter {
private __scanSets(query: string, returnKeys: true): Promise<string[]>; private __scanSets(query: string, returnKeys: true): Promise<string[]>;
private __scanSets(query: string, returnKeys = false) { private __scanSets(query: string, returnKeys = false) {
const match = this.buildKey(query); const match = this.buildKey(query);
return new Promise<string[]>((r, j) => { return new Promise<string[] | any[]>((r, j) => {
const stream = this.client.scanStream({ const stream = this.client.scanStream({
match, match,
type: 'set', type: 'set',
@ -39,7 +39,7 @@ export class RedisAdapter implements Adapter {
const keys: string[] = []; const keys: string[] = [];
stream stream
.on('data', resultKeys => keys.push(...resultKeys)) .on('data', resultKeys => keys.push(...resultKeys))
.on('end', () => (returnKeys ? r(keys.map(x => this.buildKey(x))) : r(this.get(keys)))) .on('end', () => (returnKeys ? r(keys.map(x => this.buildKey(x))) : r(this.bulkGet(keys))))
.on('error', err => j(err)); .on('error', err => j(err));
}); });
} }
@ -48,7 +48,7 @@ export class RedisAdapter implements Adapter {
scan(query: string, returnKeys: true): Promise<string[]>; scan(query: string, returnKeys: true): Promise<string[]>;
scan(query: string, returnKeys = false) { scan(query: string, returnKeys = false) {
const match = this.buildKey(query); const match = this.buildKey(query);
return new Promise<string[]>((r, j) => { return new Promise<string[] | any[]>((r, j) => {
const stream = this.client.scanStream({ const stream = this.client.scanStream({
match, match,
// omit relationships // omit relationships
@ -57,22 +57,12 @@ export class RedisAdapter implements Adapter {
const keys: string[] = []; const keys: string[] = [];
stream stream
.on('data', resultKeys => keys.push(...resultKeys)) .on('data', resultKeys => keys.push(...resultKeys))
.on('end', () => (returnKeys ? r(keys.map(x => this.buildKey(x))) : r(this.get(keys)))) .on('end', () => (returnKeys ? r(keys.map(x => this.buildKey(x))) : r(this.bulkGet(keys))))
.on('error', err => j(err)); .on('error', err => j(err));
}); });
} }
async get(keys: string[]): Promise<any[]>; async bulkGet(keys: string[]) {
async get(keys: string): Promise<any>;
async get(keys: string | string[]) {
if (!Array.isArray(keys)) {
const value = await this.client.hgetall(this.buildKey(keys));
if (value) {
return toNormal(value);
}
return;
}
const pipeline = this.client.pipeline(); const pipeline = this.client.pipeline();
for (const key of keys) { for (const key of keys) {
@ -82,46 +72,31 @@ export class RedisAdapter implements Adapter {
return (await pipeline.exec())?.filter(x => !!x[1]).map(x => toNormal(x[1] as Record<string, any>)) ?? []; return (await pipeline.exec())?.filter(x => !!x[1]).map(x => toNormal(x[1] as Record<string, any>)) ?? [];
} }
async set(id: [string, any][]): Promise<void>; async get(keys: string): Promise<any> {
async set(id: string, data: any): Promise<void>; const value = await this.client.hgetall(this.buildKey(keys));
async set(id: string | [string, any][], data?: any): Promise<void> { if (value) {
if (!Array.isArray(id)) { return toNormal(value);
await this.client.hset(this.buildKey(id), toDb(data));
return;
} }
}
async bulkSet(data: [string, any][]) {
const pipeline = this.client.pipeline(); const pipeline = this.client.pipeline();
for (const [k, v] of id) { for (const [k, v] of data) {
pipeline.hset(this.buildKey(k), toDb(v)); pipeline.hset(this.buildKey(k), toDb(v));
} }
await pipeline.exec(); await pipeline.exec();
} }
async patch(updateOnly: boolean, id: [string, any][]): Promise<void>; async set(id: string, data: any) {
async patch(updateOnly: boolean, id: string, data: any): Promise<void>; await this.client.hset(this.buildKey(id), toDb(data));
async patch(updateOnly: boolean, id: string | [string, any][], data?: any): Promise<void> { }
if (!Array.isArray(id)) {
if (updateOnly) {
await this.client.eval(
`if redis.call('exists',KEYS[1]) == 1 then redis.call('hset', KEYS[1], ${Array.from(
{ length: Object.keys(data).length * 2 },
(_, i) => `ARGV[${i + 1}]`,
)}) end`,
1,
this.buildKey(id),
...Object.entries(toDb(data)).flat(),
);
} else {
await this.client.hset(this.buildKey(id), toDb(data));
}
return;
}
async bulkPatch(updateOnly: boolean, data: [string, any][]) {
const pipeline = this.client.pipeline(); const pipeline = this.client.pipeline();
for (const [k, v] of id) { for (const [k, v] of data) {
if (updateOnly) { if (updateOnly) {
pipeline.eval( pipeline.eval(
`if redis.call('exists',KEYS[1]) == 1 then redis.call('hset', KEYS[1], ${Array.from( `if redis.call('exists',KEYS[1]) == 1 then redis.call('hset', KEYS[1], ${Array.from(
@ -140,11 +115,27 @@ export class RedisAdapter implements Adapter {
await pipeline.exec(); await pipeline.exec();
} }
async patch(updateOnly: boolean, id: string, data: any): Promise<void> {
if (updateOnly) {
await this.client.eval(
`if redis.call('exists',KEYS[1]) == 1 then redis.call('hset', KEYS[1], ${Array.from(
{ length: Object.keys(data).length * 2 },
(_, i) => `ARGV[${i + 1}]`,
)}) end`,
1,
this.buildKey(id),
...Object.entries(toDb(data)).flat(),
);
} else {
await this.client.hset(this.buildKey(id), toDb(data));
}
}
async values(to: string): Promise<any[]> { async values(to: string): Promise<any[]> {
const array: unknown[] = []; const array: unknown[] = [];
const data = await this.keys(to); const data = await this.keys(to);
if (data.length) { if (data.length) {
const items = await this.get(data); const items = await this.bulkGet(data);
for (const item of items) { for (const item of items) {
if (item) { if (item) {
array.push(item); array.push(item);
@ -164,22 +155,21 @@ export class RedisAdapter implements Adapter {
return this.client.scard(`${this.buildKey(to)}:set`); return this.client.scard(`${this.buildKey(to)}:set`);
} }
async remove(keys: string | string[]): Promise<void> { async bulkRemove(keys: string[]) {
if (!Array.isArray(keys)) {
await this.client.del(this.buildKey(keys));
return;
}
await this.client.del(...keys.map(x => this.buildKey(x))); await this.client.del(...keys.map(x => this.buildKey(x)));
} }
async remove(keys: string): Promise<void> {
await this.client.del(this.buildKey(keys));
}
async flush(): Promise<void> { async flush(): Promise<void> {
const keys = await Promise.all([ const keys = await Promise.all([
this.scan(this.buildKey('*'), true), this.scan(this.buildKey('*'), true),
this.__scanSets(this.buildKey('*'), true), this.__scanSets(this.buildKey('*'), true),
]).then(x => x.flat()); ]).then(x => x.flat());
if (!keys.length) return; if (!keys.length) return;
await this.remove(keys); await this.bulkRemove(keys);
} }
async contains(to: string, keys: string): Promise<boolean> { async contains(to: string, keys: string): Promise<boolean> {

View File

@ -7,17 +7,14 @@ export interface Adapter {
scan(query: string, keys: true): Awaitable<string[]>; scan(query: string, keys: true): Awaitable<string[]>;
scan(query: string, keys?: boolean): Awaitable<(any | string)[]>; scan(query: string, keys?: boolean): Awaitable<(any | string)[]>;
get(keys: string[]): Awaitable<any[]>; bulkGet(keys: string[]): Awaitable<any[]>;
get(keys: string): Awaitable<any | null>; get(keys: string): Awaitable<any | null>;
get(keys: string | string[]): Awaitable<any | null>;
set(keyValue: [string, any][]): Awaitable<void>; bulkSet(keyValue: [string, any][]): Awaitable<void>;
set(id: string, data: any): Awaitable<void>; set(id: string, data: any): Awaitable<void>;
set(id: string | [string, any][], data?: any): Awaitable<void>;
patch(updateOnly: boolean, keyValue: [string, any][]): Awaitable<void>; bulkPatch(updateOnly: boolean, keyValue: [string, any][]): Awaitable<void>;
patch(updateOnly: boolean, id: string, data: any): Awaitable<void>; patch(updateOnly: boolean, id: string, data: any): Awaitable<void>;
patch(updateOnly: boolean, id: string | [string, any][], data?: any): Awaitable<void>;
values(to: string): Awaitable<any[]>; values(to: string): Awaitable<any[]>;
@ -25,7 +22,8 @@ export interface Adapter {
count(to: string): Awaitable<number>; count(to: string): Awaitable<number>;
remove(keys: string | string[]): Awaitable<void>; bulkRemove(keys: string[]): Awaitable<void>;
remove(keys: string): Awaitable<void>;
flush(): Awaitable<void>; flush(): Awaitable<void>;

View File

@ -45,14 +45,26 @@ export class WorkerAdapter implements Adapter {
return this.send('scan', ...rest); return this.send('scan', ...rest);
} }
bulkGet(...rest: any[]) {
return this.send('bulkGet', ...rest);
}
get(...rest: any[]) { get(...rest: any[]) {
return this.send('get', ...rest); return this.send('get', ...rest);
} }
bulkSet(...rest: any[]) {
return this.send('bulkSet', ...rest);
}
set(...rest: any[]) { set(...rest: any[]) {
return this.send('set', ...rest); return this.send('set', ...rest);
} }
bulkPatch(...rest: any[]) {
return this.send('bulkPatch', ...rest);
}
patch(...rest: any[]) { patch(...rest: any[]) {
return this.send('patch', ...rest); return this.send('patch', ...rest);
} }
@ -69,6 +81,10 @@ export class WorkerAdapter implements Adapter {
return this.send('count', ...rest); return this.send('count', ...rest);
} }
bulkRemove(...rest: any[]) {
return this.send('bulkRemove', ...rest);
}
remove(...rest: any[]) { remove(...rest: any[]) {
return this.send('remove', ...rest); return this.send('remove', ...rest);
} }

4
src/cache/index.ts vendored
View File

@ -382,7 +382,7 @@ export class Cache {
} }
await this.adapter.bulkAddToRelationShip(relationshipsData); await this.adapter.bulkAddToRelationShip(relationshipsData);
await this.adapter.patch(false, allData); await this.adapter.bulkPatch(false, allData);
} }
async bulkSet( async bulkSet(
@ -474,7 +474,7 @@ export class Cache {
} }
await this.adapter.bulkAddToRelationShip(relationshipsData); await this.adapter.bulkAddToRelationShip(relationshipsData);
await this.adapter.set(allData); await this.adapter.bulkSet(allData);
} }
async onPacket(event: GatewayDispatchPayload) { async onPacket(event: GatewayDispatchPayload) {

View File

@ -2,7 +2,7 @@ import type { APIBan } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { GuildBasedResource } from './default/guild-based'; import { GuildBasedResource } from './default/guild-based';
import { GuildBan } from '../../structures/GuildBan'; import { type GuildBanStructure, Transformers } from '../../client/transformers';
export class Bans extends GuildBasedResource { export class Bans extends GuildBasedResource {
namespace = 'ban'; namespace = 'ban';
@ -16,31 +16,31 @@ export class Bans extends GuildBasedResource {
return rest; return rest;
} }
override get(id: string, guild: string): ReturnCache<GuildBan | undefined> { override get(id: string, guild: string): ReturnCache<GuildBanStructure | undefined> {
return fakePromise(super.get(id, guild)).then(rawBan => return fakePromise(super.get(id, guild)).then(rawBan =>
rawBan ? new GuildBan(this.client, rawBan, guild) : undefined, rawBan ? Transformers.GuildBan(this.client, rawBan, guild) : undefined,
); );
} }
override bulk(ids: string[], guild: string): ReturnCache<GuildBan[]> { override bulk(ids: string[], guild: string): ReturnCache<GuildBanStructure[]> {
return fakePromise(super.bulk(ids, guild)).then( return fakePromise(super.bulk(ids, guild)).then(
bans => bans =>
bans bans
.map(rawBan => { .map(rawBan => {
return rawBan ? new GuildBan(this.client, rawBan, guild) : undefined; return rawBan ? Transformers.GuildBan(this.client, rawBan, guild) : undefined;
}) })
.filter(Boolean) as GuildBan[], .filter(Boolean) as GuildBanStructure[],
); );
} }
override values(guild: string): ReturnCache<GuildBan[]> { override values(guild: string): ReturnCache<GuildBanStructure[]> {
return fakePromise(super.values(guild)).then( return fakePromise(super.values(guild)).then(
bans => bans =>
bans bans
.map(rawBan => { .map(rawBan => {
return rawBan ? new GuildBan(this.client, rawBan, guild) : undefined; return rawBan ? Transformers.GuildBan(this.client, rawBan, guild) : undefined;
}) })
.filter(Boolean) as GuildBan[], .filter(Boolean) as GuildBanStructure[],
); );
} }
} }

View File

@ -13,6 +13,10 @@ export class Channels extends GuildRelatedResource {
return rest; return rest;
} }
raw(id: string): ReturnCache<APIChannel | undefined> {
return super.get(id);
}
override get(id: string): ReturnCache<AllChannels | undefined> { override get(id: string): ReturnCache<AllChannels | undefined> {
return fakePromise(super.get(id)).then(rawChannel => return fakePromise(super.get(id)).then(rawChannel =>
rawChannel ? channelFrom(rawChannel, this.client) : undefined, rawChannel ? channelFrom(rawChannel, this.client) : undefined,

View File

@ -48,7 +48,7 @@ export class BaseResource<T = any> {
} }
bulk(ids: string[]): ReturnCache<T[]> { bulk(ids: string[]): ReturnCache<T[]> {
return fakePromise(this.adapter.get(ids.map(id => this.hashId(id)))).then(x => x.filter(y => y)); return fakePromise(this.adapter.bulkGet(ids.map(id => this.hashId(id)))).then(x => x.filter(y => y));
} }
set(id: string, data: any) { set(id: string, data: any) {

View File

@ -54,7 +54,7 @@ export class GuildBasedResource<T = any> {
} }
bulk(ids: string[], guild: string): ReturnCache<(T & { guild_id: string })[]> { bulk(ids: string[], guild: string): ReturnCache<(T & { guild_id: string })[]> {
return fakePromise(this.adapter.get(ids.map(id => this.hashGuildId(guild, id)))).then(x => x.filter(y => y)); return fakePromise(this.adapter.bulkGet(ids.map(id => this.hashGuildId(guild, id)))).then(x => x.filter(y => y));
} }
set(__keys: string, guild: string, data: any): ReturnCache<void>; set(__keys: string, guild: string, data: any): ReturnCache<void>;
@ -71,7 +71,7 @@ export class GuildBasedResource<T = any> {
guild, guild,
), ),
).then(() => ).then(() =>
this.adapter.set( this.adapter.bulkSet(
keys.map(([key, value]) => { keys.map(([key, value]) => {
return [this.hashGuildId(guild, key), this.parse(value, key, guild)] as const; return [this.hashGuildId(guild, key), this.parse(value, key, guild)] as const;
}), }),
@ -87,14 +87,14 @@ export class GuildBasedResource<T = any> {
any, any,
][]; ][];
return fakePromise(this.adapter.get(keys.map(([key]) => this.hashGuildId(guild, key)))).then(oldDatas => return fakePromise(this.adapter.bulkGet(keys.map(([key]) => this.hashGuildId(guild, key)))).then(oldDatas =>
fakePromise( fakePromise(
this.addToRelationship( this.addToRelationship(
keys.map(x => x[0]), keys.map(x => x[0]),
guild, guild,
), ),
).then(() => ).then(() =>
this.adapter.set( this.adapter.bulkSet(
keys.map(([key, value]) => { keys.map(([key, value]) => {
const oldData = oldDatas.find(x => x.id === key) ?? {}; const oldData = oldDatas.find(x => x.id === key) ?? {};
return [this.hashGuildId(guild, key), this.parse({ ...oldData, ...value }, key, guild)]; return [this.hashGuildId(guild, key), this.parse({ ...oldData, ...value }, key, guild)];
@ -107,7 +107,7 @@ export class GuildBasedResource<T = any> {
remove(id: string | string[], guild: string) { remove(id: string | string[], guild: string) {
const ids = Array.isArray(id) ? id : [id]; const ids = Array.isArray(id) ? id : [id];
return fakePromise(this.removeToRelationship(ids, guild)).then(() => return fakePromise(this.removeToRelationship(ids, guild)).then(() =>
this.adapter.remove(ids.map(x => this.hashGuildId(guild, x))), this.adapter.bulkRemove(ids.map(x => this.hashGuildId(guild, x))),
); );
} }

View File

@ -54,7 +54,7 @@ export class GuildRelatedResource<T = any> {
} }
bulk(ids: string[]): ReturnCache<(T & { guild_id: string })[]> { bulk(ids: string[]): ReturnCache<(T & { guild_id: string })[]> {
return fakePromise(this.adapter.get(ids.map(x => this.hashId(x)))).then(x => x.filter(y => y)); return fakePromise(this.adapter.bulkGet(ids.map(x => this.hashId(x)))).then(x => x.filter(y => y));
} }
set(__keys: string, guild: string, data: any): ReturnCache<void>; set(__keys: string, guild: string, data: any): ReturnCache<void>;
@ -72,7 +72,7 @@ export class GuildRelatedResource<T = any> {
), ),
).then( ).then(
() => () =>
this.adapter.set( this.adapter.bulkSet(
keys.map(([key, value]) => { keys.map(([key, value]) => {
return [this.hashId(key), this.parse(value, key, guild)] as const; return [this.hashId(key), this.parse(value, key, guild)] as const;
}), }),
@ -96,7 +96,7 @@ export class GuildRelatedResource<T = any> {
), ),
).then( ).then(
() => () =>
this.adapter.patch( this.adapter.bulkPatch(
false, false,
keys.map(([key, value]) => { keys.map(([key, value]) => {
return [this.hashId(key), this.parse(value, key, guild)] as const; return [this.hashId(key), this.parse(value, key, guild)] as const;
@ -105,7 +105,7 @@ export class GuildRelatedResource<T = any> {
); );
} }
return fakePromise( return fakePromise(
this.adapter.patch( this.adapter.bulkPatch(
true, true,
keys.map(([key, value]) => { keys.map(([key, value]) => {
return [this.hashId(key), value]; return [this.hashId(key), value];
@ -117,7 +117,7 @@ export class GuildRelatedResource<T = any> {
remove(id: string | string[], guild: string) { remove(id: string | string[], guild: string) {
const ids = Array.isArray(id) ? id : [id]; const ids = Array.isArray(id) ? id : [id];
return fakePromise(this.removeToRelationship(ids, guild)).then(() => return fakePromise(this.removeToRelationship(ids, guild)).then(() =>
this.adapter.remove(ids.map(x => this.hashId(x))), this.adapter.bulkRemove(ids.map(x => this.hashId(x))),
); );
} }
@ -133,7 +133,7 @@ export class GuildRelatedResource<T = any> {
return guild === '*' return guild === '*'
? (fakePromise(this.adapter.scan(this.hashId(guild))).then(x => x) as (T & { guild_id: string })[]) ? (fakePromise(this.adapter.scan(this.hashId(guild))).then(x => x) as (T & { guild_id: string })[])
: (fakePromise(this.adapter.getToRelationship(this.hashId(guild))).then(keys => : (fakePromise(this.adapter.getToRelationship(this.hashId(guild))).then(keys =>
this.adapter.get(keys.map(x => `${this.namespace}.${x}`)), this.adapter.bulkGet(keys.map(x => `${this.namespace}.${x}`)),
) as (T & { guild_id: string })[]); ) as (T & { guild_id: string })[]);
} }

View File

@ -1,8 +1,8 @@
import type { APIEmoji } from 'discord-api-types/v10'; import type { APIEmoji } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { GuildEmoji } from '../../structures';
import { GuildRelatedResource } from './default/guild-related'; import { GuildRelatedResource } from './default/guild-related';
import { type GuildEmojiStructure, Transformers } from '../../client/transformers';
export class Emojis extends GuildRelatedResource { export class Emojis extends GuildRelatedResource {
namespace = 'emoji'; namespace = 'emoji';
@ -12,21 +12,21 @@ export class Emojis extends GuildRelatedResource {
return true; return true;
} }
override get(id: string): ReturnCache<GuildEmoji | undefined> { override get(id: string): ReturnCache<GuildEmojiStructure | undefined> {
return fakePromise(super.get(id)).then(rawEmoji => return fakePromise(super.get(id)).then(rawEmoji =>
rawEmoji ? new GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id) : undefined, rawEmoji ? Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id) : undefined,
); );
} }
override bulk(ids: string[]): ReturnCache<GuildEmoji[]> { override bulk(ids: string[]): ReturnCache<GuildEmojiStructure[]> {
return fakePromise(super.bulk(ids) as (APIEmoji & { id: string; guild_id: string })[]).then(emojis => return fakePromise(super.bulk(ids) as (APIEmoji & { id: string; guild_id: string })[]).then(emojis =>
emojis.map(rawEmoji => new GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id)), emojis.map(rawEmoji => Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id)),
); );
} }
override values(guild: string): ReturnCache<GuildEmoji[]> { override values(guild: string): ReturnCache<GuildEmojiStructure[]> {
return fakePromise(super.values(guild) as (APIEmoji & { id: string; guild_id: string })[]).then(emojis => return fakePromise(super.values(guild) as (APIEmoji & { id: string; guild_id: string })[]).then(emojis =>
emojis.map(rawEmoji => new GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id)), emojis.map(rawEmoji => Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id)),
); );
} }
} }

View File

@ -1,8 +1,8 @@
import type { APIGuild } from 'discord-api-types/v10'; import type { APIGuild } from 'discord-api-types/v10';
import type { Cache, ReturnCache } from '..'; import type { Cache, ReturnCache } from '..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { Guild } from '../../structures';
import { BaseResource } from './default/base'; import { BaseResource } from './default/base';
import { type GuildStructure, Transformers } from '../../client/transformers';
export class Guilds extends BaseResource { export class Guilds extends BaseResource {
namespace = 'guild'; namespace = 'guild';
@ -12,24 +12,30 @@ export class Guilds extends BaseResource {
return true; return true;
} }
override get(id: string): ReturnCache<Guild<'cached'> | undefined> { raw(id: string): ReturnCache<APIGuild | undefined> {
return fakePromise(super.get(id)).then(guild => (guild ? new Guild<'cached'>(this.client, guild) : undefined)); return super.get(id);
} }
override bulk(ids: string[]): ReturnCache<Guild<'cached'>[]> { override get(id: string): ReturnCache<GuildStructure<'cached'> | undefined> {
return fakePromise(super.bulk(ids) as APIGuild[]).then(guilds => return fakePromise(super.get(id)).then(guild =>
guilds.map(x => new Guild<'cached'>(this.client, x)), guild ? Transformers.Guild<'cached'>(this.client, guild) : undefined,
); );
} }
override values(): ReturnCache<Guild<'cached'>[]> { override bulk(ids: string[]): ReturnCache<GuildStructure<'cached'>[]> {
return fakePromise(super.bulk(ids) as APIGuild[]).then(guilds =>
guilds.map(x => Transformers.Guild<'cached'>(this.client, x)),
);
}
override values(): ReturnCache<GuildStructure<'cached'>[]> {
return fakePromise(super.values() as APIGuild[]).then(guilds => return fakePromise(super.values() as APIGuild[]).then(guilds =>
guilds.map(x => new Guild<'cached'>(this.client, x)), guilds.map(x => Transformers.Guild<'cached'>(this.client, x)),
); );
} }
override async remove(id: string) { override async remove(id: string) {
await this.cache.adapter.remove( await this.cache.adapter.bulkRemove(
( (
await Promise.all([ await Promise.all([
this.cache.members?.keys(id) ?? [], this.cache.members?.keys(id) ?? [],

View File

@ -1,8 +1,8 @@
import type { APIGuildMember } from 'discord-api-types/v10'; import type { APIGuildMember } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { GuildMember } from '../../structures';
import { GuildBasedResource } from './default/guild-based'; import { GuildBasedResource } from './default/guild-based';
import { type GuildMemberStructure, Transformers } from '../../client/transformers';
export class Members extends GuildBasedResource { export class Members extends GuildBasedResource {
namespace = 'member'; namespace = 'member';
@ -16,38 +16,51 @@ export class Members extends GuildBasedResource {
return rest; return rest;
} }
override get(id: string, guild: string): ReturnCache<GuildMember | undefined> { raw(id: string, guild: string): ReturnCache<APIGuildMember | undefined> {
return fakePromise(super.get(id, guild) as Omit<APIGuildMember, 'user'>).then(rawMember => {
return fakePromise(this.client.cache.users?.raw(id)).then(user =>
rawMember && user
? {
...rawMember,
user,
}
: undefined,
);
});
}
override get(id: string, guild: string): ReturnCache<GuildMemberStructure | undefined> {
return fakePromise(super.get(id, guild)).then(rawMember => return fakePromise(super.get(id, guild)).then(rawMember =>
fakePromise(this.client.cache.users?.get(id)).then(user => fakePromise(this.client.cache.users?.raw(id)).then(user =>
rawMember && user ? new GuildMember(this.client, rawMember, user, guild) : undefined, rawMember && user ? Transformers.GuildMember(this.client, rawMember, user, guild) : undefined,
), ),
); );
} }
override bulk(ids: string[], guild: string): ReturnCache<GuildMember[]> { override bulk(ids: string[], guild: string): ReturnCache<GuildMemberStructure[]> {
return fakePromise(super.bulk(ids, guild)).then(members => return fakePromise(super.bulk(ids, guild)).then(members =>
fakePromise(this.client.cache.users?.bulk(ids)).then( fakePromise(this.client.cache.users?.bulkRaw(ids)).then(
users => users =>
members members
.map(rawMember => { .map(rawMember => {
const user = users?.find(x => x.id === rawMember.id); const user = users?.find(x => x.id === rawMember.id);
return user ? new GuildMember(this.client, rawMember, user, guild) : undefined; return user ? Transformers.GuildMember(this.client, rawMember, user, guild) : undefined;
}) })
.filter(Boolean) as GuildMember[], .filter(Boolean) as GuildMemberStructure[],
), ),
); );
} }
override values(guild: string): ReturnCache<GuildMember[]> { override values(guild: string): ReturnCache<GuildMemberStructure[]> {
return fakePromise(super.values(guild)).then(members => return fakePromise(super.values(guild)).then(members =>
fakePromise(this.client.cache.users?.values()).then( fakePromise(this.client.cache.users?.valuesRaw()).then(
users => users =>
members members
.map(rawMember => { .map(rawMember => {
const user = users?.find(x => x.id === rawMember.id); const user = users?.find(x => x.id === rawMember.id);
return user ? new GuildMember(this.client, rawMember, user, rawMember.guild_id) : undefined; return user ? Transformers.GuildMember(this.client, rawMember, user, rawMember.guild_id) : undefined;
}) })
.filter(Boolean) as GuildMember[], .filter(Boolean) as GuildMemberStructure[],
), ),
); );
} }

View File

@ -2,7 +2,7 @@ import type { APIMessage, APIUser } from 'discord-api-types/v10';
import { GuildRelatedResource } from './default/guild-related'; import { GuildRelatedResource } from './default/guild-related';
import type { MessageData, ReturnCache } from '../..'; import type { MessageData, ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { Message } from '../../structures'; import { type MessageStructure, Transformers } from '../../client/transformers';
export class Messages extends GuildRelatedResource { export class Messages extends GuildRelatedResource {
namespace = 'message'; namespace = 'message';
@ -19,19 +19,19 @@ export class Messages extends GuildRelatedResource {
return rest; return rest;
} }
override get(id: string): ReturnCache<Message | undefined> { override get(id: string): ReturnCache<MessageStructure | undefined> {
return fakePromise(super.get(id) as APIMessageResource | undefined).then(rawMessage => { return fakePromise(super.get(id) as APIMessageResource | undefined).then(rawMessage => {
return this.cache.users && rawMessage?.user_id return this.cache.users && rawMessage?.user_id
? fakePromise(this.cache.adapter.get(this.cache.users.hashId(rawMessage.user_id)) as APIUser | undefined).then( ? fakePromise(this.cache.adapter.get(this.cache.users.hashId(rawMessage.user_id)) as APIUser | undefined).then(
user => { user => {
return user ? new Message(this.client, { ...rawMessage!, author: user }) : undefined; return user ? Transformers.Message(this.client, { ...rawMessage!, author: user }) : undefined;
}, },
) )
: undefined; : undefined;
}); });
} }
override bulk(ids: string[]): ReturnCache<Message[]> { override bulk(ids: string[]): ReturnCache<MessageStructure[]> {
return fakePromise(super.bulk(ids) as APIMessageResource[]).then( return fakePromise(super.bulk(ids) as APIMessageResource[]).then(
messages => messages =>
messages messages
@ -40,26 +40,26 @@ export class Messages extends GuildRelatedResource {
? fakePromise( ? fakePromise(
this.cache.adapter.get(this.cache.users.hashId(rawMessage.user_id)) as APIUser | undefined, this.cache.adapter.get(this.cache.users.hashId(rawMessage.user_id)) as APIUser | undefined,
).then(user => { ).then(user => {
return user ? new Message(this.client, { ...rawMessage!, author: user }) : undefined; return user ? Transformers.Message(this.client, { ...rawMessage!, author: user }) : undefined;
}) })
: undefined; : undefined;
}) })
.filter(Boolean) as Message[], .filter(Boolean) as MessageStructure[],
); );
} }
override values(guild: string): ReturnCache<Message[]> { override values(guild: string): ReturnCache<MessageStructure[]> {
return fakePromise(super.values(guild) as APIMessageResource[]).then(messages => { return fakePromise(super.values(guild) as APIMessageResource[]).then(messages => {
const hashes: (string | undefined)[] = this.cache.users const hashes: (string | undefined)[] = this.cache.users
? messages.map(x => (x.user_id ? this.cache.users!.hashId(x.user_id) : undefined)) ? messages.map(x => (x.user_id ? this.cache.users!.hashId(x.user_id) : undefined))
: []; : [];
return fakePromise(this.cache.adapter.get(hashes.filter(Boolean) as string[]) as APIUser[]).then(users => { return fakePromise(this.cache.adapter.bulkGet(hashes.filter(Boolean) as string[]) as APIUser[]).then(users => {
return messages return messages
.map(message => { .map(message => {
const user = users.find(user => user.id === message.user_id); const user = users.find(user => user.id === message.user_id);
return user ? new Message(this.client, { ...message, author: user }) : undefined; return user ? Transformers.Message(this.client, { ...message, author: user }) : undefined;
}) })
.filter(Boolean) as Message[]; .filter(Boolean) as MessageStructure[];
}); });
}); });
} }

View File

@ -1,8 +1,8 @@
import type { APIRole } from 'discord-api-types/v10'; import type { APIRole } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { GuildRole } from '../../structures';
import { GuildRelatedResource } from './default/guild-related'; import { GuildRelatedResource } from './default/guild-related';
import { type GuildRoleStructure, Transformers } from '../../client/transformers';
export class Roles extends GuildRelatedResource { export class Roles extends GuildRelatedResource {
namespace = 'role'; namespace = 'role';
@ -12,21 +12,29 @@ export class Roles extends GuildRelatedResource {
return true; return true;
} }
override get(id: string): ReturnCache<GuildRole | undefined> { raw(id: string): ReturnCache<APIRole | undefined> {
return super.get(id);
}
override get(id: string): ReturnCache<GuildRoleStructure | undefined> {
return fakePromise(super.get(id)).then(rawRole => return fakePromise(super.get(id)).then(rawRole =>
rawRole ? new GuildRole(this.client, rawRole, rawRole.guild_id) : undefined, rawRole ? Transformers.GuildRole(this.client, rawRole, rawRole.guild_id) : undefined,
); );
} }
override bulk(ids: string[]): ReturnCache<GuildRole[]> { override bulk(ids: string[]): ReturnCache<GuildRoleStructure[]> {
return fakePromise(super.bulk(ids)).then(roles => return fakePromise(super.bulk(ids)).then(roles =>
roles.map(rawRole => new GuildRole(this.client, rawRole, rawRole.guild_id)), roles.map(rawRole => Transformers.GuildRole(this.client, rawRole, rawRole.guild_id)),
); );
} }
override values(guild: string): ReturnCache<GuildRole[]> { override values(guild: string): ReturnCache<GuildRoleStructure[]> {
return fakePromise(super.values(guild)).then(roles => return fakePromise(super.values(guild)).then(roles =>
roles.map(rawRole => new GuildRole(this.client, rawRole, rawRole.guild_id)), roles.map(rawRole => Transformers.GuildRole(this.client, rawRole, rawRole.guild_id)),
); );
} }
valuesRaw(guild: string): ReturnCache<APIRole[]> {
return super.values(guild);
}
} }

View File

@ -1,8 +1,8 @@
import type { APISticker } from 'discord-api-types/v10'; import type { APISticker } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { Sticker } from '../../structures';
import { GuildRelatedResource } from './default/guild-related'; import { GuildRelatedResource } from './default/guild-related';
import { type StickerStructure, Transformers } from '../../client/transformers';
export class Stickers extends GuildRelatedResource { export class Stickers extends GuildRelatedResource {
namespace = 'sticker'; namespace = 'sticker';
@ -12,21 +12,21 @@ export class Stickers extends GuildRelatedResource {
return true; return true;
} }
override get(id: string): ReturnCache<Sticker | undefined> { override get(id: string): ReturnCache<StickerStructure | undefined> {
return fakePromise(super.get(id)).then(rawSticker => return fakePromise(super.get(id)).then(rawSticker =>
rawSticker ? new Sticker(this.client, rawSticker) : undefined, rawSticker ? Transformers.Sticker(this.client, rawSticker) : undefined,
); );
} }
override bulk(ids: string[]): ReturnCache<Sticker[]> { override bulk(ids: string[]): ReturnCache<StickerStructure[]> {
return fakePromise(super.bulk(ids) as APISticker[]).then(emojis => return fakePromise(super.bulk(ids) as APISticker[]).then(emojis =>
emojis.map(rawSticker => new Sticker(this.client, rawSticker)), emojis.map(rawSticker => Transformers.Sticker(this.client, rawSticker)),
); );
} }
override values(guild: string): ReturnCache<Sticker[]> { override values(guild: string): ReturnCache<StickerStructure[]> {
return fakePromise(super.values(guild) as APISticker[]).then(emojis => return fakePromise(super.values(guild) as APISticker[]).then(emojis =>
emojis.map(rawSticker => new Sticker(this.client, rawSticker)), emojis.map(rawSticker => Transformers.Sticker(this.client, rawSticker)),
); );
} }
} }

View File

@ -1,8 +1,8 @@
import type { APIThreadChannel } from 'discord-api-types/v10'; import type { APIThreadChannel } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { ThreadChannel } from '../../structures';
import { GuildRelatedResource } from './default/guild-related'; import { GuildRelatedResource } from './default/guild-related';
import { type ThreadChannelStructure, Transformers } from '../../client/transformers';
export class Threads extends GuildRelatedResource { export class Threads extends GuildRelatedResource {
namespace = 'thread'; namespace = 'thread';
@ -12,21 +12,21 @@ export class Threads extends GuildRelatedResource {
return true; return true;
} }
override get(id: string): ReturnCache<ThreadChannel | undefined> { override get(id: string): ReturnCache<ThreadChannelStructure | undefined> {
return fakePromise(super.get(id)).then(rawThread => return fakePromise(super.get(id)).then(rawThread =>
rawThread ? new ThreadChannel(this.client, rawThread) : undefined, rawThread ? Transformers.ThreadChannel(this.client, rawThread) : undefined,
); );
} }
override bulk(ids: string[]): ReturnCache<ThreadChannel[]> { override bulk(ids: string[]): ReturnCache<ThreadChannelStructure[]> {
return fakePromise(super.bulk(ids) as APIThreadChannel[]).then(threads => return fakePromise(super.bulk(ids) as APIThreadChannel[]).then(threads =>
threads.map(rawThread => new ThreadChannel(this.client, rawThread)), threads.map(rawThread => Transformers.ThreadChannel(this.client, rawThread)),
); );
} }
override values(guild: string): ReturnCache<ThreadChannel[]> { override values(guild: string): ReturnCache<ThreadChannelStructure[]> {
return fakePromise(super.values(guild) as APIThreadChannel[]).then(threads => return fakePromise(super.values(guild) as APIThreadChannel[]).then(threads =>
threads.map(rawThread => new ThreadChannel(this.client, rawThread)), threads.map(rawThread => Transformers.ThreadChannel(this.client, rawThread)),
); );
} }
} }

View File

@ -1,8 +1,8 @@
import type { APIUser } from 'discord-api-types/v10'; import type { APIUser } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { User } from '../../structures';
import { BaseResource } from './default/base'; import { BaseResource } from './default/base';
import { Transformers, type UserStructure } from '../../client/transformers';
export class Users extends BaseResource { export class Users extends BaseResource {
namespace = 'user'; namespace = 'user';
@ -12,17 +12,31 @@ export class Users extends BaseResource {
return true; return true;
} }
override get(id: string): ReturnCache<User | undefined> { raw(id: string): ReturnCache<APIUser | undefined> {
return fakePromise(super.get(id)).then(rawUser => (rawUser ? new User(this.client, rawUser) : undefined)); return super.get(id);
} }
override bulk(ids: string[]): ReturnCache<User[]> { override get(id: string): ReturnCache<UserStructure | undefined> {
return fakePromise(super.get(id)).then(rawUser => (rawUser ? Transformers.User(this.client, rawUser) : undefined));
}
override bulk(ids: string[]): ReturnCache<UserStructure[]> {
return fakePromise(super.bulk(ids) as APIUser[]).then(users => return fakePromise(super.bulk(ids) as APIUser[]).then(users =>
users.map(rawUser => new User(this.client, rawUser)), users.map(rawUser => Transformers.User(this.client, rawUser)),
); );
} }
override values(): ReturnCache<User[]> { bulkRaw(ids: string[]): ReturnCache<APIUser[]> {
return fakePromise(super.values() as APIUser[]).then(users => users.map(rawUser => new User(this.client, rawUser))); return super.bulk(ids);
}
override values(): ReturnCache<UserStructure[]> {
return fakePromise(super.values() as APIUser[]).then(users =>
users.map(rawUser => Transformers.User(this.client, rawUser)),
);
}
valuesRaw(): ReturnCache<APIUser[]> {
return super.values();
} }
} }

View File

@ -1,8 +1,8 @@
import type { GatewayVoiceState } from 'discord-api-types/v10'; import type { GatewayVoiceState } from 'discord-api-types/v10';
import type { ReturnCache } from '../..'; import type { ReturnCache } from '../..';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import { VoiceState } from '../../structures';
import { GuildBasedResource } from './default/guild-based'; import { GuildBasedResource } from './default/guild-based';
import { Transformers, type VoiceStateStructure } from '../../client/transformers';
export class VoiceStates extends GuildBasedResource { export class VoiceStates extends GuildBasedResource {
namespace = 'voice_state'; namespace = 'voice_state';
@ -17,21 +17,25 @@ export class VoiceStates extends GuildBasedResource {
return rest; return rest;
} }
override get(memberId: string, guildId: string): ReturnCache<VoiceState | undefined> { override get(memberId: string, guildId: string): ReturnCache<VoiceStateStructure | undefined> {
return fakePromise(super.get(memberId, guildId)).then(state => return fakePromise(super.get(memberId, guildId)).then(state =>
state ? new VoiceState(this.client, state) : undefined, state ? Transformers.VoiceState(this.client, state) : undefined,
); );
} }
override bulk(ids: string[], guild: string): ReturnCache<VoiceState[]> { override bulk(ids: string[], guild: string): ReturnCache<VoiceStateStructure[]> {
return fakePromise(super.bulk(ids, guild)).then( return fakePromise(super.bulk(ids, guild)).then(
states => states =>
states.map(state => (state ? new VoiceState(this.client, state) : undefined)).filter(y => !!y) as VoiceState[], states
.map(state => (state ? Transformers.VoiceState(this.client, state) : undefined))
.filter(y => !!y) as VoiceStateStructure[],
); );
} }
override values(guildId: string): ReturnCache<VoiceState[]> { override values(guildId: string): ReturnCache<VoiceStateStructure[]> {
return fakePromise(super.values(guildId)).then(states => states.map(state => new VoiceState(this.client, state))); return fakePromise(super.values(guildId)).then(states =>
states.map(state => Transformers.VoiceState(this.client, state)),
);
} }
} }

View File

@ -5,9 +5,11 @@ import { Cache, MemoryAdapter } from '../cache';
import type { import type {
Command, Command,
CommandContext, CommandContext,
ContextMenuCommand,
ExtraProps, ExtraProps,
OnOptionsReturnObject, MenuCommandContext,
RegisteredMiddlewares, RegisteredMiddlewares,
SubCommand,
UsingClient, UsingClient,
} from '../commands'; } from '../commands';
import { IgnoreCommand, type InferWithPrefix, type MiddlewareContext } from '../commands/applications/shared'; import { IgnoreCommand, type InferWithPrefix, type MiddlewareContext } from '../commands/applications/shared';
@ -40,7 +42,6 @@ import { LangsHandler } from '../langs/handler';
import type { import type {
ChatInputCommandInteraction, ChatInputCommandInteraction,
ComponentInteraction, ComponentInteraction,
Message,
MessageCommandInteraction, MessageCommandInteraction,
ModalSubmitInteraction, ModalSubmitInteraction,
UserCommandInteraction, UserCommandInteraction,
@ -48,6 +49,8 @@ import type {
import type { ComponentCommand, ComponentContext, ModalCommand, ModalContext } from '../components'; import type { ComponentCommand, ComponentContext, ModalCommand, ModalContext } from '../components';
import { promises } from 'node:fs'; import { promises } from 'node:fs';
import { BanShorter } from '../common/shorters/bans'; import { BanShorter } from '../common/shorters/bans';
import { HandleCommand } from '../commands/handle';
import type { MessageStructure } from './transformers';
export class BaseClient { export class BaseClient {
rest!: ApiHandler; rest!: ApiHandler;
@ -76,6 +79,7 @@ export class BaseClient {
langs? = new LangsHandler(this.logger); langs? = new LangsHandler(this.logger);
commands? = new CommandHandler(this.logger, this); commands? = new CommandHandler(this.logger, this);
components? = new ComponentHandler(this.logger, this); components? = new ComponentHandler(this.logger, this);
handleCommand!: HandleCommand;
private _applicationId?: string; private _applicationId?: string;
private _botId?: string; private _botId?: string;
@ -101,30 +105,30 @@ export class BaseClient {
{ {
commands: { commands: {
defaults: { defaults: {
onRunError(context: CommandContext<any>, error: unknown): any { onRunError(context, error): any {
context.client.logger.fatal(`${context.command.name}.<onRunError>`, context.author.id, error); context.client.logger.fatal(`${context.command.name}.<onRunError>`, context.author.id, error);
}, },
onOptionsError(context: CommandContext<{}, never>, metadata: OnOptionsReturnObject): any { onOptionsError(context, metadata): any {
context.client.logger.fatal(`${context.command.name}.<onOptionsError>`, context.author.id, metadata); context.client.logger.fatal(`${context.command.name}.<onOptionsError>`, context.author.id, metadata);
}, },
onMiddlewaresError(context: CommandContext<{}, never>, error: string): any { onMiddlewaresError(context, error: string): any {
context.client.logger.fatal(`${context.command.name}.<onMiddlewaresError>`, context.author.id, error); context.client.logger.fatal(`${context.command.name}.<onMiddlewaresError>`, context.author.id, error);
}, },
onBotPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any { onBotPermissionsFail(context, permissions): any {
context.client.logger.fatal( context.client.logger.fatal(
`${context.command.name}.<onBotPermissionsFail>`, `${context.command.name}.<onBotPermissionsFail>`,
context.author.id, context.author.id,
permissions, permissions,
); );
}, },
onPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any { onPermissionsFail(context, permissions): any {
context.client.logger.fatal( context.client.logger.fatal(
`${context.command.name}.<onPermissionsFail>`, `${context.command.name}.<onPermissionsFail>`,
context.author.id, context.author.id,
permissions, permissions,
); );
}, },
onInternalError(client: UsingClient, command: Command, error?: unknown): any { onInternalError(client: UsingClient, command, error?: unknown): any {
client.logger.fatal(`${command.name}.<onInternalError>`, error); client.logger.fatal(`${command.name}.<onInternalError>`, error);
}, },
}, },
@ -180,7 +184,7 @@ export class BaseClient {
return new Router(this.rest).createProxy(); return new Router(this.rest).createProxy();
} }
setServices({ rest, cache, langs, middlewares, handlers }: ServicesOptions) { setServices({ rest, cache, langs, middlewares, handlers, handleCommand }: ServicesOptions) {
if (rest) { if (rest) {
this.rest = rest; this.rest = rest;
} }
@ -201,9 +205,6 @@ export class BaseClient {
this.components = undefined; this.components = undefined;
} else if (typeof handlers.components === 'function') { } else if (typeof handlers.components === 'function') {
this.components ??= new ComponentHandler(this.logger, this); this.components ??= new ComponentHandler(this.logger, this);
this.components.setHandlers({
callback: handlers.components,
});
} else { } else {
this.components = handlers.components; this.components = handlers.components;
} }
@ -213,7 +214,6 @@ export class BaseClient {
this.commands = undefined; this.commands = undefined;
} else if (typeof handlers.commands === 'object') { } else if (typeof handlers.commands === 'object') {
this.commands ??= new CommandHandler(this.logger, this); this.commands ??= new CommandHandler(this.logger, this);
this.commands.setHandlers(handlers.commands);
} else { } else {
this.commands = handlers.commands; this.commands = handlers.commands;
} }
@ -233,6 +233,8 @@ export class BaseClient {
if (langs.default) this.langs!.defaultLang = langs.default; if (langs.default) this.langs!.defaultLang = langs.default;
if (langs.aliases) this.langs!.aliases = Object.entries(langs.aliases); if (langs.aliases) this.langs!.aliases = Object.entries(langs.aliases);
} }
if (handleCommand) this.handleCommand = new handleCommand(this);
} }
protected async execute(..._options: unknown[]) { protected async execute(..._options: unknown[]) {
@ -275,6 +277,8 @@ export class BaseClient {
} else { } else {
this.cache = new Cache(0, new MemoryAdapter(), [], this); this.cache = new Cache(0, new MemoryAdapter(), [], this);
} }
if (!this.handleCommand) this.handleCommand = new HandleCommand(this);
} }
protected async onPacket(..._packet: unknown[]) { protected async onPacket(..._packet: unknown[]) {
@ -383,18 +387,25 @@ export interface BaseClientOptions {
| MessageCommandInteraction<boolean> | MessageCommandInteraction<boolean>
| ComponentInteraction | ComponentInteraction
| ModalSubmitInteraction | ModalSubmitInteraction
| When<InferWithPrefix, Message, never>, | When<InferWithPrefix, MessageStructure, never>,
) => {}; ) => {};
globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[]; globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[];
commands?: { commands?: {
defaults?: { defaults?: {
onRunError?: Command['onRunError']; onRunError?: (context: MenuCommandContext<any, never> | CommandContext, error: unknown) => unknown;
onPermissionsFail?: Command['onPermissionsFail']; onPermissionsFail?: Command['onPermissionsFail'];
onBotPermissionsFail?: Command['onBotPermissionsFail']; onBotPermissionsFail?: (
onInternalError?: Command['onInternalError']; context: MenuCommandContext<any, never> | CommandContext,
onMiddlewaresError?: Command['onMiddlewaresError']; permissions: PermissionStrings,
) => unknown;
onInternalError?: (
client: UsingClient,
command: Command | SubCommand | ContextMenuCommand,
error?: unknown,
) => unknown;
onMiddlewaresError?: (context: CommandContext | MenuCommandContext<any, never>, error: string) => unknown;
onOptionsError?: Command['onOptionsError']; onOptionsError?: Command['onOptionsError'];
onAfterRun?: Command['onAfterRun']; onAfterRun?: (context: CommandContext | MenuCommandContext<any, never>, error: unknown) => unknown;
props?: ExtraProps; props?: ExtraProps;
}; };
}; };
@ -480,7 +491,8 @@ export interface ServicesOptions {
middlewares?: Record<string, MiddlewareContext>; middlewares?: Record<string, MiddlewareContext>;
handlers?: { handlers?: {
components?: ComponentHandler | ComponentHandler['callback']; components?: ComponentHandler | ComponentHandler['callback'];
commands?: CommandHandler | Parameters<CommandHandler['setHandlers']>[0]; commands?: CommandHandler;
langs?: LangsHandler | LangsHandler['callback']; langs?: LangsHandler | LangsHandler['callback'];
}; };
handleCommand?: typeof HandleCommand;
} }

View File

@ -1,23 +1,7 @@
import { import { GatewayIntentBits, type GatewayDispatchPayload, type GatewayPresenceUpdateData } from 'discord-api-types/v10';
type APIApplicationCommandInteractionDataOption, import type { CommandContext, Message } from '..';
GatewayIntentBits,
type GatewayMessageCreateDispatchData,
type GatewayDispatchPayload,
type GatewayPresenceUpdateData,
} from 'discord-api-types/v10';
import type {
Command,
CommandContext,
ContextOptionsResolved,
Message,
MessageCommandOptionErrors,
SubCommand,
UsingClient,
} from '..';
import { import {
type Awaitable, type Awaitable,
type MakeRequired,
MergeOptions,
lazyLoadPackage, lazyLoadPackage,
type DeepPartial, type DeepPartial,
type If, type If,
@ -25,24 +9,22 @@ import {
type WatcherSendToShard, type WatcherSendToShard,
} from '../common'; } from '../common';
import { EventHandler } from '../events'; import { EventHandler } from '../events';
import { ClientUser } from '../structures';
import { ShardManager, properties, type ShardManagerOptions } from '../websocket'; import { ShardManager, properties, type ShardManagerOptions } from '../websocket';
import { MemberUpdateHandler } from '../websocket/discord/events/memberUpdate'; import { MemberUpdateHandler } from '../websocket/discord/events/memberUpdate';
import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdate'; import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdate';
import type { BaseClientOptions, InternalRuntimeConfig, ServicesOptions, StartOptions } from './base'; import type { BaseClientOptions, InternalRuntimeConfig, ServicesOptions, StartOptions } from './base';
import { BaseClient } from './base'; import { BaseClient } from './base';
import { onInteractionCreate } from './oninteractioncreate';
import { defaultArgsParser, defaultOptionsParser, onMessageCreate } from './onmessagecreate';
import { Collectors } from './collectors'; import { Collectors } from './collectors';
import { type ClientUserStructure, Transformers, type MessageStructure } from './transformers';
let parentPort: import('node:worker_threads').MessagePort; let parentPort: import('node:worker_threads').MessagePort;
export class Client<Ready extends boolean = boolean> extends BaseClient { export class Client<Ready extends boolean = boolean> extends BaseClient {
private __handleGuilds?: Set<string> = new Set(); private __handleGuilds?: Set<string> = new Set();
gateway!: ShardManager; gateway!: ShardManager;
me!: If<Ready, ClientUser>; me!: If<Ready, ClientUserStructure>;
declare options: Omit<ClientOptions, 'commands'> & { declare options: Omit<ClientOptions, 'commands'> & {
commands: MakeRequired<NonNullable<ClientOptions['commands']>, 'argsParser' | 'optionsParser'>; commands: NonNullable<ClientOptions['commands']>;
}; };
memberUpdateHandler = new MemberUpdateHandler(); memberUpdateHandler = new MemberUpdateHandler();
presenceUpdateHandler = new PresenceUpdateHandler(); presenceUpdateHandler = new PresenceUpdateHandler();
@ -51,15 +33,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
constructor(options?: ClientOptions) { constructor(options?: ClientOptions) {
super(options); super(options);
this.options = MergeOptions(
{
commands: {
argsParser: options?.commands?.argsParser ?? defaultArgsParser,
optionsParser: options?.commands?.optionsParser ?? defaultOptionsParser,
},
} satisfies ClientOptions,
this.options,
);
} }
setServices({ setServices({
@ -86,9 +59,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
this.events = undefined; this.events = undefined;
} else if (typeof rest.handlers.events === 'function') { } else if (typeof rest.handlers.events === 'function') {
this.events = new EventHandler(this); this.events = new EventHandler(this);
this.events.setHandlers({
callback: rest.handlers.events,
});
} else { } else {
this.events = rest.handlers.events; this.events = rest.handlers.events;
} }
@ -169,7 +139,10 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
} }
protected async onPacket(shardId: number, packet: GatewayDispatchPayload) { protected async onPacket(shardId: number, packet: GatewayDispatchPayload) {
await this.events?.runEvent('RAW', this, packet, shardId); Promise.allSettled([
this.events?.runEvent('RAW', this, packet, shardId, false),
this.collectors.run('RAW', packet),
]); //ignore promise
switch (packet.t) { switch (packet.t) {
//// Cases where we must obtain the old data before updating //// Cases where we must obtain the old data before updating
case 'GUILD_MEMBER_UPDATE': case 'GUILD_MEMBER_UPDATE':
@ -179,7 +152,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
await this.events?.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(packet.t, packet, this as Client<true>, shardId);
break; break;
case 'PRESENCE_UPDATE': case 'PRESENCE_UPDATE':
if (!this.presenceUpdateHandler.check(packet.d as any)) { if (!this.presenceUpdateHandler.check(packet.d)) {
return; return;
} }
await this.events?.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(packet.t, packet, this as Client<true>, shardId);
@ -198,24 +171,25 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
} }
//rest of the events //rest of the events
default: { default: {
await this.events?.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(packet.t as never, packet, this as Client<true>, shardId);
switch (packet.t) { switch (packet.t) {
case 'INTERACTION_CREATE': case 'INTERACTION_CREATE':
await onInteractionCreate(this, packet.d, shardId); await this.handleCommand.interaction(packet.d, shardId);
break; break;
case 'MESSAGE_CREATE': case 'MESSAGE_CREATE':
await onMessageCreate(this, packet.d, shardId); await this.handleCommand.message(packet.d, shardId);
break; break;
case 'READY': case 'READY':
if (!this.__handleGuilds) this.__handleGuilds = new Set();
for (const g of packet.d.guilds) { for (const g of packet.d.guilds) {
this.__handleGuilds?.add(g.id); this.__handleGuilds.add(g.id);
} }
this.botId = packet.d.user.id; this.botId = packet.d.user.id;
this.applicationId = packet.d.application.id; this.applicationId = packet.d.application.id;
this.me = new ClientUser(this, packet.d.user, packet.d.application) as never; this.me = Transformers.ClientUser(this, packet.d.user, packet.d.application) as never;
if ( if (
!( !(
this.__handleGuilds?.size && this.__handleGuilds.size &&
(this.gateway.options.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds (this.gateway.options.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds
) )
) { ) {
@ -245,24 +219,9 @@ export interface ClientOptions extends BaseClientOptions {
compress?: ShardManagerOptions['compress']; compress?: ShardManagerOptions['compress'];
}; };
commands?: BaseClientOptions['commands'] & { commands?: BaseClientOptions['commands'] & {
prefix?: (message: Message) => Promise<string[]> | string[]; prefix?: (message: MessageStructure) => Awaitable<string[]>;
deferReplyResponse?: (ctx: CommandContext) => Parameters<Message['write']>[0]; deferReplyResponse?: (ctx: CommandContext) => Parameters<Message['write']>[0];
reply?: (ctx: CommandContext) => boolean; reply?: (ctx: CommandContext) => boolean;
argsParser?: (content: string, command: SubCommand | Command, message: Message) => Record<string, string>;
optionsParser?: (
self: UsingClient,
command: Command | SubCommand,
message: GatewayMessageCreateDispatchData,
args: Partial<Record<string, string>>,
resolved: MakeRequired<ContextOptionsResolved>,
) => Awaitable<{
errors: {
name: string;
error: string;
fullError: MessageCommandOptionErrors;
}[];
options: APIApplicationCommandInteractionDataOption[];
}>;
}; };
handlePayload?: ShardManagerOptions['handlePayload']; handlePayload?: ShardManagerOptions['handlePayload'];
} }

View File

@ -11,7 +11,6 @@ import { isBufferLike } from '../api/utils/utils';
import { MergeOptions, isCloudfareWorker, type DeepPartial } from '../common'; import { MergeOptions, isCloudfareWorker, type DeepPartial } from '../common';
import type { BaseClientOptions, InternalRuntimeConfigHTTP, StartOptions } from './base'; import type { BaseClientOptions, InternalRuntimeConfigHTTP, StartOptions } from './base';
import { BaseClient } from './base'; import { BaseClient } from './base';
import { onInteractionCreate } from './oninteractioncreate';
let UWS: typeof import('uWebSockets.js') | undefined; let UWS: typeof import('uWebSockets.js') | undefined;
let nacl: typeof import('tweetnacl') | undefined; let nacl: typeof import('tweetnacl') | undefined;
@ -164,10 +163,11 @@ export class HttpClient extends BaseClient {
default: default:
return new Promise<Response>(r => { return new Promise<Response>(r => {
if (isCloudfareWorker()) if (isCloudfareWorker())
return onInteractionCreate(this, rawBody, -1) return this.handleCommand
.interaction(rawBody, -1)
.then(() => r(new Response())) .then(() => r(new Response()))
.catch(() => r(new Response())); .catch(() => r(new Response()));
return onInteractionCreate(this, rawBody, -1, async ({ body, files }) => { return this.handleCommand.interaction(rawBody, -1, async ({ body, files }) => {
let response: FormData | APIInteractionResponse; let response: FormData | APIInteractionResponse;
const headers: { 'Content-Type'?: string } = {}; const headers: { 'Content-Type'?: string } = {};
@ -224,7 +224,7 @@ export class HttpClient extends BaseClient {
.end(JSON.stringify({ type: InteractionResponseType.Pong })); .end(JSON.stringify({ type: InteractionResponseType.Pong }));
break; break;
default: default:
await onInteractionCreate(this, rawBody, -1, async ({ body, files }) => { await this.handleCommand.interaction(rawBody, -1, async ({ body, files }) => {
res.cork(() => { res.cork(() => {
let response: FormData | APIInteractionResponse; let response: FormData | APIInteractionResponse;
const headers: { 'Content-Type'?: string } = {}; const headers: { 'Content-Type'?: string } = {};

View File

@ -2,3 +2,4 @@ export type { RuntimeConfig, RuntimeConfigHTTP } from './base';
export * from './client'; export * from './client';
export * from './httpclient'; export * from './httpclient';
export * from './workerclient'; export * from './workerclient';
export * from './transformers';

View File

@ -1,244 +0,0 @@
import { ApplicationCommandType, InteractionType, type APIInteraction } from 'discord-api-types/v10';
import {
BaseCommand,
CommandContext,
MenuCommandContext,
OptionResolver,
type RegisteredMiddlewares,
type Command,
type ContextMenuCommand,
type ContextOptionsResolved,
type UsingClient,
} from '../commands';
import type {
ChatInputCommandInteraction,
ComponentInteraction,
MessageCommandInteraction,
ModalSubmitInteraction,
UserCommandInteraction,
__InternalReplyFunction,
} from '../structures';
import { AutocompleteInteraction, BaseInteraction } from '../structures';
import { ComponentContext, ModalContext } from '../components';
export async function onInteractionCreate(
self: UsingClient,
body: APIInteraction,
shardId: number,
__reply?: __InternalReplyFunction,
) {
self.debugger?.debug(`[${InteractionType[body.type] ?? body.type}] Interaction received.`);
switch (body.type) {
case InteractionType.ApplicationCommandAutocomplete:
{
const parentCommand = self.commands?.values.find(x => {
if (body.data.guild_id) {
return x.guildId?.includes(body.data.guild_id) && x.name === body.data.name;
}
return x.name === body.data.name;
});
const optionsResolver = new OptionResolver(
self,
body.data.options ?? [],
parentCommand as Command,
body.guild_id,
body.data.resolved as ContextOptionsResolved,
);
const interaction = new AutocompleteInteraction(self, body, optionsResolver, __reply);
const command = optionsResolver.getAutocomplete();
// idc, is a YOU problem
if (!command?.autocomplete)
return self.logger.warn(
`${optionsResolver.fullCommandName} ${command?.name} command does not have 'autocomplete' callback`,
);
try {
try {
await command.autocomplete(interaction);
} catch (error) {
if (!command.onAutocompleteError)
return self.logger.error(
`${optionsResolver.fullCommandName} ${command.name} just threw an error, ${
error ? (typeof error === 'object' && 'message' in error ? error.message : error) : 'Unknown'
}`,
);
await command.onAutocompleteError(interaction, error);
}
} catch (error) {
try {
await optionsResolver.getCommand()?.onInternalError?.(self, optionsResolver.getCommand()!, error);
} catch {
// supress error
}
}
}
break;
case InteractionType.ApplicationCommand:
{
switch (body.data.type) {
case ApplicationCommandType.Message:
case ApplicationCommandType.User:
{
const command = self.commands?.values.find(x => {
if (body.data.guild_id) {
return x.guildId?.includes(body.data.guild_id) && x.name === body.data.name;
}
return x.name === body.data.name;
}) as ContextMenuCommand;
const interaction = BaseInteraction.from(self, body, __reply) as
| UserCommandInteraction
| MessageCommandInteraction;
// idc, is a YOU problem
if (!command?.run)
return self.logger.warn(`${command.name ?? 'Unknown'} command does not have 'run' callback`);
const context = new MenuCommandContext(self, interaction, shardId, command);
const extendContext = self.options?.context?.(interaction) ?? {};
Object.assign(context, extendContext);
try {
if (command.botPermissions && interaction.appPermissions) {
const permissions = interaction.appPermissions.missings(
...interaction.appPermissions.values([command.botPermissions]),
);
if (!interaction.appPermissions.has('Administrator') && permissions.length) {
return command.onBotPermissionsFail(context, interaction.appPermissions.keys(permissions));
}
}
const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares(
context,
(self.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares,
true,
);
if (resultRunGlobalMiddlewares.pass) {
return;
}
if ('error' in resultRunGlobalMiddlewares) {
return command.onMiddlewaresError(context, resultRunGlobalMiddlewares.error ?? 'Unknown error');
}
const resultRunMiddlewares = await BaseCommand.__runMiddlewares(
context,
command.middlewares as keyof RegisteredMiddlewares,
false,
);
if (resultRunMiddlewares.pass) {
return;
}
if ('error' in resultRunMiddlewares) {
return command.onMiddlewaresError(context, resultRunMiddlewares.error ?? 'Unknown error');
}
try {
await command.run(context);
await command.onAfterRun?.(context, undefined);
} catch (error) {
await command.onRunError(context, error);
await command.onAfterRun?.(context, error);
}
} catch (error) {
try {
await command.onInternalError(self, error);
} catch {
// supress error
}
}
}
break;
case ApplicationCommandType.ChatInput:
{
const parentCommand = self.commands?.values.find(x => {
if (body.data.guild_id) {
return x.guildId?.includes(body.data.guild_id) && x.name === body.data.name;
}
return x.name === body.data.name;
});
const optionsResolver = new OptionResolver(
self,
body.data.options ?? [],
parentCommand as Command,
body.guild_id,
body.data.resolved as ContextOptionsResolved,
);
const interaction = BaseInteraction.from(self, body, __reply) as ChatInputCommandInteraction;
const command = optionsResolver.getCommand();
if (!command?.run)
return self.logger.warn(`${optionsResolver.fullCommandName} command does not have 'run' callback`);
const context = new CommandContext(self, interaction, optionsResolver, shardId, command);
const extendContext = self.options?.context?.(interaction) ?? {};
Object.assign(context, extendContext);
try {
if (command.botPermissions && interaction.appPermissions) {
const permissions = interaction.appPermissions.missings(
...interaction.appPermissions.values([command.botPermissions]),
);
if (!interaction.appPermissions.has('Administrator') && permissions.length) {
return command.onBotPermissionsFail?.(context, interaction.appPermissions.keys(permissions));
}
}
const [erroredOptions, result] = await command.__runOptions(context, optionsResolver);
if (erroredOptions) {
return command.onOptionsError?.(context, result);
}
const resultRunGlobalMiddlewares = await command.__runGlobalMiddlewares(context);
if (resultRunGlobalMiddlewares.pass) {
return;
}
if ('error' in resultRunGlobalMiddlewares) {
return command.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error');
}
const resultRunMiddlewares = await command.__runMiddlewares(context);
if (resultRunMiddlewares.pass) {
return;
}
if ('error' in resultRunMiddlewares) {
return command.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error');
}
try {
await command.run(context);
await command.onAfterRun?.(context, undefined);
} catch (error) {
await command.onRunError?.(context, error);
await command.onAfterRun?.(context, error);
}
} catch (error) {
try {
await command.onInternalError?.(self, context.command, error);
} catch {
// supress error
}
}
}
break;
}
}
break;
case InteractionType.ModalSubmit:
{
const interaction = BaseInteraction.from(self, body, __reply) as ModalSubmitInteraction;
if (self.components?.hasModal(interaction)) {
await self.components.onModalSubmit(interaction);
} else {
const context = new ModalContext(self, interaction);
const extended = self.options?.context?.(interaction) ?? {};
Object.assign(context, extended);
await self.components?.executeModal(context);
}
}
break;
case InteractionType.MessageComponent:
{
const interaction = BaseInteraction.from(self, body, __reply) as ComponentInteraction;
if (self.components?.hasComponent(body.message.id, interaction.customId)) {
await self.components.onComponent(body.message.id, interaction);
} else {
//@ts-expect-error
const context = new ComponentContext(self, interaction);
const extended = self.options?.context?.(interaction) ?? {};
Object.assign(context, extended);
await self.components?.executeComponent(context);
}
}
break;
}
}

View File

@ -1,471 +0,0 @@
import {
ApplicationCommandOptionType,
ChannelType,
InteractionContextType,
type APIApplicationCommandInteractionDataOption,
type GatewayMessageCreateDispatchData,
} from 'discord-api-types/v10';
import {
Command,
CommandContext,
IgnoreCommand,
type MessageCommandOptionErrors,
OptionResolver,
SubCommand,
User,
type UsingClient,
type Client,
type CommandOption,
type ContextOptionsResolved,
type SeyfertChannelOption,
type SeyfertIntegerOption,
type SeyfertNumberOption,
type SeyfertStringOption,
type WorkerClient,
} from '..';
import type { MakeRequired } from '../common';
import { Message } from '../structures';
function getCommandFromContent(
commandRaw: string[],
self: Client | WorkerClient,
): {
command?: Command | SubCommand;
parent?: Command;
fullCommandName: string;
} {
const rawParentName = commandRaw[0];
const rawGroupName = commandRaw.length === 3 ? commandRaw[1] : undefined;
const rawSubcommandName = rawGroupName ? commandRaw[2] : commandRaw[1];
const parent = self.commands!.values.find(
x =>
(!('ignore' in x) || x.ignore !== IgnoreCommand.Message) &&
(x.name === rawParentName || ('aliases' in x ? x.aliases?.includes(rawParentName) : false)),
);
const fullCommandName = `${rawParentName}${
rawGroupName ? ` ${rawGroupName} ${rawSubcommandName}` : `${rawSubcommandName ? ` ${rawSubcommandName}` : ''}`
}`;
if (!(parent instanceof Command)) return { fullCommandName };
if (rawGroupName && !parent.groups?.[rawGroupName] && !parent.groupsAliases?.[rawGroupName])
return getCommandFromContent([rawParentName, rawGroupName], self);
if (
rawSubcommandName &&
!parent.options?.some(
x => x instanceof SubCommand && (x.name === rawSubcommandName || x.aliases?.includes(rawSubcommandName)),
)
)
return getCommandFromContent([rawParentName], self);
const groupName = rawGroupName ? parent.groupsAliases?.[rawGroupName] || rawGroupName : undefined;
const command =
groupName || rawSubcommandName
? (parent.options?.find(opt => {
if (opt instanceof SubCommand) {
if (groupName) {
if (opt.group !== groupName) return false;
}
if (opt.group && !groupName) return false;
return rawSubcommandName === opt.name || opt.aliases?.includes(rawSubcommandName);
}
return false;
}) as SubCommand)
: parent;
return {
command,
fullCommandName,
parent,
};
}
export async function onMessageCreate(
self: Client | WorkerClient,
rawMessage: GatewayMessageCreateDispatchData,
shardId: number,
) {
if (!self.options?.commands?.prefix) return;
const message = new Message(self, rawMessage);
const prefixes = (await self.options.commands.prefix(message)).sort((a, b) => b.length - a.length);
const prefix = prefixes.find(x => message.content.startsWith(x));
if (!(prefix !== undefined && message.content.startsWith(prefix))) return;
const content = message.content.slice(prefix.length).trimStart();
const { fullCommandName, command, parent } = getCommandFromContent(
content
.split(' ')
.filter(x => x)
.slice(0, 3),
self,
);
if (!command) return;
if (!command.run) return self.logger.warn(`${fullCommandName} command does not have 'run' callback`);
if (!(command.contexts.includes(InteractionContextType.BotDM) || message.guildId)) return;
if (!command.contexts.includes(InteractionContextType.Guild) && message.guildId) return;
if (command.guildId && !command.guildId?.includes(message.guildId!)) return;
const resolved: MakeRequired<ContextOptionsResolved> = {
channels: {},
roles: {},
users: {},
members: {},
attachments: {},
};
let newContent = content;
for (const i of fullCommandName.split(' ')) {
newContent = newContent.slice(newContent.indexOf(i) + i.length);
}
const args = self.options.commands.argsParser(newContent.slice(1), command, message);
const { options, errors } = await self.options.commands.optionsParser(self, command, rawMessage, args, resolved);
const optionsResolver = new OptionResolver(self, options, parent as Command, message.guildId, resolved);
const context = new CommandContext(self, message, optionsResolver, shardId, command);
//@ts-expect-error
const extendContext = self.options?.context?.(message) ?? {};
Object.assign(context, extendContext);
try {
if (errors.length) {
return command.onOptionsError?.(
context,
Object.fromEntries(
errors.map(x => {
return [
x.name,
{
failed: true,
value: x.error,
parseError: x.fullError,
},
];
}),
),
);
}
if (command.defaultMemberPermissions && message.guildId) {
const memberPermissions = await self.members.permissions(message.guildId, message.author.id);
const permissions = memberPermissions.missings(...memberPermissions.values([command.defaultMemberPermissions]));
if (
!memberPermissions.has('Administrator') &&
permissions.length &&
(await message.guild())!.ownerId !== message.author.id
) {
return command.onPermissionsFail?.(context, memberPermissions.keys(permissions));
}
}
if (command.botPermissions && message.guildId) {
const meMember = await self.cache.members?.get(self.botId, message.guildId);
if (!meMember) return; //enable member cache and "Guilds" intent, lol
const appPermissions = await meMember.fetchPermissions();
const permissions = appPermissions.missings(...appPermissions.values([command.botPermissions]));
if (!appPermissions.has('Administrator') && permissions.length) {
return command.onBotPermissionsFail?.(context, appPermissions.keys(permissions));
}
}
const [erroredOptions, result] = await command.__runOptions(context, optionsResolver);
if (erroredOptions) {
return command.onOptionsError?.(context, result);
}
const resultRunGlobalMiddlewares = await command.__runGlobalMiddlewares(context);
if (resultRunGlobalMiddlewares.pass) {
return;
}
if ('error' in resultRunGlobalMiddlewares) {
return command.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error');
}
const resultRunMiddlewares = await command.__runMiddlewares(context);
if (resultRunMiddlewares.pass) {
return;
}
if ('error' in resultRunMiddlewares) {
return command.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error');
}
try {
await command.run?.(context);
await command.onAfterRun?.(context, undefined);
} catch (error) {
await command.onRunError?.(context, error);
await command.onAfterRun?.(context, error);
}
} catch (error) {
try {
await command.onInternalError?.(self, context.command, error);
} catch {
// supress error
}
}
}
export async function defaultOptionsParser(
self: UsingClient,
command: Command | SubCommand,
message: GatewayMessageCreateDispatchData,
args: Partial<Record<string, string>>,
resolved: MakeRequired<ContextOptionsResolved>,
) {
const options: APIApplicationCommandInteractionDataOption[] = [];
const errors: { name: string; error: string; fullError: MessageCommandOptionErrors }[] = [];
for (const i of (command.options ?? []) as (CommandOption & { type: ApplicationCommandOptionType })[]) {
try {
let value: string | boolean | number | undefined;
let indexAttachment = -1;
switch (i.type) {
case ApplicationCommandOptionType.Attachment:
if (message.attachments[++indexAttachment]) {
value = message.attachments[indexAttachment].id;
resolved.attachments[value] = message.attachments[indexAttachment];
}
break;
case ApplicationCommandOptionType.Boolean:
if (args[i.name]) {
value = ['yes', 'y', 'true', 'treu'].includes(args[i.name]!.toLowerCase());
}
break;
case ApplicationCommandOptionType.Channel:
{
const rawId =
message.content.match(/(?<=<#)[0-9]{17,19}(?=>)/g)?.find(x => args[i.name]?.includes(x)) ||
args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
if (rawId) {
const channel =
(await self.cache.channels?.get(rawId)) ?? (i.required ? await self.channels.fetch(rawId) : undefined);
if (channel) {
if ('channel_types' in i) {
if (!(i as SeyfertChannelOption).channel_types!.includes(channel.type)) {
errors.push({
name: i.name,
error: `The entered channel type is not one of ${(i as SeyfertChannelOption)
.channel_types!.map(t => ChannelType[t])
.join(', ')}`,
fullError: ['CHANNEL_TYPES', (i as SeyfertChannelOption).channel_types!],
});
break;
}
}
value = rawId;
resolved.channels[rawId] = channel;
}
}
}
break;
case ApplicationCommandOptionType.Mentionable:
{
const matches = message.content.match(/<@[0-9]{17,19}(?=>)|<@&[0-9]{17,19}(?=>)/g) ?? [];
for (const match of matches) {
if (match.includes('&')) {
const rawId = match.slice(3);
if (rawId) {
const role =
(await self.cache.roles?.get(rawId)) ??
(i.required ? (await self.roles.list(message.guild_id!)).find(x => x.id === rawId) : undefined);
if (role) {
value = rawId;
resolved.roles[rawId] = role;
break;
}
}
} else {
const rawId = match.slice(2);
const raw = message.mentions.find(x => rawId === x.id);
if (raw) {
const { member, ...user } = raw;
value = raw.id;
resolved.users[raw.id] = user;
if (member) resolved.members[raw.id] = member;
break;
}
}
}
}
break;
case ApplicationCommandOptionType.Role:
{
const rawId =
message.mention_roles.find(x => args[i.name]?.includes(x)) || args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
if (rawId) {
const role =
(await self.cache.roles?.get(rawId)) ??
(i.required ? (await self.roles.list(message.guild_id!)).find(x => x.id === rawId) : undefined);
if (role) {
value = rawId;
resolved.roles[rawId] = role;
}
}
}
break;
case ApplicationCommandOptionType.User:
{
const rawId =
message.mentions.find(x => args[i.name]?.includes(x.id))?.id || args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
if (rawId) {
const raw =
message.mentions.find(x => args[i.name]?.includes(x.id)) ??
(await self.cache.users?.get(rawId)) ??
(i.required ? await self.users.fetch(rawId) : undefined);
if (raw) {
value = raw.id;
if (raw instanceof User) {
resolved.users[raw.id] = raw;
if (message.guild_id) {
const member =
message.mentions.find(x => args[i.name]?.includes(x.id))?.member ??
(await self.cache.members?.get(rawId, message.guild_id)) ??
(i.required ? await self.members.fetch(rawId, message.guild_id) : undefined);
if (member) resolved.members[raw.id] = member;
}
} else {
const { member, ...user } = raw;
resolved.users[user.id] = user;
if (member) resolved.members[user.id] = member;
}
}
}
}
break;
case ApplicationCommandOptionType.String:
{
value = args[i.name];
const option = i as SeyfertStringOption;
if (!value) break;
if (option.min_length) {
if (value.length < option.min_length) {
value = undefined;
errors.push({
name: i.name,
error: `The entered string has less than ${option.min_length} characters. The minimum required is ${option.min_length} characters.`,
fullError: ['STRING_MIN_LENGTH', option.min_length],
});
break;
}
}
if (option.max_length) {
if (value.length > option.max_length) {
value = undefined;
errors.push({
name: i.name,
error: `The entered string has more than ${option.max_length} characters. The maximum required is ${option.max_length} characters.`,
fullError: ['STRING_MAX_LENGTH', option.max_length],
});
break;
}
}
if (option.choices?.length) {
const choice = option.choices.find(x => x.name === value);
if (!choice) {
value = undefined;
errors.push({
name: i.name,
error: `The entered choice is invalid. Please choose one of the following options: ${option.choices
.map(x => x.name)
.join(', ')}.`,
fullError: ['STRING_INVALID_CHOICE', option.choices],
});
break;
}
value = choice.value;
}
}
break;
case ApplicationCommandOptionType.Number:
case ApplicationCommandOptionType.Integer:
{
const option = i as SeyfertNumberOption | SeyfertIntegerOption;
if (!option.choices?.length) {
value = Number(args[i.name]);
if (args[i.name] === undefined) {
value = undefined;
break;
}
if (Number.isNaN(value)) {
value = undefined;
errors.push({
name: i.name,
error: 'The entered choice is an invalid number.',
fullError: ['NUMBER_NAN', args[i.name]],
});
break;
}
if (option.min_value) {
if (value < option.min_value) {
value = undefined;
errors.push({
name: i.name,
error: `The entered number is less than ${option.min_value}. The minimum allowed is ${option.min_value}`,
fullError: ['NUMBER_MIN_VALUE', option.min_value],
});
break;
}
}
if (option.max_value) {
if (value > option.max_value) {
value = undefined;
errors.push({
name: i.name,
error: `The entered number is greater than ${option.max_value}. The maximum allowed is ${option.max_value}`,
fullError: ['NUMBER_MAX_VALUE', option.max_value],
});
break;
}
}
break;
}
const choice = option.choices.find(x => x.name === args[i.name]);
if (!choice) {
value = undefined;
errors.push({
name: i.name,
error: `The entered choice is invalid. Please choose one of the following options: ${option.choices
.map(x => x.name)
.join(', ')}.`,
fullError: ['NUMBER_INVALID_CHOICE', option.choices],
});
break;
}
value = choice.value;
}
break;
default:
break;
}
if (value !== undefined) {
options.push({
name: i.name,
type: i.type,
value,
} as APIApplicationCommandInteractionDataOption);
} else if (i.required)
if (!errors.some(x => x.name === i.name))
errors.push({
error: 'Option is required but returned undefined',
name: i.name,
fullError: ['OPTION_REQUIRED'],
});
} catch (e) {
errors.push({
error: e && typeof e === 'object' && 'message' in e ? (e.message as string) : `${e}`,
name: i.name,
fullError: ['UNKNOWN', e],
});
}
}
return { errors, options };
}
export function defaultArgsParser(content: string) {
const args: Record<string, string> = {};
for (const i of content.match(/-(.*?)(?=\s-|$)/gs) ?? []) {
args[i.slice(1).split(' ')[0]] = i.split(' ').slice(1).join(' ');
}
return args;
}
//-(.*?)(?=\s-|$)/gs
//-(?<text>[^-]*)/gm

193
src/client/transformers.ts Normal file
View File

@ -0,0 +1,193 @@
import type { ChannelType } from 'discord-api-types/v10';
import { type CustomStructures, OptionResolver } from '../commands';
import {
AnonymousGuild,
AutoModerationRule,
BaseChannel,
BaseGuildChannel,
CategoryChannel,
ClientUser,
DMChannel,
DirectoryChannel,
ForumChannel,
Guild,
GuildEmoji,
GuildMember,
GuildRole,
GuildTemplate,
InteractionGuildMember,
MediaChannel,
Message,
NewsChannel,
Poll,
StageChannel,
Sticker,
TextGuildChannel,
ThreadChannel,
User,
VoiceChannel,
VoiceState,
Webhook,
WebhookMessage,
} from '../structures';
import type { StructStates } from '../common/types/util';
import { GuildBan } from '../structures/GuildBan';
export type PollStructure = InferCustomStructure<Poll, 'Poll'>;
export type ClientUserStructure = InferCustomStructure<ClientUser, 'ClientUser'>;
export type AnonymousGuildStructure = InferCustomStructure<AnonymousGuild, 'AnonymousGuild'>;
export type AutoModerationRuleStructure = InferCustomStructure<AutoModerationRule, 'AutoModerationRule'>;
export type BaseChannelStructure = InferCustomStructure<BaseChannel<ChannelType>, 'BaseChannel'>;
export type BaseGuildChannelStructure = InferCustomStructure<BaseGuildChannel, 'BaseGuildChannel'>;
export type TextGuildChannelStructure = InferCustomStructure<TextGuildChannel, 'TextGuildChannel'>;
export type DMChannelStructure = InferCustomStructure<DMChannel, 'DMChannel'>;
export type VoiceChannelStructure = InferCustomStructure<VoiceChannel, 'VoiceChannel'>;
export type StageChannelStructure = InferCustomStructure<StageChannel, 'StageChannel'>;
export type MediaChannelStructure = InferCustomStructure<MediaChannel, 'MediaChannel'>;
export type ForumChannelStructure = InferCustomStructure<ForumChannel, 'ForumChannel'>;
export type ThreadChannelStructure = InferCustomStructure<ThreadChannel, 'ThreadChannel'>;
export type CategoryChannelStructure = InferCustomStructure<CategoryChannel, 'CategoryChannel'>;
export type NewsChannelStructure = InferCustomStructure<NewsChannel, 'NewsChannel'>;
export type DirectoryChannelStructure = InferCustomStructure<DirectoryChannel, 'DirectoryChannel'>;
export type GuildStructure<State extends StructStates = 'api'> = InferCustomStructure<Guild<State>, 'Guild'>;
export type GuildBanStructure = InferCustomStructure<GuildBan, 'GuildBan'>;
export type GuildEmojiStructure = InferCustomStructure<GuildEmoji, 'GuildEmoji'>;
export type GuildMemberStructure = InferCustomStructure<GuildMember, 'GuildMember'>;
export type InteractionGuildMemberStructure = InferCustomStructure<InteractionGuildMember, 'InteractionGuildMember'>;
export type GuildRoleStructure = InferCustomStructure<GuildRole, 'GuildRole'>;
export type GuildTemplateStructure = InferCustomStructure<GuildTemplate, 'GuildTemplate'>;
export type MessageStructure = InferCustomStructure<Message, 'Message'>;
export type WebhookMessageStructure = InferCustomStructure<WebhookMessage, 'WebhookMessage'>;
export type StickerStructure = InferCustomStructure<Sticker, 'Sticker'>;
export type UserStructure = InferCustomStructure<User, 'User'>;
export type VoiceStateStructure = InferCustomStructure<VoiceState, 'VoiceState'>;
export type WebhookStructure = InferCustomStructure<Webhook, 'Webhook'>;
export type OptionResolverStructure = InferCustomStructure<OptionResolver, 'OptionResolver'>;
export class Transformers {
static AnonymousGuild(...args: ConstructorParameters<typeof AnonymousGuild>): AnonymousGuildStructure {
return new AnonymousGuild(...args);
}
static AutoModerationRule(...args: ConstructorParameters<typeof AutoModerationRule>): AutoModerationRuleStructure {
return new AutoModerationRule(...args);
}
static BaseChannel(...args: ConstructorParameters<typeof BaseChannel>): BaseChannelStructure {
return new BaseChannel(...args);
}
static BaseGuildChannel(...args: ConstructorParameters<typeof BaseGuildChannel>): BaseGuildChannelStructure {
return new BaseGuildChannel(...args);
}
static TextGuildChannel(...args: ConstructorParameters<typeof TextGuildChannel>): TextGuildChannelStructure {
return new TextGuildChannel(...args);
}
static DMChannel(...args: ConstructorParameters<typeof DMChannel>): DMChannelStructure {
return new DMChannel(...args);
}
static VoiceChannel(...args: ConstructorParameters<typeof VoiceChannel>): VoiceChannelStructure {
return new VoiceChannel(...args);
}
static StageChannel(...args: ConstructorParameters<typeof StageChannel>): StageChannelStructure {
return new StageChannel(...args);
}
static MediaChannel(...args: ConstructorParameters<typeof MediaChannel>): MediaChannelStructure {
return new MediaChannel(...args);
}
static ForumChannel(...args: ConstructorParameters<typeof ForumChannel>): ForumChannelStructure {
return new ForumChannel(...args);
}
static ThreadChannel(...args: ConstructorParameters<typeof ThreadChannel>): ThreadChannelStructure {
return new ThreadChannel(...args);
}
static CategoryChannel(...args: ConstructorParameters<typeof CategoryChannel>): CategoryChannelStructure {
return new CategoryChannel(...args);
}
static NewsChannel(...args: ConstructorParameters<typeof NewsChannel>): NewsChannelStructure {
return new NewsChannel(...args);
}
static DirectoryChannel(...args: ConstructorParameters<typeof DirectoryChannel>): DirectoryChannelStructure {
return new DirectoryChannel(...args);
}
static ClientUser(...args: ConstructorParameters<typeof ClientUser>): ClientUserStructure {
return new ClientUser(...args);
}
static Guild<State extends StructStates = 'api'>(
...args: ConstructorParameters<typeof Guild>
): GuildStructure<State> {
return new Guild<State>(...args);
}
static GuildBan(...args: ConstructorParameters<typeof GuildBan>): GuildBanStructure {
return new GuildBan(...args);
}
static GuildEmoji(...args: ConstructorParameters<typeof GuildEmoji>): GuildEmojiStructure {
return new GuildEmoji(...args);
}
static GuildMember(...args: ConstructorParameters<typeof GuildMember>): GuildMemberStructure {
return new GuildMember(...args);
}
static InteractionGuildMember(
...args: ConstructorParameters<typeof InteractionGuildMember>
): InteractionGuildMemberStructure {
return new InteractionGuildMember(...args);
}
static GuildRole(...args: ConstructorParameters<typeof GuildRole>): GuildRoleStructure {
return new GuildRole(...args);
}
static GuildTemplate(...args: ConstructorParameters<typeof GuildTemplate>): GuildTemplateStructure {
return new GuildTemplate(...args);
}
static Message(...args: ConstructorParameters<typeof Message>): MessageStructure {
return new Message(...args);
}
static WebhookMessage(...args: ConstructorParameters<typeof WebhookMessage>): WebhookMessageStructure {
return new WebhookMessage(...args);
}
static Poll(...args: ConstructorParameters<typeof Poll>): PollStructure {
return new Poll(...args);
}
static Sticker(...args: ConstructorParameters<typeof Sticker>): StickerStructure {
return new Sticker(...args);
}
static User(...args: ConstructorParameters<typeof User>): UserStructure {
return new User(...args);
}
static VoiceState(...args: ConstructorParameters<typeof VoiceState>): VoiceStateStructure {
return new VoiceState(...args);
}
static Webhook(...args: ConstructorParameters<typeof Webhook>): WebhookStructure {
return new Webhook(...args);
}
static OptionResolver(...args: ConstructorParameters<typeof OptionResolver>): OptionResolverStructure {
return new OptionResolver(...args);
}
}
export type InferCustomStructure<T, N extends string> = CustomStructures extends Record<N, infer P> ? P : T;

View File

@ -3,9 +3,8 @@ import { randomUUID } from 'node:crypto';
import { ApiHandler, Logger } from '..'; import { ApiHandler, Logger } from '..';
import type { Cache } from '../cache'; import type { Cache } from '../cache';
import { WorkerAdapter } from '../cache'; import { WorkerAdapter } from '../cache';
import { LogLevels, MergeOptions, lazyLoadPackage, type DeepPartial, type When } from '../common'; import { LogLevels, lazyLoadPackage, type DeepPartial, type When } from '../common';
import { EventHandler } from '../events'; import { EventHandler } from '../events';
import { ClientUser } from '../structures';
import { Shard, properties, type ShardManagerOptions, type WorkerData } from '../websocket'; import { Shard, properties, type ShardManagerOptions, type WorkerData } from '../websocket';
import type { import type {
WorkerReady, WorkerReady,
@ -23,9 +22,9 @@ import type { ManagerMessages } from '../websocket/discord/workermanager';
import type { BaseClientOptions, ServicesOptions, StartOptions } from './base'; import type { BaseClientOptions, ServicesOptions, StartOptions } from './base';
import { BaseClient } from './base'; import { BaseClient } from './base';
import type { Client, ClientOptions } from './client'; import type { Client, ClientOptions } from './client';
import { onInteractionCreate } from './oninteractioncreate';
import { defaultArgsParser, defaultOptionsParser, onMessageCreate } from './onmessagecreate';
import { Collectors } from './collectors'; import { Collectors } from './collectors';
import { type ClientUserStructure, Transformers } from './transformers';
let workerData: WorkerData; let workerData: WorkerData;
let manager: import('node:worker_threads').MessagePort; let manager: import('node:worker_threads').MessagePort;
@ -49,7 +48,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
collectors = new Collectors(); collectors = new Collectors();
events? = new EventHandler(this); events? = new EventHandler(this);
me!: When<Ready, ClientUser>; me!: When<Ready, ClientUserStructure>;
promises = new Map<string, { resolve: (value: any) => void; timeout: NodeJS.Timeout }>(); promises = new Map<string, { resolve: (value: any) => void; timeout: NodeJS.Timeout }>();
shards = new Map<number, Shard>(); shards = new Map<number, Shard>();
@ -58,15 +57,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
constructor(options?: WorkerClientOptions) { constructor(options?: WorkerClientOptions) {
super(options); super(options);
this.options = MergeOptions(
{
commands: {
argsParser: defaultArgsParser,
optionsParser: defaultOptionsParser,
},
} satisfies Partial<WorkerClientOptions>,
this.options,
);
if (!process.env.SEYFERT_SPAWNING) { if (!process.env.SEYFERT_SPAWNING) {
throw new Error('WorkerClient cannot spawn without manager'); throw new Error('WorkerClient cannot spawn without manager');
} }
@ -129,9 +120,6 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
this.events = undefined; this.events = undefined;
} else if (typeof rest.handlers.events === 'function') { } else if (typeof rest.handlers.events === 'function') {
this.events = new EventHandler(this); this.events = new EventHandler(this);
this.events.setHandlers({
callback: rest.handlers.events,
});
} else { } else {
this.events = rest.handlers.events; this.events = rest.handlers.events;
} }
@ -336,7 +324,10 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
} }
protected async onPacket(packet: GatewayDispatchPayload, shardId: number) { protected async onPacket(packet: GatewayDispatchPayload, shardId: number) {
await this.events?.execute('RAW', packet, this as WorkerClient<true>, shardId); Promise.allSettled([
this.events?.runEvent('RAW', this, packet, shardId, false),
this.collectors.run('RAW', packet),
]); //ignore promise
switch (packet.t) { switch (packet.t) {
case 'GUILD_CREATE': { case 'GUILD_CREATE': {
if (this.__handleGuilds?.has(packet.d.id)) { if (this.__handleGuilds?.has(packet.d.id)) {
@ -355,19 +346,19 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
break; break;
} }
default: { default: {
await this.events?.execute(packet.t, packet, this, shardId); await this.events?.execute(packet.t as never, packet, this, shardId);
switch (packet.t) { switch (packet.t) {
case 'READY': case 'READY':
if (!this.__handleGuilds) this.__handleGuilds = new Set();
for (const g of packet.d.guilds) { for (const g of packet.d.guilds) {
this.__handleGuilds?.add(g.id); this.__handleGuilds.add(g.id);
} }
this.botId = packet.d.user.id; this.botId = packet.d.user.id;
this.applicationId = packet.d.application.id; this.applicationId = packet.d.application.id;
this.me = new ClientUser(this, packet.d.user, packet.d.application) as never; this.me = Transformers.ClientUser(this, packet.d.user, packet.d.application) as never;
if ( if (
!( !(
this.__handleGuilds?.size && this.__handleGuilds.size && (workerData.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds
(workerData.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds
) )
) { ) {
if ([...this.shards.values()].every(shard => shard.data.session_id)) { if ([...this.shards.values()].every(shard => shard.data.session_id)) {
@ -382,10 +373,10 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
this.debugger?.debug(`#${shardId} [${packet.d.user.username}](${this.botId}) is online...`); this.debugger?.debug(`#${shardId} [${packet.d.user.username}](${this.botId}) is online...`);
break; break;
case 'INTERACTION_CREATE': case 'INTERACTION_CREATE':
await onInteractionCreate(this, packet.d, shardId); await this.handleCommand.interaction(packet.d, shardId);
break; break;
case 'MESSAGE_CREATE': case 'MESSAGE_CREATE':
await onMessageCreate(this, packet.d, shardId); await this.handleCommand.message(packet.d, shardId);
break; break;
} }
break; break;

View File

@ -17,10 +17,9 @@ import type {
SeyfertStringOption, SeyfertStringOption,
} from '../..'; } from '../..';
import type { Attachment } from '../../builders'; import type { Attachment } from '../../builders';
import { magicImport, type FlatObjectKeys } from '../../common'; import { type Awaitable, magicImport, type FlatObjectKeys } from '../../common';
import type { AllChannels, AutocompleteInteraction, GuildRole, InteractionGuildMember, User } from '../../structures'; import type { AllChannels, AutocompleteInteraction } from '../../structures';
import type { Groups, RegisteredMiddlewares } from '../decorators'; import type { Groups, RegisteredMiddlewares } from '../decorators';
import type { OptionResolver } from '../optionresolver';
import type { CommandContext } from './chatcontext'; import type { CommandContext } from './chatcontext';
import type { import type {
DefaultLocale, DefaultLocale,
@ -33,6 +32,12 @@ import type {
UsingClient, UsingClient,
} from './shared'; } from './shared';
import { inspect } from 'node:util'; import { inspect } from 'node:util';
import type {
GuildRoleStructure,
InteractionGuildMemberStructure,
OptionResolverStructure,
UserStructure,
} from '../../client/transformers';
export interface ReturnOptionsTypes { export interface ReturnOptionsTypes {
1: never; // subcommand 1: never; // subcommand
@ -40,10 +45,10 @@ export interface ReturnOptionsTypes {
3: string; 3: string;
4: number; // integer 4: number; // integer
5: boolean; 5: boolean;
6: InteractionGuildMember | User; 6: InteractionGuildMemberStructure | UserStructure;
7: AllChannels; 7: AllChannels;
8: GuildRole; 8: GuildRoleStructure;
9: GuildRole | AllChannels | User; 9: GuildRoleStructure | AllChannels | UserStructure;
10: number; // number 10: number; // number
11: Attachment; 11: Attachment;
} }
@ -58,7 +63,7 @@ type Wrap<N extends ApplicationCommandOptionType> = N extends
data: { context: CommandContext; value: ReturnOptionsTypes[N] }, data: { context: CommandContext; value: ReturnOptionsTypes[N] },
ok: OKFunction<any>, ok: OKFunction<any>,
fail: StopFunction, fail: StopFunction,
): void; ): Awaitable<void>;
} & { } & {
description: string; description: string;
description_localizations?: APIApplicationCommandBasicOption['description_localizations']; description_localizations?: APIApplicationCommandBasicOption['description_localizations'];
@ -144,7 +149,7 @@ export class BaseCommand {
/** @internal */ /** @internal */
async __runOptions( async __runOptions(
ctx: CommandContext<{}, never>, ctx: CommandContext<{}, never>,
resolver: OptionResolver, resolver: OptionResolverStructure,
): Promise<[boolean, OnOptionsReturnObject]> { ): Promise<[boolean, OnOptionsReturnObject]> {
if (!this?.options?.length) { if (!this?.options?.length) {
return [false, {}]; return [false, {}];

View File

@ -1,19 +1,21 @@
import { MessageFlags } from 'discord-api-types/v10'; import { MessageFlags } from 'discord-api-types/v10';
import type { AllChannels, Guild, InferWithPrefix, ReturnCache, WebhookMessage } from '../..'; import type { AllChannels, InferWithPrefix, Message, ReturnCache } from '../..';
import type { Client, WorkerClient } from '../../client'; import type { Client, WorkerClient } from '../../client';
import type { If, UnionToTuple, When } from '../../common'; import type { If, UnionToTuple, When } from '../../common';
import type { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest } from '../../common/types/write'; import type { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest } from '../../common/types/write';
import { import { ChatInputCommandInteraction } from '../../structures';
Message,
type ChatInputCommandInteraction,
type GuildMember,
type InteractionGuildMember,
} from '../../structures';
import { BaseContext } from '../basecontext'; import { BaseContext } from '../basecontext';
import type { RegisteredMiddlewares } from '../decorators'; import type { RegisteredMiddlewares } from '../decorators';
import type { OptionResolver } from '../optionresolver';
import type { Command, ContextOptions, OptionsRecord, SubCommand } from './chat'; import type { Command, ContextOptions, OptionsRecord, SubCommand } from './chat';
import type { CommandMetadata, ExtendContext, GlobalMetadata, UsingClient } from './shared'; import type { CommandMetadata, ExtendContext, GlobalMetadata, UsingClient } from './shared';
import type {
GuildMemberStructure,
GuildStructure,
InteractionGuildMemberStructure,
MessageStructure,
OptionResolverStructure,
WebhookMessageStructure,
} from '../../client/transformers';
export interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never> export interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
extends BaseContext, extends BaseContext,
@ -23,22 +25,22 @@ export class CommandContext<
T extends OptionsRecord = {}, T extends OptionsRecord = {},
M extends keyof RegisteredMiddlewares = never, M extends keyof RegisteredMiddlewares = never,
> extends BaseContext { > extends BaseContext {
message!: If<InferWithPrefix, Message | undefined, undefined>; message!: If<InferWithPrefix, MessageStructure | undefined, undefined>;
interaction!: If<InferWithPrefix, ChatInputCommandInteraction | undefined, ChatInputCommandInteraction>; interaction!: If<InferWithPrefix, ChatInputCommandInteraction | undefined, ChatInputCommandInteraction>;
messageResponse?: If<InferWithPrefix, Message | undefined>; messageResponse?: If<InferWithPrefix, MessageStructure | undefined>;
constructor( constructor(
readonly client: UsingClient, readonly client: UsingClient,
data: ChatInputCommandInteraction | Message, data: ChatInputCommandInteraction | MessageStructure,
readonly resolver: OptionResolver, readonly resolver: OptionResolverStructure,
readonly shardId: number, readonly shardId: number,
readonly command: Command | SubCommand, readonly command: Command | SubCommand,
) { ) {
super(client); super(client);
if (data instanceof Message) { if (data instanceof ChatInputCommandInteraction) {
this.message = data as never;
} else {
this.interaction = data; this.interaction = data;
} else {
this.message = data as never;
} }
} }
@ -61,7 +63,7 @@ export class CommandContext<
async write<FR extends boolean = false>( async write<FR extends boolean = false>(
body: InteractionCreateBodyRequest, body: InteractionCreateBodyRequest,
fetchReply?: FR, fetchReply?: FR,
): Promise<When<FR, WebhookMessage | Message, void | WebhookMessage | Message>> { ): Promise<When<FR, WebhookMessageStructure | MessageStructure, void | WebhookMessageStructure | MessageStructure>> {
if (this.interaction) return this.interaction.write(body, fetchReply); if (this.interaction) return this.interaction.write(body, fetchReply);
const options = (this.client as Client | WorkerClient).options?.commands; const options = (this.client as Client | WorkerClient).options?.commands;
return (this.messageResponse = await (this.message! as Message)[ return (this.messageResponse = await (this.message! as Message)[
@ -90,7 +92,7 @@ export class CommandContext<
editOrReply<FR extends boolean = false>( editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest,
fetchReply?: FR, fetchReply?: FR,
): Promise<When<FR, WebhookMessage | Message, void | WebhookMessage | Message>> { ): Promise<When<FR, WebhookMessageStructure | MessageStructure, void | WebhookMessageStructure | MessageStructure>> {
if (this.interaction) return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); if (this.interaction) return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply);
if (this.messageResponse) { if (this.messageResponse) {
return this.editResponse(body); return this.editResponse(body);
@ -99,7 +101,7 @@ export class CommandContext<
} }
async fetchResponse(): Promise< async fetchResponse(): Promise<
If<InferWithPrefix, WebhookMessage | Message | undefined, WebhookMessage | undefined> If<InferWithPrefix, WebhookMessageStructure | MessageStructure | undefined, WebhookMessageStructure | undefined>
> { > {
if (this.interaction) return this.interaction.fetchResponse(); if (this.interaction) return this.interaction.fetchResponse();
this.messageResponse = await this.messageResponse?.fetch(); this.messageResponse = await this.messageResponse?.fetch();
@ -122,8 +124,8 @@ export class CommandContext<
} }
} }
me(mode?: 'rest' | 'flow'): Promise<GuildMember>; me(mode?: 'rest' | 'flow'): Promise<GuildMemberStructure>;
me(mode?: 'cache'): ReturnCache<GuildMember | undefined>; me(mode?: 'cache'): ReturnCache<GuildMemberStructure | undefined>;
me(mode: 'cache' | 'rest' | 'flow' = 'cache') { me(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId) if (!this.guildId)
return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve(); return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve();
@ -138,8 +140,8 @@ export class CommandContext<
} }
} }
guild(mode?: 'rest' | 'flow'): Promise<Guild<'cached' | 'api'> | undefined>; guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'> | undefined>;
guild(mode?: 'cache'): ReturnCache<Guild<'cached'> | undefined>; guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'cache') { guild(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId) if (!this.guildId)
return (mode === 'cache' return (mode === 'cache'
@ -159,23 +161,23 @@ export class CommandContext<
} }
get guildId() { get guildId() {
return this.interaction?.guildId || (this.message! as Message | undefined)?.guildId; return this.interaction?.guildId || (this.message! as MessageStructure | undefined)?.guildId;
} }
get channelId() { get channelId() {
return this.interaction?.channelId || (this.message! as Message).channelId; return this.interaction?.channelId || (this.message! as MessageStructure).channelId;
} }
get author() { get author() {
return this.interaction?.user || (this.message! as Message).author; return this.interaction?.user || (this.message! as MessageStructure).author;
} }
get member(): If< get member(): If<
InferWithPrefix, InferWithPrefix,
GuildMember | InteractionGuildMember | undefined, GuildMemberStructure | InteractionGuildMemberStructure | undefined,
InteractionGuildMember | undefined InteractionGuildMemberStructure | undefined
> { > {
return this.interaction?.member || ((this.message! as Message)?.member as any); return this.interaction?.member || ((this.message! as MessageStructure)?.member as any);
} }
isChat(): this is CommandContext { isChat(): this is CommandContext {

View File

@ -64,10 +64,10 @@ export abstract class ContextMenuCommand {
onBotPermissionsFail(context: MenuCommandContext<any, never>, permissions: PermissionStrings): any { onBotPermissionsFail(context: MenuCommandContext<any, never>, permissions: PermissionStrings): any {
context.client.logger.fatal(`${this.name}.<onBotPermissionsFail>`, context.author.id, permissions); context.client.logger.fatal(`${this.name}.<onBotPermissionsFail>`, context.author.id, permissions);
} }
onPermissionsFail(context: MenuCommandContext<any, never>, permissions: PermissionStrings): any { // onPermissionsFail(context: MenuCommandContext<any, never>, permissions: PermissionStrings): any {
context.client.logger.fatal(`${this.name}.<onPermissionsFail>`, context.author.id, permissions); // context.client.logger.fatal(`${this.name}.<onPermissionsFail>`, context.author.id, permissions);
} // }
onInternalError(client: UsingClient, error?: unknown): any { onInternalError(client: UsingClient, command: ContextMenuCommand, error?: unknown): any {
client.logger.fatal(error); client.logger.fatal(command.name, error);
} }
} }

View File

@ -1,5 +1,5 @@
import { type APIMessage, ApplicationCommandType, MessageFlags } from 'discord-api-types/v10'; import { type APIMessage, ApplicationCommandType, MessageFlags } from 'discord-api-types/v10';
import type { ContextMenuCommand, ReturnCache, WebhookMessage } from '../..'; import type { ContextMenuCommand, ReturnCache } from '../..';
import { import {
toSnakeCase, toSnakeCase,
type InteractionCreateBodyRequest, type InteractionCreateBodyRequest,
@ -8,20 +8,20 @@ import {
type UnionToTuple, type UnionToTuple,
type When, type When,
} from '../../common'; } from '../../common';
import { import type { AllChannels, MessageCommandInteraction, UserCommandInteraction } from '../../structures';
Message,
User,
type AllChannels,
type Guild,
type GuildMember,
type MessageCommandInteraction,
type UserCommandInteraction,
} from '../../structures';
import { BaseContext } from '../basecontext'; import { BaseContext } from '../basecontext';
import type { RegisteredMiddlewares } from '../decorators'; import type { RegisteredMiddlewares } from '../decorators';
import type { CommandMetadata, ExtendContext, GlobalMetadata, UsingClient } from './shared'; import type { CommandMetadata, ExtendContext, GlobalMetadata, UsingClient } from './shared';
import {
type GuildMemberStructure,
type GuildStructure,
type MessageStructure,
Transformers,
type UserStructure,
type WebhookMessageStructure,
} from '../../client/transformers';
export type InteractionTarget<T> = T extends MessageCommandInteraction ? Message : User; export type InteractionTarget<T> = T extends MessageCommandInteraction ? MessageStructure : UserStructure;
export interface MenuCommandContext< export interface MenuCommandContext<
T extends MessageCommandInteraction | UserCommandInteraction, T extends MessageCommandInteraction | UserCommandInteraction,
@ -50,11 +50,11 @@ export class MenuCommandContext<
switch (this.interaction.data.type) { switch (this.interaction.data.type) {
case ApplicationCommandType.Message: { case ApplicationCommandType.Message: {
const data = this.interaction.data.resolved.messages[this.interaction.data.targetId as Lowercase<string>]; const data = this.interaction.data.resolved.messages[this.interaction.data.targetId as Lowercase<string>];
return new Message(this.client, toSnakeCase(data) as APIMessage) as never; return Transformers.Message(this.client, toSnakeCase(data) as APIMessage) as never;
} }
case ApplicationCommandType.User: { case ApplicationCommandType.User: {
const data = this.interaction.data.resolved.users[this.interaction.data.targetId as Lowercase<string>]; const data = this.interaction.data.resolved.users[this.interaction.data.targetId as Lowercase<string>];
return new User(this.client, toSnakeCase(data)) as never; return Transformers.User(this.client, toSnakeCase(data)) as never;
} }
} }
} }
@ -70,7 +70,7 @@ export class MenuCommandContext<
write<FR extends boolean = false>( write<FR extends boolean = false>(
body: InteractionCreateBodyRequest, body: InteractionCreateBodyRequest,
fetchReply?: FR, fetchReply?: FR,
): Promise<When<FR, WebhookMessage, void | WebhookMessage>> { ): Promise<When<FR, WebhookMessageStructure, void | WebhookMessageStructure>> {
return this.interaction.write(body, fetchReply); return this.interaction.write(body, fetchReply);
} }
@ -93,7 +93,7 @@ export class MenuCommandContext<
editOrReply<FR extends boolean = false>( editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest,
fetchReply?: FR, fetchReply?: FR,
): Promise<When<FR, WebhookMessage | Message, void | WebhookMessage | Message>> { ): Promise<When<FR, WebhookMessageStructure | MessageStructure, void | WebhookMessageStructure | MessageStructure>> {
return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply);
} }
@ -109,8 +109,8 @@ export class MenuCommandContext<
return this.client.channels.fetch(this.channelId, mode === 'rest'); return this.client.channels.fetch(this.channelId, mode === 'rest');
} }
me(mode?: 'rest' | 'flow'): Promise<GuildMember>; me(mode?: 'rest' | 'flow'): Promise<GuildMemberStructure>;
me(mode?: 'cache'): ReturnCache<GuildMember | undefined>; me(mode?: 'cache'): ReturnCache<GuildMemberStructure | undefined>;
me(mode: 'cache' | 'rest' | 'flow' = 'cache') { me(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId) if (!this.guildId)
return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve(); return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve();
@ -122,8 +122,8 @@ export class MenuCommandContext<
} }
} }
guild(mode?: 'rest' | 'flow'): Promise<Guild<'cached' | 'api'> | undefined>; guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'> | undefined>;
guild(mode?: 'cache'): ReturnCache<Guild<'cached'> | undefined>; guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'cache') { guild(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId) if (!this.guildId)
return ( return (
@ -158,10 +158,10 @@ export class MenuCommandContext<
} }
isMenuUser(): this is MenuCommandContext<UserCommandInteraction> { isMenuUser(): this is MenuCommandContext<UserCommandInteraction> {
return this.target instanceof User; return this.interaction.data.type === ApplicationCommandType.User;
} }
isMenuMessage(): this is MenuCommandContext<MessageCommandInteraction> { isMenuMessage(): this is MenuCommandContext<MessageCommandInteraction> {
return this.target instanceof Message; return this.interaction.data.type === ApplicationCommandType.Message;
} }
} }

View File

@ -17,6 +17,7 @@ export interface ExtraProps {}
export interface UsingClient extends BaseClient {} export interface UsingClient extends BaseClient {}
export type ParseClient<T extends BaseClient> = T; export type ParseClient<T extends BaseClient> = T;
export interface InternalOptions {} export interface InternalOptions {}
export interface CustomStructures {}
export type MiddlewareContext<T = any, C = any> = (context: { export type MiddlewareContext<T = any, C = any> = (context: {
context: C; context: C;

806
src/commands/handle.ts Normal file
View File

@ -0,0 +1,806 @@
import {
type APIApplicationCommandInteraction,
ApplicationCommandType,
InteractionType,
type APIInteraction,
type GatewayMessageCreateDispatchData,
InteractionContextType,
type APIApplicationCommandInteractionDataOption,
ApplicationCommandOptionType,
ChannelType,
type APIInteractionDataResolvedChannel,
} from 'discord-api-types/v10';
import {
Command,
type ContextOptionsResolved,
type UsingClient,
type CommandAutocompleteOption,
type ContextMenuCommand,
MenuCommandContext,
BaseCommand,
CommandContext,
type RegisteredMiddlewares,
SubCommand,
IgnoreCommand,
type CommandOption,
type MessageCommandOptionErrors,
type SeyfertChannelOption,
type SeyfertIntegerOption,
type SeyfertNumberOption,
type SeyfertStringOption,
} from '.';
import {
AutocompleteInteraction,
BaseInteraction,
type ComponentInteraction,
type ModalSubmitInteraction,
type ChatInputCommandInteraction,
type MessageCommandInteraction,
type UserCommandInteraction,
type __InternalReplyFunction,
} from '../structures';
import type { PermissionsBitField } from '../structures/extra/Permissions';
import { ComponentContext, ModalContext } from '../components';
import type { Client, WorkerClient } from '../client';
import type { MakeRequired } from '../common';
import { type MessageStructure, Transformers, type OptionResolverStructure } from '../client/transformers';
export type CommandOptionWithType = CommandOption & {
type: ApplicationCommandOptionType;
};
export interface CommandFromContent {
command?: Command | SubCommand;
parent?: Command;
fullCommandName: string;
}
export class HandleCommand {
constructor(public client: UsingClient) {}
async autocomplete(
interaction: AutocompleteInteraction,
optionsResolver: OptionResolverStructure,
command?: CommandAutocompleteOption,
) {
// idc, is a YOU problem
if (!command?.autocomplete) {
return this.client.logger.warn(
`${optionsResolver.fullCommandName} ${command?.name} command does not have 'autocomplete' callback`,
);
}
try {
try {
try {
await command.autocomplete(interaction);
} catch (error) {
if (!command.onAutocompleteError)
return this.client.logger.error(
`${optionsResolver.fullCommandName} ${command.name} just threw an error, ${
error ? (typeof error === 'object' && 'message' in error ? error.message : error) : 'Unknown'
}`,
);
await command.onAutocompleteError(interaction, error);
}
} catch (error) {
await optionsResolver.getCommand()?.onInternalError?.(this.client, optionsResolver.getCommand()!, error);
}
} catch (error) {
// pass
}
}
async contextMenuMessage(
command: ContextMenuCommand,
interaction: MessageCommandInteraction,
context: MenuCommandContext<MessageCommandInteraction>,
) {
// @ts-expect-error
return this.contextMenuUser(command, interaction, context);
}
async contextMenuUser(
command: ContextMenuCommand,
interaction: UserCommandInteraction,
context: MenuCommandContext<UserCommandInteraction>,
) {
if (command.botPermissions && interaction.appPermissions) {
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
if (permissions) return command.onBotPermissionsFail(context, permissions);
}
const resultGlobal = await this.runGlobalMiddlewares(command, context);
if (typeof resultGlobal === 'boolean') return;
const resultMiddle = await this.runMiddlewares(command, context);
if (typeof resultMiddle === 'boolean') return;
try {
try {
await command.run!(context);
await command.onAfterRun?.(context, undefined);
} catch (error) {
await command.onRunError(context, error);
await command.onAfterRun?.(context, error);
}
} catch (error) {
try {
await command.onInternalError(this.client, command, error);
} catch {
// pass
}
}
}
async chatInput(
command: Command | SubCommand,
interaction: ChatInputCommandInteraction,
resolver: OptionResolverStructure,
context: CommandContext,
) {
if (command.botPermissions && interaction.appPermissions) {
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
if (permissions) return command.onBotPermissionsFail?.(context, permissions);
}
if (!(await this.runOptions(command, context, resolver))) return;
const resultGlobal = await this.runGlobalMiddlewares(command, context);
if (typeof resultGlobal === 'boolean') return;
const resultMiddle = await this.runMiddlewares(command, context);
if (typeof resultMiddle === 'boolean') return;
try {
try {
await command.run!(context);
await command.onAfterRun?.(context, undefined);
} catch (error) {
await command.onRunError?.(context, error);
await command.onAfterRun?.(context, error);
}
} catch (error) {
try {
await command.onInternalError?.(this.client, command, error);
} catch {
// pass
}
}
}
async modal(interaction: ModalSubmitInteraction) {
const context = new ModalContext(this.client, interaction);
const extended = this.client.options?.context?.(interaction) ?? {};
Object.assign(context, extended);
await this.client.components?.executeModal(context);
}
async messageComponent(interaction: ComponentInteraction) {
//@ts-expect-error
const context = new ComponentContext(this.client, interaction);
const extended = this.client.options?.context?.(interaction) ?? {};
Object.assign(context, extended);
await this.client.components?.executeComponent(context);
}
async interaction(body: APIInteraction, shardId: number, __reply?: __InternalReplyFunction) {
this.client.debugger?.debug(`[${InteractionType[body.type] ?? body.type}] Interaction received.`);
switch (body.type) {
case InteractionType.ApplicationCommandAutocomplete:
{
const optionsResolver = this.makeResolver(
this.client,
body.data.options ?? [],
this.getCommand<Command>(body.data),
body.guild_id,
body.data.resolved as ContextOptionsResolved,
);
const interaction = new AutocompleteInteraction(this.client, body, optionsResolver, __reply);
const command = optionsResolver.getAutocomplete();
await this.autocomplete(interaction, optionsResolver, command);
}
break;
case InteractionType.ApplicationCommand: {
switch (body.data.type) {
case ApplicationCommandType.Message: {
const data = this.makeMenuCommand(body, shardId, __reply);
if (!data) return;
// @ts-expect-error
this.contextMenuMessage(data.command, data.interaction, data.context);
break;
}
case ApplicationCommandType.User: {
const data = this.makeMenuCommand(body, shardId, __reply);
if (!data) return;
// @ts-expect-error
this.contextMenuUser(data.command, data.interaction, data.context);
break;
}
case ApplicationCommandType.ChatInput: {
const parentCommand = this.getCommand<Command>(body.data);
const optionsResolver = this.makeResolver(
this.client,
body.data.options ?? [],
parentCommand,
body.guild_id,
body.data.resolved as ContextOptionsResolved,
);
const interaction = BaseInteraction.from(this.client, body, __reply) as ChatInputCommandInteraction;
const command = optionsResolver.getCommand();
if (!command?.run)
return this.client.logger.warn(`${optionsResolver.fullCommandName} command does not have 'run' callback`);
const context = new CommandContext(this.client, interaction, optionsResolver, shardId, command);
const extendContext = this.client.options?.context?.(interaction) ?? {};
Object.assign(context, extendContext);
await this.chatInput(command, interaction, optionsResolver, context);
break;
}
}
break;
}
case InteractionType.ModalSubmit:
{
const interaction = BaseInteraction.from(this.client, body, __reply) as ModalSubmitInteraction;
if (this.client.components?.hasModal(interaction)) {
await this.client.components.onModalSubmit(interaction);
} else await this.modal(interaction);
}
break;
case InteractionType.MessageComponent:
{
const interaction = BaseInteraction.from(this.client, body, __reply) as ComponentInteraction;
if (this.client.components?.hasComponent(body.message.id, interaction.customId)) {
await this.client.components.onComponent(body.message.id, interaction);
} else await this.messageComponent(interaction);
}
break;
}
}
async message(rawMessage: GatewayMessageCreateDispatchData, shardId: number) {
const self = this.client as Client | WorkerClient;
if (!self.options.commands?.prefix) return;
const message = Transformers.Message(this.client, rawMessage);
const prefixes = (await self.options.commands.prefix(message)).sort((a, b) => b.length - a.length);
const prefix = prefixes.find(x => rawMessage.content.startsWith(x));
if (!(prefix !== undefined && rawMessage.content.startsWith(prefix))) return;
const content = rawMessage.content.slice(prefix.length).trimStart();
const { fullCommandName, command, parent, argsContent } = this.resolveCommandFromContent(
content,
prefix,
rawMessage,
);
if (!command || argsContent === undefined) return;
if (!command.run) return self.logger.warn(`${fullCommandName} command does not have 'run' callback`);
if (!(command.contexts.includes(InteractionContextType.BotDM) || rawMessage.guild_id)) return;
if (!command.contexts.includes(InteractionContextType.Guild) && rawMessage.guild_id) return;
if (command.guildId && !command.guildId?.includes(rawMessage.guild_id!)) return;
const resolved: MakeRequired<ContextOptionsResolved> = {
channels: {},
roles: {},
users: {},
members: {},
attachments: {},
};
const args = this.argsParser(argsContent, command, message);
const { options, errors } = await this.argsOptionsParser(command, rawMessage, args, resolved);
const optionsResolver = this.makeResolver(self, options, parent as Command, rawMessage.guild_id, resolved);
const context = new CommandContext(self, message, optionsResolver, shardId, command);
//@ts-expect-error
const extendContext = self.options?.context?.(message) ?? {};
Object.assign(context, extendContext);
try {
if (errors.length) {
return command.onOptionsError?.(
context,
Object.fromEntries(
errors.map(x => {
return [
x.name,
{
failed: true,
value: x.error,
parseError: x.fullError,
},
];
}),
),
);
}
if (command.defaultMemberPermissions && rawMessage.guild_id) {
const memberPermissions = await self.members.permissions(rawMessage.guild_id, rawMessage.author.id);
const permissions = this.checkPermissions(memberPermissions, command.defaultMemberPermissions);
const guild = await this.client.guilds.raw(rawMessage.guild_id);
if (permissions && guild.owner_id !== rawMessage.author.id) {
return command.onPermissionsFail?.(context, memberPermissions.keys(permissions));
}
}
if (command.botPermissions && rawMessage.guild_id) {
const meMember = await self.cache.members?.get(self.botId, rawMessage.guild_id);
if (!meMember) return; //enable member cache and "Guilds" intent, lol
const appPermissions = await meMember.fetchPermissions();
const permissions = this.checkPermissions(appPermissions, command.botPermissions);
if (!appPermissions.has('Administrator') && permissions) {
return command.onBotPermissionsFail?.(context, permissions);
}
}
if (!(await this.runOptions(command, context, optionsResolver))) return;
const resultGlobal = await this.runGlobalMiddlewares(command, context);
if (typeof resultGlobal === 'boolean') return;
const resultMiddle = await this.runMiddlewares(command, context);
if (typeof resultMiddle === 'boolean') return;
try {
await command.run!(context);
await command.onAfterRun?.(context, undefined);
} catch (error) {
await command.onRunError?.(context, error);
await command.onAfterRun?.(context, error);
}
} catch (error) {
try {
await command.onInternalError?.(this.client, command, error);
} catch {}
}
}
argsParser(content: string, _command: SubCommand | Command, _message: MessageStructure): Record<string, string> {
const args: Record<string, string> = {};
for (const i of content.match(/-(.*?)(?=\s-|$)/gs) ?? []) {
args[i.slice(1).split(' ')[0]] = i.split(' ').slice(1).join(' ');
}
return args;
}
resolveCommandFromContent(
content: string,
_prefix: string,
_message: GatewayMessageCreateDispatchData,
): CommandFromContent & { argsContent?: string } {
const result = this.getCommandFromContent(
content
.split(' ')
.filter(x => x)
.slice(0, 3),
);
if (!result.command) return result;
let newContent = content;
for (const i of result.fullCommandName.split(' ')) {
newContent = newContent.slice(newContent.indexOf(i) + i.length);
}
return {
...result,
argsContent: newContent.slice(1),
};
}
getCommandFromContent(commandRaw: string[]): CommandFromContent {
const rawParentName = commandRaw[0];
const rawGroupName = commandRaw.length === 3 ? commandRaw[1] : undefined;
const rawSubcommandName = rawGroupName ? commandRaw[2] : commandRaw[1];
const parent = this.getParentMessageCommand(rawParentName);
const fullCommandName = `${rawParentName}${
rawGroupName ? ` ${rawGroupName} ${rawSubcommandName}` : `${rawSubcommandName ? ` ${rawSubcommandName}` : ''}`
}`;
if (!(parent instanceof Command)) return { fullCommandName };
if (rawGroupName && !parent.groups?.[rawGroupName] && !parent.groupsAliases?.[rawGroupName])
return this.getCommandFromContent([rawParentName, rawGroupName]);
if (
rawSubcommandName &&
!parent.options?.some(
x => x instanceof SubCommand && (x.name === rawSubcommandName || x.aliases?.includes(rawSubcommandName)),
)
)
return this.getCommandFromContent([rawParentName]);
const groupName = rawGroupName ? parent.groupsAliases?.[rawGroupName] || rawGroupName : undefined;
const command =
groupName || rawSubcommandName
? (parent.options?.find(opt => {
if (opt instanceof SubCommand) {
if (groupName) {
if (opt.group !== groupName) return false;
}
if (opt.group && !groupName) return false;
return rawSubcommandName === opt.name || opt.aliases?.includes(rawSubcommandName);
}
return false;
}) as SubCommand)
: parent;
return {
command,
fullCommandName,
parent,
};
}
makeResolver(...args: Parameters<(typeof Transformers)['OptionResolver']>) {
return Transformers.OptionResolver(...args);
}
getParentMessageCommand(rawParentName: string) {
return this.client.commands!.values.find(
x =>
(!('ignore' in x) || x.ignore !== IgnoreCommand.Message) &&
(x.name === rawParentName || ('aliases' in x ? x.aliases?.includes(rawParentName) : false)),
);
}
getCommand<T extends Command | ContextMenuCommand>(data: {
guild_id?: string;
name: string;
}): T | undefined {
return this.client.commands!.values.find(command => {
if (data.guild_id) {
return command.guildId?.includes(data.guild_id) && command.name === data.name;
}
return command.name === data.name;
}) as T;
}
checkPermissions(app: PermissionsBitField, bot: bigint) {
const permissions = app.missings(...app.values([bot]));
if (!app.has('Administrator') && permissions.length) {
return app.keys(permissions);
}
return false;
}
async fetchChannel(_option: CommandOptionWithType, id: string) {
return this.client.channels.raw(id);
}
async fetchUser(_option: CommandOptionWithType, id: string) {
return this.client.users.raw(id);
}
async fetchMember(_option: CommandOptionWithType, id: string, guildId: string) {
return this.client.members.raw(guildId, id);
}
async fetchRole(_option: CommandOptionWithType, id: string, guildId?: string) {
return guildId ? (await this.client.roles.listRaw(guildId)).find(x => x.id === id) : undefined;
}
async runGlobalMiddlewares(
command: Command | ContextMenuCommand | SubCommand,
context: CommandContext<{}, never> | MenuCommandContext<any>,
) {
try {
const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares(
context,
(this.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares,
true,
);
if (resultRunGlobalMiddlewares.pass) {
return false;
}
if ('error' in resultRunGlobalMiddlewares) {
await command.onMiddlewaresError?.(context as never, resultRunGlobalMiddlewares.error ?? 'Unknown error');
return;
}
return resultRunGlobalMiddlewares;
} catch (e) {
try {
await command.onInternalError?.(this.client, command as never, e);
} catch {}
}
return false;
}
async runMiddlewares(
command: Command | ContextMenuCommand | SubCommand,
context: CommandContext<{}, never> | MenuCommandContext<any>,
) {
try {
const resultRunMiddlewares = await BaseCommand.__runMiddlewares(
context,
command.middlewares as keyof RegisteredMiddlewares,
false,
);
if (resultRunMiddlewares.pass) {
return false;
}
if ('error' in resultRunMiddlewares) {
await command.onMiddlewaresError?.(context as never, resultRunMiddlewares.error ?? 'Unknown error');
return;
}
return resultRunMiddlewares;
} catch (e) {
try {
await command.onInternalError?.(this.client, command as never, e);
} catch {}
}
return false;
}
makeMenuCommand(body: APIApplicationCommandInteraction, shardId: number, __reply?: __InternalReplyFunction) {
const command = this.getCommand<ContextMenuCommand>(body.data);
const interaction = BaseInteraction.from(this.client, body, __reply) as
| UserCommandInteraction
| MessageCommandInteraction;
// idc, is a YOU problem
if (!command?.run)
return this.client.logger.warn(`${command?.name ?? 'Unknown'} command does not have 'run' callback`);
const context = new MenuCommandContext(this.client, interaction, shardId, command);
const extendContext = this.client.options?.context?.(interaction) ?? {};
Object.assign(context, extendContext);
return { command, interaction, context };
}
async runOptions(command: Command | SubCommand, context: CommandContext, resolver: OptionResolverStructure) {
const [erroredOptions, result] = await command.__runOptions(context, resolver);
if (erroredOptions) {
try {
await command.onOptionsError?.(context, result);
} catch (e) {
try {
await command.onInternalError?.(this.client, command, e);
} catch {}
}
return false;
}
return true;
}
async argsOptionsParser(
command: Command | SubCommand,
message: GatewayMessageCreateDispatchData,
args: Partial<Record<string, string>>,
resolved: MakeRequired<ContextOptionsResolved>,
) {
const options: APIApplicationCommandInteractionDataOption[] = [];
const errors: { name: string; error: string; fullError: MessageCommandOptionErrors }[] = [];
for (const i of (command.options ?? []) as (CommandOption & { type: ApplicationCommandOptionType })[]) {
try {
let value: string | boolean | number | undefined;
let indexAttachment = -1;
switch (i.type) {
case ApplicationCommandOptionType.Attachment:
if (message.attachments[++indexAttachment]) {
value = message.attachments[indexAttachment].id;
resolved.attachments[value] = message.attachments[indexAttachment];
}
break;
case ApplicationCommandOptionType.Boolean:
if (args[i.name]) {
value = ['yes', 'y', 'true', 'treu'].includes(args[i.name]!.toLowerCase());
}
break;
case ApplicationCommandOptionType.Channel:
{
const rawId =
message.content.match(/(?<=<#)[0-9]{17,19}(?=>)/g)?.find(x => args[i.name]?.includes(x)) ||
args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
if (!rawId) continue;
const channel = (await this.client.cache.channels?.raw(rawId)) ?? (await this.fetchChannel(i, rawId));
if (channel) {
if ('channel_types' in i) {
if (!(i as SeyfertChannelOption).channel_types!.includes(channel.type)) {
errors.push({
name: i.name,
error: `The entered channel type is not one of ${(i as SeyfertChannelOption)
.channel_types!.map(t => ChannelType[t])
.join(', ')}`,
fullError: ['CHANNEL_TYPES', (i as SeyfertChannelOption).channel_types!],
});
break;
}
}
value = rawId;
//discord funny memoentnt!!!!!!!!
resolved.channels[rawId] = channel as APIInteractionDataResolvedChannel;
}
}
break;
case ApplicationCommandOptionType.Mentionable:
{
const matches = message.content.match(/<@[0-9]{17,19}(?=>)|<@&[0-9]{17,19}(?=>)/g) ?? [];
for (const match of matches) {
if (match.includes('&')) {
const rawId = match.slice(3);
if (rawId) {
const role =
(await this.client.cache.roles?.raw(rawId)) ?? (await this.fetchRole(i, rawId, message.guild_id));
if (role) {
value = rawId;
resolved.roles[rawId] = role;
break;
}
}
} else {
const rawId = match.slice(2);
const raw = message.mentions.find(x => rawId === x.id);
if (raw) {
const { member, ...user } = raw;
value = raw.id;
resolved.users[raw.id] = user;
if (member) resolved.members[raw.id] = member;
break;
}
}
}
}
break;
case ApplicationCommandOptionType.Role:
{
const rawId =
message.mention_roles.find(x => args[i.name]?.includes(x)) || args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
if (!rawId) continue;
const role =
(await this.client.cache.roles?.raw(rawId)) ?? (await this.fetchRole(i, rawId, message.guild_id));
if (role) {
value = rawId;
resolved.roles[rawId] = role;
}
}
break;
case ApplicationCommandOptionType.User:
{
const rawId =
message.mentions.find(x => args[i.name]?.includes(x.id))?.id ||
args[i.name]?.match(/[0-9]{17,19}/g)?.[0];
if (!rawId) continue;
const raw =
message.mentions.find(x => args[i.name]?.includes(x.id)) ??
(await this.client.cache.users?.raw(rawId)) ??
(await this.fetchUser(i, rawId));
if (raw) {
value = raw.id;
resolved.users[raw.id] = raw;
if (message.guild_id) {
const member =
message.mentions.find(x => args[i.name]?.includes(x.id))?.member ??
(await this.client.cache.members?.raw(rawId, message.guild_id)) ??
(await this.fetchMember(i, rawId, message.guild_id));
if (member) resolved.members[raw.id] = member;
}
}
}
break;
case ApplicationCommandOptionType.String:
{
value = args[i.name];
const option = i as SeyfertStringOption;
if (!value) break;
if (option.min_length) {
if (value.length < option.min_length) {
value = undefined;
errors.push({
name: i.name,
error: `The entered string has less than ${option.min_length} characters. The minimum required is ${option.min_length} characters.`,
fullError: ['STRING_MIN_LENGTH', option.min_length],
});
break;
}
}
if (option.max_length) {
if (value.length > option.max_length) {
value = undefined;
errors.push({
name: i.name,
error: `The entered string has more than ${option.max_length} characters. The maximum required is ${option.max_length} characters.`,
fullError: ['STRING_MAX_LENGTH', option.max_length],
});
break;
}
}
if (option.choices?.length) {
const choice = option.choices.find(x => x.name === value);
if (!choice) {
value = undefined;
errors.push({
name: i.name,
error: `The entered choice is invalid. Please choose one of the following options: ${option.choices
.map(x => x.name)
.join(', ')}.`,
fullError: ['STRING_INVALID_CHOICE', option.choices],
});
break;
}
value = choice.value;
}
}
break;
case ApplicationCommandOptionType.Number:
case ApplicationCommandOptionType.Integer:
{
const option = i as SeyfertNumberOption | SeyfertIntegerOption;
if (!option.choices?.length) {
value = Number(args[i.name]);
if (args[i.name] === undefined) {
value = undefined;
break;
}
if (Number.isNaN(value)) {
value = undefined;
errors.push({
name: i.name,
error: 'The entered choice is an invalid number.',
fullError: ['NUMBER_NAN', args[i.name]],
});
break;
}
if (option.min_value) {
if (value < option.min_value) {
value = undefined;
errors.push({
name: i.name,
error: `The entered number is less than ${option.min_value}. The minimum allowed is ${option.min_value}`,
fullError: ['NUMBER_MIN_VALUE', option.min_value],
});
break;
}
}
if (option.max_value) {
if (value > option.max_value) {
value = undefined;
errors.push({
name: i.name,
error: `The entered number is greater than ${option.max_value}. The maximum allowed is ${option.max_value}`,
fullError: ['NUMBER_MAX_VALUE', option.max_value],
});
break;
}
}
break;
}
const choice = option.choices.find(x => x.name === args[i.name]);
if (!choice) {
value = undefined;
errors.push({
name: i.name,
error: `The entered choice is invalid. Please choose one of the following options: ${option.choices
.map(x => x.name)
.join(', ')}.`,
fullError: ['NUMBER_INVALID_CHOICE', option.choices],
});
break;
}
value = choice.value;
}
break;
default:
break;
}
if (value !== undefined) {
options.push({
name: i.name,
type: i.type,
value,
} as APIApplicationCommandInteractionDataOption);
} else if (i.required)
if (!errors.some(x => x.name === i.name))
errors.push({
error: 'Option is required but returned undefined',
name: i.name,
fullError: ['OPTION_REQUIRED'],
});
} catch (e) {
errors.push({
error: e && typeof e === 'object' && 'message' in e ? (e.message as string) : `${e}`,
name: i.name,
fullError: ['UNKNOWN', e],
});
}
}
return { errors, options };
}
}

View File

@ -10,9 +10,9 @@ import {
type APIApplicationCommandChannelOption, type APIApplicationCommandChannelOption,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { basename, dirname } from 'node:path'; import { basename, dirname } from 'node:path';
import type { Logger } from '../common'; import type { Logger, MakeRequired, NulleableCoalising, OmitInsert } from '../common';
import { BaseHandler } from '../common'; import { BaseHandler } from '../common';
import { Command, SubCommand } from './applications/chat'; import { Command, type CommandOption, SubCommand } from './applications/chat';
import { ContextMenuCommand } from './applications/menu'; import { ContextMenuCommand } from './applications/menu';
import type { UsingClient } from './applications/shared'; import type { UsingClient } from './applications/shared';
import { promises } from 'node:fs'; import { promises } from 'node:fs';
@ -169,125 +169,100 @@ export class CommandHandler extends BaseHandler {
return false; return false;
} }
async load(commandsDir: string, client: UsingClient, instances?: { new (): Command | ContextMenuCommand }[]) { async load(commandsDir: string, client: UsingClient) {
const result = const result = await this.loadFilesK<FileLoaded<null>>(await this.getFiles(commandsDir));
instances?.map(x => {
const i = new x();
return { name: i.name, file: x, path: i.__filePath ?? '*' };
}) ??
(
await this.loadFilesK<{ new (): Command | SubCommand | ContextMenuCommand }>(await this.getFiles(commandsDir))
).filter(x => x.file);
this.values = []; this.values = [];
for (const command of result) { for (const { commands, file } of result.map(x => ({ commands: this.onFile(x.file), file: x }))) {
let commandInstance; if (!commands) continue;
try { for (const command of commands) {
commandInstance = this.onCommand(command.file); let commandInstance;
if (!commandInstance) continue; try {
} catch (e) { commandInstance = this.onCommand(command);
if (e instanceof Error && e.message.includes('is not a constructor')) { if (!commandInstance) continue;
this.logger.warn( } catch (e) {
`${command.path if (e instanceof Error && e.message.includes('is not a constructor')) {
.split(process.cwd()) this.logger.warn(
.slice(1) `${file.path
.join(process.cwd())} doesn't export the class by \`export default <Command>\``, .split(process.cwd())
); .slice(1)
} else this.logger.warn(e, command); .join(process.cwd())} doesn't export the class by \`export default <Command>\``,
continue; );
} } else this.logger.warn(e, command);
if (commandInstance instanceof ContextMenuCommand) { continue;
this.values.push(commandInstance); }
commandInstance.__filePath = command.path; if (commandInstance instanceof SubCommand) continue;
this.__parseCommandLocales(commandInstance);
commandInstance.__filePath = file.path;
commandInstance.props ??= client.options.commands?.defaults?.props ?? {}; commandInstance.props ??= client.options.commands?.defaults?.props ?? {};
continue; const isAvailableCommand = this.stablishCommandDefaults(commandInstance);
} if (isAvailableCommand) {
if (!(commandInstance instanceof Command)) { commandInstance = isAvailableCommand;
continue; if (commandInstance.__autoload) {
} //@AutoLoad
commandInstance.onAfterRun ??= client.options.commands?.defaults?.onAfterRun; const options = await this.getFiles(dirname(file.path));
commandInstance.onBotPermissionsFail ??= client.options.commands?.defaults?.onBotPermissionsFail; for (const option of options) {
commandInstance.onInternalError ??= client.options.commands?.defaults?.onInternalError; if (file.name === basename(option)) {
commandInstance.onMiddlewaresError ??= client.options.commands?.defaults?.onMiddlewaresError; continue;
commandInstance.onOptionsError ??= client.options.commands?.defaults?.onOptionsError; }
commandInstance.onPermissionsFail ??= client.options.commands?.defaults?.onPermissionsFail; try {
commandInstance.onRunError ??= client.options.commands?.defaults?.onRunError; const fileSubCommands = this.onFile(result.find(x => x.path === option)!.file);
commandInstance.__filePath = command.path; if (!fileSubCommands) {
commandInstance.options ??= [] as NonNullable<Command['options']>; this.logger.warn(`SubCommand returned (${fileSubCommands}) ignoring.`);
commandInstance.props ??= client.options.commands?.defaults?.props ?? {}; continue;
if (commandInstance.__autoload) { }
//@AutoLoad for (const fileSubCommand of fileSubCommands) {
const options = await this.getFiles(dirname(command.path)); const subCommand = this.onSubCommand(fileSubCommand as HandleableSubCommand);
for (const option of options) { if (subCommand && subCommand instanceof SubCommand) {
if (command.name === basename(option)) { subCommand.__filePath = option;
continue; commandInstance.options.push(subCommand);
} } else {
try { this.logger.warn(subCommand ? 'SubCommand expected' : 'Invalid SubCommand', subCommand);
const subCommand = this.onSubCommand(result.find(x => x.path === option)!.file as { new (): SubCommand }); }
if (subCommand && subCommand instanceof SubCommand) { }
subCommand.__filePath = option; } catch {
commandInstance.options.push(subCommand); //pass
}
} }
} catch { }
//pass for (const option of commandInstance.options ?? []) {
if (option instanceof SubCommand) this.stablishSubCommandDefaults(commandInstance, option);
} }
} }
} this.stablishContextCommandDefaults(commandInstance);
this.values.push(commandInstance);
for (const option of commandInstance.options ?? []) { this.parseLocales(commandInstance);
if (option instanceof SubCommand) {
option.middlewares = (commandInstance.middlewares ?? []).concat(option.middlewares ?? []);
option.onMiddlewaresError =
option.onMiddlewaresError?.bind(option) ??
commandInstance.onMiddlewaresError?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onMiddlewaresError;
option.onRunError =
option.onRunError?.bind(option) ??
commandInstance.onRunError?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onRunError;
option.onOptionsError =
option.onOptionsError?.bind(option) ??
commandInstance.onOptionsError?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onOptionsError;
option.onInternalError =
option.onInternalError?.bind(option) ??
commandInstance.onInternalError?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onInternalError;
option.onAfterRun =
option.onAfterRun?.bind(option) ??
commandInstance.onAfterRun?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onAfterRun;
option.onBotPermissionsFail =
option.onBotPermissionsFail?.bind(option) ??
commandInstance.onBotPermissionsFail?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onBotPermissionsFail;
option.onPermissionsFail =
option.onPermissionsFail?.bind(option) ??
commandInstance.onPermissionsFail?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onPermissionsFail;
option.botPermissions ??= commandInstance.botPermissions;
option.defaultMemberPermissions ??= commandInstance.defaultMemberPermissions;
option.contexts ??= commandInstance.contexts;
option.integrationTypes ??= commandInstance.integrationTypes;
option.props ??= commandInstance.props;
}
}
this.values.push(commandInstance);
this.__parseCommandLocales(commandInstance);
for (const i of commandInstance.options ?? []) {
if (i instanceof SubCommand) {
this.__parseCommandLocales(i);
}
} }
} }
return this.values; return this.values;
} }
private __parseCommandLocales(command: Command | SubCommand | ContextMenuCommand) { parseLocales(command: Command | SubCommand | ContextMenuCommand) {
this.parseGlobalLocales(command);
if (command instanceof ContextMenuCommand) {
this.parseContextMenuLocales(command);
return command;
}
if (command instanceof Command && command.__tGroups) {
this.parseCommandLocales(command);
for (const option of command.options ?? []) {
if (option instanceof SubCommand) {
this.parseSubCommandLocales(option);
continue;
}
// @ts-expect-error
if (option.locales) this.parseCommandOptionLocales(option);
}
}
if (command instanceof SubCommand) {
this.parseSubCommandLocales(command);
}
return command;
}
parseGlobalLocales(command: Command | SubCommand | ContextMenuCommand) {
if (command.__t) { if (command.__t) {
command.name_localizations = {}; command.name_localizations = {};
command.description_localizations = {}; command.description_localizations = {};
@ -310,60 +285,57 @@ export class CommandHandler extends BaseHandler {
} }
} }
} }
}
if (command instanceof ContextMenuCommand) return; parseCommandOptionLocales(option: MakeRequired<CommandOption, 'locales'>) {
option.name_localizations = {};
option.description_localizations = {};
for (const locale of Object.keys(this.client.langs!.values)) {
const locales = this.client.langs!.aliases.find(x => x[0] === locale)?.[1] ?? [];
if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString);
for (const options of command.options ?? []) { if (option.locales.name) {
if (options instanceof SubCommand || !options.locales) continue; for (const i of locales) {
options.name_localizations = {}; const valueName = this.client.langs!.getKey(locale, option.locales.name!);
options.description_localizations = {}; if (valueName) option.name_localizations[i] = valueName;
for (const locale of Object.keys(this.client.langs!.values)) {
const locales = this.client.langs!.aliases.find(x => x[0] === locale)?.[1] ?? [];
if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString);
if (options.locales.name) {
for (const i of locales) {
const valueName = this.client.langs!.getKey(locale, options.locales.name!);
if (valueName) options.name_localizations[i] = valueName;
}
} }
}
if (options.locales.description) { if (option.locales.description) {
for (const i of locales) { for (const i of locales) {
const valueKey = this.client.langs!.getKey(locale, options.locales.description!); const valueKey = this.client.langs!.getKey(locale, option.locales.description!);
if (valueKey) options.description_localizations[i] = valueKey; if (valueKey) option.description_localizations[i] = valueKey;
}
} }
} }
} }
}
if (command instanceof Command && command.__tGroups) { parseCommandLocales(command: Command) {
command.groups = {}; command.groups = {};
for (const locale of Object.keys(this.client.langs!.values)) { for (const locale of Object.keys(this.client.langs!.values)) {
const locales = this.client.langs!.aliases.find(x => x[0] === locale)?.[1] ?? []; const locales = this.client.langs!.aliases.find(x => x[0] === locale)?.[1] ?? [];
if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString); if (Object.values<string>(Locale).includes(locale)) locales.push(locale as LocaleString);
for (const group in command.__tGroups) { for (const group in command.__tGroups) {
command.groups[group] ??= { command.groups[group] ??= {
defaultDescription: command.__tGroups[group].defaultDescription, defaultDescription: command.__tGroups[group].defaultDescription,
description: [], description: [],
name: [], name: [],
}; };
if (command.__tGroups[group].name) { if (command.__tGroups[group].name) {
for (const i of locales) { for (const i of locales) {
const valueName = this.client.langs!.getKey(locale, command.__tGroups[group].name!); const valueName = this.client.langs!.getKey(locale, command.__tGroups[group].name!);
if (valueName) { if (valueName) {
command.groups[group].name!.push([i, valueName]); command.groups[group].name!.push([i, valueName]);
}
} }
} }
}
if (command.__tGroups[group].description) { if (command.__tGroups[group].description) {
for (const i of locales) { for (const i of locales) {
const valueKey = this.client.langs!.getKey(locale, command.__tGroups[group].description!); const valueKey = this.client.langs!.getKey(locale, command.__tGroups[group].description!);
if (valueKey) { if (valueKey) {
command.groups[group].description!.push([i, valueKey]); command.groups[group].description!.push([i, valueKey]);
}
} }
} }
} }
@ -371,21 +343,103 @@ export class CommandHandler extends BaseHandler {
} }
} }
setHandlers({ parseContextMenuLocales(command: ContextMenuCommand) {
onCommand, return command;
onSubCommand,
}: {
onCommand?: CommandHandler['onCommand'];
onSubCommand?: CommandHandler['onSubCommand'];
}) {
if (onCommand) this.onCommand = onCommand;
if (onSubCommand) this.onSubCommand = onSubCommand;
} }
onCommand = (file: { new (): Command | SubCommand | ContextMenuCommand }): parseSubCommandLocales(command: SubCommand) {
| Command for (const i of command.options ?? []) {
| SubCommand // @ts-expect-error
| ContextMenuCommand if (i.locales) this.parseCommandOptionLocales(i);
| false => new file(); }
onSubCommand = (file: { new (): SubCommand }): SubCommand | false => new file(); return command;
}
stablishContextCommandDefaults(commandInstance: InstanceType<HandleableCommand>): ContextMenuCommand | false {
if (!(commandInstance instanceof ContextMenuCommand)) return false;
commandInstance.onAfterRun ??= this.client.options.commands?.defaults?.onAfterRun;
//@ts-expect-error magic.
commandInstance.onBotPermissionsFail ??= this.client.options.commands?.defaults?.onBotPermissionsFail;
//@ts-expect-error magic.
commandInstance.onInternalError ??= this.client.options.commands?.defaults?.onInternalError;
//@ts-expect-error magic.
commandInstance.onMiddlewaresError ??= this.client.options.commands?.defaults?.onMiddlewaresError;
//@ts-expect-error magic.
commandInstance.onPermissionsFail ??= this.client.options.commands?.defaults?.onPermissionsFail;
//@ts-expect-error magic.
commandInstance.onRunError ??= this.client.options.commands?.defaults?.onRunError;
return commandInstance;
}
stablishCommandDefaults(
commandInstance: InstanceType<HandleableCommand>,
): OmitInsert<Command, 'options', { options: NonNullable<Command['options']> }> | false {
if (!(commandInstance instanceof Command)) return false;
commandInstance.onAfterRun ??= this.client.options.commands?.defaults?.onAfterRun;
commandInstance.onBotPermissionsFail ??= this.client.options.commands?.defaults?.onBotPermissionsFail;
commandInstance.onInternalError ??= this.client.options.commands?.defaults?.onInternalError;
commandInstance.onMiddlewaresError ??= this.client.options.commands?.defaults?.onMiddlewaresError;
commandInstance.onOptionsError ??= this.client.options.commands?.defaults?.onOptionsError;
commandInstance.onPermissionsFail ??= this.client.options.commands?.defaults?.onPermissionsFail;
commandInstance.onRunError ??= this.client.options.commands?.defaults?.onRunError;
commandInstance.options ??= [];
return commandInstance as any;
}
stablishSubCommandDefaults(commandInstance: Command, option: SubCommand): SubCommand {
option.middlewares = (commandInstance.middlewares ?? []).concat(option.middlewares ?? []);
option.onMiddlewaresError =
option.onMiddlewaresError?.bind(option) ??
commandInstance.onMiddlewaresError?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onMiddlewaresError;
option.onRunError =
option.onRunError?.bind(option) ??
commandInstance.onRunError?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onRunError;
option.onOptionsError =
option.onOptionsError?.bind(option) ??
commandInstance.onOptionsError?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onOptionsError;
option.onInternalError =
option.onInternalError?.bind(option) ??
commandInstance.onInternalError?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onInternalError;
option.onAfterRun =
option.onAfterRun?.bind(option) ??
commandInstance.onAfterRun?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onAfterRun;
option.onBotPermissionsFail =
option.onBotPermissionsFail?.bind(option) ??
commandInstance.onBotPermissionsFail?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onBotPermissionsFail;
option.onPermissionsFail =
option.onPermissionsFail?.bind(option) ??
commandInstance.onPermissionsFail?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onPermissionsFail;
option.botPermissions ??= commandInstance.botPermissions;
option.defaultMemberPermissions ??= commandInstance.defaultMemberPermissions;
option.contexts ??= commandInstance.contexts;
option.integrationTypes ??= commandInstance.integrationTypes;
option.props ??= commandInstance.props;
return option;
}
onFile(file: FileLoaded): HandleableCommand[] | undefined {
return file.default ? [file.default] : undefined;
}
onCommand(file: HandleableCommand): Command | SubCommand | ContextMenuCommand | false {
return new file();
}
onSubCommand(file: HandleableSubCommand): SubCommand | false {
return new file();
}
} }
export type FileLoaded<T = null> = {
default?: NulleableCoalising<T, HandleableCommand>;
} & Record<string, NulleableCoalising<T, HandleableCommand>>;
export type HandleableCommand = new () => Command | SubCommand | ContextMenuCommand;
export type HandleableSubCommand = new () => SubCommand;

View File

@ -8,23 +8,26 @@ import {
type APIUser, type APIUser,
ApplicationCommandOptionType, ApplicationCommandOptionType,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { Attachment, GuildMember } from '..'; import { Attachment } from '..';
import type { MakeRequired } from '../common'; import type { MakeRequired } from '../common';
import type { AllChannels } from '../structures'; import type { AllChannels } from '../structures';
import { GuildRole, InteractionGuildMember, User } from '../structures';
import channelFrom from '../structures/channels'; import channelFrom from '../structures/channels';
import type { Command, CommandAutocompleteOption, CommandOption, SubCommand } from './applications/chat'; import type { Command, CommandAutocompleteOption, CommandOption, SubCommand } from './applications/chat';
import type { UsingClient } from './applications/shared'; import type { UsingClient } from './applications/shared';
import {
type GuildMemberStructure,
type GuildRoleStructure,
type InteractionGuildMemberStructure,
Transformers,
type UserStructure,
} from '../client/transformers';
export type ContextOptionsResolved = { export type ContextOptionsResolved = {
members?: Record< members?: Record<string, APIGuildMember | Omit<APIGuildMember, 'user'> | APIInteractionGuildMember>;
string, users?: Record<string, APIUser>;
APIGuildMember | Omit<APIGuildMember, 'user'> | APIInteractionGuildMember | GuildMember | InteractionGuildMember roles?: Record<string, APIRole>;
>; channels?: Record<string, APIInteractionDataResolvedChannel>;
users?: Record<string, APIUser | User>; attachments?: Record<string, APIAttachment>;
roles?: Record<string, APIRole | GuildRole>;
channels?: Record<string, APIInteractionDataResolvedChannel | AllChannels>;
attachments?: Record<string, APIAttachment | Attachment>;
}; };
export class OptionResolver { export class OptionResolver {
@ -164,18 +167,16 @@ export class OptionResolver {
const value = resolve.value as string; const value = resolve.value as string;
const user = resolved.users?.[value]; const user = resolved.users?.[value];
if (user) { if (user) {
resolve.user = user instanceof User ? user : new User(this.client, user); resolve.user = Transformers.User(this.client, user);
} }
const member = resolved.members?.[value]; const member = resolved.members?.[value];
if (member) { if (member) {
resolve.member = resolve.member =
member instanceof GuildMember || member instanceof InteractionGuildMember 'permissions' in member
? member ? Transformers.InteractionGuildMember(this.client, member, user!, this.guildId!)
: 'permissions' in member : Transformers.GuildMember(this.client, member, user!, this.guildId!);
? new InteractionGuildMember(this.client, member, user!, this.guildId!)
: new GuildMember(this.client, member, user!, this.guildId!);
} }
const channel = resolved.channels?.[value]; const channel = resolved.channels?.[value];
@ -185,7 +186,7 @@ export class OptionResolver {
const role = resolved.roles?.[value]; const role = resolved.roles?.[value];
if (role) { if (role) {
resolve.role = role instanceof GuildRole ? role : new GuildRole(this.client, role, this.guildId!); resolve.role = Transformers.GuildRole(this.client, role, this.guildId!);
} }
const attachment = resolved.attachments?.[value]; const attachment = resolved.attachments?.[value];
@ -203,11 +204,11 @@ export interface OptionResolved {
type: ApplicationCommandOptionType; type: ApplicationCommandOptionType;
value?: string | number | boolean; value?: string | number | boolean;
options?: OptionResolved[]; options?: OptionResolved[];
user?: User; user?: UserStructure;
member?: GuildMember | InteractionGuildMember; member?: GuildMemberStructure | InteractionGuildMemberStructure;
attachment?: Attachment; attachment?: Attachment;
channel?: AllChannels; channel?: AllChannels;
role?: GuildRole; role?: GuildRoleStructure;
focused?: boolean; focused?: boolean;
} }

View File

@ -130,7 +130,7 @@ export class BaseHandler {
* @param paths The paths of the files to load. * @param paths The paths of the files to load.
* @returns A Promise that resolves to an array of loaded files. * @returns A Promise that resolves to an array of loaded files.
*/ */
protected async loadFiles<T extends NonNullable<unknown>>(paths: string[]): Promise<T[]> { protected loadFiles<T extends NonNullable<unknown>>(paths: string[]): Promise<T[]> {
return Promise.all(paths.map(path => magicImport(path).then(file => file.default ?? file))); return Promise.all(paths.map(path => magicImport(path).then(file => file.default ?? file)));
} }
@ -139,13 +139,13 @@ export class BaseHandler {
* @param paths The paths of the files to load. * @param paths The paths of the files to load.
* @returns A Promise that resolves to an array of objects containing name, file, and path. * @returns A Promise that resolves to an array of objects containing name, file, and path.
*/ */
protected async loadFilesK<T>(paths: string[]): Promise<{ name: string; file: T; path: string }[]> { protected loadFilesK<T>(paths: string[]): Promise<{ name: string; file: T; path: string }[]> {
return Promise.all( return Promise.all(
paths.map(path => paths.map(path =>
magicImport(path).then(file => { magicImport(path).then(file => {
return { return {
name: basename(path), name: basename(path),
file: file.default ?? file, file,
path, path,
}; };
}), }),

View File

@ -5,7 +5,7 @@ import type {
RESTPutAPIGuildBanJSONBody, RESTPutAPIGuildBanJSONBody,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { GuildBan } from '../../structures/GuildBan'; import { Transformers } from '../../client/transformers';
export class BanShorter extends BaseShorter { export class BanShorter extends BaseShorter {
/** /**
@ -58,7 +58,7 @@ export class BanShorter extends BaseShorter {
ban = await this.client.proxy.guilds(guildId).bans(userId).get(); ban = await this.client.proxy.guilds(guildId).bans(userId).get();
await this.client.cache.members?.set(ban.user!.id, guildId, ban); await this.client.cache.members?.set(ban.user!.id, guildId, ban);
return new GuildBan(this.client, ban, guildId); return Transformers.GuildBan(this.client, ban, guildId);
} }
/** /**
@ -81,6 +81,6 @@ export class BanShorter extends BaseShorter {
bans.map<[string, APIBan]>(x => [x.user!.id, x]), bans.map<[string, APIBan]>(x => [x.user!.id, x]),
guildId, guildId,
); );
return bans.map(m => new GuildBan(this.client, m, guildId)); return bans.map(m => Transformers.GuildBan(this.client, m, guildId));
} }
} }

View File

@ -1,15 +1,17 @@
import { import {
type APIChannel,
PermissionFlagsBits, PermissionFlagsBits,
type RESTGetAPIChannelMessagesQuery, type RESTGetAPIChannelMessagesQuery,
type RESTPatchAPIChannelJSONBody, type RESTPatchAPIChannelJSONBody,
type RESTPostAPIChannelThreadsJSONBody, type RESTPostAPIChannelThreadsJSONBody,
type RESTPostAPIGuildForumThreadsJSONBody, type RESTPostAPIGuildForumThreadsJSONBody,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { BaseChannel, Message, type GuildMember, type GuildRole } from '../../structures'; import { BaseChannel, type GuildRole, type GuildMember } from '../../structures';
import channelFrom, { type AllChannels } from '../../structures/channels'; import channelFrom, { type AllChannels } from '../../structures/channels';
import { PermissionsBitField } from '../../structures/extra/Permissions'; import { PermissionsBitField } from '../../structures/extra/Permissions';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { MergeOptions } from '../it/utils'; import { MergeOptions } from '../it/utils';
import { type MessageStructure, Transformers } from '../../client/transformers';
export class ChannelShorter extends BaseShorter { export class ChannelShorter extends BaseShorter {
/** /**
@ -19,15 +21,19 @@ export class ChannelShorter extends BaseShorter {
* @returns A Promise that resolves to the fetched channel. * @returns A Promise that resolves to the fetched channel.
*/ */
async fetch(id: string, force?: boolean): Promise<AllChannels> { async fetch(id: string, force?: boolean): Promise<AllChannels> {
return channelFrom(await this.raw(id, force), this.client);
}
async raw(id: string, force?: boolean): Promise<APIChannel> {
let channel; let channel;
if (!force) { if (!force) {
channel = await this.client.cache.channels?.get(id); channel = await this.client.cache.channels?.raw(id);
if (channel) return channel; if (channel) return channel;
} }
channel = await this.client.proxy.channels(id).get(); channel = await this.client.proxy.channels(id).get();
await this.client.cache.channels?.patch(id, undefined, channel); await this.client.cache.channels?.patch(id, undefined, channel);
return channelFrom(channel, this.client); return channel;
} }
/** /**
@ -77,7 +83,7 @@ export class ChannelShorter extends BaseShorter {
await this.client.proxy.channels(id).typing.post(); await this.client.proxy.channels(id).typing.post();
} }
async pins(channelId: string): Promise<Message[]> { async pins(channelId: string): Promise<MessageStructure[]> {
const messages = await this.client.proxy.channels(channelId).pins.get(); const messages = await this.client.proxy.channels(channelId).pins.get();
await this.client.cache.messages?.patch( await this.client.cache.messages?.patch(
messages.map(x => { messages.map(x => {
@ -85,7 +91,7 @@ export class ChannelShorter extends BaseShorter {
}) satisfies [string, any][], }) satisfies [string, any][],
channelId, channelId,
); );
return messages.map(message => new Message(this.client, message)); return messages.map(message => Transformers.Message(this.client, message));
} }
/** /**
@ -194,7 +200,7 @@ export class ChannelShorter extends BaseShorter {
}) satisfies [string, any][], }) satisfies [string, any][],
channelId, channelId,
); );
return result.map(message => new Message(this.client, message)); return result.map(message => Transformers.Message(this.client, message));
} }
setVoiceStatus(channelId: string, status: string | null = null) { setVoiceStatus(channelId: string, status: string | null = null) {

View File

@ -1,9 +1,9 @@
import type { APIEmoji, RESTPatchAPIGuildEmojiJSONBody, RESTPostAPIGuildEmojiJSONBody } from 'discord-api-types/v10'; import type { APIEmoji, RESTPatchAPIGuildEmojiJSONBody, RESTPostAPIGuildEmojiJSONBody } from 'discord-api-types/v10';
import { GuildEmoji } from '../..';
import { resolveImage } from '../../builders'; import { resolveImage } from '../../builders';
import type { ImageResolvable } from '../types/resolvables'; import type { ImageResolvable } from '../types/resolvables';
import type { OmitInsert } from '../types/util'; import type { OmitInsert } from '../types/util';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { Transformers } from '../../client/transformers';
export class EmojiShorter extends BaseShorter { export class EmojiShorter extends BaseShorter {
/** /**
@ -25,7 +25,7 @@ export class EmojiShorter extends BaseShorter {
emojis.map<[string, APIEmoji]>(x => [x.id!, x]), emojis.map<[string, APIEmoji]>(x => [x.id!, x]),
guildId, guildId,
); );
return emojis.map(m => new GuildEmoji(this.client, m, guildId)); return emojis.map(m => Transformers.GuildEmoji(this.client, m, guildId));
} }
/** /**
@ -42,7 +42,7 @@ export class EmojiShorter extends BaseShorter {
await this.client.cache.emojis?.setIfNI('GuildEmojisAndStickers', emoji.id!, guildId, emoji); await this.client.cache.emojis?.setIfNI('GuildEmojisAndStickers', emoji.id!, guildId, emoji);
return new GuildEmoji(this.client, emoji, guildId); return Transformers.GuildEmoji(this.client, emoji, guildId);
} }
/** /**
@ -59,7 +59,7 @@ export class EmojiShorter extends BaseShorter {
if (emoji) return emoji; if (emoji) return emoji;
} }
emoji = await this.client.proxy.guilds(guildId).emojis(emojiId).get(); emoji = await this.client.proxy.guilds(guildId).emojis(emojiId).get();
return new GuildEmoji(this.client, emoji, guildId); return Transformers.GuildEmoji(this.client, emoji, guildId);
} }
/** /**
@ -84,6 +84,6 @@ export class EmojiShorter extends BaseShorter {
async edit(guildId: string, emojiId: string, body: RESTPatchAPIGuildEmojiJSONBody, reason?: string) { async edit(guildId: string, emojiId: string, body: RESTPatchAPIGuildEmojiJSONBody, reason?: string) {
const emoji = await this.client.proxy.guilds(guildId).emojis(emojiId).patch({ body, reason }); const emoji = await this.client.proxy.guilds(guildId).emojis(emojiId).patch({ body, reason });
await this.client.cache.emojis?.setIfNI('GuildEmojisAndStickers', emoji.id!, guildId, emoji); await this.client.cache.emojis?.setIfNI('GuildEmojisAndStickers', emoji.id!, guildId, emoji);
return new GuildEmoji(this.client, emoji, guildId); return Transformers.GuildEmoji(this.client, emoji, guildId);
} }
} }

View File

@ -12,17 +12,10 @@ import type {
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { toSnakeCase, type ObjectToLower } from '..'; import { toSnakeCase, type ObjectToLower } from '..';
import { resolveFiles } from '../../builders'; import { resolveFiles } from '../../builders';
import { import { BaseChannel, GuildMember, type CreateStickerBodyRequest } from '../../structures';
AnonymousGuild,
AutoModerationRule,
BaseChannel,
Guild,
GuildMember,
Sticker,
type CreateStickerBodyRequest,
} from '../../structures';
import channelFrom from '../../structures/channels'; import channelFrom from '../../structures/channels';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { type GuildStructure, Transformers } from '../../client/transformers';
export class GuildShorter extends BaseShorter { export class GuildShorter extends BaseShorter {
/** /**
@ -30,10 +23,10 @@ export class GuildShorter extends BaseShorter {
* @param body The data for creating the guild. * @param body The data for creating the guild.
* @returns A Promise that resolves to the created guild. * @returns A Promise that resolves to the created guild.
*/ */
async create(body: RESTPostAPIGuildsJSONBody): Promise<Guild<'api'>> { async create(body: RESTPostAPIGuildsJSONBody): Promise<GuildStructure<'api'>> {
const guild = await this.client.proxy.guilds.post({ body }); const guild = await this.client.proxy.guilds.post({ body });
await this.client.cache.guilds?.setIfNI('Guilds', guild.id, guild); await this.client.cache.guilds?.setIfNI('Guilds', guild.id, guild);
return new Guild<'api'>(this.client, guild); return Transformers.Guild<'api'>(this.client, guild);
} }
/** /**
@ -43,14 +36,18 @@ export class GuildShorter extends BaseShorter {
* @returns A Promise that resolves to the fetched guild. * @returns A Promise that resolves to the fetched guild.
*/ */
async fetch(id: string, force = false) { async fetch(id: string, force = false) {
return Transformers.Guild<'api'>(this.client, await this.raw(id, force));
}
async raw(id: string, force = false) {
if (!force) { if (!force) {
const guild = await this.client.cache.guilds?.get(id); const guild = await this.client.cache.guilds?.raw(id);
if (guild) return guild; if (guild) return guild;
} }
const data = await this.client.proxy.guilds(id).get(); const data = await this.client.proxy.guilds(id).get();
await this.client.cache.guilds?.patch(id, data); await this.client.cache.guilds?.patch(id, data);
return (await this.client.cache.guilds?.get(id)) ?? new Guild<'api'>(this.client, data); return (await this.client.cache.guilds?.raw(id)) ?? data;
} }
/** /**
@ -72,17 +69,17 @@ export class GuildShorter extends BaseShorter {
return this.client.proxy return this.client.proxy
.users('@me') .users('@me')
.guilds.get({ query }) .guilds.get({ query })
.then(guilds => guilds.map(guild => new AnonymousGuild(this.client, { ...guild, splash: null }))); .then(guilds => guilds.map(guild => Transformers.AnonymousGuild(this.client, { ...guild, splash: null })));
} }
async fetchSelf(id: string, force = false) { async fetchSelf(id: string, force = false) {
if (!force) { if (!force) {
const self = await this.client.cache.members?.get(this.client.botId, id); const self = await this.client.cache.members?.raw(this.client.botId, id);
if (self) return self; if (self?.user) return new GuildMember(this.client, self, self.user, id);
} }
const self = await this.client.proxy.guilds(id).members(this.client.botId).get(); const self = await this.client.proxy.guilds(id).members(this.client.botId).get();
await this.client.cache.members?.patch(self.user!.id, id, self); await this.client.cache.members?.patch(self.user.id, id, self);
return new GuildMember(this.client, self, self.user!, id); return new GuildMember(this.client, self, self.user, id);
} }
leave(id: string) { leave(id: string) {
@ -211,7 +208,7 @@ export class GuildShorter extends BaseShorter {
this.client.proxy this.client.proxy
.guilds(guildId) .guilds(guildId)
['auto-moderation'].rules.get() ['auto-moderation'].rules.get()
.then(rules => rules.map(rule => new AutoModerationRule(this.client, rule))), .then(rules => rules.map(rule => Transformers.AutoModerationRule(this.client, rule))),
/** /**
* Creates a new auto-moderation rule in the guild. * Creates a new auto-moderation rule in the guild.
@ -223,7 +220,7 @@ export class GuildShorter extends BaseShorter {
this.client.proxy this.client.proxy
.guilds(guildId) .guilds(guildId)
['auto-moderation'].rules.post({ body }) ['auto-moderation'].rules.post({ body })
.then(rule => new AutoModerationRule(this.client, rule)), .then(rule => Transformers.AutoModerationRule(this.client, rule)),
/** /**
* Deletes an auto-moderation rule from the guild. * Deletes an auto-moderation rule from the guild.
@ -247,7 +244,7 @@ export class GuildShorter extends BaseShorter {
.guilds(guildId) .guilds(guildId)
['auto-moderation'].rules(ruleId) ['auto-moderation'].rules(ruleId)
.get() .get()
.then(rule => new AutoModerationRule(this.client, rule)); .then(rule => Transformers.AutoModerationRule(this.client, rule));
}, },
/** /**
@ -268,7 +265,7 @@ export class GuildShorter extends BaseShorter {
.guilds(guildId) .guilds(guildId)
['auto-moderation'].rules(ruleId) ['auto-moderation'].rules(ruleId)
.patch({ body: toSnakeCase(body), reason }) .patch({ body: toSnakeCase(body), reason })
.then(rule => new AutoModerationRule(this.client, rule)); .then(rule => Transformers.AutoModerationRule(this.client, rule));
}, },
}; };
} }
@ -289,7 +286,7 @@ export class GuildShorter extends BaseShorter {
stickers.map(st => [st.id, st] as any), stickers.map(st => [st.id, st] as any),
guildId, guildId,
); );
return stickers.map(st => new Sticker(this.client, st)); return stickers.map(st => Transformers.Sticker(this.client, st));
}, },
/** /**
@ -305,7 +302,7 @@ export class GuildShorter extends BaseShorter {
.guilds(guildId) .guilds(guildId)
.stickers.post({ reason, body: json, files: [{ ...fileResolve[0], key: 'file' }], appendToFormData: true }); .stickers.post({ reason, body: json, files: [{ ...fileResolve[0], key: 'file' }], appendToFormData: true });
await this.client.cache.stickers?.setIfNI('GuildEmojisAndStickers', sticker.id, guildId, sticker); await this.client.cache.stickers?.setIfNI('GuildEmojisAndStickers', sticker.id, guildId, sticker);
return new Sticker(this.client, sticker); return Transformers.Sticker(this.client, sticker);
}, },
/** /**
@ -319,7 +316,7 @@ export class GuildShorter extends BaseShorter {
edit: async (guildId: string, stickerId: string, body: RESTPatchAPIGuildStickerJSONBody, reason?: string) => { edit: async (guildId: string, stickerId: string, body: RESTPatchAPIGuildStickerJSONBody, reason?: string) => {
const sticker = await this.client.proxy.guilds(guildId).stickers(stickerId).patch({ body, reason }); const sticker = await this.client.proxy.guilds(guildId).stickers(stickerId).patch({ body, reason });
await this.client.cache.stickers?.setIfNI('GuildEmojisAndStickers', stickerId, guildId, sticker); await this.client.cache.stickers?.setIfNI('GuildEmojisAndStickers', stickerId, guildId, sticker);
return new Sticker(this.client, sticker); return Transformers.Sticker(this.client, sticker);
}, },
/** /**
@ -337,7 +334,7 @@ export class GuildShorter extends BaseShorter {
} }
sticker = await this.client.proxy.guilds(guildId).stickers(stickerId).get(); sticker = await this.client.proxy.guilds(guildId).stickers(stickerId).get();
await this.client.cache.stickers?.patch(stickerId, guildId, sticker); await this.client.cache.stickers?.patch(stickerId, guildId, sticker);
return new Sticker(this.client, sticker); return Transformers.Sticker(this.client, sticker);
}, },
/** /**

View File

@ -1,4 +1,5 @@
import { BaseInteraction, WebhookMessage, resolveFiles, type ReplyInteractionBody, Modal } from '../..'; import { BaseInteraction, resolveFiles, type ReplyInteractionBody, Modal } from '../..';
import { Transformers } from '../../client/transformers';
import type { InteractionMessageUpdateBodyRequest, MessageWebhookCreateBodyRequest } from '../types/write'; import type { InteractionMessageUpdateBodyRequest, MessageWebhookCreateBodyRequest } from '../types/write';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
@ -42,7 +43,7 @@ export class InteractionShorter extends BaseShorter {
body: BaseInteraction.transformBody(data, parsedFiles, this.client), body: BaseInteraction.transformBody(data, parsedFiles, this.client),
files: parsedFiles, files: parsedFiles,
}); });
return new WebhookMessage(this.client, apiMessage, this.client.applicationId, token); return Transformers.WebhookMessage(this.client, apiMessage, this.client.applicationId, token);
} }
editOriginal(token: string, body: InteractionMessageUpdateBodyRequest) { editOriginal(token: string, body: InteractionMessageUpdateBodyRequest) {
@ -69,6 +70,6 @@ export class InteractionShorter extends BaseShorter {
body: BaseInteraction.transformBody(body, parsedFiles, this.client), body: BaseInteraction.transformBody(body, parsedFiles, this.client),
files: parsedFiles, files: parsedFiles,
}); });
return new WebhookMessage(this.client, apiMessage, this.client.applicationId, token); return Transformers.WebhookMessage(this.client, apiMessage, this.client.applicationId, token);
} }
} }

View File

@ -7,10 +7,10 @@ import type {
RESTPutAPIGuildBanJSONBody, RESTPutAPIGuildBanJSONBody,
RESTPutAPIGuildMemberJSONBody, RESTPutAPIGuildMemberJSONBody,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { GuildMember } from '../../structures';
import { PermissionsBitField } from '../../structures/extra/Permissions'; import { PermissionsBitField } from '../../structures/extra/Permissions';
import type { GuildMemberResolvable } from '../types/resolvables'; import type { GuildMemberResolvable } from '../types/resolvables';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { Transformers } from '../../client/transformers';
export class MemberShorter extends BaseShorter { export class MemberShorter extends BaseShorter {
/** /**
@ -52,7 +52,7 @@ export class MemberShorter extends BaseShorter {
members.map(x => [x.user!.id, x]), members.map(x => [x.user!.id, x]),
guildId, guildId,
); );
return members.map(m => new GuildMember(this.client, m, m.user!, guildId)); return members.map(m => Transformers.GuildMember(this.client, m, m.user!, guildId));
} }
/** /**
@ -100,7 +100,7 @@ export class MemberShorter extends BaseShorter {
async edit(guildId: string, memberId: string, body: RESTPatchAPIGuildMemberJSONBody, reason?: string) { async edit(guildId: string, memberId: string, body: RESTPatchAPIGuildMemberJSONBody, reason?: string) {
const member = await this.client.proxy.guilds(guildId).members(memberId).patch({ body, reason }); const member = await this.client.proxy.guilds(guildId).members(memberId).patch({ body, reason });
await this.client.cache.members?.setIfNI('GuildMembers', memberId, guildId, member); await this.client.cache.members?.setIfNI('GuildMembers', memberId, guildId, member);
return new GuildMember(this.client, member, member.user!, guildId); return Transformers.GuildMember(this.client, member, member.user!, guildId);
} }
/** /**
@ -122,7 +122,7 @@ export class MemberShorter extends BaseShorter {
await this.client.cache.members?.setIfNI('GuildMembers', member.user!.id, guildId, member); await this.client.cache.members?.setIfNI('GuildMembers', member.user!.id, guildId, member);
return new GuildMember(this.client, member, member.user!, guildId); return Transformers.GuildMember(this.client, member, member.user!, guildId);
} }
/** /**
@ -133,15 +133,20 @@ export class MemberShorter extends BaseShorter {
* @returns A Promise that resolves to the fetched member. * @returns A Promise that resolves to the fetched member.
*/ */
async fetch(guildId: string, memberId: string, force = false) { async fetch(guildId: string, memberId: string, force = false) {
const member = await this.raw(guildId, memberId, force);
return Transformers.GuildMember(this.client, member, member.user, guildId);
}
async raw(guildId: string, memberId: string, force = false) {
let member; let member;
if (!force) { if (!force) {
member = await this.client.cache.members?.get(memberId, guildId); member = await this.client.cache.members?.raw(memberId, guildId);
if (member) return member; if (member) return member;
} }
member = await this.client.proxy.guilds(guildId).members(memberId).get(); member = await this.client.proxy.guilds(guildId).members(memberId).get();
await this.client.cache.members?.set(member.user!.id, guildId, member); await this.client.cache.members?.set(member.user.id, guildId, member);
return new GuildMember(this.client, member, member.user!, guildId); return member;
} }
/** /**
@ -161,7 +166,7 @@ export class MemberShorter extends BaseShorter {
query, query,
}); });
await this.client.cache.members?.set(members.map(x => [x.user!.id, x]) as [string, APIGuildMember][], guildId); await this.client.cache.members?.set(members.map(x => [x.user!.id, x]) as [string, APIGuildMember][], guildId);
return members.map(m => new GuildMember(this.client, m, m.user!, guildId)); return members.map(m => Transformers.GuildMember(this.client, m, m.user!, guildId));
} }
/** /**

View File

@ -4,11 +4,12 @@ import type {
RESTPostAPIChannelMessagesThreadsJSONBody, RESTPostAPIChannelMessagesThreadsJSONBody,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { resolveFiles } from '../../builders'; import { resolveFiles } from '../../builders';
import { Message, MessagesMethods, User } from '../../structures'; import { MessagesMethods } from '../../structures';
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../types/write'; import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../types/write';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import type { ValidAnswerId } from '../../api/Routes/channels'; import type { ValidAnswerId } from '../../api/Routes/channels';
import { Transformers } from '../../client/transformers';
export class MessageShorter extends BaseShorter { export class MessageShorter extends BaseShorter {
async write(channelId: string, { files, ...body }: MessageCreateBodyRequest) { async write(channelId: string, { files, ...body }: MessageCreateBodyRequest) {
@ -27,7 +28,7 @@ export class MessageShorter extends BaseShorter {
}) })
.then(async message => { .then(async message => {
await this.client.cache.messages?.setIfNI('GuildMessages', message.id, message.channel_id, message); await this.client.cache.messages?.setIfNI('GuildMessages', message.id, message.channel_id, message);
return new Message(this.client, message); return Transformers.Message(this.client, message);
}); });
} }
@ -42,7 +43,7 @@ export class MessageShorter extends BaseShorter {
}) })
.then(async message => { .then(async message => {
await this.client.cache.messages?.setIfNI('GuildMessages', message.id, message.channel_id, message); await this.client.cache.messages?.setIfNI('GuildMessages', message.id, message.channel_id, message);
return new Message(this.client, message); return Transformers.Message(this.client, message);
}); });
} }
@ -53,7 +54,7 @@ export class MessageShorter extends BaseShorter {
.crosspost.post({ reason }) .crosspost.post({ reason })
.then(async m => { .then(async m => {
await this.client.cache.messages?.setIfNI('GuildMessages', m.id, m.channel_id, m); await this.client.cache.messages?.setIfNI('GuildMessages', m.id, m.channel_id, m);
return new Message(this.client, m); return Transformers.Message(this.client, m);
}); });
} }
@ -75,7 +76,7 @@ export class MessageShorter extends BaseShorter {
.get() .get()
.then(async x => { .then(async x => {
await this.client.cache.messages?.set(x.id, x.channel_id, x); await this.client.cache.messages?.set(x.id, x.channel_id, x);
return new Message(this.client, x); return Transformers.Message(this.client, x);
}); });
} }
@ -99,7 +100,7 @@ export class MessageShorter extends BaseShorter {
.channels(channelId) .channels(channelId)
.polls(messageId) .polls(messageId)
.expire.post() .expire.post()
.then(message => new Message(this.client, message)); .then(message => Transformers.Message(this.client, message));
} }
getAnswerVoters(channelId: string, messageId: string, answerId: ValidAnswerId) { getAnswerVoters(channelId: string, messageId: string, answerId: ValidAnswerId) {
@ -108,6 +109,6 @@ export class MessageShorter extends BaseShorter {
.polls(messageId) .polls(messageId)
.answers(answerId) .answers(answerId)
.get() .get()
.then(data => data.users.map(user => new User(this.client, user))); .then(data => data.users.map(user => Transformers.User(this.client, user)));
} }
} }

View File

@ -1,8 +1,8 @@
import type { RESTGetAPIChannelMessageReactionUsersQuery } from 'discord-api-types/v10'; import type { RESTGetAPIChannelMessageReactionUsersQuery } from 'discord-api-types/v10';
import { User } from '../../structures';
import { encodeEmoji, resolveEmoji } from '../../structures/extra/functions'; import { encodeEmoji, resolveEmoji } from '../../structures/extra/functions';
import type { EmojiResolvable } from '../types/resolvables'; import type { EmojiResolvable } from '../types/resolvables';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { Transformers, type UserStructure } from '../../client/transformers';
export class ReactionShorter extends BaseShorter { export class ReactionShorter extends BaseShorter {
async add(messageId: string, channelId: string, emoji: EmojiResolvable): Promise<void> { async add(messageId: string, channelId: string, emoji: EmojiResolvable): Promise<void> {
@ -30,7 +30,7 @@ export class ReactionShorter extends BaseShorter {
channelId: string, channelId: string,
emoji: EmojiResolvable, emoji: EmojiResolvable,
query?: RESTGetAPIChannelMessageReactionUsersQuery, query?: RESTGetAPIChannelMessageReactionUsersQuery,
): Promise<User[]> { ): Promise<UserStructure[]> {
const rawEmoji = await resolveEmoji(emoji, this.client.cache); const rawEmoji = await resolveEmoji(emoji, this.client.cache);
if (!rawEmoji) { if (!rawEmoji) {
@ -42,7 +42,7 @@ export class ReactionShorter extends BaseShorter {
.messages(messageId) .messages(messageId)
.reactions(encodeEmoji(rawEmoji)) .reactions(encodeEmoji(rawEmoji))
.get({ query }) .get({ query })
.then(u => u.map(user => new User(this.client, user))); .then(u => u.map(user => Transformers.User(this.client, user)));
} }
async purge(messageId: string, channelId: string, emoji?: EmojiResolvable): Promise<void> { async purge(messageId: string, channelId: string, emoji?: EmojiResolvable): Promise<void> {

View File

@ -4,8 +4,8 @@ import type {
RESTPatchAPIGuildRolePositionsJSONBody, RESTPatchAPIGuildRolePositionsJSONBody,
RESTPostAPIGuildRoleJSONBody, RESTPostAPIGuildRoleJSONBody,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { GuildRole } from '../../structures';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { Transformers } from '../../client/transformers';
export class RoleShorter extends BaseShorter { export class RoleShorter extends BaseShorter {
/** /**
@ -18,7 +18,7 @@ export class RoleShorter extends BaseShorter {
async create(guildId: string, body: RESTPostAPIGuildRoleJSONBody, reason?: string) { async create(guildId: string, body: RESTPostAPIGuildRoleJSONBody, reason?: string) {
const res = await this.client.proxy.guilds(guildId).roles.post({ body, reason }); const res = await this.client.proxy.guilds(guildId).roles.post({ body, reason });
await this.client.cache.roles?.setIfNI('Guilds', res.id, guildId, res); await this.client.cache.roles?.setIfNI('Guilds', res.id, guildId, res);
return new GuildRole(this.client, res, guildId); return Transformers.GuildRole(this.client, res, guildId);
} }
/** /**
@ -28,9 +28,14 @@ export class RoleShorter extends BaseShorter {
* @returns A Promise that resolves to an array of roles. * @returns A Promise that resolves to an array of roles.
*/ */
async list(guildId: string, force = false) { async list(guildId: string, force = false) {
const roles = await this.listRaw(guildId, force);
return roles.map(r => Transformers.GuildRole(this.client, r, guildId));
}
async listRaw(guildId: string, force = false) {
let roles: APIRole[] = []; let roles: APIRole[] = [];
if (!force) { if (!force) {
const cachedRoles = (await this.client.cache.roles?.values(guildId)) ?? []; const cachedRoles = (await this.client.cache.roles?.valuesRaw(guildId)) ?? [];
if (cachedRoles.length) { if (cachedRoles.length) {
return cachedRoles; return cachedRoles;
} }
@ -40,7 +45,7 @@ export class RoleShorter extends BaseShorter {
roles.map<[string, APIRole]>(r => [r.id, r]), roles.map<[string, APIRole]>(r => [r.id, r]),
guildId, guildId,
); );
return roles.map(r => new GuildRole(this.client, r, guildId)); return roles;
} }
/** /**
@ -54,7 +59,7 @@ export class RoleShorter extends BaseShorter {
async edit(guildId: string, roleId: string, body: RESTPatchAPIGuildRoleJSONBody, reason?: string) { async edit(guildId: string, roleId: string, body: RESTPatchAPIGuildRoleJSONBody, reason?: string) {
const res = await this.client.proxy.guilds(guildId).roles(roleId).patch({ body, reason }); const res = await this.client.proxy.guilds(guildId).roles(roleId).patch({ body, reason });
await this.client.cache.roles?.setIfNI('Guilds', roleId, guildId, res); await this.client.cache.roles?.setIfNI('Guilds', roleId, guildId, res);
return new GuildRole(this.client, res, guildId); return Transformers.GuildRole(this.client, res, guildId);
} }
/** /**
@ -67,7 +72,7 @@ export class RoleShorter extends BaseShorter {
async delete(guildId: string, roleId: string, reason?: string) { async delete(guildId: string, roleId: string, reason?: string) {
const res = await this.client.proxy.guilds(guildId).roles(roleId).delete({ reason }); const res = await this.client.proxy.guilds(guildId).roles(roleId).delete({ reason });
this.client.cache.roles?.removeIfNI('Guilds', roleId, guildId); this.client.cache.roles?.removeIfNI('Guilds', roleId, guildId);
return new GuildRole(this.client, res, guildId); return Transformers.GuildRole(this.client, res, guildId);
} }
/** /**
@ -86,6 +91,6 @@ export class RoleShorter extends BaseShorter {
guildId, guildId,
); );
} }
return roles.map(x => new GuildRole(this.client, x, guildId)); return roles.map(x => Transformers.GuildRole(this.client, x, guildId));
} }
} }

View File

@ -1,27 +1,27 @@
import type { RESTPatchAPIGuildTemplateJSONBody, RESTPostAPIGuildTemplatesJSONBody } from 'discord-api-types/v10'; import type { RESTPatchAPIGuildTemplateJSONBody, RESTPostAPIGuildTemplatesJSONBody } from 'discord-api-types/v10';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { GuildTemplate } from '../..'; import { Transformers } from '../../client/transformers';
export class TemplateShorter extends BaseShorter { export class TemplateShorter extends BaseShorter {
fetch(code: string) { fetch(code: string) {
return this.client.proxy.guilds return this.client.proxy.guilds
.templates(code) .templates(code)
.get() .get()
.then(template => new GuildTemplate(this.client, template)); .then(template => Transformers.GuildTemplate(this.client, template));
} }
list(guildId: string) { list(guildId: string) {
return this.client.proxy return this.client.proxy
.guilds(guildId) .guilds(guildId)
.templates.get() .templates.get()
.then(list => list.map(template => new GuildTemplate(this.client, template))); .then(list => list.map(template => Transformers.GuildTemplate(this.client, template)));
} }
create(guildId: string, body: RESTPostAPIGuildTemplatesJSONBody) { create(guildId: string, body: RESTPostAPIGuildTemplatesJSONBody) {
return this.client.proxy return this.client.proxy
.guilds(guildId) .guilds(guildId)
.templates.post({ body }) .templates.post({ body })
.then(template => new GuildTemplate(this.client, template)); .then(template => Transformers.GuildTemplate(this.client, template));
} }
sync(guildId: string, code: string) { sync(guildId: string, code: string) {
@ -29,7 +29,7 @@ export class TemplateShorter extends BaseShorter {
.guilds(guildId) .guilds(guildId)
.templates(code) .templates(code)
.put({}) .put({})
.then(template => new GuildTemplate(this.client, template)); .then(template => Transformers.GuildTemplate(this.client, template));
} }
edit(guildId: string, code: string, body: RESTPatchAPIGuildTemplateJSONBody) { edit(guildId: string, code: string, body: RESTPatchAPIGuildTemplateJSONBody) {
@ -37,7 +37,7 @@ export class TemplateShorter extends BaseShorter {
.guilds(guildId) .guilds(guildId)
.templates(code) .templates(code)
.patch({ body }) .patch({ body })
.then(template => new GuildTemplate(this.client, template)); .then(template => Transformers.GuildTemplate(this.client, template));
} }
delete(guildId: string, code: string) { delete(guildId: string, code: string) {
@ -45,6 +45,6 @@ export class TemplateShorter extends BaseShorter {
.guilds(guildId) .guilds(guildId)
.templates(code) .templates(code)
.delete() .delete()
.then(template => new GuildTemplate(this.client, template)); .then(template => Transformers.GuildTemplate(this.client, template));
} }
} }

View File

@ -7,10 +7,10 @@ import type {
RESTPostAPIChannelThreadsJSONBody, RESTPostAPIChannelThreadsJSONBody,
RESTPostAPIGuildForumThreadsJSONBody, RESTPostAPIGuildForumThreadsJSONBody,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import type { ThreadChannel } from '../../structures';
import channelFrom from '../../structures/channels'; import channelFrom from '../../structures/channels';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import type { MakeRequired, When } from '../types/util'; import type { MakeRequired, When } from '../types/util';
import type { ThreadChannelStructure } from '../../client/transformers';
export class ThreadShorter extends BaseShorter { export class ThreadShorter extends BaseShorter {
/** /**
@ -29,7 +29,7 @@ export class ThreadShorter extends BaseShorter {
.channels(channelId) .channels(channelId)
.threads.post({ body, reason }) .threads.post({ body, reason })
// When testing this, discord returns the thread object, but in discord api types it does not. // When testing this, discord returns the thread object, but in discord api types it does not.
.then(thread => channelFrom(thread, this.client) as ThreadChannel) .then(thread => channelFrom(thread, this.client) as ThreadChannelStructure)
); );
} }
@ -44,7 +44,7 @@ export class ThreadShorter extends BaseShorter {
.channels(channelId) .channels(channelId)
.messages(messageId) .messages(messageId)
.threads.post({ body, reason }) .threads.post({ body, reason })
.then(thread => channelFrom(thread, this.client) as ThreadChannel); .then(thread => channelFrom(thread, this.client) as ThreadChannelStructure);
} }
async join(threadId: string) { async join(threadId: string) {
@ -56,7 +56,7 @@ export class ThreadShorter extends BaseShorter {
} }
async lock(threadId: string, locked = true, reason?: string) { async lock(threadId: string, locked = true, reason?: string) {
return this.edit(threadId, { locked }, reason).then(x => channelFrom(x, this.client) as ThreadChannel); return this.edit(threadId, { locked }, reason).then(x => channelFrom(x, this.client) as ThreadChannelStructure);
} }
async edit(threadId: string, body: RESTPatchAPIChannelJSONBody, reason?: string) { async edit(threadId: string, body: RESTPatchAPIChannelJSONBody, reason?: string) {
@ -98,7 +98,7 @@ export class ThreadShorter extends BaseShorter {
const data = await this.client.proxy.channels(channelId).threads.archived[type].get({ query }); const data = await this.client.proxy.channels(channelId).threads.archived[type].get({ query });
return { return {
threads: data.threads.map(thread => channelFrom(thread, this.client) as ThreadChannel), threads: data.threads.map(thread => channelFrom(thread, this.client) as ThreadChannelStructure),
members: data.members as GetAPIChannelThreadMemberResult[], members: data.members as GetAPIChannelThreadMemberResult[],
hasMore: data.has_more, hasMore: data.has_more,
}; };
@ -107,7 +107,7 @@ export class ThreadShorter extends BaseShorter {
async listJoinedArchivedPrivate(channelId: string, query?: RESTGetAPIChannelThreadsArchivedQuery) { async listJoinedArchivedPrivate(channelId: string, query?: RESTGetAPIChannelThreadsArchivedQuery) {
const data = await this.client.proxy.channels(channelId).users('@me').threads.archived.private.get({ query }); const data = await this.client.proxy.channels(channelId).users('@me').threads.archived.private.get({ query });
return { return {
threads: data.threads.map(thread => channelFrom(thread, this.client) as ThreadChannel), threads: data.threads.map(thread => channelFrom(thread, this.client) as ThreadChannelStructure),
members: data.members as GetAPIChannelThreadMemberResult[], members: data.members as GetAPIChannelThreadMemberResult[],
hasMore: data.has_more, hasMore: data.has_more,
}; };

View File

@ -1,4 +1,5 @@
import { BaseChannel, DMChannel, User } from '../../structures'; import { type DMChannelStructure, Transformers } from '../../client/transformers';
import { BaseChannel } from '../../structures';
import type { MessageCreateBodyRequest } from '../types/write'; import type { MessageCreateBodyRequest } from '../types/write';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
@ -6,30 +7,34 @@ export class UsersShorter extends BaseShorter {
async createDM(userId: string, force = false) { async createDM(userId: string, force = false) {
if (!force) { if (!force) {
const dm = await this.client.cache.channels?.get(userId); const dm = await this.client.cache.channels?.get(userId);
if (dm) return dm as DMChannel; if (dm) return dm as DMChannelStructure;
} }
const data = await this.client.proxy.users('@me').channels.post({ const data = await this.client.proxy.users('@me').channels.post({
body: { recipient_id: userId }, body: { recipient_id: userId },
}); });
await this.client.cache.channels?.set(userId, '@me', data); await this.client.cache.channels?.set(userId, '@me', data);
return new DMChannel(this.client, data); return Transformers.DMChannel(this.client, data);
} }
async deleteDM(userId: string, reason?: string) { async deleteDM(userId: string, reason?: string) {
const res = await this.client.proxy.channels(userId).delete({ reason }); const res = await this.client.proxy.channels(userId).delete({ reason });
await this.client.cache.channels?.removeIfNI(BaseChannel.__intent__('@me'), res.id, '@me'); await this.client.cache.channels?.removeIfNI(BaseChannel.__intent__('@me'), res.id, '@me');
return new DMChannel(this.client, res); return Transformers.DMChannel(this.client, res);
} }
async fetch(userId: string, force = false) { async fetch(userId: string, force = false) {
return Transformers.User(this.client, await this.raw(userId, force));
}
async raw(userId: string, force = false) {
if (!force) { if (!force) {
const user = await this.client.cache.users?.get(userId); const user = await this.client.cache.users?.raw(userId);
if (user) return user; if (user) return user;
} }
const data = await this.client.proxy.users(userId).get(); const data = await this.client.proxy.users(userId).get();
await this.client.cache.users?.patch(userId, data); await this.client.cache.users?.patch(userId, data);
return new User(this.client, data); return data;
} }
async write(userId: string, body: MessageCreateBodyRequest) { async write(userId: string, body: MessageCreateBodyRequest) {

View File

@ -7,19 +7,18 @@ import type {
import { resolveFiles } from '../../builders'; import { resolveFiles } from '../../builders';
import { import {
MessagesMethods, MessagesMethods,
Webhook,
WebhookMessage,
type MessageWebhookMethodEditParams, type MessageWebhookMethodEditParams,
type MessageWebhookMethodWriteParams, type MessageWebhookMethodWriteParams,
} from '../../structures'; } from '../../structures';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
import { Transformers } from '../../client/transformers';
export class WebhookShorter extends BaseShorter { export class WebhookShorter extends BaseShorter {
async create(channelId: string, body: RESTPostAPIChannelWebhookJSONBody) { async create(channelId: string, body: RESTPostAPIChannelWebhookJSONBody) {
const webhook = await this.client.proxy.channels(channelId).webhooks.post({ const webhook = await this.client.proxy.channels(channelId).webhooks.post({
body, body,
}); });
return new Webhook(this.client, webhook); return Transformers.Webhook(this.client, webhook);
} }
/** /**
* Deletes a webhook. * Deletes a webhook.
@ -50,12 +49,12 @@ export class WebhookShorter extends BaseShorter {
return this.client.proxy return this.client.proxy
.webhooks(webhookId)(options.token) .webhooks(webhookId)(options.token)
.patch({ body, reason: options.reason, auth: false }) .patch({ body, reason: options.reason, auth: false })
.then(webhook => new Webhook(this.client, webhook)); .then(webhook => Transformers.Webhook(this.client, webhook));
} }
return this.client.proxy return this.client.proxy
.webhooks(webhookId) .webhooks(webhookId)
.patch({ body, reason: options.reason }) .patch({ body, reason: options.reason })
.then(webhook => new Webhook(this.client, webhook)); .then(webhook => Transformers.Webhook(this.client, webhook));
} }
/** /**
@ -71,7 +70,7 @@ export class WebhookShorter extends BaseShorter {
} else { } else {
webhook = await this.client.proxy.webhooks(webhookId).get(); webhook = await this.client.proxy.webhooks(webhookId).get();
} }
return new Webhook(this.client, webhook); return Transformers.Webhook(this.client, webhook);
} }
/** /**
@ -92,7 +91,7 @@ export class WebhookShorter extends BaseShorter {
return this.client.proxy return this.client.proxy
.webhooks(webhookId)(token) .webhooks(webhookId)(token)
.post({ ...payload, files: parsedFiles, body: transformedBody }) .post({ ...payload, files: parsedFiles, body: transformedBody })
.then(m => (m?.id ? new WebhookMessage(this.client, m, webhookId, token) : null)); .then(m => (m?.id ? Transformers.WebhookMessage(this.client, m, webhookId, token) : null));
} }
/** /**
@ -119,7 +118,7 @@ export class WebhookShorter extends BaseShorter {
.webhooks(webhookId)(token) .webhooks(webhookId)(token)
.messages(messageId) .messages(messageId)
.patch({ ...json, auth: false, files: parsedFiles, body: transformedBody }) .patch({ ...json, auth: false, files: parsedFiles, body: transformedBody })
.then(m => new WebhookMessage(this.client, m, webhookId, token)); .then(m => Transformers.WebhookMessage(this.client, m, webhookId, token));
} }
/** /**
@ -147,17 +146,17 @@ export class WebhookShorter extends BaseShorter {
.webhooks(webhookId)(token) .webhooks(webhookId)(token)
.messages(messageId) .messages(messageId)
.get({ auth: false, query: { threadId } }); .get({ auth: false, query: { threadId } });
return message ? new WebhookMessage(this.client, message, webhookId, token) : undefined; return message ? Transformers.WebhookMessage(this.client, message, webhookId, token) : undefined;
} }
async listFromGuild(guildId: string) { async listFromGuild(guildId: string) {
const webhooks = await this.client.proxy.guilds(guildId).webhooks.get(); const webhooks = await this.client.proxy.guilds(guildId).webhooks.get();
return webhooks.map(webhook => new Webhook(this.client, webhook)); return webhooks.map(webhook => Transformers.Webhook(this.client, webhook));
} }
async listFromChannel(channelId: string) { async listFromChannel(channelId: string) {
const webhooks = await this.client.proxy.channels(channelId).webhooks.get(); const webhooks = await this.client.proxy.channels(channelId).webhooks.get();
return webhooks.map(webhook => new Webhook(this.client, webhook)); return webhooks.map(webhook => Transformers.Webhook(this.client, webhook));
} }
} }

View File

@ -79,6 +79,10 @@ export type IsStrictlyUndefined<T> = AuxIsStrictlyUndefined<T> extends true
export type If<T extends boolean, A, B = null> = T extends true ? A : B extends null ? A | null : B; export type If<T extends boolean, A, B = null> = T extends true ? A : B extends null ? A | null : B;
export type NulleableCoalising<A, B> = NonFalsy<A> extends never ? B : A;
export type TupleOr<A, T> = ValueOf<A> extends never ? A : TupleOr<ArrayFirsElement<T>, Tail<T>>;
export type PickPartial<T, K extends keyof T> = { export type PickPartial<T, K extends keyof T> = {
[P in keyof T]?: T[P] | undefined; [P in keyof T]?: T[P] | undefined;
} & { } & {

View File

@ -1,14 +1,20 @@
import type { ButtonStyle, ComponentType } from 'discord-api-types/v10'; import type {
import { Button, type ButtonStylesForID } from '../builders'; APIButtonComponentWithCustomId,
APIButtonComponentWithSKUId,
APIButtonComponentWithURL,
ButtonStyle,
ComponentType,
} from 'discord-api-types/v10';
import { Button } from '../builders';
import { BaseComponent } from './BaseComponent'; import { BaseComponent } from './BaseComponent';
export class LinkButtonComponent extends BaseComponent<ComponentType.Button> { export class LinkButtonComponent extends BaseComponent<ComponentType.Button> {
declare data: APIButtonComponentWithURL;
get style() { get style() {
return this.data.style as ButtonStyle.Link; return this.data.style;
} }
get url(): string { get url(): string {
// @ts-expect-error
return this.data.url; return this.data.url;
} }
@ -25,15 +31,20 @@ export class LinkButtonComponent extends BaseComponent<ComponentType.Button> {
} }
toBuilder() { toBuilder() {
return new Button<false>(this.data as never); return new Button(this.data);
} }
} }
export type ButtonStyleExludeLink = Exclude<ButtonStyle, ButtonStyle.Link>; export type ButtonStyleExludeLink = Exclude<ButtonStyle, ButtonStyle.Link>;
export class ButtonComponent extends BaseComponent<ComponentType.Button> { export class ButtonComponent extends BaseComponent<ComponentType.Button> {
declare data: APIButtonComponentWithCustomId;
get style() { get style() {
return this.data.style as ButtonStylesForID; return this.data.style;
}
get customId() {
return this.data.custom_id;
} }
get label() { get label() {
@ -49,6 +60,25 @@ export class ButtonComponent extends BaseComponent<ComponentType.Button> {
} }
toBuilder() { toBuilder() {
return new Button<true>(this.data as never); return new Button(this.data);
}
}
export class SKUButtonComponent extends BaseComponent<ComponentType.Button> {
declare data: APIButtonComponentWithSKUId;
get style() {
return this.data.style;
}
get skuId() {
return this.data.sku_id;
}
get disabled() {
return this.data.disabled;
}
toBuilder() {
return new Button(this.data as never);
} }
} }

View File

@ -4,15 +4,11 @@ import type {
ButtonInteraction, ButtonInteraction,
ChannelSelectMenuInteraction, ChannelSelectMenuInteraction,
ComponentCommand, ComponentCommand,
Guild,
GuildMember,
MentionableSelectMenuInteraction, MentionableSelectMenuInteraction,
Message,
ReturnCache, ReturnCache,
RoleSelectMenuInteraction, RoleSelectMenuInteraction,
StringSelectMenuInteraction, StringSelectMenuInteraction,
UserSelectMenuInteraction, UserSelectMenuInteraction,
WebhookMessage,
} from '..'; } from '..';
import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands'; import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands';
import { BaseContext } from '../commands/basecontext'; import { BaseContext } from '../commands/basecontext';
@ -24,6 +20,12 @@ import type {
UnionToTuple, UnionToTuple,
When, When,
} from '../common'; } from '../common';
import type {
GuildMemberStructure,
GuildStructure,
MessageStructure,
WebhookMessageStructure,
} from '../client/transformers';
export interface ComponentContext< export interface ComponentContext<
Type extends keyof ContextComponentCommandInteractionMap = keyof ContextComponentCommandInteractionMap, Type extends keyof ContextComponentCommandInteractionMap = keyof ContextComponentCommandInteractionMap,
@ -109,7 +111,7 @@ export class ComponentContext<
editOrReply<FR extends boolean = false>( editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest,
fetchReply?: FR, fetchReply?: FR,
): Promise<When<FR, WebhookMessage | Message, void | WebhookMessage | Message>> { ): Promise<When<FR, WebhookMessageStructure | MessageStructure, void | WebhookMessageStructure | MessageStructure>> {
return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply);
} }
@ -143,8 +145,8 @@ export class ComponentContext<
* @param mode - The mode to fetch the member. * @param mode - The mode to fetch the member.
* @returns A promise that resolves to the bot member. * @returns A promise that resolves to the bot member.
*/ */
me(mode?: 'rest' | 'flow'): Promise<GuildMember>; me(mode?: 'rest' | 'flow'): Promise<GuildMemberStructure>;
me(mode?: 'cache'): ReturnCache<GuildMember | undefined>; me(mode?: 'cache'): ReturnCache<GuildMemberStructure | undefined>;
me(mode: 'cache' | 'rest' | 'flow' = 'cache') { me(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId) if (!this.guildId)
return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve(); return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve();
@ -161,8 +163,8 @@ export class ComponentContext<
* @param mode - The mode to fetch the guild. * @param mode - The mode to fetch the guild.
* @returns A promise that resolves to the guild. * @returns A promise that resolves to the guild.
*/ */
guild(mode?: 'rest' | 'flow'): Promise<Guild<'cached' | 'api'> | undefined>; guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'> | undefined>;
guild(mode?: 'cache'): ReturnCache<Guild<'cached'> | undefined>; guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'cache') { guild(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId) if (!this.guildId)
return ( return (

View File

@ -1,6 +1,7 @@
import type { ComponentCallback, ListenerOptions, ModalSubmitCallback } from '../builders/types'; import type { ComponentCallback, ListenerOptions, ModalSubmitCallback } from '../builders/types';
import { LimitedCollection } from '../collection'; import { LimitedCollection } from '../collection';
import { BaseCommand, type RegisteredMiddlewares, type UsingClient } from '../commands'; import { BaseCommand, type RegisteredMiddlewares, type UsingClient } from '../commands';
import type { FileLoaded } from '../commands/handler';
import { BaseHandler, magicImport, type Logger, type OnFailCallback } from '../common'; import { BaseHandler, magicImport, type Logger, type OnFailCallback } from '../common';
import type { ComponentInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from '../structures'; import type { ComponentInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from '../structures';
import { ComponentCommand, InteractionCommandType } from './componentcommand'; import { ComponentCommand, InteractionCommandType } from './componentcommand';
@ -17,12 +18,15 @@ type COMPONENTS = {
__run: (customId: string | string[] | RegExp, callback: ComponentCallback) => any; __run: (customId: string | string[] | RegExp, callback: ComponentCallback) => any;
}; };
export type CollectorInteraction = ComponentInteraction | StringSelectMenuInteraction;
export type ComponentCommands = ComponentCommand | ModalCommand;
export class ComponentHandler extends BaseHandler { export class ComponentHandler extends BaseHandler {
onFail: OnFailCallback = err => this.logger.warn('<Client>.components.onFail', err); onFail: OnFailCallback = err => this.logger.warn('<Client>.components.onFail', err);
readonly values = new Map<string, COMPONENTS>(); readonly values = new Map<string, COMPONENTS>();
// 10 minutes timeout, because discord dont send an event when the user cancel the modal // 10 minutes timeout, because discord dont send an event when the user cancel the modal
readonly modals = new LimitedCollection<string, ModalSubmitCallback>({ expire: 60e3 * 10 }); readonly modals = new LimitedCollection<string, ModalSubmitCallback>({ expire: 60e3 * 10 });
readonly commands: (ComponentCommand | ModalCommand)[] = []; readonly commands: ComponentCommands[] = [];
protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts')); protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts'));
constructor( constructor(
@ -36,9 +40,10 @@ export class ComponentHandler extends BaseHandler {
messageId: string, messageId: string,
options: ListenerOptions = {}, options: ListenerOptions = {},
): { ): {
run< run<T extends CollectorInteraction = CollectorInteraction>(
T extends ComponentInteraction | StringSelectMenuInteraction = ComponentInteraction | StringSelectMenuInteraction, customId: string | string[] | RegExp,
>(customId: string | string[] | RegExp, callback: ComponentCallback<T>): any; callback: ComponentCallback<T>,
): any;
stop(reason?: string): any; stop(reason?: string): any;
} { } {
this.values.set(messageId, { this.values.set(messageId, {
@ -149,38 +154,41 @@ export class ComponentHandler extends BaseHandler {
this.deleteValue(id, 'messageDelete'); this.deleteValue(id, 'messageDelete');
} }
async load(componentsDir: string, instances?: { new (): ModalCommand | ComponentCommand }[]) { stablishDefaults(component: ComponentCommands) {
const paths = component.props ??= this.client.options.commands?.defaults?.props ?? {};
instances?.map(x => { const is = component instanceof ModalCommand ? 'modals' : 'components';
const i = new x(); component.onInternalError ??= this.client.options?.[is]?.defaults?.onInternalError;
return { file: x, path: i.__filePath ?? '*' }; component.onMiddlewaresError ??= this.client.options?.[is]?.defaults?.onMiddlewaresError;
}) ?? (await this.loadFilesK<{ new (): ModalCommand | ComponentCommand }>(await this.getFiles(componentsDir))); component.onRunError ??= this.client.options?.[is]?.defaults?.onRunError;
component.onAfterRun ??= this.client.options?.[is]?.defaults?.onAfterRun;
}
for (const value of paths) { async load(componentsDir: string) {
let component; const paths = await this.loadFilesK<FileLoaded<new () => ComponentCommands>>(await this.getFiles(componentsDir));
try {
component = this.callback(value.file); for (const { components, file } of paths.map(x => ({ components: this.onFile(x.file), file: x }))) {
if (!component) continue; if (!components) continue;
} catch (e) { for (const value of components) {
if (e instanceof Error && e.message.includes('is not a constructor')) { let component;
this.logger.warn( try {
`${value.path component = this.callback(value);
.split(process.cwd()) if (!component) continue;
.slice(1) } catch (e) {
.join(process.cwd())} doesn't export the class by \`export default <ComponentCommand>\``, if (e instanceof Error && e.message.includes('is not a constructor')) {
); this.logger.warn(
} else this.logger.warn(e, value); `${file.path
continue; .split(process.cwd())
.slice(1)
.join(process.cwd())} doesn't export the class by \`export default <ComponentCommand>\``,
);
} else this.logger.warn(e, value);
continue;
}
if (!(component instanceof ModalCommand || component instanceof ComponentCommand)) continue;
this.stablishDefaults(component);
component.__filePath = file.path;
this.commands.push(component);
} }
if (!(component instanceof ModalCommand || component instanceof ComponentCommand)) continue;
component.props ??= this.client.options.commands?.defaults?.props ?? {};
const is = component instanceof ModalCommand ? 'modals' : 'components';
component.onInternalError ??= this.client.options?.[is]?.defaults?.onInternalError;
component.onMiddlewaresError ??= this.client.options?.[is]?.defaults?.onMiddlewaresError;
component.onRunError ??= this.client.options?.[is]?.defaults?.onRunError;
component.onAfterRun ??= this.client.options?.[is]?.defaults?.onAfterRun;
component.__filePath = value.path;
this.commands.push(component);
} }
} }
@ -217,6 +225,45 @@ export class ComponentHandler extends BaseHandler {
} }
} }
async execute(i: ComponentCommands, context: ComponentContext | ModalContext) {
try {
const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares(
context,
(context.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares,
true,
);
if (resultRunGlobalMiddlewares.pass) {
return;
}
if ('error' in resultRunGlobalMiddlewares) {
return i.onMiddlewaresError?.(context as never, resultRunGlobalMiddlewares.error ?? 'Unknown error');
}
const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false);
if (resultRunMiddlewares.pass) {
return;
}
if ('error' in resultRunMiddlewares) {
return i.onMiddlewaresError?.(context as never, resultRunMiddlewares.error ?? 'Unknown error');
}
try {
await i.run(context as never);
await i.onAfterRun?.(context as never, undefined);
} catch (error) {
await i.onRunError?.(context as never, error);
await i.onAfterRun?.(context as never, error);
}
} catch (error) {
try {
await i.onInternalError?.(this.client, error);
} catch (e) {
// supress error
this.logger.error(e);
}
}
}
async executeComponent(context: ComponentContext) { async executeComponent(context: ComponentContext) {
for (const i of this.commands) { for (const i of this.commands) {
try { try {
@ -226,42 +273,7 @@ export class ComponentHandler extends BaseHandler {
(await i.filter(context)) (await i.filter(context))
) { ) {
context.command = i; context.command = i;
try { await this.execute(i, context);
const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares(
context,
(context.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares,
true,
);
if (resultRunGlobalMiddlewares.pass) {
return;
}
if ('error' in resultRunGlobalMiddlewares) {
return i.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error');
}
const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false);
if (resultRunMiddlewares.pass) {
return;
}
if ('error' in resultRunMiddlewares) {
return i.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error');
}
try {
await i.run(context);
await i.onAfterRun?.(context, undefined);
} catch (error) {
await i.onRunError?.(context, error);
await i.onAfterRun?.(context, error);
}
} catch (error) {
try {
await i.onInternalError?.(this.client, error);
} catch {
// supress error
}
}
break;
} }
} catch (e) { } catch (e) {
await this.onFail(e); await this.onFail(e);
@ -274,42 +286,7 @@ export class ComponentHandler extends BaseHandler {
try { try {
if (i.type === InteractionCommandType.MODAL && (await i.filter(context))) { if (i.type === InteractionCommandType.MODAL && (await i.filter(context))) {
context.command = i; context.command = i;
try { await this.execute(i, context);
const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares(
context,
(context.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares,
true,
);
if (resultRunGlobalMiddlewares.pass) {
return;
}
if ('error' in resultRunGlobalMiddlewares) {
return i.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error');
}
const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false);
if (resultRunMiddlewares.pass) {
return;
}
if ('error' in resultRunMiddlewares) {
return i.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error');
}
try {
await i.run(context);
await i.onAfterRun?.(context, undefined);
} catch (error) {
await i.onRunError?.(context, error);
await i.onAfterRun?.(context, error);
}
} catch (error) {
try {
await i.onInternalError?.(this.client, error);
} catch {
// supress error
}
}
break;
} }
} catch (e) { } catch (e) {
await this.onFail(e); await this.onFail(e);
@ -317,9 +294,11 @@ export class ComponentHandler extends BaseHandler {
} }
} }
setHandlers({ callback }: { callback: ComponentHandler['callback'] }) { onFile(file: FileLoaded<new () => ComponentCommands>): (new () => ComponentCommands)[] | undefined {
this.callback = callback; return file.default ? [file.default] : undefined;
} }
callback = (file: { new (): ModalCommand | ComponentCommand }): ModalCommand | ComponentCommand | false => new file(); callback(file: { new (): ComponentCommands }): ComponentCommands | false {
return new file();
}
} }

View File

@ -1,6 +1,6 @@
import { type APIMessageActionRowComponent, ButtonStyle, ComponentType } from 'discord-api-types/v10'; import { type APIMessageActionRowComponent, ButtonStyle, ComponentType } from 'discord-api-types/v10';
import { BaseComponent } from './BaseComponent'; import { BaseComponent } from './BaseComponent';
import { ButtonComponent, LinkButtonComponent } from './ButtonComponent'; import { ButtonComponent, LinkButtonComponent, SKUButtonComponent } from './ButtonComponent';
import { ChannelSelectMenuComponent } from './ChannelSelectMenuComponent'; import { ChannelSelectMenuComponent } from './ChannelSelectMenuComponent';
import { MentionableSelectMenuComponent } from './MentionableSelectMenuComponent'; import { MentionableSelectMenuComponent } from './MentionableSelectMenuComponent';
import { RoleSelectMenuComponent } from './RoleSelectMenuComponent'; import { RoleSelectMenuComponent } from './RoleSelectMenuComponent';
@ -11,6 +11,7 @@ import { UserSelectMenuComponent } from './UserSelectMenuComponent';
export type MessageComponents = export type MessageComponents =
| ButtonComponent | ButtonComponent
| LinkButtonComponent | LinkButtonComponent
| SKUButtonComponent
| RoleSelectMenuComponent | RoleSelectMenuComponent
| UserSelectMenuComponent | UserSelectMenuComponent
| StringSelectMenuComponent | StringSelectMenuComponent
@ -39,6 +40,9 @@ export function componentFactory(
if (component.style === ButtonStyle.Link) { if (component.style === ButtonStyle.Link) {
return new LinkButtonComponent(component); return new LinkButtonComponent(component);
} }
if (component.style === ButtonStyle.Premium) {
return new SKUButtonComponent(component);
}
return new ButtonComponent(component); return new ButtonComponent(component);
case ComponentType.ChannelSelect: case ComponentType.ChannelSelect:
return new ChannelSelectMenuComponent(component); return new ChannelSelectMenuComponent(component);

View File

@ -1,14 +1,5 @@
import { MessageFlags } from 'discord-api-types/v10'; import { MessageFlags } from 'discord-api-types/v10';
import type { import type { AllChannels, ModalCommand, ModalSubmitInteraction, ReturnCache } from '..';
AllChannels,
Guild,
GuildMember,
Message,
ModalCommand,
ModalSubmitInteraction,
ReturnCache,
WebhookMessage,
} from '..';
import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands'; import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands';
import { BaseContext } from '../commands/basecontext'; import { BaseContext } from '../commands/basecontext';
import type { import type {
@ -18,6 +9,12 @@ import type {
UnionToTuple, UnionToTuple,
When, When,
} from '../common'; } from '../common';
import type {
GuildMemberStructure,
GuildStructure,
MessageStructure,
WebhookMessageStructure,
} from '../client/transformers';
export interface ModalContext extends BaseContext, ExtendContext {} export interface ModalContext extends BaseContext, ExtendContext {}
@ -90,7 +87,7 @@ export class ModalContext<M extends keyof RegisteredMiddlewares = never> extends
editOrReply<FR extends boolean = false>( editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest, body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest,
fetchReply?: FR, fetchReply?: FR,
): Promise<When<FR, WebhookMessage | Message, void | WebhookMessage | Message>> { ): Promise<When<FR, WebhookMessageStructure | MessageStructure, void | WebhookMessageStructure | MessageStructure>> {
return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply); return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply);
} }
@ -125,8 +122,8 @@ export class ModalContext<M extends keyof RegisteredMiddlewares = never> extends
* @param mode - The mode to fetch the member. * @param mode - The mode to fetch the member.
* @returns A promise that resolves to the bot member. * @returns A promise that resolves to the bot member.
*/ */
me(mode?: 'rest' | 'flow'): Promise<GuildMember>; me(mode?: 'rest' | 'flow'): Promise<GuildMemberStructure>;
me(mode?: 'cache'): ReturnCache<GuildMember | undefined>; me(mode?: 'cache'): ReturnCache<GuildMemberStructure | undefined>;
me(mode: 'cache' | 'rest' | 'flow' = 'cache') { me(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId) if (!this.guildId)
return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve(); return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve();
@ -143,8 +140,8 @@ export class ModalContext<M extends keyof RegisteredMiddlewares = never> extends
* @param mode - The mode to fetch the guild. * @param mode - The mode to fetch the guild.
* @returns A promise that resolves to the guild. * @returns A promise that resolves to the guild.
*/ */
guild(mode?: 'rest' | 'flow'): Promise<Guild<'cached' | 'api'> | undefined>; guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'> | undefined>;
guild(mode?: 'cache'): ReturnCache<Guild<'cached'> | undefined>; guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'cache') { guild(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId) if (!this.guildId)
return ( return (

View File

@ -9,6 +9,7 @@ import { BaseHandler, ReplaceRegex, magicImport, type MakeRequired, type SnakeCa
import type { ClientEvents } from '../events/hooks'; import type { ClientEvents } from '../events/hooks';
import * as RawEvents from '../events/hooks'; import * as RawEvents from '../events/hooks';
import type { ClientEvent, CustomEvents, CustomEventsKeys, ClientNameEvents } from './event'; import type { ClientEvent, CustomEvents, CustomEventsKeys, ClientNameEvents } from './event';
import type { FileLoaded } from '../commands/handler';
export type EventValue = MakeRequired<ClientEvent, '__filePath'> & { fired?: boolean }; export type EventValue = MakeRequired<ClientEvent, '__filePath'> & { fired?: boolean };
@ -25,25 +26,29 @@ export class EventHandler extends BaseHandler {
values: Partial<Record<GatewayEvents | CustomEventsKeys, EventValue>> = {}; values: Partial<Record<GatewayEvents | CustomEventsKeys, EventValue>> = {};
async load(eventsDir: string, instances?: { file: ClientEvent; path: string }[]) { async load(eventsDir: string) {
const discordEvents = Object.keys(RawEvents).map(x => ReplaceRegex.camel(x.toLowerCase())) as ClientNameEvents[]; const discordEvents = Object.keys(RawEvents).map(x => ReplaceRegex.camel(x.toLowerCase())) as ClientNameEvents[];
const paths = await this.loadFilesK<{ file: ClientEvent }>(await this.getFiles(eventsDir));
for (const i of instances ?? (await this.loadFilesK<ClientEvent>(await this.getFiles(eventsDir)))) { for (const { events, file } of paths.map(x => ({ events: this.onFile(x.file), file: x }))) {
const instance = this.callback(i.file); if (!events) continue;
if (!instance) continue; for (const i of events) {
if (typeof instance?.run !== 'function') { const instance = this.callback(i);
this.logger.warn( if (!instance) continue;
i.path.split(process.cwd()).slice(1).join(process.cwd()), if (typeof instance?.run !== 'function') {
'Missing run function, use `export default {...}` syntax', this.logger.warn(
); file.path.split(process.cwd()).slice(1).join(process.cwd()),
continue; 'Missing run function, use `export default {...}` syntax',
);
continue;
}
instance.__filePath = file.path;
this.values[
discordEvents.includes(instance.data.name)
? (ReplaceRegex.snake(instance.data.name).toUpperCase() as GatewayEvents)
: (instance.data.name as CustomEventsKeys)
] = instance as EventValue;
} }
instance.__filePath = i.path;
this.values[
discordEvents.includes(instance.data.name)
? (ReplaceRegex.snake(instance.data.name).toUpperCase() as GatewayEvents)
: (instance.data.name as CustomEventsKeys)
] = instance as EventValue;
} }
} }
@ -53,7 +58,7 @@ export class EventHandler extends BaseHandler {
{ {
const { d: data } = args[0] as GatewayMessageCreateDispatch; const { d: data } = args[0] as GatewayMessageCreateDispatch;
if (args[1].components?.values.has(data.interaction_metadata?.id ?? data.id)) { if (args[1].components?.values.has(data.interaction_metadata?.id ?? data.id)) {
args[1].components.values.get(data.interaction_metadata?.id ?? data.id)!.messageId = data.id; args[1].components.values.get(data.interaction_metadata!.id ?? data.id)!.messageId = data.id;
} }
} }
break; break;
@ -81,29 +86,33 @@ export class EventHandler extends BaseHandler {
} }
await Promise.all([ await Promise.all([
this.runEvent(args[0].t, args[1], args[0].d, args[2]), this.runEvent(args[0].t as never, args[1], args[0].d, args[2]),
this.client.collectors.run(args[0].t, args[0].d), this.client.collectors.run(args[0].t as never, args[0].d as never),
]); ]);
} }
async runEvent(name: GatewayEvents, client: Client | WorkerClient, packet: any, shardId: number) { async runEvent(name: GatewayEvents, client: Client | WorkerClient, packet: any, shardId: number, runCache = true) {
const Event = this.values[name]; const Event = this.values[name];
if (!Event) { if (!Event) {
return this.client.cache.onPacket({ return runCache
t: name, ? this.client.cache.onPacket({
d: packet, t: name,
} as GatewayDispatchPayload); d: packet,
} as GatewayDispatchPayload)
: undefined;
} }
try { try {
if (Event.data.once && Event.fired) { if (Event.data.once && Event.fired) {
return this.client.cache.onPacket({ return runCache
t: name, ? this.client.cache.onPacket({
d: packet, t: name,
} as GatewayDispatchPayload); d: packet,
} as GatewayDispatchPayload)
: undefined;
} }
Event.fired = true; Event.fired = true;
const hook = await RawEvents[name]?.(client, packet as never); const hook = await RawEvents[name]?.(client, packet as never);
if (name !== 'RAW') if (runCache)
await this.client.cache.onPacket({ await this.client.cache.onPacket({
t: name, t: name,
d: packet, d: packet,
@ -153,8 +162,8 @@ export class EventHandler extends BaseHandler {
} }
} }
setHandlers({ callback }: { callback: EventHandler['callback'] }) { onFile(file: FileLoaded<ClientEvent>): ClientEvent[] | undefined {
this.callback = callback; return file.default ? [file.default] : undefined;
} }
callback = (file: ClientEvent): ClientEvent | false => file; callback = (file: ClientEvent): ClientEvent | false => file;

View File

@ -5,8 +5,8 @@ import type {
GatewayAutoModerationRuleUpdateDispatchData, GatewayAutoModerationRuleUpdateDispatchData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { toCamelCase } from '../../common'; import { toCamelCase } from '../../common';
import { AutoModerationRule } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import { Transformers } from '../../client/transformers';
export const AUTO_MODERATION_ACTION_EXECUTION = ( export const AUTO_MODERATION_ACTION_EXECUTION = (
_self: UsingClient, _self: UsingClient,
@ -16,13 +16,13 @@ export const AUTO_MODERATION_ACTION_EXECUTION = (
}; };
export const AUTO_MODERATION_RULE_CREATE = (self: UsingClient, data: GatewayAutoModerationRuleCreateDispatchData) => { export const AUTO_MODERATION_RULE_CREATE = (self: UsingClient, data: GatewayAutoModerationRuleCreateDispatchData) => {
return new AutoModerationRule(self, data); return Transformers.AutoModerationRule(self, data);
}; };
export const AUTO_MODERATION_RULE_DELETE = (self: UsingClient, data: GatewayAutoModerationRuleDeleteDispatchData) => { export const AUTO_MODERATION_RULE_DELETE = (self: UsingClient, data: GatewayAutoModerationRuleDeleteDispatchData) => {
return new AutoModerationRule(self, data); return Transformers.AutoModerationRule(self, data);
}; };
export const AUTO_MODERATION_RULE_UPDATE = (self: UsingClient, data: GatewayAutoModerationRuleUpdateDispatchData) => { export const AUTO_MODERATION_RULE_UPDATE = (self: UsingClient, data: GatewayAutoModerationRuleUpdateDispatchData) => {
return new AutoModerationRule(self, data); return Transformers.AutoModerationRule(self, data);
}; };

View File

@ -1,10 +1,10 @@
import type { ClientUserStructure } from '../../client/transformers';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import type { ClientUser } from '../../structures';
export const BOT_READY = (_self: UsingClient, me: ClientUser) => { export const BOT_READY = (_self: UsingClient, me: ClientUserStructure) => {
return me; return me;
}; };
export const WORKER_READY = (_self: UsingClient, me: ClientUser) => { export const WORKER_READY = (_self: UsingClient, me: ClientUserStructure) => {
return me; return me;
}; };

View File

@ -1,9 +1,9 @@
import type { GatewayDispatchPayload, GatewayReadyDispatchData, GatewayResumedDispatch } from 'discord-api-types/v10'; import type { GatewayDispatchPayload, GatewayReadyDispatchData, GatewayResumedDispatch } from 'discord-api-types/v10';
import { ClientUser } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import { Transformers } from '../../client/transformers';
export const READY = (self: UsingClient, data: GatewayReadyDispatchData) => { export const READY = (self: UsingClient, data: GatewayReadyDispatchData) => {
return new ClientUser(self, data.user, data.application); return Transformers.ClientUser(self, data.user, data.application);
}; };
export const RESUMED = (_self: UsingClient, _data: GatewayResumedDispatch['d']) => { export const RESUMED = (_self: UsingClient, _data: GatewayResumedDispatch['d']) => {

View File

@ -22,23 +22,28 @@ import type {
GatewayGuildUpdateDispatchData, GatewayGuildUpdateDispatchData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { toCamelCase } from '../../common'; import { toCamelCase } from '../../common';
import { Guild, GuildEmoji, GuildMember, GuildRole, Sticker, User } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import {
type GuildMemberStructure,
type GuildRoleStructure,
type GuildStructure,
Transformers,
} from '../../client/transformers';
export const GUILD_AUDIT_LOG_ENTRY_CREATE = (_self: UsingClient, data: GatewayGuildAuditLogEntryCreateDispatchData) => { export const GUILD_AUDIT_LOG_ENTRY_CREATE = (_self: UsingClient, data: GatewayGuildAuditLogEntryCreateDispatchData) => {
return toCamelCase(data); return toCamelCase(data);
}; };
export const GUILD_BAN_ADD = (self: UsingClient, data: GatewayGuildBanAddDispatchData) => { export const GUILD_BAN_ADD = (self: UsingClient, data: GatewayGuildBanAddDispatchData) => {
return { ...toCamelCase(data), user: new User(self, data.user) }; return { ...toCamelCase(data), user: Transformers.User(self, data.user) };
}; };
export const GUILD_BAN_REMOVE = (self: UsingClient, data: GatewayGuildBanRemoveDispatchData) => { export const GUILD_BAN_REMOVE = (self: UsingClient, data: GatewayGuildBanRemoveDispatchData) => {
return { ...toCamelCase(data), user: new User(self, data.user) }; return { ...toCamelCase(data), user: Transformers.User(self, data.user) };
}; };
export const GUILD_CREATE = (self: UsingClient, data: GatewayGuildCreateDispatchData) => { export const GUILD_CREATE = (self: UsingClient, data: GatewayGuildCreateDispatchData) => {
return new Guild<'create'>(self, data); return Transformers.Guild<'create'>(self, data);
}; };
export const GUILD_DELETE = async (self: UsingClient, data: GatewayGuildDeleteDispatchData) => { export const GUILD_DELETE = async (self: UsingClient, data: GatewayGuildDeleteDispatchData) => {
@ -48,7 +53,7 @@ export const GUILD_DELETE = async (self: UsingClient, data: GatewayGuildDeleteDi
export const GUILD_EMOJIS_UPDATE = (self: UsingClient, data: GatewayGuildEmojisUpdateDispatchData) => { export const GUILD_EMOJIS_UPDATE = (self: UsingClient, data: GatewayGuildEmojisUpdateDispatchData) => {
return { return {
...toCamelCase(data), ...toCamelCase(data),
emojis: data.emojis.map(x => new GuildEmoji(self, x, data.guild_id)), emojis: data.emojis.map(x => Transformers.GuildEmoji(self, x, data.guild_id)),
}; };
}; };
@ -57,26 +62,26 @@ export const GUILD_INTEGRATIONS_UPDATE = (_self: UsingClient, data: GatewayGuild
}; };
export const GUILD_MEMBER_ADD = (self: UsingClient, data: GatewayGuildMemberAddDispatchData) => { export const GUILD_MEMBER_ADD = (self: UsingClient, data: GatewayGuildMemberAddDispatchData) => {
return new GuildMember(self, data, data.user!, data.guild_id); return Transformers.GuildMember(self, data, data.user!, data.guild_id);
}; };
export const GUILD_MEMBER_REMOVE = (self: UsingClient, data: GatewayGuildMemberRemoveDispatchData) => { export const GUILD_MEMBER_REMOVE = (self: UsingClient, data: GatewayGuildMemberRemoveDispatchData) => {
return { ...toCamelCase(data), user: new User(self, data.user) }; return { ...toCamelCase(data), user: Transformers.User(self, data.user) };
}; };
export const GUILD_MEMBERS_CHUNK = (self: UsingClient, data: GatewayGuildMembersChunkDispatchData) => { export const GUILD_MEMBERS_CHUNK = (self: UsingClient, data: GatewayGuildMembersChunkDispatchData) => {
return { return {
...toCamelCase(data), ...toCamelCase(data),
members: data.members.map(x => new GuildMember(self, x, x.user!, data.guild_id)), members: data.members.map(x => Transformers.GuildMember(self, x, x.user!, data.guild_id)),
}; };
}; };
export const GUILD_MEMBER_UPDATE = async ( export const GUILD_MEMBER_UPDATE = async (
self: UsingClient, self: UsingClient,
data: GatewayGuildMemberUpdateDispatchData, data: GatewayGuildMemberUpdateDispatchData,
): Promise<[member: GuildMember, old?: GuildMember]> => { ): Promise<[member: GuildMemberStructure, old?: GuildMemberStructure]> => {
const oldData = await self.cache.members?.get(data.user.id, data.guild_id); const oldData = await self.cache.members?.get(data.user.id, data.guild_id);
return [new GuildMember(self, data, data.user, data.guild_id), oldData]; return [Transformers.GuildMember(self, data, data.user, data.guild_id), oldData];
}; };
export const GUILD_SCHEDULED_EVENT_CREATE = ( export const GUILD_SCHEDULED_EVENT_CREATE = (
@ -115,7 +120,7 @@ export const GUILD_SCHEDULED_EVENT_USER_REMOVE = (
}; };
export const GUILD_ROLE_CREATE = (self: UsingClient, data: GatewayGuildRoleCreateDispatchData) => { export const GUILD_ROLE_CREATE = (self: UsingClient, data: GatewayGuildRoleCreateDispatchData) => {
return new GuildRole(self, data.role, data.guild_id); return Transformers.GuildRole(self, data.role, data.guild_id);
}; };
export const GUILD_ROLE_DELETE = async (self: UsingClient, data: GatewayGuildRoleDeleteDispatchData) => { export const GUILD_ROLE_DELETE = async (self: UsingClient, data: GatewayGuildRoleDeleteDispatchData) => {
@ -125,20 +130,20 @@ export const GUILD_ROLE_DELETE = async (self: UsingClient, data: GatewayGuildRol
export const GUILD_ROLE_UPDATE = async ( export const GUILD_ROLE_UPDATE = async (
self: UsingClient, self: UsingClient,
data: GatewayGuildRoleUpdateDispatchData, data: GatewayGuildRoleUpdateDispatchData,
): Promise<[role: GuildRole, old?: GuildRole]> => { ): Promise<[role: GuildRoleStructure, old?: GuildRoleStructure]> => {
return [new GuildRole(self, data.role, data.guild_id), await self.cache.roles?.get(data.role.id)]; return [Transformers.GuildRole(self, data.role, data.guild_id), await self.cache.roles?.get(data.role.id)];
}; };
export const GUILD_STICKERS_UPDATE = (self: UsingClient, data: GatewayGuildStickersUpdateDispatchData) => { export const GUILD_STICKERS_UPDATE = (self: UsingClient, data: GatewayGuildStickersUpdateDispatchData) => {
return { return {
...toCamelCase(data), ...toCamelCase(data),
stickers: data.stickers.map(x => new Sticker(self, x)), stickers: data.stickers.map(x => Transformers.Sticker(self, x)),
}; };
}; };
export const GUILD_UPDATE = async ( export const GUILD_UPDATE = async (
self: UsingClient, self: UsingClient,
data: GatewayGuildUpdateDispatchData, data: GatewayGuildUpdateDispatchData,
): Promise<[guild: Guild, old?: Guild<'cached'>]> => { ): Promise<[guild: GuildStructure, old?: GuildStructure<'cached'>]> => {
return [new Guild(self, data), await self.cache.guilds?.get(data.id)]; return [Transformers.Guild(self, data), await self.cache.guilds?.get(data.id)];
}; };

View File

@ -4,14 +4,14 @@ import type {
GatewayIntegrationUpdateDispatchData, GatewayIntegrationUpdateDispatchData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { toCamelCase } from '../../common'; import { toCamelCase } from '../../common';
import { User } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import { Transformers } from '../../client/transformers';
export const INTEGRATION_CREATE = (self: UsingClient, data: GatewayIntegrationCreateDispatchData) => { export const INTEGRATION_CREATE = (self: UsingClient, data: GatewayIntegrationCreateDispatchData) => {
return data.user return data.user
? { ? {
...toCamelCase(data), ...toCamelCase(data),
user: new User(self, data.user!), user: Transformers.User(self, data.user!),
} }
: toCamelCase(data); : toCamelCase(data);
}; };
@ -20,7 +20,7 @@ export const INTEGRATION_UPDATE = (self: UsingClient, data: GatewayIntegrationUp
return data.user return data.user
? { ? {
...toCamelCase(data), ...toCamelCase(data),
user: new User(self, data.user!), user: Transformers.User(self, data.user!),
} }
: toCamelCase(data); : toCamelCase(data);
}; };

View File

@ -11,17 +11,17 @@ import type {
GatewayMessageUpdateDispatchData, GatewayMessageUpdateDispatchData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { type MakeRequired, type PartialClass, toCamelCase, type ObjectToLower } from '../../common'; import { type MakeRequired, type PartialClass, toCamelCase, type ObjectToLower } from '../../common';
import { Message } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import { type MessageStructure, Transformers } from '../../client/transformers';
export const MESSAGE_CREATE = (self: UsingClient, data: GatewayMessageCreateDispatchData) => { export const MESSAGE_CREATE = (self: UsingClient, data: GatewayMessageCreateDispatchData) => {
return new Message(self, data); return Transformers.Message(self, data);
}; };
export const MESSAGE_DELETE = async ( export const MESSAGE_DELETE = async (
self: UsingClient, self: UsingClient,
data: GatewayMessageDeleteDispatchData, data: GatewayMessageDeleteDispatchData,
): Promise<Message | ObjectToLower<GatewayMessageDeleteDispatchData>> => { ): Promise<MessageStructure | ObjectToLower<GatewayMessageDeleteDispatchData>> => {
return (await self.cache.messages?.get(data.id)) ?? toCamelCase(data); return (await self.cache.messages?.get(data.id)) ?? toCamelCase(data);
}; };
@ -57,7 +57,7 @@ export const MESSAGE_UPDATE = async (
): Promise< ): Promise<
[ [
message: MakeRequired< message: MakeRequired<
PartialClass<Message>, PartialClass<MessageStructure>, //sus
| 'id' | 'id'
| 'channelId' | 'channelId'
| 'createdAt' | 'createdAt'
@ -71,14 +71,10 @@ export const MESSAGE_UPDATE = async (
| 'user' | 'user'
| 'author' | 'author'
>, >,
old: undefined | Message, old: undefined | MessageStructure,
] ]
> => { > => {
return [new Message(self, data as APIMessage), await self.cache.messages?.get(data.id)]; return [Transformers.Message(self, data as APIMessage), await self.cache.messages?.get(data.id)];
};
export const MESSAGE_POLL_VOTE_ADD = (_: UsingClient, data: GatewayMessagePollVoteDispatchData) => {
return toCamelCase(data);
}; };
export const MESSAGE_POLL_VOTE_REMOVE = (_: UsingClient, data: GatewayMessagePollVoteDispatchData) => { export const MESSAGE_POLL_VOTE_REMOVE = (_: UsingClient, data: GatewayMessagePollVoteDispatchData) => {

View File

@ -3,5 +3,5 @@ import type { UsingClient } from '../../commands';
import { toCamelCase } from '../../common'; import { toCamelCase } from '../../common';
export const PRESENCE_UPDATE = async (self: UsingClient, data: GatewayPresenceUpdateDispatchData) => { export const PRESENCE_UPDATE = async (self: UsingClient, data: GatewayPresenceUpdateDispatchData) => {
return [toCamelCase(data), await self.cache.presences?.get(data.user.id)]; return [toCamelCase(data), await self.cache.presences?.get(data.user.id)] as const;
}; };

View File

@ -7,15 +7,15 @@ import type {
GatewayThreadUpdateDispatchData, GatewayThreadUpdateDispatchData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { toCamelCase } from '../../common'; import { toCamelCase } from '../../common';
import { ThreadChannel } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import { type ThreadChannelStructure, Transformers } from '../../client/transformers';
export const THREAD_CREATE = (self: UsingClient, data: GatewayThreadCreateDispatchData) => { export const THREAD_CREATE = (self: UsingClient, data: GatewayThreadCreateDispatchData) => {
return new ThreadChannel(self, data); return Transformers.ThreadChannel(self, data);
}; };
export const THREAD_DELETE = (self: UsingClient, data: GatewayThreadDeleteDispatchData) => { export const THREAD_DELETE = (self: UsingClient, data: GatewayThreadDeleteDispatchData) => {
return new ThreadChannel(self, data); return Transformers.ThreadChannel(self, data);
}; };
export const THREAD_LIST_SYNC = (_self: UsingClient, data: GatewayThreadListSyncDispatchData) => { export const THREAD_LIST_SYNC = (_self: UsingClient, data: GatewayThreadListSyncDispatchData) => {
@ -33,6 +33,6 @@ export const THREAD_MEMBERS_UPDATE = (_self: UsingClient, data: GatewayThreadMem
export const THREAD_UPDATE = async ( export const THREAD_UPDATE = async (
self: UsingClient, self: UsingClient,
data: GatewayThreadUpdateDispatchData, data: GatewayThreadUpdateDispatchData,
): Promise<[thread: ThreadChannel, old?: ThreadChannel]> => { ): Promise<[thread: ThreadChannelStructure, old?: ThreadChannelStructure]> => {
return [new ThreadChannel(self, data), await self.cache.threads?.get(data.id)]; return [Transformers.ThreadChannel(self, data), await self.cache.threads?.get(data.id)];
}; };

View File

@ -1,13 +1,13 @@
import type { GatewayTypingStartDispatchData } from 'discord-api-types/v10'; import type { GatewayTypingStartDispatchData } from 'discord-api-types/v10';
import { toCamelCase } from '../../common'; import { toCamelCase } from '../../common';
import { GuildMember } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import { Transformers } from '../../client/transformers';
export const TYPING_START = (self: UsingClient, data: GatewayTypingStartDispatchData) => { export const TYPING_START = (self: UsingClient, data: GatewayTypingStartDispatchData) => {
return data.member return data.member
? { ? {
...toCamelCase(data), ...toCamelCase(data),
member: new GuildMember(self, data.member, data.member.user!, data.guild_id!), member: Transformers.GuildMember(self, data.member, data.member.user!, data.guild_id!),
} }
: toCamelCase(data); : toCamelCase(data);
}; };

View File

@ -1,10 +1,10 @@
import type { GatewayUserUpdateDispatchData } from 'discord-api-types/v10'; import type { GatewayUserUpdateDispatchData } from 'discord-api-types/v10';
import { User } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import { Transformers, type UserStructure } from '../../client/transformers';
export const USER_UPDATE = async ( export const USER_UPDATE = async (
self: UsingClient, self: UsingClient,
data: GatewayUserUpdateDispatchData, data: GatewayUserUpdateDispatchData,
): Promise<[user: User, old?: User]> => { ): Promise<[user: UserStructure, old?: UserStructure]> => {
return [new User(self, data), await self.cache.users?.get(data.id)]; return [Transformers.User(self, data), await self.cache.users?.get(data.id)];
}; };

View File

@ -1,7 +1,7 @@
import type { GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData } from '../../types'; import type { GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData } from '../../types';
import { toCamelCase } from '../../common'; import { toCamelCase } from '../../common';
import { VoiceState } from '../../structures';
import type { UsingClient } from '../../commands'; import type { UsingClient } from '../../commands';
import { Transformers, type VoiceStateStructure } from '../../client/transformers';
export const VOICE_SERVER_UPDATE = (_self: UsingClient, data: GatewayVoiceServerUpdateDispatchData) => { export const VOICE_SERVER_UPDATE = (_self: UsingClient, data: GatewayVoiceServerUpdateDispatchData) => {
return toCamelCase(data); return toCamelCase(data);
@ -10,7 +10,7 @@ export const VOICE_SERVER_UPDATE = (_self: UsingClient, data: GatewayVoiceServer
export const VOICE_STATE_UPDATE = async ( export const VOICE_STATE_UPDATE = async (
self: UsingClient, self: UsingClient,
data: GatewayVoiceStateUpdateDispatchData, data: GatewayVoiceStateUpdateDispatchData,
): Promise<[VoiceState] | [state: VoiceState, old?: VoiceState]> => { ): Promise<[state: VoiceStateStructure] | [state: VoiceStateStructure, old?: VoiceStateStructure]> => {
if (!data.guild_id) return [new VoiceState(self, data)]; if (!data.guild_id) return [Transformers.VoiceState(self, data)];
return [new VoiceState(self, data), await self.cache.voiceStates?.get(data.user_id, data.guild_id)]; return [Transformers.VoiceState(self, data), await self.cache.voiceStates?.get(data.user_id, data.guild_id)];
}; };

View File

@ -26,7 +26,7 @@ export { ShardManager, WorkerManager } from './websocket/discord';
export * from './structures'; export * from './structures';
// //
export * from './client'; export * from './client';
// ///
export function throwError(msg: string): never { export function throwError(msg: string): never {
throw new Error(msg); throw new Error(msg);

View File

@ -23,8 +23,8 @@ import type { UsingClient } from '../commands';
import { Formatter, type MessageCreateBodyRequest, type ObjectToLower, type ToClass } from '../common'; import { Formatter, type MessageCreateBodyRequest, type ObjectToLower, type ToClass } from '../common';
import type { ImageOptions, MethodContext } from '../common/types/options'; import type { ImageOptions, MethodContext } from '../common/types/options';
import type { GuildMemberResolvable } from '../common/types/resolvables'; import type { GuildMemberResolvable } from '../common/types/resolvables';
import { User } from './User';
import { PermissionsBitField } from './extra/Permissions'; import { PermissionsBitField } from './extra/Permissions';
import { Transformers, type UserStructure } from '../client/transformers';
export interface BaseGuildMember extends DiscordBase, ObjectToLower<Omit<APIGuildMember, 'user' | 'roles'>> {} export interface BaseGuildMember extends DiscordBase, ObjectToLower<Omit<APIGuildMember, 'user' | 'roles'>> {}
export class BaseGuildMember extends DiscordBase { export class BaseGuildMember extends DiscordBase {
@ -127,17 +127,17 @@ export interface GuildMember extends ObjectToLower<Omit<APIGuildMember, 'user' |
* @link https://discord.com/developers/docs/resources/guild#guild-member-object * @link https://discord.com/developers/docs/resources/guild#guild-member-object
*/ */
export class GuildMember extends BaseGuildMember { export class GuildMember extends BaseGuildMember {
user: User; user: UserStructure;
private __me?: GuildMember; private __me?: GuildMember;
constructor( constructor(
client: UsingClient, client: UsingClient,
data: GuildMemberData, data: GuildMemberData,
user: APIUser | User, user: APIUser,
/** the choosen guild id */ /** the choosen guild id */
readonly guildId: string, readonly guildId: string,
) { ) {
super(client, data, user.id, guildId); super(client, data, user.id, guildId);
this.user = user instanceof User ? user : new User(client, user); this.user = Transformers.User(client, user);
} }
get tag() { get tag() {
@ -242,7 +242,7 @@ export class InteractionGuildMember extends (GuildMember as unknown as ToClass<
constructor( constructor(
client: UsingClient, client: UsingClient,
data: APIInteractionDataResolvedGuildMember, data: APIInteractionDataResolvedGuildMember,
user: APIUser | User, user: APIUser,
/** the choosen guild id */ /** the choosen guild id */
guildId: string, guildId: string,
) { ) {

View File

@ -40,7 +40,7 @@ import {
import { mix } from 'ts-mixer'; import { mix } from 'ts-mixer';
import type { RawFile } from '../api'; import type { RawFile } from '../api';
import { ActionRow, Embed, Modal, PollBuilder, resolveAttachment, resolveFiles } from '../builders'; import { ActionRow, Embed, Modal, PollBuilder, resolveAttachment, resolveFiles } from '../builders';
import { OptionResolver, type ContextOptionsResolved, type UsingClient } from '../commands'; import type { ContextOptionsResolved, UsingClient } from '../commands';
import type { ObjectToLower, OmitInsert, ToClass, When } from '../common'; import type { ObjectToLower, OmitInsert, ToClass, When } from '../common';
import type { import type {
ComponentInteractionMessageUpdate, ComponentInteractionMessageUpdate,
@ -51,13 +51,19 @@ import type {
MessageWebhookCreateBodyRequest, MessageWebhookCreateBodyRequest,
ModalCreateBodyRequest, ModalCreateBodyRequest,
} from '../common/types/write'; } from '../common/types/write';
import { InteractionGuildMember, type AllChannels } from './'; import type { AllChannels } from './';
import { GuildRole } from './GuildRole';
import { Message, type WebhookMessage } from './Message';
import { User } from './User';
import channelFrom from './channels'; import channelFrom from './channels';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
import { PermissionsBitField } from './extra/Permissions'; import { PermissionsBitField } from './extra/Permissions';
import {
type GuildRoleStructure,
type InteractionGuildMemberStructure,
type MessageStructure,
Transformers,
type UserStructure,
type WebhookMessageStructure,
type OptionResolverStructure,
} from '../client/transformers';
export type ReplyInteractionBody = export type ReplyInteractionBody =
| { type: InteractionResponseType.Modal; data: ModalCreateBodyRequest } | { type: InteractionResponseType.Modal; data: ModalCreateBodyRequest }
@ -82,10 +88,10 @@ export class BaseInteraction<
FromGuild extends boolean = boolean, FromGuild extends boolean = boolean,
Type extends APIInteraction = APIInteraction, Type extends APIInteraction = APIInteraction,
> extends DiscordBase<Type> { > extends DiscordBase<Type> {
user: User; user: UserStructure;
member!: When<FromGuild, InteractionGuildMember, undefined>; member!: When<FromGuild, InteractionGuildMemberStructure, undefined>;
channel?: AllChannels; channel?: AllChannels;
message?: Message; message?: MessageStructure;
replied?: Promise<boolean> | boolean; replied?: Promise<boolean> | boolean;
appPermissions?: PermissionsBitField; appPermissions?: PermissionsBitField;
@ -96,15 +102,15 @@ export class BaseInteraction<
) { ) {
super(client, interaction); super(client, interaction);
if (interaction.member) { if (interaction.member) {
this.member = new InteractionGuildMember( this.member = Transformers.InteractionGuildMember(
client, client,
interaction.member, interaction.member,
interaction.member!.user, interaction.member.user,
interaction.guild_id!, interaction.guild_id!,
) as never; ) as never;
} }
if (interaction.message) { if (interaction.message) {
this.message = new Message(client, interaction.message); this.message = Transformers.Message(client, interaction.message);
} }
if (interaction.app_permissions) { if (interaction.app_permissions) {
this.appPermissions = new PermissionsBitField(Number(interaction.app_permissions)); this.appPermissions = new PermissionsBitField(Number(interaction.app_permissions));
@ -112,7 +118,7 @@ export class BaseInteraction<
if (interaction.channel) { if (interaction.channel) {
this.channel = channelFrom(interaction.channel, client); this.channel = channelFrom(interaction.channel, client);
} }
this.user = this.member?.user ?? new User(client, interaction.user!); this.user = this.member?.user ?? Transformers.User(client, interaction.user!);
} }
static transformBodyRequest( static transformBodyRequest(
@ -315,17 +321,17 @@ export class AutocompleteInteraction<FromGuild extends boolean = boolean> extend
> { > {
declare type: InteractionType.ApplicationCommandAutocomplete; declare type: InteractionType.ApplicationCommandAutocomplete;
declare data: ObjectToLower<APIApplicationCommandAutocompleteInteraction['data']>; declare data: ObjectToLower<APIApplicationCommandAutocompleteInteraction['data']>;
options: OptionResolver; options: OptionResolverStructure;
constructor( constructor(
client: UsingClient, client: UsingClient,
interaction: APIApplicationCommandAutocompleteInteraction, interaction: APIApplicationCommandAutocompleteInteraction,
resolver?: OptionResolver, resolver?: OptionResolverStructure,
protected __reply?: __InternalReplyFunction, protected __reply?: __InternalReplyFunction,
) { ) {
super(client, interaction); super(client, interaction);
this.options = this.options =
resolver ?? resolver ??
new OptionResolver( Transformers.OptionResolver(
client, client,
interaction.data.options, interaction.data.options,
undefined, undefined,
@ -363,7 +369,7 @@ export class Interaction<
async write<FR extends boolean = false>( async write<FR extends boolean = false>(
body: InteractionCreateBodyRequest, body: InteractionCreateBodyRequest,
fetchReply?: FR, fetchReply?: FR,
): Promise<When<FR, WebhookMessage, void>> { ): Promise<When<FR, WebhookMessageStructure, void>> {
(await this.reply({ (await this.reply({
type: InteractionResponseType.ChannelMessageWithSource, type: InteractionResponseType.ChannelMessageWithSource,
data: body, data: body,
@ -382,7 +388,7 @@ export class Interaction<
async editOrReply<FR extends boolean = false>( async editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest, body: InteractionCreateBodyRequest,
fetchReply?: FR, fetchReply?: FR,
): Promise<When<FR, WebhookMessage, void>>; ): Promise<When<FR, WebhookMessageStructure, void>>;
async editOrReply<FR extends true = true>(body: InteractionMessageUpdateBodyRequest, fetchReply?: FR) { async editOrReply<FR extends true = true>(body: InteractionMessageUpdateBodyRequest, fetchReply?: FR) {
if (await this.replied) { if (await this.replied) {
const { content, embeds, allowed_mentions, components, files, attachments } = body; const { content, embeds, allowed_mentions, components, files, attachments } = body;
@ -444,7 +450,7 @@ export class ComponentInteraction<
declare channelId: string; declare channelId: string;
declare channel: AllChannels; declare channel: AllChannels;
declare type: InteractionType.MessageComponent; declare type: InteractionType.MessageComponent;
declare message: Message; declare message: MessageStructure;
update(data: ComponentInteractionMessageUpdate) { update(data: ComponentInteractionMessageUpdate) {
return this.reply({ return this.reply({
@ -536,9 +542,9 @@ export class ChannelSelectMenuInteraction extends SelectMenuInteraction {
} }
export class MentionableSelectMenuInteraction extends SelectMenuInteraction { export class MentionableSelectMenuInteraction extends SelectMenuInteraction {
roles: GuildRole[]; roles: GuildRoleStructure[];
members: InteractionGuildMember[]; members: InteractionGuildMemberStructure[];
users: User[]; users: UserStructure[];
constructor( constructor(
client: UsingClient, client: UsingClient,
interaction: APIMessageComponentSelectMenuInteraction, interaction: APIMessageComponentSelectMenuInteraction,
@ -547,25 +553,24 @@ export class MentionableSelectMenuInteraction extends SelectMenuInteraction {
super(client, interaction); super(client, interaction);
const resolved = (interaction.data as APIMessageMentionableSelectInteractionData).resolved; const resolved = (interaction.data as APIMessageMentionableSelectInteractionData).resolved;
this.roles = resolved.roles this.roles = resolved.roles
? this.values.map(x => new GuildRole(this.client, resolved.roles![x], this.guildId!)) ? this.values.map(x => Transformers.GuildRole(this.client, resolved.roles![x], this.guildId!))
: []; : [];
this.members = resolved.members this.members = resolved.members
? this.values.map( ? this.values.map(x =>
x => Transformers.InteractionGuildMember(
new InteractionGuildMember( this.client,
this.client, resolved.members![x],
resolved.members![x], resolved.users![this.values!.find(u => u === x)!]!,
this.users!.find(u => u.id === x)!, this.guildId!,
this.guildId!, ),
),
) )
: []; : [];
this.users = resolved.users ? this.values.map(x => new User(this.client, resolved.users![x])) : []; this.users = resolved.users ? this.values.map(x => Transformers.User(this.client, resolved.users![x])) : [];
} }
} }
export class RoleSelectMenuInteraction extends SelectMenuInteraction { export class RoleSelectMenuInteraction extends SelectMenuInteraction {
roles: GuildRole[]; roles: GuildRoleStructure[];
constructor( constructor(
client: UsingClient, client: UsingClient,
interaction: APIMessageComponentSelectMenuInteraction, interaction: APIMessageComponentSelectMenuInteraction,
@ -573,13 +578,13 @@ export class RoleSelectMenuInteraction extends SelectMenuInteraction {
) { ) {
super(client, interaction); super(client, interaction);
const resolved = (interaction.data as APIMessageRoleSelectInteractionData).resolved; const resolved = (interaction.data as APIMessageRoleSelectInteractionData).resolved;
this.roles = this.values.map(x => new GuildRole(this.client, resolved.roles[x], this.guildId!)); this.roles = this.values.map(x => Transformers.GuildRole(this.client, resolved.roles[x], this.guildId!));
} }
} }
export class UserSelectMenuInteraction extends SelectMenuInteraction { export class UserSelectMenuInteraction extends SelectMenuInteraction {
members: InteractionGuildMember[]; members: InteractionGuildMemberStructure[];
users: User[]; users: UserStructure[];
constructor( constructor(
client: UsingClient, client: UsingClient,
interaction: APIMessageComponentSelectMenuInteraction, interaction: APIMessageComponentSelectMenuInteraction,
@ -587,16 +592,15 @@ export class UserSelectMenuInteraction extends SelectMenuInteraction {
) { ) {
super(client, interaction); super(client, interaction);
const resolved = (interaction.data as APIMessageUserSelectInteractionData).resolved; const resolved = (interaction.data as APIMessageUserSelectInteractionData).resolved;
this.users = this.values.map(x => new User(this.client, resolved.users[x])); this.users = this.values.map(x => Transformers.User(this.client, resolved.users[x]));
this.members = resolved.members this.members = resolved.members
? this.values.map( ? this.values.map(x =>
x => Transformers.InteractionGuildMember(
new InteractionGuildMember( this.client,
this.client, resolved.members![x],
resolved.members![x], resolved.users[this.values!.find(u => u === x)!]!,
this.users!.find(u => u.id === x)!, this.guildId!,
this.guildId!, ),
),
) )
: []; : [];
} }

View File

@ -13,12 +13,16 @@ import type { EmojiResolvable } from '../common/types/resolvables';
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../common/types/write'; import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../common/types/write';
import type { ActionRowMessageComponents } from '../components'; import type { ActionRowMessageComponents } from '../components';
import { MessageActionRowComponent } from '../components/ActionRow'; import { MessageActionRowComponent } from '../components/ActionRow';
import { GuildMember } from './GuildMember';
import { User } from './User';
import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook'; import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
import { messageLink } from './extra/functions'; import { messageLink } from './extra/functions';
import { Embed, Poll } from '..'; import { Embed } from '..';
import {
type PollStructure,
Transformers,
type GuildMemberStructure,
type UserStructure,
} from '../client/transformers';
export type MessageData = APIMessage | GatewayMessageCreateDispatchData; export type MessageData = APIMessage | GatewayMessageCreateDispatchData;
@ -27,14 +31,14 @@ export interface BaseMessage
ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> { ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {
timestamp?: number; timestamp?: number;
guildId?: string; guildId?: string;
author: User; author: UserStructure;
member?: GuildMember; member?: GuildMemberStructure;
components: MessageActionRowComponent<ActionRowMessageComponents>[]; components: MessageActionRowComponent<ActionRowMessageComponents>[];
poll?: Poll; poll?: PollStructure;
mentions: { mentions: {
roles: string[]; roles: string[];
channels: APIChannelMention[]; channels: APIChannelMention[];
users: (GuildMember | User)[]; users: (GuildMemberStructure | UserStructure)[];
}; };
} }
export class BaseMessage extends DiscordBase { export class BaseMessage extends DiscordBase {
@ -83,32 +87,31 @@ export class BaseMessage extends DiscordBase {
} }
if ('author' in data && data.author) { if ('author' in data && data.author) {
this.author = new User(this.client, data.author); this.author = Transformers.User(this.client, data.author);
} }
if ('member' in data && data.member) { if ('member' in data && data.member) {
this.member = new GuildMember(this.client, data.member, this.author, this.guildId!); this.member = Transformers.GuildMember(this.client, data.member, data.author, this.guildId!);
} }
if (data.mentions?.length) { if (data.mentions?.length) {
this.mentions.users = this.guildId this.mentions.users = this.guildId
? data.mentions.map( ? data.mentions.map(m =>
m => Transformers.GuildMember(
new GuildMember( this.client,
this.client, {
{ ...(m as APIUser & { member?: Omit<APIGuildMember, 'user'> }).member!,
...(m as APIUser & { member?: Omit<APIGuildMember, 'user'> }).member!, user: m,
user: m, },
}, m,
m, this.guildId!,
this.guildId!, ),
),
) )
: data.mentions.map(u => new User(this.client, u)); : data.mentions.map(u => Transformers.User(this.client, u));
} }
if (data.poll) { if (data.poll) {
this.poll = new Poll(this.client, data.poll, this.channelId, this.id); this.poll = Transformers.Poll(this.client, data.poll, this.channelId, this.id);
} }
} }
} }

View File

@ -6,17 +6,17 @@ import type {
import type { RawFile, UsingClient } from '..'; import type { RawFile, UsingClient } from '..';
import type { Attachment, AttachmentBuilder } from '../builders'; import type { Attachment, AttachmentBuilder } from '../builders';
import type { MethodContext, ObjectToLower } from '../common'; import type { MethodContext, ObjectToLower } from '../common';
import { User } from './User';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
import { Transformers, type UserStructure } from '../client/transformers';
export interface Sticker extends DiscordBase, ObjectToLower<Omit<APISticker, 'user'>> {} export interface Sticker extends DiscordBase, ObjectToLower<Omit<APISticker, 'user'>> {}
export class Sticker extends DiscordBase { export class Sticker extends DiscordBase {
user?: User; user?: UserStructure;
constructor(client: UsingClient, data: APISticker) { constructor(client: UsingClient, data: APISticker) {
super(client, data); super(client, data);
if (data.user) { if (data.user) {
this.user = new User(this.client, data.user); this.user = Transformers.User(this.client, data.user);
} }
} }

View File

@ -1,5 +1,6 @@
import { GuildMember, type UsingClient } from '../'; import type { UsingClient } from '../';
import type { VoiceStateResource } from '../cache/resources/voice-states'; import type { VoiceStateResource } from '../cache/resources/voice-states';
import { type GuildMemberStructure, Transformers } from '../client/transformers';
import type { ObjectToLower } from '../common'; import type { ObjectToLower } from '../common';
import type { GatewayVoiceState } from '../types'; import type { GatewayVoiceState } from '../types';
import { Base } from './extra/Base'; import { Base } from './extra/Base';
@ -7,12 +8,13 @@ import { Base } from './extra/Base';
export interface VoiceState extends Base, ObjectToLower<Omit<VoiceStateResource, 'member'>> {} export interface VoiceState extends Base, ObjectToLower<Omit<VoiceStateResource, 'member'>> {}
export class VoiceState extends Base { export class VoiceState extends Base {
protected withMember?: GuildMember; protected withMember?: GuildMemberStructure;
constructor(client: UsingClient, data: GatewayVoiceState) { constructor(client: UsingClient, data: GatewayVoiceState) {
super(client); super(client);
const { member, ...rest } = data; const { member, ...rest } = data;
this.__patchThis(rest); this.__patchThis(rest);
if (member?.user && data.guild_id) this.withMember = new GuildMember(client, member, member.user, data.guild_id); if (member?.user && data.guild_id)
this.withMember = Transformers.GuildMember(client, member, member.user, data.guild_id);
} }
isMuted() { isMuted() {

View File

@ -17,9 +17,8 @@ import type {
MethodContext, MethodContext,
ObjectToLower, ObjectToLower,
} from '../common'; } from '../common';
import { AnonymousGuild } from './AnonymousGuild';
import { User } from './User';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
import { type AnonymousGuildStructure, Transformers, type UserStructure } from '../client/transformers';
export interface Webhook extends DiscordBase, ObjectToLower<Omit<APIWebhook, 'user' | 'source_guild'>> {} export interface Webhook extends DiscordBase, ObjectToLower<Omit<APIWebhook, 'user' | 'source_guild'>> {}
@ -28,9 +27,9 @@ export interface Webhook extends DiscordBase, ObjectToLower<Omit<APIWebhook, 'us
*/ */
export class Webhook extends DiscordBase { export class Webhook extends DiscordBase {
/** The user associated with the webhook, if applicable. */ /** The user associated with the webhook, if applicable. */
user?: User; user?: UserStructure;
/** The source guild of the webhook, if applicable. */ /** The source guild of the webhook, if applicable. */
sourceGuild?: Partial<AnonymousGuild>; sourceGuild?: Partial<AnonymousGuildStructure>;
/** Methods related to interacting with messages through the webhook. */ /** Methods related to interacting with messages through the webhook. */
messages!: ReturnType<typeof Webhook.messages>; messages!: ReturnType<typeof Webhook.messages>;
/** /**
@ -42,11 +41,11 @@ export class Webhook extends DiscordBase {
super(client, data); super(client, data);
if (data.user) { if (data.user) {
this.user = new User(this.client, data.user); this.user = Transformers.User(this.client, data.user);
} }
if (data.source_guild) { if (data.source_guild) {
this.sourceGuild = new AnonymousGuild(this.client, data.source_guild); this.sourceGuild = Transformers.AnonymousGuild(this.client, data.source_guild);
} }
Object.assign(this, { Object.assign(this, {

View File

@ -42,6 +42,22 @@ import type { GuildRole } from './GuildRole';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
import { channelLink } from './extra/functions'; import { channelLink } from './extra/functions';
import { Collection, Formatter, type RawFile } from '..'; import { Collection, Formatter, type RawFile } from '..';
import {
type BaseChannelStructure,
type BaseGuildChannelStructure,
type CategoryChannelStructure,
type DMChannelStructure,
type DirectoryChannelStructure,
type ForumChannelStructure,
type GuildMemberStructure,
type MediaChannelStructure,
type NewsChannelStructure,
type StageChannelStructure,
type TextGuildChannelStructure,
type ThreadChannelStructure,
Transformers,
type VoiceChannelStructure,
} from '../client/transformers';
export class BaseChannel<T extends ChannelType> extends DiscordBase<APIChannelBase<ChannelType>> { export class BaseChannel<T extends ChannelType> extends DiscordBase<APIChannelBase<ChannelType>> {
declare type: T; declare type: T;
@ -293,32 +309,32 @@ export class TextBaseGuildChannel extends BaseGuildChannel {}
export default function channelFrom(data: APIChannelBase<ChannelType>, client: UsingClient): AllChannels { export default function channelFrom(data: APIChannelBase<ChannelType>, client: UsingClient): AllChannels {
switch (data.type) { switch (data.type) {
case ChannelType.GuildStageVoice: case ChannelType.GuildStageVoice:
return new StageChannel(client, data); return Transformers.StageChannel(client, data);
case ChannelType.GuildMedia: case ChannelType.GuildMedia:
return new MediaChannel(client, data); return Transformers.MediaChannel(client, data);
case ChannelType.DM: case ChannelType.DM:
return new DMChannel(client, data); return Transformers.DMChannel(client, data);
case ChannelType.GuildForum: case ChannelType.GuildForum:
return new ForumChannel(client, data); return Transformers.ForumChannel(client, data);
case ChannelType.AnnouncementThread: case ChannelType.AnnouncementThread:
case ChannelType.PrivateThread: case ChannelType.PrivateThread:
case ChannelType.PublicThread: case ChannelType.PublicThread:
return new ThreadChannel(client, data); return Transformers.ThreadChannel(client, data);
case ChannelType.GuildDirectory: case ChannelType.GuildDirectory:
return new DirectoryChannel(client, data); return Transformers.DirectoryChannel(client, data);
case ChannelType.GuildVoice: case ChannelType.GuildVoice:
return new VoiceChannel(client, data); return Transformers.VoiceChannel(client, data);
case ChannelType.GuildText: case ChannelType.GuildText:
return new TextGuildChannel(client, data as APIGuildChannel<ChannelType>); return Transformers.TextGuildChannel(client, data as APIGuildChannel<ChannelType>);
case ChannelType.GuildCategory: case ChannelType.GuildCategory:
return new CategoryChannel(client, data); return Transformers.CategoryChannel(client, data);
case ChannelType.GuildAnnouncement: case ChannelType.GuildAnnouncement:
return new NewsChannel(client, data); return Transformers.NewsChannel(client, data);
default: default:
if ('guild_id' in data) { if ('guild_id' in data) {
return new BaseGuildChannel(client, data as APIGuildChannel<ChannelType>); return Transformers.BaseGuildChannel(client, data as APIGuildChannel<ChannelType>);
} }
return new BaseChannel(client, data); return Transformers.BaseChannel(client, data);
} }
} }
@ -389,7 +405,7 @@ export class VoiceChannelMethods extends DiscordBase {
} }
public async members(force?: boolean) { public async members(force?: boolean) {
const collection = new Collection<string, GuildMember>(); const collection = new Collection<string, GuildMemberStructure>();
const states = await this.states(); const states = await this.states();
@ -571,30 +587,39 @@ export class NewsChannel extends BaseChannel<ChannelType.GuildAnnouncement> {
export class DirectoryChannel extends BaseChannel<ChannelType.GuildDirectory> {} export class DirectoryChannel extends BaseChannel<ChannelType.GuildDirectory> {}
export type AllGuildChannels = export type AllGuildChannels =
| TextGuildChannel | TextGuildChannelStructure
| VoiceChannel | VoiceChannelStructure
| MediaChannel | MediaChannelStructure
| ForumChannel | ForumChannelStructure
| ThreadChannel | ThreadChannelStructure
| CategoryChannel | CategoryChannelStructure
| NewsChannel | NewsChannelStructure
| DirectoryChannel | DirectoryChannelStructure
| StageChannel; | StageChannelStructure;
export type AllTextableChannels = TextGuildChannel | VoiceChannel | DMChannel | NewsChannel | ThreadChannel; export type AllTextableChannels =
export type AllGuildTextableChannels = TextGuildChannel | VoiceChannel | NewsChannel | ThreadChannel; | TextGuildChannelStructure
export type AllGuildVoiceChannels = VoiceChannel | StageChannel; | VoiceChannelStructure
| DMChannelStructure
| NewsChannelStructure
| ThreadChannelStructure;
export type AllGuildTextableChannels =
| TextGuildChannelStructure
| VoiceChannelStructure
| NewsChannelStructure
| ThreadChannelStructure;
export type AllGuildVoiceChannels = VoiceChannelStructure | StageChannelStructure;
export type AllChannels = export type AllChannels =
| BaseChannel<ChannelType> | BaseChannelStructure
| BaseGuildChannel | BaseGuildChannelStructure
| TextGuildChannel | TextGuildChannelStructure
| DMChannel | DMChannelStructure
| VoiceChannel | VoiceChannelStructure
| MediaChannel | MediaChannelStructure
| ForumChannel | ForumChannelStructure
| ThreadChannel | ThreadChannelStructure
| CategoryChannel | CategoryChannelStructure
| NewsChannel | NewsChannelStructure
| DirectoryChannel | DirectoryChannelStructure
| StageChannel; | StageChannelStructure;

View File

@ -1,22 +1,15 @@
import type { APIUser, GatewayActivity, GatewayPresenceUpdateDispatchData } from 'discord-api-types/v10'; import type { GatewayActivity, GatewayPresenceUpdateDispatchData } from 'discord-api-types/v10';
type FixedGatewayPresenceUpdateDispatchData =
| (Omit<GatewayPresenceUpdateDispatchData, 'user'> & { user_id: string; user: undefined })
| (Omit<GatewayPresenceUpdateDispatchData, 'user'> & {
user: Partial<APIUser> & Pick<APIUser, 'id'>;
user_id: undefined;
});
export class PresenceUpdateHandler { export class PresenceUpdateHandler {
presenceUpdate = new Map<string, { timeout: NodeJS.Timeout; presence: FixedGatewayPresenceUpdateDispatchData }>(); presenceUpdate = new Map<string, { timeout: NodeJS.Timeout; presence: GatewayPresenceUpdateDispatchData }>();
check(presence: FixedGatewayPresenceUpdateDispatchData) { check(presence: GatewayPresenceUpdateDispatchData) {
if (!this.presenceUpdate.has(presence.user?.id ?? presence.user_id!)) { if (!this.presenceUpdate.has(presence.user.id)) {
this.setPresence(presence); this.setPresence(presence);
return true; return true;
} }
const data = this.presenceUpdate.get(presence.user?.id ?? presence.user_id!)!; const data = this.presenceUpdate.get(presence.user.id)!;
if (this.presenceEquals(data.presence, presence)) { if (this.presenceEquals(data.presence, presence)) {
return false; return false;
@ -29,19 +22,16 @@ export class PresenceUpdateHandler {
return true; return true;
} }
setPresence(presence: FixedGatewayPresenceUpdateDispatchData) { setPresence(presence: GatewayPresenceUpdateDispatchData) {
this.presenceUpdate.set(presence.user?.id ?? presence.user_id!, { this.presenceUpdate.set(presence.user.id, {
presence, presence,
timeout: setTimeout(() => { timeout: setTimeout(() => {
this.presenceUpdate.delete(presence.user?.id ?? presence.user_id!); this.presenceUpdate.delete(presence.user.id);
}, 1.5e3), }, 1.5e3),
}); });
} }
presenceEquals( presenceEquals(oldPresence: GatewayPresenceUpdateDispatchData, newPresence: GatewayPresenceUpdateDispatchData) {
oldPresence: FixedGatewayPresenceUpdateDispatchData,
newPresence: FixedGatewayPresenceUpdateDispatchData,
) {
return ( return (
newPresence && newPresence &&
oldPresence.status === newPresence.status && oldPresence.status === newPresence.status &&

View File

@ -27,12 +27,16 @@ export type WorkerSendCacheRequest = CreateWorkerMessage<
nonce: string; nonce: string;
method: method:
| 'scan' | 'scan'
| 'bulkGet'
| 'get' | 'get'
| 'bulkSet'
| 'set' | 'set'
| 'bulkPatch'
| 'patch' | 'patch'
| 'values' | 'values'
| 'keys' | 'keys'
| 'count' | 'count'
| 'bulkRemove'
| 'remove' | 'remove'
| 'flush' | 'flush'
| 'contains' | 'contains'

View File

@ -235,7 +235,7 @@ export class WorkerManager extends Map<
} }
break; break;
case 'PRESENCE_UPDATE': case 'PRESENCE_UPDATE':
if (!this.presenceUpdateHandler.check(message.payload.d as any)) { if (!this.presenceUpdateHandler.check(message.payload.d)) {
return; return;
} }
break; break;