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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HUSKY: 0

View File

@ -19,12 +19,12 @@ jobs:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Create Release Pull Request
uses: changesets/action@v1

View File

@ -22,12 +22,12 @@ jobs:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: npx tsc

View File

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

94
pnpm-lock.yaml generated
View File

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

View File

@ -1,26 +1,22 @@
import {
type APIButtonComponentWithCustomId,
type APIButtonComponentWithURL,
type APIMessageComponentEmoji,
type ButtonStyle,
ComponentType,
type APIButtonComponent,
} from 'discord-api-types/v10';
import { throwError } from '..';
import type { EmojiResolvable, When } from '../common';
import type { EmojiResolvable } from '../common';
import { resolvePartialEmoji } from '../structures/extra/functions';
export type ButtonStylesForID = Exclude<ButtonStyle, ButtonStyle.Link>;
/**
* Represents a 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.
* @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;
}
@ -30,8 +26,7 @@ export class Button<Type extends boolean = boolean> {
* @returns The modified Button instance.
*/
setCustomId(id: string) {
// @ts-expect-error
this.data.custom_id = id;
(this.data as Extract<APIButtonComponent, { custom_id?: string }>).custom_id = id;
return this;
}
@ -41,8 +36,7 @@ export class Button<Type extends boolean = boolean> {
* @returns The modified Button instance.
*/
setURL(url: string) {
// @ts-expect-error
this.data.url = url;
(this.data as Extract<APIButtonComponent, { url?: string }>).url = url;
return this;
}
@ -52,7 +46,7 @@ export class Button<Type extends boolean = boolean> {
* @returns The modified Button instance.
*/
setLabel(label: string) {
this.data.label = label;
(this.data as Extract<APIButtonComponent, { label?: string }>).label = label;
return this;
}
@ -63,8 +57,9 @@ export class Button<Type extends boolean = boolean> {
*/
setEmoji(emoji: EmojiResolvable) {
const resolve = resolvePartialEmoji(emoji);
if (!resolve) return throwError('Invalid Emoji');
this.data.emoji = resolve as APIMessageComponentEmoji;
if (!resolve) throw new Error('Invalid Emoji');
(this.data as Extract<APIButtonComponent, { emoji?: APIMessageComponentEmoji }>).emoji =
resolve as APIMessageComponentEmoji;
return this;
}
@ -83,11 +78,16 @@ export class Button<Type extends boolean = boolean> {
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.
* @returns The JSON representation of the Button instance.
*/
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;
}
get(keys: string): any;
get(keys: string[]): any[];
get(keys: string | string[]) {
if (!Array.isArray(keys)) {
const data = this.storage.get(keys);
return data ? JSON.parse(data) : null;
}
bulkGet(keys: string[]) {
return keys
.map(x => {
const data = this.storage.get(x);
@ -35,22 +29,22 @@ export class MemoryAdapter implements Adapter {
.filter(x => x);
}
set(keys: string, data: any): void;
set(keys: [string, any][]): void;
set(keys: string | [string, any][], data?: any): void {
if (Array.isArray(keys)) {
get(keys: string) {
const data = this.storage.get(keys);
return data ? JSON.parse(data) : null;
}
bulkSet(keys: [string, any][]) {
for (const [key, value] of keys) {
this.storage.set(key, JSON.stringify(value));
}
} else {
this.storage.set(keys, JSON.stringify(data));
}
}
patch(updateOnly: boolean, keys: string, data: any): void;
patch(updateOnly: boolean, keys: [string, any][]): void;
patch(updateOnly: boolean, keys: string | [string, any][], data?: any): void {
if (Array.isArray(keys)) {
set(key: string, data: any) {
this.storage.set(key, JSON.stringify(data));
}
bulkPatch(updateOnly: boolean, keys: [string, any][]) {
for (const [key, value] of keys) {
const oldData = this.get(key);
if (updateOnly && !oldData) {
@ -61,7 +55,9 @@ export class MemoryAdapter implements Adapter {
Array.isArray(value) ? JSON.stringify(value) : JSON.stringify({ ...(oldData ?? {}), ...value }),
);
}
} else {
}
patch(updateOnly: boolean, keys: string, data: any) {
const oldData = this.get(keys);
if (updateOnly && !oldData) {
return;
@ -71,7 +67,6 @@ export class MemoryAdapter implements Adapter {
Array.isArray(data) ? JSON.stringify(data) : JSON.stringify({ ...(oldData ?? {}), ...data }),
);
}
}
values(to: string) {
const array: any[] = [];
@ -96,14 +91,16 @@ export class MemoryAdapter implements Adapter {
return this.getToRelationship(to).length;
}
remove(keys: string): void;
remove(keys: string[]): void;
remove(keys: string | string[]) {
for (const i of Array.isArray(keys) ? keys : [keys]) {
bulkRemove(keys: string[]) {
for (const i of keys) {
this.storage.delete(i);
}
}
remove(key: string) {
this.storage.delete(key);
}
flush(): void {
this.storage.clear();
this.relationships.clear();

View File

@ -9,18 +9,23 @@ export interface ResourceLimitedMemoryAdapter {
export interface LimitedMemoryAdapterOptions {
default?: ResourceLimitedMemoryAdapter;
guild?: ResourceLimitedMemoryAdapter;
user?: ResourceLimitedMemoryAdapter;
ban?: ResourceLimitedMemoryAdapter;
member?: ResourceLimitedMemoryAdapter;
voice_state?: ResourceLimitedMemoryAdapter;
channel?: ResourceLimitedMemoryAdapter;
emoji?: ResourceLimitedMemoryAdapter;
overwrite?: ResourceLimitedMemoryAdapter;
presence?: ResourceLimitedMemoryAdapter;
role?: ResourceLimitedMemoryAdapter;
stage_instance?: ResourceLimitedMemoryAdapter;
sticker?: ResourceLimitedMemoryAdapter;
thread?: ResourceLimitedMemoryAdapter;
overwrite?: ResourceLimitedMemoryAdapter;
message?: ResourceLimitedMemoryAdapter;
}
export class LimitedMemoryAdapter implements Adapter {
@ -58,13 +63,7 @@ export class LimitedMemoryAdapter implements Adapter {
return values;
}
get(keys: string): any;
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;
}
bulkGet(keys: string[]) {
const iterator = [...this.storage.values()];
return keys
.map(key => {
@ -74,6 +73,11 @@ export class LimitedMemoryAdapter implements Adapter {
.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) {
const __guildId = Array.isArray(data) ? data[0].guild_id : data.guild_id;
const namespace = `${key.split('.')[0]}${__guildId ? `.${__guildId}` : ''}`;
@ -96,6 +100,7 @@ export class LimitedMemoryAdapter implements Adapter {
case 'user':
self.removeToRelationship(namespace, k.split('.')[1]);
break;
case 'ban':
case 'member':
case 'voice_state':
{
@ -124,22 +129,17 @@ export class LimitedMemoryAdapter implements Adapter {
this.storage.get(namespace)!.set(key, JSON.stringify(data));
}
set(keys: string, data: any): void;
set(keys: [string, any][]): void;
set(keys: string | [string, any][], data?: any): void {
if (Array.isArray(keys)) {
bulkSet(keys: [string, any][]) {
for (const [key, value] of keys) {
this.__set(key, value);
}
} else {
this.__set(keys, data);
}
}
patch(updateOnly: boolean, keys: string, data: any): void;
patch(updateOnly: boolean, keys: [string, any][]): void;
patch(updateOnly: boolean, keys: string | [string, any][], data?: any): void {
if (Array.isArray(keys)) {
set(keys: string, data: any) {
this.__set(keys, data);
}
bulkPatch(updateOnly: boolean, keys: [string, any][]) {
for (const [key, value] of keys) {
const oldData = this.get(key);
if (updateOnly && !oldData) {
@ -147,14 +147,15 @@ export class LimitedMemoryAdapter implements Adapter {
}
this.__set(key, Array.isArray(value) ? value : { ...(oldData ?? {}), ...value });
}
} else {
}
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) {
const array: any[] = [];
@ -179,14 +180,16 @@ export class LimitedMemoryAdapter implements Adapter {
return this.getToRelationship(to).length;
}
remove(keys: string): void;
remove(keys: string[]): void;
remove(keys: string | string[]) {
for (const i of Array.isArray(keys) ? keys : [keys]) {
bulkRemove(keys: string[]) {
for (const i of keys) {
this.storage.get(i.split('.')[0])?.delete(i);
}
}
remove(key: string) {
this.storage.get(key.split('.')[0])?.delete(key);
}
flush(): void {
this.storage.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 = false) {
const match = this.buildKey(query);
return new Promise<string[]>((r, j) => {
return new Promise<string[] | any[]>((r, j) => {
const stream = this.client.scanStream({
match,
type: 'set',
@ -39,7 +39,7 @@ export class RedisAdapter implements Adapter {
const keys: string[] = [];
stream
.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));
});
}
@ -48,7 +48,7 @@ export class RedisAdapter implements Adapter {
scan(query: string, returnKeys: true): Promise<string[]>;
scan(query: string, returnKeys = false) {
const match = this.buildKey(query);
return new Promise<string[]>((r, j) => {
return new Promise<string[] | any[]>((r, j) => {
const stream = this.client.scanStream({
match,
// omit relationships
@ -57,22 +57,12 @@ export class RedisAdapter implements Adapter {
const keys: string[] = [];
stream
.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));
});
}
async get(keys: string[]): Promise<any[]>;
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;
}
async bulkGet(keys: string[]) {
const pipeline = this.client.pipeline();
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>)) ?? [];
}
async set(id: [string, any][]): Promise<void>;
async set(id: string, data: any): Promise<void>;
async set(id: string | [string, any][], data?: any): Promise<void> {
if (!Array.isArray(id)) {
await this.client.hset(this.buildKey(id), toDb(data));
return;
async get(keys: string): Promise<any> {
const value = await this.client.hgetall(this.buildKey(keys));
if (value) {
return toNormal(value);
}
}
async bulkSet(data: [string, any][]) {
const pipeline = this.client.pipeline();
for (const [k, v] of id) {
for (const [k, v] of data) {
pipeline.hset(this.buildKey(k), toDb(v));
}
await pipeline.exec();
}
async patch(updateOnly: boolean, id: [string, any][]): Promise<void>;
async patch(updateOnly: boolean, id: string, data: any): Promise<void>;
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 {
async set(id: string, data: any) {
await this.client.hset(this.buildKey(id), toDb(data));
}
return;
}
async bulkPatch(updateOnly: boolean, data: [string, any][]) {
const pipeline = this.client.pipeline();
for (const [k, v] of id) {
for (const [k, v] of data) {
if (updateOnly) {
pipeline.eval(
`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();
}
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[]> {
const array: unknown[] = [];
const data = await this.keys(to);
if (data.length) {
const items = await this.get(data);
const items = await this.bulkGet(data);
for (const item of items) {
if (item) {
array.push(item);
@ -164,13 +155,12 @@ export class RedisAdapter implements Adapter {
return this.client.scard(`${this.buildKey(to)}:set`);
}
async remove(keys: string | string[]): Promise<void> {
if (!Array.isArray(keys)) {
await this.client.del(this.buildKey(keys));
return;
async bulkRemove(keys: string[]) {
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> {
@ -179,7 +169,7 @@ export class RedisAdapter implements Adapter {
this.__scanSets(this.buildKey('*'), true),
]).then(x => x.flat());
if (!keys.length) return;
await this.remove(keys);
await this.bulkRemove(keys);
}
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?: boolean): Awaitable<(any | string)[]>;
get(keys: string[]): Awaitable<any[]>;
bulkGet(keys: string[]): Awaitable<any[]>;
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 | [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 | [string, any][], data?: any): Awaitable<void>;
values(to: string): Awaitable<any[]>;
@ -25,7 +22,8 @@ export interface Adapter {
count(to: string): Awaitable<number>;
remove(keys: string | string[]): Awaitable<void>;
bulkRemove(keys: string[]): Awaitable<void>;
remove(keys: string): Awaitable<void>;
flush(): Awaitable<void>;

View File

@ -45,14 +45,26 @@ export class WorkerAdapter implements Adapter {
return this.send('scan', ...rest);
}
bulkGet(...rest: any[]) {
return this.send('bulkGet', ...rest);
}
get(...rest: any[]) {
return this.send('get', ...rest);
}
bulkSet(...rest: any[]) {
return this.send('bulkSet', ...rest);
}
set(...rest: any[]) {
return this.send('set', ...rest);
}
bulkPatch(...rest: any[]) {
return this.send('bulkPatch', ...rest);
}
patch(...rest: any[]) {
return this.send('patch', ...rest);
}
@ -69,6 +81,10 @@ export class WorkerAdapter implements Adapter {
return this.send('count', ...rest);
}
bulkRemove(...rest: any[]) {
return this.send('bulkRemove', ...rest);
}
remove(...rest: any[]) {
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.patch(false, allData);
await this.adapter.bulkPatch(false, allData);
}
async bulkSet(
@ -474,7 +474,7 @@ export class Cache {
}
await this.adapter.bulkAddToRelationShip(relationshipsData);
await this.adapter.set(allData);
await this.adapter.bulkSet(allData);
}
async onPacket(event: GatewayDispatchPayload) {

View File

@ -2,7 +2,7 @@ import type { APIBan } from 'discord-api-types/v10';
import type { ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { GuildBasedResource } from './default/guild-based';
import { GuildBan } from '../../structures/GuildBan';
import { type GuildBanStructure, Transformers } from '../../client/transformers';
export class Bans extends GuildBasedResource {
namespace = 'ban';
@ -16,31 +16,31 @@ export class Bans extends GuildBasedResource {
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 =>
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(
bans =>
bans
.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(
bans =>
bans
.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;
}
raw(id: string): ReturnCache<APIChannel | undefined> {
return super.get(id);
}
override get(id: string): ReturnCache<AllChannels | undefined> {
return fakePromise(super.get(id)).then(rawChannel =>
rawChannel ? channelFrom(rawChannel, this.client) : undefined,

View File

@ -48,7 +48,7 @@ export class BaseResource<T = any> {
}
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) {

View File

@ -54,7 +54,7 @@ export class GuildBasedResource<T = any> {
}
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>;
@ -71,7 +71,7 @@ export class GuildBasedResource<T = any> {
guild,
),
).then(() =>
this.adapter.set(
this.adapter.bulkSet(
keys.map(([key, value]) => {
return [this.hashGuildId(guild, key), this.parse(value, key, guild)] as const;
}),
@ -87,14 +87,14 @@ export class GuildBasedResource<T = 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(
this.addToRelationship(
keys.map(x => x[0]),
guild,
),
).then(() =>
this.adapter.set(
this.adapter.bulkSet(
keys.map(([key, value]) => {
const oldData = oldDatas.find(x => x.id === key) ?? {};
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) {
const ids = Array.isArray(id) ? id : [id];
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 })[]> {
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>;
@ -72,7 +72,7 @@ export class GuildRelatedResource<T = any> {
),
).then(
() =>
this.adapter.set(
this.adapter.bulkSet(
keys.map(([key, value]) => {
return [this.hashId(key), this.parse(value, key, guild)] as const;
}),
@ -96,7 +96,7 @@ export class GuildRelatedResource<T = any> {
),
).then(
() =>
this.adapter.patch(
this.adapter.bulkPatch(
false,
keys.map(([key, value]) => {
return [this.hashId(key), this.parse(value, key, guild)] as const;
@ -105,7 +105,7 @@ export class GuildRelatedResource<T = any> {
);
}
return fakePromise(
this.adapter.patch(
this.adapter.bulkPatch(
true,
keys.map(([key, value]) => {
return [this.hashId(key), value];
@ -117,7 +117,7 @@ export class GuildRelatedResource<T = any> {
remove(id: string | string[], guild: string) {
const ids = Array.isArray(id) ? id : [id];
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 === '*'
? (fakePromise(this.adapter.scan(this.hashId(guild))).then(x => x) as (T & { guild_id: string })[])
: (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 })[]);
}

View File

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

View File

@ -1,8 +1,8 @@
import type { APIGuildMember } from 'discord-api-types/v10';
import type { ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { GuildMember } from '../../structures';
import { GuildBasedResource } from './default/guild-based';
import { type GuildMemberStructure, Transformers } from '../../client/transformers';
export class Members extends GuildBasedResource {
namespace = 'member';
@ -16,38 +16,51 @@ export class Members extends GuildBasedResource {
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 =>
fakePromise(this.client.cache.users?.get(id)).then(user =>
rawMember && user ? new GuildMember(this.client, rawMember, user, guild) : undefined,
fakePromise(this.client.cache.users?.raw(id)).then(user =>
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 =>
fakePromise(this.client.cache.users?.bulk(ids)).then(
fakePromise(this.client.cache.users?.bulkRaw(ids)).then(
users =>
members
.map(rawMember => {
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 =>
fakePromise(this.client.cache.users?.values()).then(
fakePromise(this.client.cache.users?.valuesRaw()).then(
users =>
members
.map(rawMember => {
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 type { MessageData, ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { Message } from '../../structures';
import { type MessageStructure, Transformers } from '../../client/transformers';
export class Messages extends GuildRelatedResource {
namespace = 'message';
@ -19,19 +19,19 @@ export class Messages extends GuildRelatedResource {
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 this.cache.users && rawMessage?.user_id
? fakePromise(this.cache.adapter.get(this.cache.users.hashId(rawMessage.user_id)) as APIUser | undefined).then(
user => {
return user ? new Message(this.client, { ...rawMessage!, author: user }) : undefined;
return user ? Transformers.Message(this.client, { ...rawMessage!, author: user }) : undefined;
},
)
: undefined;
});
}
override bulk(ids: string[]): ReturnCache<Message[]> {
override bulk(ids: string[]): ReturnCache<MessageStructure[]> {
return fakePromise(super.bulk(ids) as APIMessageResource[]).then(
messages =>
messages
@ -40,26 +40,26 @@ export class Messages extends GuildRelatedResource {
? fakePromise(
this.cache.adapter.get(this.cache.users.hashId(rawMessage.user_id)) as APIUser | undefined,
).then(user => {
return user ? new Message(this.client, { ...rawMessage!, author: user }) : undefined;
return user ? Transformers.Message(this.client, { ...rawMessage!, author: user }) : 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 => {
const hashes: (string | undefined)[] = this.cache.users
? 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
.map(message => {
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 { ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { GuildRole } from '../../structures';
import { GuildRelatedResource } from './default/guild-related';
import { type GuildRoleStructure, Transformers } from '../../client/transformers';
export class Roles extends GuildRelatedResource {
namespace = 'role';
@ -12,21 +12,29 @@ export class Roles extends GuildRelatedResource {
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 =>
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 =>
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 =>
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 { ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { Sticker } from '../../structures';
import { GuildRelatedResource } from './default/guild-related';
import { type StickerStructure, Transformers } from '../../client/transformers';
export class Stickers extends GuildRelatedResource {
namespace = 'sticker';
@ -12,21 +12,21 @@ export class Stickers extends GuildRelatedResource {
return true;
}
override get(id: string): ReturnCache<Sticker | undefined> {
override get(id: string): ReturnCache<StickerStructure | undefined> {
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 =>
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 =>
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 { ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { ThreadChannel } from '../../structures';
import { GuildRelatedResource } from './default/guild-related';
import { type ThreadChannelStructure, Transformers } from '../../client/transformers';
export class Threads extends GuildRelatedResource {
namespace = 'thread';
@ -12,21 +12,21 @@ export class Threads extends GuildRelatedResource {
return true;
}
override get(id: string): ReturnCache<ThreadChannel | undefined> {
override get(id: string): ReturnCache<ThreadChannelStructure | undefined> {
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 =>
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 =>
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 { ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { User } from '../../structures';
import { BaseResource } from './default/base';
import { Transformers, type UserStructure } from '../../client/transformers';
export class Users extends BaseResource {
namespace = 'user';
@ -12,17 +12,31 @@ export class Users extends BaseResource {
return true;
}
override get(id: string): ReturnCache<User | undefined> {
return fakePromise(super.get(id)).then(rawUser => (rawUser ? new User(this.client, rawUser) : undefined));
raw(id: string): ReturnCache<APIUser | 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 =>
users.map(rawUser => new User(this.client, rawUser)),
users.map(rawUser => Transformers.User(this.client, rawUser)),
);
}
override values(): ReturnCache<User[]> {
return fakePromise(super.values() as APIUser[]).then(users => users.map(rawUser => new User(this.client, rawUser)));
bulkRaw(ids: string[]): ReturnCache<APIUser[]> {
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 { ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { VoiceState } from '../../structures';
import { GuildBasedResource } from './default/guild-based';
import { Transformers, type VoiceStateStructure } from '../../client/transformers';
export class VoiceStates extends GuildBasedResource {
namespace = 'voice_state';
@ -17,21 +17,25 @@ export class VoiceStates extends GuildBasedResource {
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 =>
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(
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[]> {
return fakePromise(super.values(guildId)).then(states => states.map(state => new VoiceState(this.client, state)));
override values(guildId: string): ReturnCache<VoiceStateStructure[]> {
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 {
Command,
CommandContext,
ContextMenuCommand,
ExtraProps,
OnOptionsReturnObject,
MenuCommandContext,
RegisteredMiddlewares,
SubCommand,
UsingClient,
} from '../commands';
import { IgnoreCommand, type InferWithPrefix, type MiddlewareContext } from '../commands/applications/shared';
@ -40,7 +42,6 @@ import { LangsHandler } from '../langs/handler';
import type {
ChatInputCommandInteraction,
ComponentInteraction,
Message,
MessageCommandInteraction,
ModalSubmitInteraction,
UserCommandInteraction,
@ -48,6 +49,8 @@ import type {
import type { ComponentCommand, ComponentContext, ModalCommand, ModalContext } from '../components';
import { promises } from 'node:fs';
import { BanShorter } from '../common/shorters/bans';
import { HandleCommand } from '../commands/handle';
import type { MessageStructure } from './transformers';
export class BaseClient {
rest!: ApiHandler;
@ -76,6 +79,7 @@ export class BaseClient {
langs? = new LangsHandler(this.logger);
commands? = new CommandHandler(this.logger, this);
components? = new ComponentHandler(this.logger, this);
handleCommand!: HandleCommand;
private _applicationId?: string;
private _botId?: string;
@ -101,30 +105,30 @@ export class BaseClient {
{
commands: {
defaults: {
onRunError(context: CommandContext<any>, error: unknown): any {
onRunError(context, error): any {
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);
},
onMiddlewaresError(context: CommandContext<{}, never>, error: string): any {
onMiddlewaresError(context, error: string): any {
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.command.name}.<onBotPermissionsFail>`,
context.author.id,
permissions,
);
},
onPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any {
onPermissionsFail(context, permissions): any {
context.client.logger.fatal(
`${context.command.name}.<onPermissionsFail>`,
context.author.id,
permissions,
);
},
onInternalError(client: UsingClient, command: Command, error?: unknown): any {
onInternalError(client: UsingClient, command, error?: unknown): any {
client.logger.fatal(`${command.name}.<onInternalError>`, error);
},
},
@ -180,7 +184,7 @@ export class BaseClient {
return new Router(this.rest).createProxy();
}
setServices({ rest, cache, langs, middlewares, handlers }: ServicesOptions) {
setServices({ rest, cache, langs, middlewares, handlers, handleCommand }: ServicesOptions) {
if (rest) {
this.rest = rest;
}
@ -201,9 +205,6 @@ export class BaseClient {
this.components = undefined;
} else if (typeof handlers.components === 'function') {
this.components ??= new ComponentHandler(this.logger, this);
this.components.setHandlers({
callback: handlers.components,
});
} else {
this.components = handlers.components;
}
@ -213,7 +214,6 @@ export class BaseClient {
this.commands = undefined;
} else if (typeof handlers.commands === 'object') {
this.commands ??= new CommandHandler(this.logger, this);
this.commands.setHandlers(handlers.commands);
} else {
this.commands = handlers.commands;
}
@ -233,6 +233,8 @@ export class BaseClient {
if (langs.default) this.langs!.defaultLang = langs.default;
if (langs.aliases) this.langs!.aliases = Object.entries(langs.aliases);
}
if (handleCommand) this.handleCommand = new handleCommand(this);
}
protected async execute(..._options: unknown[]) {
@ -275,6 +277,8 @@ export class BaseClient {
} else {
this.cache = new Cache(0, new MemoryAdapter(), [], this);
}
if (!this.handleCommand) this.handleCommand = new HandleCommand(this);
}
protected async onPacket(..._packet: unknown[]) {
@ -383,18 +387,25 @@ export interface BaseClientOptions {
| MessageCommandInteraction<boolean>
| ComponentInteraction
| ModalSubmitInteraction
| When<InferWithPrefix, Message, never>,
| When<InferWithPrefix, MessageStructure, never>,
) => {};
globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[];
commands?: {
defaults?: {
onRunError?: Command['onRunError'];
onRunError?: (context: MenuCommandContext<any, never> | CommandContext, error: unknown) => unknown;
onPermissionsFail?: Command['onPermissionsFail'];
onBotPermissionsFail?: Command['onBotPermissionsFail'];
onInternalError?: Command['onInternalError'];
onMiddlewaresError?: Command['onMiddlewaresError'];
onBotPermissionsFail?: (
context: MenuCommandContext<any, never> | CommandContext,
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'];
onAfterRun?: Command['onAfterRun'];
onAfterRun?: (context: CommandContext | MenuCommandContext<any, never>, error: unknown) => unknown;
props?: ExtraProps;
};
};
@ -480,7 +491,8 @@ export interface ServicesOptions {
middlewares?: Record<string, MiddlewareContext>;
handlers?: {
components?: ComponentHandler | ComponentHandler['callback'];
commands?: CommandHandler | Parameters<CommandHandler['setHandlers']>[0];
commands?: CommandHandler;
langs?: LangsHandler | LangsHandler['callback'];
};
handleCommand?: typeof HandleCommand;
}

View File

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

View File

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

View File

@ -2,3 +2,4 @@ export type { RuntimeConfig, RuntimeConfigHTTP } from './base';
export * from './client';
export * from './httpclient';
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 type { Cache } 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 { ClientUser } from '../structures';
import { Shard, properties, type ShardManagerOptions, type WorkerData } from '../websocket';
import type {
WorkerReady,
@ -23,9 +22,9 @@ import type { ManagerMessages } from '../websocket/discord/workermanager';
import type { BaseClientOptions, ServicesOptions, StartOptions } from './base';
import { BaseClient } from './base';
import type { Client, ClientOptions } from './client';
import { onInteractionCreate } from './oninteractioncreate';
import { defaultArgsParser, defaultOptionsParser, onMessageCreate } from './onmessagecreate';
import { Collectors } from './collectors';
import { type ClientUserStructure, Transformers } from './transformers';
let workerData: WorkerData;
let manager: import('node:worker_threads').MessagePort;
@ -49,7 +48,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
collectors = new Collectors();
events? = new EventHandler(this);
me!: When<Ready, ClientUser>;
me!: When<Ready, ClientUserStructure>;
promises = new Map<string, { resolve: (value: any) => void; timeout: NodeJS.Timeout }>();
shards = new Map<number, Shard>();
@ -58,15 +57,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
constructor(options?: WorkerClientOptions) {
super(options);
this.options = MergeOptions(
{
commands: {
argsParser: defaultArgsParser,
optionsParser: defaultOptionsParser,
},
} satisfies Partial<WorkerClientOptions>,
this.options,
);
if (!process.env.SEYFERT_SPAWNING) {
throw new Error('WorkerClient cannot spawn without manager');
}
@ -129,9 +120,6 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
this.events = undefined;
} else if (typeof rest.handlers.events === 'function') {
this.events = new EventHandler(this);
this.events.setHandlers({
callback: rest.handlers.events,
});
} else {
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) {
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) {
case 'GUILD_CREATE': {
if (this.__handleGuilds?.has(packet.d.id)) {
@ -355,19 +346,19 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
break;
}
default: {
await this.events?.execute(packet.t, packet, this, shardId);
await this.events?.execute(packet.t as never, packet, this, shardId);
switch (packet.t) {
case 'READY':
if (!this.__handleGuilds) this.__handleGuilds = new Set();
for (const g of packet.d.guilds) {
this.__handleGuilds?.add(g.id);
this.__handleGuilds.add(g.id);
}
this.botId = packet.d.user.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 (
!(
this.__handleGuilds?.size &&
(workerData.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds
this.__handleGuilds.size && (workerData.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds
)
) {
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...`);
break;
case 'INTERACTION_CREATE':
await onInteractionCreate(this, packet.d, shardId);
await this.handleCommand.interaction(packet.d, shardId);
break;
case 'MESSAGE_CREATE':
await onMessageCreate(this, packet.d, shardId);
await this.handleCommand.message(packet.d, shardId);
break;
}
break;

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { type APIMessage, ApplicationCommandType, MessageFlags } from 'discord-api-types/v10';
import type { ContextMenuCommand, ReturnCache, WebhookMessage } from '../..';
import type { ContextMenuCommand, ReturnCache } from '../..';
import {
toSnakeCase,
type InteractionCreateBodyRequest,
@ -8,20 +8,20 @@ import {
type UnionToTuple,
type When,
} from '../../common';
import {
Message,
User,
type AllChannels,
type Guild,
type GuildMember,
type MessageCommandInteraction,
type UserCommandInteraction,
} from '../../structures';
import type { AllChannels, MessageCommandInteraction, UserCommandInteraction } from '../../structures';
import { BaseContext } from '../basecontext';
import type { RegisteredMiddlewares } from '../decorators';
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<
T extends MessageCommandInteraction | UserCommandInteraction,
@ -50,11 +50,11 @@ export class MenuCommandContext<
switch (this.interaction.data.type) {
case ApplicationCommandType.Message: {
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: {
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>(
body: InteractionCreateBodyRequest,
fetchReply?: FR,
): Promise<When<FR, WebhookMessage, void | WebhookMessage>> {
): Promise<When<FR, WebhookMessageStructure, void | WebhookMessageStructure>> {
return this.interaction.write(body, fetchReply);
}
@ -93,7 +93,7 @@ export class MenuCommandContext<
editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest,
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);
}
@ -109,8 +109,8 @@ export class MenuCommandContext<
return this.client.channels.fetch(this.channelId, mode === 'rest');
}
me(mode?: 'rest' | 'flow'): Promise<GuildMember>;
me(mode?: 'cache'): ReturnCache<GuildMember | undefined>;
me(mode?: 'rest' | 'flow'): Promise<GuildMemberStructure>;
me(mode?: 'cache'): ReturnCache<GuildMemberStructure | undefined>;
me(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId)
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?: 'cache'): ReturnCache<Guild<'cached'> | undefined>;
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'> | undefined>;
guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId)
return (
@ -158,10 +158,10 @@ export class MenuCommandContext<
}
isMenuUser(): this is MenuCommandContext<UserCommandInteraction> {
return this.target instanceof User;
return this.interaction.data.type === ApplicationCommandType.User;
}
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 type ParseClient<T extends BaseClient> = T;
export interface InternalOptions {}
export interface CustomStructures {}
export type MiddlewareContext<T = any, C = any> = (context: {
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,
} from 'discord-api-types/v10';
import { basename, dirname } from 'node:path';
import type { Logger } from '../common';
import type { Logger, MakeRequired, NulleableCoalising, OmitInsert } 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 type { UsingClient } from './applications/shared';
import { promises } from 'node:fs';
@ -169,26 +169,21 @@ export class CommandHandler extends BaseHandler {
return false;
}
async load(commandsDir: string, client: UsingClient, instances?: { new (): Command | ContextMenuCommand }[]) {
const result =
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);
async load(commandsDir: string, client: UsingClient) {
const result = await this.loadFilesK<FileLoaded<null>>(await this.getFiles(commandsDir));
this.values = [];
for (const command of result) {
for (const { commands, file } of result.map(x => ({ commands: this.onFile(x.file), file: x }))) {
if (!commands) continue;
for (const command of commands) {
let commandInstance;
try {
commandInstance = this.onCommand(command.file);
commandInstance = this.onCommand(command);
if (!commandInstance) continue;
} catch (e) {
if (e instanceof Error && e.message.includes('is not a constructor')) {
this.logger.warn(
`${command.path
`${file.path
.split(process.cwd())
.slice(1)
.join(process.cwd())} doesn't export the class by \`export default <Command>\``,
@ -196,47 +191,202 @@ export class CommandHandler extends BaseHandler {
} else this.logger.warn(e, command);
continue;
}
if (commandInstance instanceof ContextMenuCommand) {
this.values.push(commandInstance);
commandInstance.__filePath = command.path;
this.__parseCommandLocales(commandInstance);
commandInstance.props ??= client.options.commands?.defaults?.props ?? {};
continue;
}
if (!(commandInstance instanceof Command)) {
continue;
}
commandInstance.onAfterRun ??= client.options.commands?.defaults?.onAfterRun;
commandInstance.onBotPermissionsFail ??= client.options.commands?.defaults?.onBotPermissionsFail;
commandInstance.onInternalError ??= client.options.commands?.defaults?.onInternalError;
commandInstance.onMiddlewaresError ??= client.options.commands?.defaults?.onMiddlewaresError;
commandInstance.onOptionsError ??= client.options.commands?.defaults?.onOptionsError;
commandInstance.onPermissionsFail ??= client.options.commands?.defaults?.onPermissionsFail;
commandInstance.onRunError ??= client.options.commands?.defaults?.onRunError;
commandInstance.__filePath = command.path;
commandInstance.options ??= [] as NonNullable<Command['options']>;
if (commandInstance instanceof SubCommand) continue;
commandInstance.__filePath = file.path;
commandInstance.props ??= client.options.commands?.defaults?.props ?? {};
const isAvailableCommand = this.stablishCommandDefaults(commandInstance);
if (isAvailableCommand) {
commandInstance = isAvailableCommand;
if (commandInstance.__autoload) {
//@AutoLoad
const options = await this.getFiles(dirname(command.path));
const options = await this.getFiles(dirname(file.path));
for (const option of options) {
if (command.name === basename(option)) {
if (file.name === basename(option)) {
continue;
}
try {
const subCommand = this.onSubCommand(result.find(x => x.path === option)!.file as { new (): SubCommand });
const fileSubCommands = this.onFile(result.find(x => x.path === option)!.file);
if (!fileSubCommands) {
this.logger.warn(`SubCommand returned (${fileSubCommands}) ignoring.`);
continue;
}
for (const fileSubCommand of fileSubCommands) {
const subCommand = this.onSubCommand(fileSubCommand as HandleableSubCommand);
if (subCommand && subCommand instanceof SubCommand) {
subCommand.__filePath = option;
commandInstance.options.push(subCommand);
} else {
this.logger.warn(subCommand ? 'SubCommand expected' : 'Invalid SubCommand', subCommand);
}
}
} catch {
//pass
}
}
}
for (const option of commandInstance.options ?? []) {
if (option instanceof SubCommand) this.stablishSubCommandDefaults(commandInstance, option);
}
}
this.stablishContextCommandDefaults(commandInstance);
this.values.push(commandInstance);
this.parseLocales(commandInstance);
}
}
return this.values;
}
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) {
command.name_localizations = {};
command.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);
if (command.__t.name) {
for (const i of locales) {
const valueName = this.client.langs!.getKey(locale, command.__t.name!);
if (valueName) command.name_localizations[i] = valueName;
}
}
if (command.__t.description) {
for (const i of locales) {
const valueKey = this.client.langs!.getKey(locale, command.__t.description!);
if (valueKey) command.description_localizations[i] = valueKey;
}
}
}
}
}
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);
if (option.locales.name) {
for (const i of locales) {
const valueName = this.client.langs!.getKey(locale, option.locales.name!);
if (valueName) option.name_localizations[i] = valueName;
}
}
if (option.locales.description) {
for (const i of locales) {
const valueKey = this.client.langs!.getKey(locale, option.locales.description!);
if (valueKey) option.description_localizations[i] = valueKey;
}
}
}
}
parseCommandLocales(command: Command) {
command.groups = {};
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 group in command.__tGroups) {
command.groups[group] ??= {
defaultDescription: command.__tGroups[group].defaultDescription,
description: [],
name: [],
};
if (command.__tGroups[group].name) {
for (const i of locales) {
const valueName = this.client.langs!.getKey(locale, command.__tGroups[group].name!);
if (valueName) {
command.groups[group].name!.push([i, valueName]);
}
}
}
if (command.__tGroups[group].description) {
for (const i of locales) {
const valueKey = this.client.langs!.getKey(locale, command.__tGroups[group].description!);
if (valueKey) {
command.groups[group].description!.push([i, valueKey]);
}
}
}
}
}
}
parseContextMenuLocales(command: ContextMenuCommand) {
return command;
}
parseSubCommandLocales(command: SubCommand) {
for (const i of command.options ?? []) {
// @ts-expect-error
if (i.locales) this.parseCommandOptionLocales(i);
}
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) ??
@ -271,121 +421,25 @@ export class CommandHandler extends BaseHandler {
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();
}
}
this.values.push(commandInstance);
this.__parseCommandLocales(commandInstance);
export type FileLoaded<T = null> = {
default?: NulleableCoalising<T, HandleableCommand>;
} & Record<string, NulleableCoalising<T, HandleableCommand>>;
for (const i of commandInstance.options ?? []) {
if (i instanceof SubCommand) {
this.__parseCommandLocales(i);
}
}
}
return this.values;
}
private __parseCommandLocales(command: Command | SubCommand | ContextMenuCommand) {
if (command.__t) {
command.name_localizations = {};
command.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);
if (command.__t.name) {
for (const i of locales) {
const valueName = this.client.langs!.getKey(locale, command.__t.name!);
if (valueName) command.name_localizations[i] = valueName;
}
}
if (command.__t.description) {
for (const i of locales) {
const valueKey = this.client.langs!.getKey(locale, command.__t.description!);
if (valueKey) command.description_localizations[i] = valueKey;
}
}
}
}
if (command instanceof ContextMenuCommand) return;
for (const options of command.options ?? []) {
if (options instanceof SubCommand || !options.locales) continue;
options.name_localizations = {};
options.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);
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) {
for (const i of locales) {
const valueKey = this.client.langs!.getKey(locale, options.locales.description!);
if (valueKey) options.description_localizations[i] = valueKey;
}
}
}
}
if (command instanceof Command && command.__tGroups) {
command.groups = {};
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 group in command.__tGroups) {
command.groups[group] ??= {
defaultDescription: command.__tGroups[group].defaultDescription,
description: [],
name: [],
};
if (command.__tGroups[group].name) {
for (const i of locales) {
const valueName = this.client.langs!.getKey(locale, command.__tGroups[group].name!);
if (valueName) {
command.groups[group].name!.push([i, valueName]);
}
}
}
if (command.__tGroups[group].description) {
for (const i of locales) {
const valueKey = this.client.langs!.getKey(locale, command.__tGroups[group].description!);
if (valueKey) {
command.groups[group].description!.push([i, valueKey]);
}
}
}
}
}
}
}
setHandlers({
onCommand,
onSubCommand,
}: {
onCommand?: CommandHandler['onCommand'];
onSubCommand?: CommandHandler['onSubCommand'];
}) {
if (onCommand) this.onCommand = onCommand;
if (onSubCommand) this.onSubCommand = onSubCommand;
}
onCommand = (file: { new (): Command | SubCommand | ContextMenuCommand }):
| Command
| SubCommand
| ContextMenuCommand
| false => new file();
onSubCommand = (file: { new (): SubCommand }): SubCommand | false => new file();
}
export type HandleableCommand = new () => Command | SubCommand | ContextMenuCommand;
export type HandleableSubCommand = new () => SubCommand;

View File

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

View File

@ -130,7 +130,7 @@ export class BaseHandler {
* @param paths The paths of the files to load.
* @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)));
}
@ -139,13 +139,13 @@ export class BaseHandler {
* @param paths The paths of the files to load.
* @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(
paths.map(path =>
magicImport(path).then(file => {
return {
name: basename(path),
file: file.default ?? file,
file,
path,
};
}),

View File

@ -5,7 +5,7 @@ import type {
RESTPutAPIGuildBanJSONBody,
} from 'discord-api-types/v10';
import { BaseShorter } from './base';
import { GuildBan } from '../../structures/GuildBan';
import { Transformers } from '../../client/transformers';
export class BanShorter extends BaseShorter {
/**
@ -58,7 +58,7 @@ export class BanShorter extends BaseShorter {
ban = await this.client.proxy.guilds(guildId).bans(userId).get();
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]),
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 {
type APIChannel,
PermissionFlagsBits,
type RESTGetAPIChannelMessagesQuery,
type RESTPatchAPIChannelJSONBody,
type RESTPostAPIChannelThreadsJSONBody,
type RESTPostAPIGuildForumThreadsJSONBody,
} 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 { PermissionsBitField } from '../../structures/extra/Permissions';
import { BaseShorter } from './base';
import { MergeOptions } from '../it/utils';
import { type MessageStructure, Transformers } from '../../client/transformers';
export class ChannelShorter extends BaseShorter {
/**
@ -19,15 +21,19 @@ export class ChannelShorter extends BaseShorter {
* @returns A Promise that resolves to the fetched channel.
*/
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;
if (!force) {
channel = await this.client.cache.channels?.get(id);
channel = await this.client.cache.channels?.raw(id);
if (channel) return channel;
}
channel = await this.client.proxy.channels(id).get();
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();
}
async pins(channelId: string): Promise<Message[]> {
async pins(channelId: string): Promise<MessageStructure[]> {
const messages = await this.client.proxy.channels(channelId).pins.get();
await this.client.cache.messages?.patch(
messages.map(x => {
@ -85,7 +91,7 @@ export class ChannelShorter extends BaseShorter {
}) satisfies [string, any][],
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][],
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) {

View File

@ -1,9 +1,9 @@
import type { APIEmoji, RESTPatchAPIGuildEmojiJSONBody, RESTPostAPIGuildEmojiJSONBody } from 'discord-api-types/v10';
import { GuildEmoji } from '../..';
import { resolveImage } from '../../builders';
import type { ImageResolvable } from '../types/resolvables';
import type { OmitInsert } from '../types/util';
import { BaseShorter } from './base';
import { Transformers } from '../../client/transformers';
export class EmojiShorter extends BaseShorter {
/**
@ -25,7 +25,7 @@ export class EmojiShorter extends BaseShorter {
emojis.map<[string, APIEmoji]>(x => [x.id!, x]),
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);
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;
}
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) {
const emoji = await this.client.proxy.guilds(guildId).emojis(emojiId).patch({ body, reason });
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';
import { toSnakeCase, type ObjectToLower } from '..';
import { resolveFiles } from '../../builders';
import {
AnonymousGuild,
AutoModerationRule,
BaseChannel,
Guild,
GuildMember,
Sticker,
type CreateStickerBodyRequest,
} from '../../structures';
import { BaseChannel, GuildMember, type CreateStickerBodyRequest } from '../../structures';
import channelFrom from '../../structures/channels';
import { BaseShorter } from './base';
import { type GuildStructure, Transformers } from '../../client/transformers';
export class GuildShorter extends BaseShorter {
/**
@ -30,10 +23,10 @@ export class GuildShorter extends BaseShorter {
* @param body The data for creating the 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 });
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.
*/
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) {
const guild = await this.client.cache.guilds?.get(id);
const guild = await this.client.cache.guilds?.raw(id);
if (guild) return guild;
}
const data = await this.client.proxy.guilds(id).get();
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
.users('@me')
.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) {
if (!force) {
const self = await this.client.cache.members?.get(this.client.botId, id);
if (self) return self;
const self = await this.client.cache.members?.raw(this.client.botId, id);
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();
await this.client.cache.members?.patch(self.user!.id, id, self);
return new GuildMember(this.client, self, self.user!, id);
await this.client.cache.members?.patch(self.user.id, id, self);
return new GuildMember(this.client, self, self.user, id);
}
leave(id: string) {
@ -211,7 +208,7 @@ export class GuildShorter extends BaseShorter {
this.client.proxy
.guilds(guildId)
['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.
@ -223,7 +220,7 @@ export class GuildShorter extends BaseShorter {
this.client.proxy
.guilds(guildId)
['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.
@ -247,7 +244,7 @@ export class GuildShorter extends BaseShorter {
.guilds(guildId)
['auto-moderation'].rules(ruleId)
.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)
['auto-moderation'].rules(ruleId)
.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),
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)
.stickers.post({ reason, body: json, files: [{ ...fileResolve[0], key: 'file' }], appendToFormData: true });
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) => {
const sticker = await this.client.proxy.guilds(guildId).stickers(stickerId).patch({ body, reason });
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();
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 { BaseShorter } from './base';
@ -42,7 +43,7 @@ export class InteractionShorter extends BaseShorter {
body: BaseInteraction.transformBody(data, parsedFiles, this.client),
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) {
@ -69,6 +70,6 @@ export class InteractionShorter extends BaseShorter {
body: BaseInteraction.transformBody(body, parsedFiles, this.client),
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,
RESTPutAPIGuildMemberJSONBody,
} from 'discord-api-types/v10';
import { GuildMember } from '../../structures';
import { PermissionsBitField } from '../../structures/extra/Permissions';
import type { GuildMemberResolvable } from '../types/resolvables';
import { BaseShorter } from './base';
import { Transformers } from '../../client/transformers';
export class MemberShorter extends BaseShorter {
/**
@ -52,7 +52,7 @@ export class MemberShorter extends BaseShorter {
members.map(x => [x.user!.id, x]),
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) {
const member = await this.client.proxy.guilds(guildId).members(memberId).patch({ body, reason });
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);
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.
*/
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;
if (!force) {
member = await this.client.cache.members?.get(memberId, guildId);
member = await this.client.cache.members?.raw(memberId, guildId);
if (member) return member;
}
member = await this.client.proxy.guilds(guildId).members(memberId).get();
await this.client.cache.members?.set(member.user!.id, guildId, member);
return new GuildMember(this.client, member, member.user!, guildId);
await this.client.cache.members?.set(member.user.id, guildId, member);
return member;
}
/**
@ -161,7 +166,7 @@ export class MemberShorter extends BaseShorter {
query,
});
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,
} from 'discord-api-types/v10';
import { resolveFiles } from '../../builders';
import { Message, MessagesMethods, User } from '../../structures';
import { MessagesMethods } from '../../structures';
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../types/write';
import { BaseShorter } from './base';
import type { ValidAnswerId } from '../../api/Routes/channels';
import { Transformers } from '../../client/transformers';
export class MessageShorter extends BaseShorter {
async write(channelId: string, { files, ...body }: MessageCreateBodyRequest) {
@ -27,7 +28,7 @@ export class MessageShorter extends BaseShorter {
})
.then(async 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 => {
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 })
.then(async 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()
.then(async 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)
.polls(messageId)
.expire.post()
.then(message => new Message(this.client, message));
.then(message => Transformers.Message(this.client, message));
}
getAnswerVoters(channelId: string, messageId: string, answerId: ValidAnswerId) {
@ -108,6 +109,6 @@ export class MessageShorter extends BaseShorter {
.polls(messageId)
.answers(answerId)
.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 { User } from '../../structures';
import { encodeEmoji, resolveEmoji } from '../../structures/extra/functions';
import type { EmojiResolvable } from '../types/resolvables';
import { BaseShorter } from './base';
import { Transformers, type UserStructure } from '../../client/transformers';
export class ReactionShorter extends BaseShorter {
async add(messageId: string, channelId: string, emoji: EmojiResolvable): Promise<void> {
@ -30,7 +30,7 @@ export class ReactionShorter extends BaseShorter {
channelId: string,
emoji: EmojiResolvable,
query?: RESTGetAPIChannelMessageReactionUsersQuery,
): Promise<User[]> {
): Promise<UserStructure[]> {
const rawEmoji = await resolveEmoji(emoji, this.client.cache);
if (!rawEmoji) {
@ -42,7 +42,7 @@ export class ReactionShorter extends BaseShorter {
.messages(messageId)
.reactions(encodeEmoji(rawEmoji))
.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> {

View File

@ -4,8 +4,8 @@ import type {
RESTPatchAPIGuildRolePositionsJSONBody,
RESTPostAPIGuildRoleJSONBody,
} from 'discord-api-types/v10';
import { GuildRole } from '../../structures';
import { BaseShorter } from './base';
import { Transformers } from '../../client/transformers';
export class RoleShorter extends BaseShorter {
/**
@ -18,7 +18,7 @@ export class RoleShorter extends BaseShorter {
async create(guildId: string, body: RESTPostAPIGuildRoleJSONBody, reason?: string) {
const res = await this.client.proxy.guilds(guildId).roles.post({ body, reason });
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.
*/
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[] = [];
if (!force) {
const cachedRoles = (await this.client.cache.roles?.values(guildId)) ?? [];
const cachedRoles = (await this.client.cache.roles?.valuesRaw(guildId)) ?? [];
if (cachedRoles.length) {
return cachedRoles;
}
@ -40,7 +45,7 @@ export class RoleShorter extends BaseShorter {
roles.map<[string, APIRole]>(r => [r.id, r]),
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) {
const res = await this.client.proxy.guilds(guildId).roles(roleId).patch({ body, reason });
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) {
const res = await this.client.proxy.guilds(guildId).roles(roleId).delete({ reason });
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,
);
}
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 { BaseShorter } from './base';
import { GuildTemplate } from '../..';
import { Transformers } from '../../client/transformers';
export class TemplateShorter extends BaseShorter {
fetch(code: string) {
return this.client.proxy.guilds
.templates(code)
.get()
.then(template => new GuildTemplate(this.client, template));
.then(template => Transformers.GuildTemplate(this.client, template));
}
list(guildId: string) {
return this.client.proxy
.guilds(guildId)
.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) {
return this.client.proxy
.guilds(guildId)
.templates.post({ body })
.then(template => new GuildTemplate(this.client, template));
.then(template => Transformers.GuildTemplate(this.client, template));
}
sync(guildId: string, code: string) {
@ -29,7 +29,7 @@ export class TemplateShorter extends BaseShorter {
.guilds(guildId)
.templates(code)
.put({})
.then(template => new GuildTemplate(this.client, template));
.then(template => Transformers.GuildTemplate(this.client, template));
}
edit(guildId: string, code: string, body: RESTPatchAPIGuildTemplateJSONBody) {
@ -37,7 +37,7 @@ export class TemplateShorter extends BaseShorter {
.guilds(guildId)
.templates(code)
.patch({ body })
.then(template => new GuildTemplate(this.client, template));
.then(template => Transformers.GuildTemplate(this.client, template));
}
delete(guildId: string, code: string) {
@ -45,6 +45,6 @@ export class TemplateShorter extends BaseShorter {
.guilds(guildId)
.templates(code)
.delete()
.then(template => new GuildTemplate(this.client, template));
.then(template => Transformers.GuildTemplate(this.client, template));
}
}

View File

@ -7,10 +7,10 @@ import type {
RESTPostAPIChannelThreadsJSONBody,
RESTPostAPIGuildForumThreadsJSONBody,
} from 'discord-api-types/v10';
import type { ThreadChannel } from '../../structures';
import channelFrom from '../../structures/channels';
import { BaseShorter } from './base';
import type { MakeRequired, When } from '../types/util';
import type { ThreadChannelStructure } from '../../client/transformers';
export class ThreadShorter extends BaseShorter {
/**
@ -29,7 +29,7 @@ export class ThreadShorter extends BaseShorter {
.channels(channelId)
.threads.post({ body, reason })
// 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)
.messages(messageId)
.threads.post({ body, reason })
.then(thread => channelFrom(thread, this.client) as ThreadChannel);
.then(thread => channelFrom(thread, this.client) as ThreadChannelStructure);
}
async join(threadId: string) {
@ -56,7 +56,7 @@ export class ThreadShorter extends BaseShorter {
}
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) {
@ -98,7 +98,7 @@ export class ThreadShorter extends BaseShorter {
const data = await this.client.proxy.channels(channelId).threads.archived[type].get({ query });
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[],
hasMore: data.has_more,
};
@ -107,7 +107,7 @@ export class ThreadShorter extends BaseShorter {
async listJoinedArchivedPrivate(channelId: string, query?: RESTGetAPIChannelThreadsArchivedQuery) {
const data = await this.client.proxy.channels(channelId).users('@me').threads.archived.private.get({ query });
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[],
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 { BaseShorter } from './base';
@ -6,30 +7,34 @@ export class UsersShorter extends BaseShorter {
async createDM(userId: string, force = false) {
if (!force) {
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({
body: { recipient_id: userId },
});
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) {
const res = await this.client.proxy.channels(userId).delete({ reason });
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) {
return Transformers.User(this.client, await this.raw(userId, force));
}
async raw(userId: string, force = false) {
if (!force) {
const user = await this.client.cache.users?.get(userId);
const user = await this.client.cache.users?.raw(userId);
if (user) return user;
}
const data = await this.client.proxy.users(userId).get();
await this.client.cache.users?.patch(userId, data);
return new User(this.client, data);
return data;
}
async write(userId: string, body: MessageCreateBodyRequest) {

View File

@ -7,19 +7,18 @@ import type {
import { resolveFiles } from '../../builders';
import {
MessagesMethods,
Webhook,
WebhookMessage,
type MessageWebhookMethodEditParams,
type MessageWebhookMethodWriteParams,
} from '../../structures';
import { BaseShorter } from './base';
import { Transformers } from '../../client/transformers';
export class WebhookShorter extends BaseShorter {
async create(channelId: string, body: RESTPostAPIChannelWebhookJSONBody) {
const webhook = await this.client.proxy.channels(channelId).webhooks.post({
body,
});
return new Webhook(this.client, webhook);
return Transformers.Webhook(this.client, webhook);
}
/**
* Deletes a webhook.
@ -50,12 +49,12 @@ export class WebhookShorter extends BaseShorter {
return this.client.proxy
.webhooks(webhookId)(options.token)
.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
.webhooks(webhookId)
.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 {
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
.webhooks(webhookId)(token)
.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)
.messages(messageId)
.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)
.messages(messageId)
.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) {
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) {
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 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> = {
[P in keyof T]?: T[P] | undefined;
} & {

View File

@ -1,14 +1,20 @@
import type { ButtonStyle, ComponentType } from 'discord-api-types/v10';
import { Button, type ButtonStylesForID } from '../builders';
import type {
APIButtonComponentWithCustomId,
APIButtonComponentWithSKUId,
APIButtonComponentWithURL,
ButtonStyle,
ComponentType,
} from 'discord-api-types/v10';
import { Button } from '../builders';
import { BaseComponent } from './BaseComponent';
export class LinkButtonComponent extends BaseComponent<ComponentType.Button> {
declare data: APIButtonComponentWithURL;
get style() {
return this.data.style as ButtonStyle.Link;
return this.data.style;
}
get url(): string {
// @ts-expect-error
return this.data.url;
}
@ -25,15 +31,20 @@ export class LinkButtonComponent extends BaseComponent<ComponentType.Button> {
}
toBuilder() {
return new Button<false>(this.data as never);
return new Button(this.data);
}
}
export type ButtonStyleExludeLink = Exclude<ButtonStyle, ButtonStyle.Link>;
export class ButtonComponent extends BaseComponent<ComponentType.Button> {
declare data: APIButtonComponentWithCustomId;
get style() {
return this.data.style as ButtonStylesForID;
return this.data.style;
}
get customId() {
return this.data.custom_id;
}
get label() {
@ -49,6 +60,25 @@ export class ButtonComponent extends BaseComponent<ComponentType.Button> {
}
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,
ChannelSelectMenuInteraction,
ComponentCommand,
Guild,
GuildMember,
MentionableSelectMenuInteraction,
Message,
ReturnCache,
RoleSelectMenuInteraction,
StringSelectMenuInteraction,
UserSelectMenuInteraction,
WebhookMessage,
} from '..';
import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands';
import { BaseContext } from '../commands/basecontext';
@ -24,6 +20,12 @@ import type {
UnionToTuple,
When,
} from '../common';
import type {
GuildMemberStructure,
GuildStructure,
MessageStructure,
WebhookMessageStructure,
} from '../client/transformers';
export interface ComponentContext<
Type extends keyof ContextComponentCommandInteractionMap = keyof ContextComponentCommandInteractionMap,
@ -109,7 +111,7 @@ export class ComponentContext<
editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest,
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);
}
@ -143,8 +145,8 @@ export class ComponentContext<
* @param mode - The mode to fetch the member.
* @returns A promise that resolves to the bot member.
*/
me(mode?: 'rest' | 'flow'): Promise<GuildMember>;
me(mode?: 'cache'): ReturnCache<GuildMember | undefined>;
me(mode?: 'rest' | 'flow'): Promise<GuildMemberStructure>;
me(mode?: 'cache'): ReturnCache<GuildMemberStructure | undefined>;
me(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId)
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.
* @returns A promise that resolves to the guild.
*/
guild(mode?: 'rest' | 'flow'): Promise<Guild<'cached' | 'api'> | undefined>;
guild(mode?: 'cache'): ReturnCache<Guild<'cached'> | undefined>;
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'> | undefined>;
guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId)
return (

View File

@ -1,6 +1,7 @@
import type { ComponentCallback, ListenerOptions, ModalSubmitCallback } from '../builders/types';
import { LimitedCollection } from '../collection';
import { BaseCommand, type RegisteredMiddlewares, type UsingClient } from '../commands';
import type { FileLoaded } from '../commands/handler';
import { BaseHandler, magicImport, type Logger, type OnFailCallback } from '../common';
import type { ComponentInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from '../structures';
import { ComponentCommand, InteractionCommandType } from './componentcommand';
@ -17,12 +18,15 @@ type COMPONENTS = {
__run: (customId: string | string[] | RegExp, callback: ComponentCallback) => any;
};
export type CollectorInteraction = ComponentInteraction | StringSelectMenuInteraction;
export type ComponentCommands = ComponentCommand | ModalCommand;
export class ComponentHandler extends BaseHandler {
onFail: OnFailCallback = err => this.logger.warn('<Client>.components.onFail', err);
readonly values = new Map<string, COMPONENTS>();
// 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 commands: (ComponentCommand | ModalCommand)[] = [];
readonly commands: ComponentCommands[] = [];
protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts'));
constructor(
@ -36,9 +40,10 @@ export class ComponentHandler extends BaseHandler {
messageId: string,
options: ListenerOptions = {},
): {
run<
T extends ComponentInteraction | StringSelectMenuInteraction = ComponentInteraction | StringSelectMenuInteraction,
>(customId: string | string[] | RegExp, callback: ComponentCallback<T>): any;
run<T extends CollectorInteraction = CollectorInteraction>(
customId: string | string[] | RegExp,
callback: ComponentCallback<T>,
): any;
stop(reason?: string): any;
} {
this.values.set(messageId, {
@ -149,22 +154,29 @@ export class ComponentHandler extends BaseHandler {
this.deleteValue(id, 'messageDelete');
}
async load(componentsDir: string, instances?: { new (): ModalCommand | ComponentCommand }[]) {
const paths =
instances?.map(x => {
const i = new x();
return { file: x, path: i.__filePath ?? '*' };
}) ?? (await this.loadFilesK<{ new (): ModalCommand | ComponentCommand }>(await this.getFiles(componentsDir)));
stablishDefaults(component: ComponentCommands) {
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;
}
for (const value of paths) {
async load(componentsDir: string) {
const paths = await this.loadFilesK<FileLoaded<new () => ComponentCommands>>(await this.getFiles(componentsDir));
for (const { components, file } of paths.map(x => ({ components: this.onFile(x.file), file: x }))) {
if (!components) continue;
for (const value of components) {
let component;
try {
component = this.callback(value.file);
component = this.callback(value);
if (!component) continue;
} catch (e) {
if (e instanceof Error && e.message.includes('is not a constructor')) {
this.logger.warn(
`${value.path
`${file.path
.split(process.cwd())
.slice(1)
.join(process.cwd())} doesn't export the class by \`export default <ComponentCommand>\``,
@ -173,16 +185,12 @@ export class ComponentHandler extends BaseHandler {
continue;
}
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.stablishDefaults(component);
component.__filePath = file.path;
this.commands.push(component);
}
}
}
async reload(path: string) {
if (!this.client.components) return;
@ -217,15 +225,7 @@ export class ComponentHandler extends BaseHandler {
}
}
async executeComponent(context: ComponentContext) {
for (const i of this.commands) {
try {
if (
i.type === InteractionCommandType.COMPONENT &&
i.cType === context.interaction.componentType &&
(await i.filter(context))
) {
context.command = i;
async execute(i: ComponentCommands, context: ComponentContext | ModalContext) {
try {
const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares(
context,
@ -236,7 +236,7 @@ export class ComponentHandler extends BaseHandler {
return;
}
if ('error' in resultRunGlobalMiddlewares) {
return i.onMiddlewaresError?.(context, resultRunGlobalMiddlewares.error ?? 'Unknown error');
return i.onMiddlewaresError?.(context as never, resultRunGlobalMiddlewares.error ?? 'Unknown error');
}
const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false);
@ -244,24 +244,36 @@ export class ComponentHandler extends BaseHandler {
return;
}
if ('error' in resultRunMiddlewares) {
return i.onMiddlewaresError?.(context, resultRunMiddlewares.error ?? 'Unknown error');
return i.onMiddlewaresError?.(context as never, resultRunMiddlewares.error ?? 'Unknown error');
}
try {
await i.run(context);
await i.onAfterRun?.(context, undefined);
await i.run(context as never);
await i.onAfterRun?.(context as never, undefined);
} catch (error) {
await i.onRunError?.(context, error);
await i.onAfterRun?.(context, 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 {
} catch (e) {
// supress error
this.logger.error(e);
}
}
break;
}
async executeComponent(context: ComponentContext) {
for (const i of this.commands) {
try {
if (
i.type === InteractionCommandType.COMPONENT &&
i.cType === context.interaction.componentType &&
(await i.filter(context))
) {
context.command = i;
await this.execute(i, context);
}
} catch (e) {
await this.onFail(e);
@ -274,42 +286,7 @@ export class ComponentHandler extends BaseHandler {
try {
if (i.type === InteractionCommandType.MODAL && (await i.filter(context))) {
context.command = i;
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, 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;
await this.execute(i, context);
}
} catch (e) {
await this.onFail(e);
@ -317,9 +294,11 @@ export class ComponentHandler extends BaseHandler {
}
}
setHandlers({ callback }: { callback: ComponentHandler['callback'] }) {
this.callback = callback;
onFile(file: FileLoaded<new () => ComponentCommands>): (new () => ComponentCommands)[] | undefined {
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 { BaseComponent } from './BaseComponent';
import { ButtonComponent, LinkButtonComponent } from './ButtonComponent';
import { ButtonComponent, LinkButtonComponent, SKUButtonComponent } from './ButtonComponent';
import { ChannelSelectMenuComponent } from './ChannelSelectMenuComponent';
import { MentionableSelectMenuComponent } from './MentionableSelectMenuComponent';
import { RoleSelectMenuComponent } from './RoleSelectMenuComponent';
@ -11,6 +11,7 @@ import { UserSelectMenuComponent } from './UserSelectMenuComponent';
export type MessageComponents =
| ButtonComponent
| LinkButtonComponent
| SKUButtonComponent
| RoleSelectMenuComponent
| UserSelectMenuComponent
| StringSelectMenuComponent
@ -39,6 +40,9 @@ export function componentFactory(
if (component.style === ButtonStyle.Link) {
return new LinkButtonComponent(component);
}
if (component.style === ButtonStyle.Premium) {
return new SKUButtonComponent(component);
}
return new ButtonComponent(component);
case ComponentType.ChannelSelect:
return new ChannelSelectMenuComponent(component);

View File

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

View File

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

View File

@ -5,8 +5,8 @@ import type {
GatewayAutoModerationRuleUpdateDispatchData,
} from 'discord-api-types/v10';
import { toCamelCase } from '../../common';
import { AutoModerationRule } from '../../structures';
import type { UsingClient } from '../../commands';
import { Transformers } from '../../client/transformers';
export const AUTO_MODERATION_ACTION_EXECUTION = (
_self: UsingClient,
@ -16,13 +16,13 @@ export const AUTO_MODERATION_ACTION_EXECUTION = (
};
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) => {
return new AutoModerationRule(self, data);
return Transformers.AutoModerationRule(self, data);
};
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 { ClientUser } from '../../structures';
export const BOT_READY = (_self: UsingClient, me: ClientUser) => {
export const BOT_READY = (_self: UsingClient, me: ClientUserStructure) => {
return me;
};
export const WORKER_READY = (_self: UsingClient, me: ClientUser) => {
export const WORKER_READY = (_self: UsingClient, me: ClientUserStructure) => {
return me;
};

View File

@ -1,9 +1,9 @@
import type { GatewayDispatchPayload, GatewayReadyDispatchData, GatewayResumedDispatch } from 'discord-api-types/v10';
import { ClientUser } from '../../structures';
import type { UsingClient } from '../../commands';
import { Transformers } from '../../client/transformers';
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']) => {

View File

@ -22,23 +22,28 @@ import type {
GatewayGuildUpdateDispatchData,
} from 'discord-api-types/v10';
import { toCamelCase } from '../../common';
import { Guild, GuildEmoji, GuildMember, GuildRole, Sticker, User } from '../../structures';
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) => {
return toCamelCase(data);
};
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) => {
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) => {
return new Guild<'create'>(self, data);
return Transformers.Guild<'create'>(self, data);
};
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) => {
return {
...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) => {
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) => {
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) => {
return {
...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 (
self: UsingClient,
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);
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 = (
@ -115,7 +120,7 @@ export const GUILD_SCHEDULED_EVENT_USER_REMOVE = (
};
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) => {
@ -125,20 +130,20 @@ export const GUILD_ROLE_DELETE = async (self: UsingClient, data: GatewayGuildRol
export const GUILD_ROLE_UPDATE = async (
self: UsingClient,
data: GatewayGuildRoleUpdateDispatchData,
): Promise<[role: GuildRole, old?: GuildRole]> => {
return [new GuildRole(self, data.role, data.guild_id), await self.cache.roles?.get(data.role.id)];
): Promise<[role: GuildRoleStructure, old?: GuildRoleStructure]> => {
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) => {
return {
...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 (
self: UsingClient,
data: GatewayGuildUpdateDispatchData,
): Promise<[guild: Guild, old?: Guild<'cached'>]> => {
return [new Guild(self, data), await self.cache.guilds?.get(data.id)];
): Promise<[guild: GuildStructure, old?: GuildStructure<'cached'>]> => {
return [Transformers.Guild(self, data), await self.cache.guilds?.get(data.id)];
};

View File

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

View File

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

View File

@ -3,5 +3,5 @@ import type { UsingClient } from '../../commands';
import { toCamelCase } from '../../common';
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,
} from 'discord-api-types/v10';
import { toCamelCase } from '../../common';
import { ThreadChannel } from '../../structures';
import type { UsingClient } from '../../commands';
import { type ThreadChannelStructure, Transformers } from '../../client/transformers';
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) => {
return new ThreadChannel(self, data);
return Transformers.ThreadChannel(self, data);
};
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 (
self: UsingClient,
data: GatewayThreadUpdateDispatchData,
): Promise<[thread: ThreadChannel, old?: ThreadChannel]> => {
return [new ThreadChannel(self, data), await self.cache.threads?.get(data.id)];
): Promise<[thread: ThreadChannelStructure, old?: ThreadChannelStructure]> => {
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 { toCamelCase } from '../../common';
import { GuildMember } from '../../structures';
import type { UsingClient } from '../../commands';
import { Transformers } from '../../client/transformers';
export const TYPING_START = (self: UsingClient, data: GatewayTypingStartDispatchData) => {
return data.member
? {
...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);
};

View File

@ -1,10 +1,10 @@
import type { GatewayUserUpdateDispatchData } from 'discord-api-types/v10';
import { User } from '../../structures';
import type { UsingClient } from '../../commands';
import { Transformers, type UserStructure } from '../../client/transformers';
export const USER_UPDATE = async (
self: UsingClient,
data: GatewayUserUpdateDispatchData,
): Promise<[user: User, old?: User]> => {
return [new User(self, data), await self.cache.users?.get(data.id)];
): Promise<[user: UserStructure, old?: UserStructure]> => {
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 { toCamelCase } from '../../common';
import { VoiceState } from '../../structures';
import type { UsingClient } from '../../commands';
import { Transformers, type VoiceStateStructure } from '../../client/transformers';
export const VOICE_SERVER_UPDATE = (_self: UsingClient, data: GatewayVoiceServerUpdateDispatchData) => {
return toCamelCase(data);
@ -10,7 +10,7 @@ export const VOICE_SERVER_UPDATE = (_self: UsingClient, data: GatewayVoiceServer
export const VOICE_STATE_UPDATE = async (
self: UsingClient,
data: GatewayVoiceStateUpdateDispatchData,
): Promise<[VoiceState] | [state: VoiceState, old?: VoiceState]> => {
if (!data.guild_id) return [new VoiceState(self, data)];
return [new VoiceState(self, data), await self.cache.voiceStates?.get(data.user_id, data.guild_id)];
): Promise<[state: VoiceStateStructure] | [state: VoiceStateStructure, old?: VoiceStateStructure]> => {
if (!data.guild_id) return [Transformers.VoiceState(self, data)];
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 './client';
//
///
export function throwError(msg: string): never {
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 type { ImageOptions, MethodContext } from '../common/types/options';
import type { GuildMemberResolvable } from '../common/types/resolvables';
import { User } from './User';
import { PermissionsBitField } from './extra/Permissions';
import { Transformers, type UserStructure } from '../client/transformers';
export interface BaseGuildMember extends DiscordBase, ObjectToLower<Omit<APIGuildMember, 'user' | 'roles'>> {}
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
*/
export class GuildMember extends BaseGuildMember {
user: User;
user: UserStructure;
private __me?: GuildMember;
constructor(
client: UsingClient,
data: GuildMemberData,
user: APIUser | User,
user: APIUser,
/** the choosen guild id */
readonly guildId: string,
) {
super(client, data, user.id, guildId);
this.user = user instanceof User ? user : new User(client, user);
this.user = Transformers.User(client, user);
}
get tag() {
@ -242,7 +242,7 @@ export class InteractionGuildMember extends (GuildMember as unknown as ToClass<
constructor(
client: UsingClient,
data: APIInteractionDataResolvedGuildMember,
user: APIUser | User,
user: APIUser,
/** the choosen guild id */
guildId: string,
) {

View File

@ -40,7 +40,7 @@ import {
import { mix } from 'ts-mixer';
import type { RawFile } from '../api';
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 {
ComponentInteractionMessageUpdate,
@ -51,13 +51,19 @@ import type {
MessageWebhookCreateBodyRequest,
ModalCreateBodyRequest,
} from '../common/types/write';
import { InteractionGuildMember, type AllChannels } from './';
import { GuildRole } from './GuildRole';
import { Message, type WebhookMessage } from './Message';
import { User } from './User';
import type { AllChannels } from './';
import channelFrom from './channels';
import { DiscordBase } from './extra/DiscordBase';
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 =
| { type: InteractionResponseType.Modal; data: ModalCreateBodyRequest }
@ -82,10 +88,10 @@ export class BaseInteraction<
FromGuild extends boolean = boolean,
Type extends APIInteraction = APIInteraction,
> extends DiscordBase<Type> {
user: User;
member!: When<FromGuild, InteractionGuildMember, undefined>;
user: UserStructure;
member!: When<FromGuild, InteractionGuildMemberStructure, undefined>;
channel?: AllChannels;
message?: Message;
message?: MessageStructure;
replied?: Promise<boolean> | boolean;
appPermissions?: PermissionsBitField;
@ -96,15 +102,15 @@ export class BaseInteraction<
) {
super(client, interaction);
if (interaction.member) {
this.member = new InteractionGuildMember(
this.member = Transformers.InteractionGuildMember(
client,
interaction.member,
interaction.member!.user,
interaction.member.user,
interaction.guild_id!,
) as never;
}
if (interaction.message) {
this.message = new Message(client, interaction.message);
this.message = Transformers.Message(client, interaction.message);
}
if (interaction.app_permissions) {
this.appPermissions = new PermissionsBitField(Number(interaction.app_permissions));
@ -112,7 +118,7 @@ export class BaseInteraction<
if (interaction.channel) {
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(
@ -315,17 +321,17 @@ export class AutocompleteInteraction<FromGuild extends boolean = boolean> extend
> {
declare type: InteractionType.ApplicationCommandAutocomplete;
declare data: ObjectToLower<APIApplicationCommandAutocompleteInteraction['data']>;
options: OptionResolver;
options: OptionResolverStructure;
constructor(
client: UsingClient,
interaction: APIApplicationCommandAutocompleteInteraction,
resolver?: OptionResolver,
resolver?: OptionResolverStructure,
protected __reply?: __InternalReplyFunction,
) {
super(client, interaction);
this.options =
resolver ??
new OptionResolver(
Transformers.OptionResolver(
client,
interaction.data.options,
undefined,
@ -363,7 +369,7 @@ export class Interaction<
async write<FR extends boolean = false>(
body: InteractionCreateBodyRequest,
fetchReply?: FR,
): Promise<When<FR, WebhookMessage, void>> {
): Promise<When<FR, WebhookMessageStructure, void>> {
(await this.reply({
type: InteractionResponseType.ChannelMessageWithSource,
data: body,
@ -382,7 +388,7 @@ export class Interaction<
async editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest,
fetchReply?: FR,
): Promise<When<FR, WebhookMessage, void>>;
): Promise<When<FR, WebhookMessageStructure, void>>;
async editOrReply<FR extends true = true>(body: InteractionMessageUpdateBodyRequest, fetchReply?: FR) {
if (await this.replied) {
const { content, embeds, allowed_mentions, components, files, attachments } = body;
@ -444,7 +450,7 @@ export class ComponentInteraction<
declare channelId: string;
declare channel: AllChannels;
declare type: InteractionType.MessageComponent;
declare message: Message;
declare message: MessageStructure;
update(data: ComponentInteractionMessageUpdate) {
return this.reply({
@ -536,9 +542,9 @@ export class ChannelSelectMenuInteraction extends SelectMenuInteraction {
}
export class MentionableSelectMenuInteraction extends SelectMenuInteraction {
roles: GuildRole[];
members: InteractionGuildMember[];
users: User[];
roles: GuildRoleStructure[];
members: InteractionGuildMemberStructure[];
users: UserStructure[];
constructor(
client: UsingClient,
interaction: APIMessageComponentSelectMenuInteraction,
@ -547,25 +553,24 @@ export class MentionableSelectMenuInteraction extends SelectMenuInteraction {
super(client, interaction);
const resolved = (interaction.data as APIMessageMentionableSelectInteractionData).resolved;
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.values.map(
x =>
new InteractionGuildMember(
? this.values.map(x =>
Transformers.InteractionGuildMember(
this.client,
resolved.members![x],
this.users!.find(u => u.id === x)!,
resolved.users![this.values!.find(u => u === x)!]!,
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 {
roles: GuildRole[];
roles: GuildRoleStructure[];
constructor(
client: UsingClient,
interaction: APIMessageComponentSelectMenuInteraction,
@ -573,13 +578,13 @@ export class RoleSelectMenuInteraction extends SelectMenuInteraction {
) {
super(client, interaction);
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 {
members: InteractionGuildMember[];
users: User[];
members: InteractionGuildMemberStructure[];
users: UserStructure[];
constructor(
client: UsingClient,
interaction: APIMessageComponentSelectMenuInteraction,
@ -587,14 +592,13 @@ export class UserSelectMenuInteraction extends SelectMenuInteraction {
) {
super(client, interaction);
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.values.map(
x =>
new InteractionGuildMember(
? this.values.map(x =>
Transformers.InteractionGuildMember(
this.client,
resolved.members![x],
this.users!.find(u => u.id === x)!,
resolved.users[this.values!.find(u => u === x)!]!,
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 { ActionRowMessageComponents } from '../components';
import { MessageActionRowComponent } from '../components/ActionRow';
import { GuildMember } from './GuildMember';
import { User } from './User';
import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook';
import { DiscordBase } from './extra/DiscordBase';
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;
@ -27,14 +31,14 @@ export interface BaseMessage
ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {
timestamp?: number;
guildId?: string;
author: User;
member?: GuildMember;
author: UserStructure;
member?: GuildMemberStructure;
components: MessageActionRowComponent<ActionRowMessageComponents>[];
poll?: Poll;
poll?: PollStructure;
mentions: {
roles: string[];
channels: APIChannelMention[];
users: (GuildMember | User)[];
users: (GuildMemberStructure | UserStructure)[];
};
}
export class BaseMessage extends DiscordBase {
@ -83,18 +87,17 @@ export class BaseMessage extends DiscordBase {
}
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) {
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) {
this.mentions.users = this.guildId
? data.mentions.map(
m =>
new GuildMember(
? data.mentions.map(m =>
Transformers.GuildMember(
this.client,
{
...(m as APIUser & { member?: Omit<APIGuildMember, 'user'> }).member!,
@ -104,11 +107,11 @@ export class BaseMessage extends DiscordBase {
this.guildId!,
),
)
: data.mentions.map(u => new User(this.client, u));
: data.mentions.map(u => Transformers.User(this.client, u));
}
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 { Attachment, AttachmentBuilder } from '../builders';
import type { MethodContext, ObjectToLower } from '../common';
import { User } from './User';
import { DiscordBase } from './extra/DiscordBase';
import { Transformers, type UserStructure } from '../client/transformers';
export interface Sticker extends DiscordBase, ObjectToLower<Omit<APISticker, 'user'>> {}
export class Sticker extends DiscordBase {
user?: User;
user?: UserStructure;
constructor(client: UsingClient, data: APISticker) {
super(client, data);
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 GuildMemberStructure, Transformers } from '../client/transformers';
import type { ObjectToLower } from '../common';
import type { GatewayVoiceState } from '../types';
import { Base } from './extra/Base';
@ -7,12 +8,13 @@ import { Base } from './extra/Base';
export interface VoiceState extends Base, ObjectToLower<Omit<VoiceStateResource, 'member'>> {}
export class VoiceState extends Base {
protected withMember?: GuildMember;
protected withMember?: GuildMemberStructure;
constructor(client: UsingClient, data: GatewayVoiceState) {
super(client);
const { member, ...rest } = data;
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() {

View File

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

View File

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

View File

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

View File

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

View File

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