@discord-selfbot/core 14.0.1 → 14.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +406 -313
- package/docs/README.md +1 -1
- package/docs/llms.txt +1 -1
- package/docs/reference/index.md +1 -1
- package/package.json +1 -1
- package/src/client/BaseClient.ts +2 -0
- package/src/client/Client.ts +1 -0
- package/src/client/WebhookClient.ts +14 -9
- package/src/client/websocket/GatewayDispatch.ts +192 -0
- package/src/client/websocket/WebSocketManager.ts +6 -61
- package/src/managers/GuildForumThreadManager.ts +4 -27
- package/src/managers/MessageManager.ts +3 -28
- package/src/rest/APIRequest.ts +3 -31
- package/src/rest/RESTManager.ts +32 -2
- package/src/rest/RequestHandler.ts +12 -15
- package/src/structures/Channel.ts +26 -187
- package/src/structures/ChannelConstruction.ts +177 -0
- package/src/structures/interfaces/TextBasedChannel.ts +5 -25
- package/src/util/OutboundMessagePipeline.ts +54 -0
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent docs
|
|
2
2
|
|
|
3
|
-
Version-matched documentation for `@discord-selfbot/core@14.0.
|
|
3
|
+
Version-matched documentation for `@discord-selfbot/core@14.0.2`.
|
|
4
4
|
|
|
5
5
|
Start at [`llms.txt`](./llms.txt). The `reference/` tree is generated from the public typings and the API prose store; the guides and examples are generated from `apps/docs/content/docs`. Do not edit this tree by hand.
|
|
6
6
|
|
package/docs/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @discord-selfbot/core 14.0.
|
|
1
|
+
# @discord-selfbot/core 14.0.2 — Agent docs
|
|
2
2
|
|
|
3
3
|
Version-matched documentation for coding agents. Read this file first, then open the page for the task. The reference is generated from the public typings; exact types stay in `typings/index.d.ts`.
|
|
4
4
|
|
package/docs/reference/index.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# API reference
|
|
2
2
|
|
|
3
|
-
Public runtime surface of `@discord-selfbot/core@14.0.
|
|
3
|
+
Public runtime surface of `@discord-selfbot/core@14.0.2`. Every page is generated from `typings/index.d.ts`.
|
|
4
4
|
|
|
5
5
|
Type-only exports stay in `typings/index.d.ts`; this reference does not repeat them.
|
|
6
6
|
|
package/package.json
CHANGED
package/src/client/BaseClient.ts
CHANGED
package/src/client/Client.ts
CHANGED
|
@@ -7,12 +7,26 @@ import type { WebhookClientData, ClientOptions } from '../../typings';
|
|
|
7
7
|
|
|
8
8
|
const { Error } = _errorsModule;
|
|
9
9
|
|
|
10
|
+
let _webhookPatched = false;
|
|
11
|
+
|
|
10
12
|
class WebhookClient extends BaseClient {
|
|
11
13
|
declare id: string | null;
|
|
12
14
|
|
|
13
15
|
constructor(data: WebhookClientData, options?: ClientOptions) {
|
|
14
16
|
super(options);
|
|
15
17
|
Object.defineProperty(this, 'client', { value: this });
|
|
18
|
+
if (!_webhookPatched) {
|
|
19
|
+
try {
|
|
20
|
+
const WebhookMod = require('../structures/Webhook');
|
|
21
|
+
const Webhook = WebhookMod.default ?? WebhookMod;
|
|
22
|
+
if (typeof Webhook.applyToClass === 'function') {
|
|
23
|
+
Webhook.applyToClass(WebhookClient);
|
|
24
|
+
_webhookPatched = true;
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// Ignore a missing Webhook structure module.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
16
30
|
let id: string | null = null;
|
|
17
31
|
let token: string | null = null;
|
|
18
32
|
|
|
@@ -68,12 +82,3 @@ class WebhookClient extends BaseClient {
|
|
|
68
82
|
|
|
69
83
|
export default WebhookClient;
|
|
70
84
|
export { WebhookClient };
|
|
71
|
-
|
|
72
|
-
setTimeout(() => {
|
|
73
|
-
try {
|
|
74
|
-
const Webhook = require('../structures/Webhook');
|
|
75
|
-
Webhook.applyToClass(WebhookClient);
|
|
76
|
-
} catch {
|
|
77
|
-
// Ignore a missing Webhook structure module.
|
|
78
|
-
}
|
|
79
|
-
}, 0);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { setImmediate } from 'node:timers';
|
|
2
|
+
import PacketHandlers from './handlers';
|
|
3
|
+
import _ConstantsModule from '../../util/Constants';
|
|
4
|
+
import type Client from '../Client';
|
|
5
|
+
import type WebSocketShard from './WebSocketShard';
|
|
6
|
+
import type ActionsManager from '../actions/ActionsManager';
|
|
7
|
+
|
|
8
|
+
const { Events, Status, WSEvents } = _ConstantsModule;
|
|
9
|
+
|
|
10
|
+
export interface GatewayPacket {
|
|
11
|
+
['t']?: string;
|
|
12
|
+
['d']?: unknown;
|
|
13
|
+
['op']?: number;
|
|
14
|
+
['s']?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type PacketHandler = (
|
|
18
|
+
client: Client,
|
|
19
|
+
packet: GatewayPacket,
|
|
20
|
+
shard?: WebSocketShard
|
|
21
|
+
) => unknown;
|
|
22
|
+
|
|
23
|
+
interface ActionLike {
|
|
24
|
+
handle: (data: unknown, shard?: WebSocketShard) => unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const BEFORE_READY_WHITELIST = new Set<string>([
|
|
28
|
+
WSEvents.READY,
|
|
29
|
+
WSEvents.RESUMED,
|
|
30
|
+
WSEvents.GUILD_CREATE,
|
|
31
|
+
WSEvents.GUILD_DELETE,
|
|
32
|
+
WSEvents.GUILD_MEMBERS_CHUNK,
|
|
33
|
+
WSEvents.GUILD_MEMBER_ADD,
|
|
34
|
+
WSEvents.GUILD_MEMBER_REMOVE,
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const ACTION_EVENT_TYPES = [
|
|
38
|
+
'APPLICATION_COMMAND_PERMISSIONS_UPDATE',
|
|
39
|
+
'AUTO_MODERATION_ACTION_EXECUTION',
|
|
40
|
+
'AUTO_MODERATION_RULE_CREATE',
|
|
41
|
+
'AUTO_MODERATION_RULE_DELETE',
|
|
42
|
+
'AUTO_MODERATION_RULE_UPDATE',
|
|
43
|
+
'CHANNEL_CREATE',
|
|
44
|
+
'CHANNEL_DELETE',
|
|
45
|
+
'CHANNEL_UPDATE',
|
|
46
|
+
'GUILD_AUDIT_LOG_ENTRY_CREATE',
|
|
47
|
+
'GUILD_BAN_ADD',
|
|
48
|
+
'GUILD_BAN_REMOVE',
|
|
49
|
+
'GUILD_CHANNELS_POSITION_UPDATE',
|
|
50
|
+
'GUILD_DELETE',
|
|
51
|
+
'GUILD_EMOJI_CREATE',
|
|
52
|
+
'GUILD_EMOJI_DELETE',
|
|
53
|
+
'GUILD_EMOJI_UPDATE',
|
|
54
|
+
'GUILD_EMOJIS_UPDATE',
|
|
55
|
+
'GUILD_INTEGRATIONS_UPDATE',
|
|
56
|
+
'GUILD_MEMBER_REMOVE',
|
|
57
|
+
'GUILD_MEMBER_UPDATE',
|
|
58
|
+
'GUILD_ROLE_CREATE',
|
|
59
|
+
'GUILD_ROLE_DELETE',
|
|
60
|
+
'GUILD_ROLE_UPDATE',
|
|
61
|
+
'GUILD_ROLES_POSITION_UPDATE',
|
|
62
|
+
'GUILD_SCHEDULED_EVENT_CREATE',
|
|
63
|
+
'GUILD_SCHEDULED_EVENT_DELETE',
|
|
64
|
+
'GUILD_SCHEDULED_EVENT_UPDATE',
|
|
65
|
+
'GUILD_SCHEDULED_EVENT_USER_ADD',
|
|
66
|
+
'GUILD_SCHEDULED_EVENT_USER_REMOVE',
|
|
67
|
+
'GUILD_STICKER_CREATE',
|
|
68
|
+
'GUILD_STICKER_DELETE',
|
|
69
|
+
'GUILD_STICKER_UPDATE',
|
|
70
|
+
'GUILD_STICKERS_UPDATE',
|
|
71
|
+
'GUILD_UPDATE',
|
|
72
|
+
'INVITE_CREATE',
|
|
73
|
+
'INVITE_DELETE',
|
|
74
|
+
'MESSAGE_CREATE',
|
|
75
|
+
'MESSAGE_DELETE',
|
|
76
|
+
'MESSAGE_DELETE_BULK',
|
|
77
|
+
'MESSAGE_POLL_VOTE_ADD',
|
|
78
|
+
'MESSAGE_POLL_VOTE_REMOVE',
|
|
79
|
+
'MESSAGE_REACTION_ADD',
|
|
80
|
+
'MESSAGE_REACTION_REMOVE',
|
|
81
|
+
'MESSAGE_REACTION_REMOVE_ALL',
|
|
82
|
+
'MESSAGE_REACTION_REMOVE_EMOJI',
|
|
83
|
+
'MESSAGE_UPDATE',
|
|
84
|
+
'PRESENCE_UPDATE',
|
|
85
|
+
'STAGE_INSTANCE_CREATE',
|
|
86
|
+
'STAGE_INSTANCE_DELETE',
|
|
87
|
+
'STAGE_INSTANCE_UPDATE',
|
|
88
|
+
'THREAD_CREATE',
|
|
89
|
+
'THREAD_DELETE',
|
|
90
|
+
'THREAD_LIST_SYNC',
|
|
91
|
+
'THREAD_MEMBER_UPDATE',
|
|
92
|
+
'THREAD_MEMBERS_UPDATE',
|
|
93
|
+
'TYPING_START',
|
|
94
|
+
'USER_UPDATE',
|
|
95
|
+
'VOICE_STATE_UPDATE',
|
|
96
|
+
'WEBHOOKS_UPDATE',
|
|
97
|
+
] as const;
|
|
98
|
+
|
|
99
|
+
function toActionPropertyName(eventType: string): string {
|
|
100
|
+
return eventType
|
|
101
|
+
.split('_')
|
|
102
|
+
.map((part) => part.charAt(0) + part.slice(1).toLowerCase())
|
|
103
|
+
.join('');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isActionLike(value: unknown): value is ActionLike {
|
|
107
|
+
return (
|
|
108
|
+
typeof value === 'object' &&
|
|
109
|
+
value !== null &&
|
|
110
|
+
'handle' in value &&
|
|
111
|
+
typeof (value as { handle?: unknown }).handle === 'function'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class GatewayDispatch {
|
|
116
|
+
private readonly client: Client;
|
|
117
|
+
private readonly packetQueue: {
|
|
118
|
+
packet: GatewayPacket;
|
|
119
|
+
shard?: WebSocketShard;
|
|
120
|
+
}[] = [];
|
|
121
|
+
private readonly registry = new Map<string, PacketHandler>();
|
|
122
|
+
|
|
123
|
+
constructor(client: Client) {
|
|
124
|
+
this.client = client;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
registerDefaults(actions: ActionsManager): void {
|
|
128
|
+
const handlers = PacketHandlers as Record<
|
|
129
|
+
string,
|
|
130
|
+
PacketHandler | undefined
|
|
131
|
+
>;
|
|
132
|
+
for (const [eventType, handler] of Object.entries(handlers)) {
|
|
133
|
+
if (typeof handler === 'function') {
|
|
134
|
+
this.registry.set(eventType, handler);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const eventType of ACTION_EVENT_TYPES) {
|
|
139
|
+
if (this.registry.has(eventType)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const actionName = toActionPropertyName(eventType);
|
|
143
|
+
const action = Reflect.get(actions, actionName);
|
|
144
|
+
if (!isActionLike(action)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
this.registry.set(eventType, (_client, packet, shard) =>
|
|
148
|
+
action.handle(packet.d, shard)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
handle(packet?: GatewayPacket, shard?: WebSocketShard): boolean {
|
|
154
|
+
const manager = this.client.ws;
|
|
155
|
+
|
|
156
|
+
if (
|
|
157
|
+
packet &&
|
|
158
|
+
manager.status !== Status.READY &&
|
|
159
|
+
!BEFORE_READY_WHITELIST.has(packet.t ?? '')
|
|
160
|
+
) {
|
|
161
|
+
this.packetQueue.push({ packet, shard });
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (this.packetQueue.length) {
|
|
166
|
+
const item = this.packetQueue.shift();
|
|
167
|
+
if (item) {
|
|
168
|
+
setImmediate(() => {
|
|
169
|
+
this.handle(item.packet, item.shard);
|
|
170
|
+
}).unref();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!packet) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const eventType = packet.t;
|
|
179
|
+
if (eventType) {
|
|
180
|
+
const handler = this.registry.get(eventType);
|
|
181
|
+
if (handler) {
|
|
182
|
+
handler(this.client, packet, shard);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default GatewayDispatch;
|
|
@@ -1,33 +1,16 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
|
-
import { setImmediate } from 'node:timers';
|
|
3
2
|
import { Collection } from '@discordjs/collection';
|
|
4
3
|
import { RPCErrorCodes } from 'discord-api-types/v10';
|
|
5
4
|
import WebSocketShard from './WebSocketShard';
|
|
6
|
-
import
|
|
5
|
+
import GatewayDispatch from './GatewayDispatch';
|
|
6
|
+
import type { GatewayPacket } from './GatewayDispatch';
|
|
7
7
|
import _errorsModule from '../../errors';
|
|
8
8
|
|
|
9
9
|
const { Error } = _errorsModule;
|
|
10
10
|
import _ConstantsModule from '../../util/Constants';
|
|
11
11
|
import type Client from '../Client';
|
|
12
12
|
|
|
13
|
-
const { Events, ShardEvents, Status, WSCodes
|
|
14
|
-
|
|
15
|
-
const BeforeReadyWhitelist: readonly string[] = [
|
|
16
|
-
WSEvents.READY,
|
|
17
|
-
WSEvents.RESUMED,
|
|
18
|
-
WSEvents.GUILD_CREATE,
|
|
19
|
-
WSEvents.GUILD_DELETE,
|
|
20
|
-
WSEvents.GUILD_MEMBERS_CHUNK,
|
|
21
|
-
WSEvents.GUILD_MEMBER_ADD,
|
|
22
|
-
WSEvents.GUILD_MEMBER_REMOVE,
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
interface GatewayPacket {
|
|
26
|
-
['t']?: string;
|
|
27
|
-
d?: unknown;
|
|
28
|
-
op?: number;
|
|
29
|
-
s?: number;
|
|
30
|
-
}
|
|
13
|
+
const { Events, ShardEvents, Status, WSCodes } = _ConstantsModule;
|
|
31
14
|
|
|
32
15
|
interface GatewayInfo {
|
|
33
16
|
url: string;
|
|
@@ -56,7 +39,7 @@ class WebSocketManager extends EventEmitter {
|
|
|
56
39
|
declare totalShards: number;
|
|
57
40
|
declare shards: Collection<number, WebSocketShard>;
|
|
58
41
|
declare shardQueue: Set<WebSocketShard>;
|
|
59
|
-
declare
|
|
42
|
+
declare dispatch: GatewayDispatch;
|
|
60
43
|
declare status: number;
|
|
61
44
|
declare destroyed: boolean;
|
|
62
45
|
declare reconnecting: boolean;
|
|
@@ -79,7 +62,7 @@ class WebSocketManager extends EventEmitter {
|
|
|
79
62
|
writable: true,
|
|
80
63
|
});
|
|
81
64
|
|
|
82
|
-
|
|
65
|
+
this.dispatch = new GatewayDispatch(client);
|
|
83
66
|
|
|
84
67
|
this.status = Status.IDLE;
|
|
85
68
|
|
|
@@ -319,45 +302,7 @@ class WebSocketManager extends EventEmitter {
|
|
|
319
302
|
}
|
|
320
303
|
|
|
321
304
|
handlePacket(packet?: GatewayPacket, shard?: WebSocketShard) {
|
|
322
|
-
|
|
323
|
-
packet &&
|
|
324
|
-
this.status !== Status.READY &&
|
|
325
|
-
!BeforeReadyWhitelist.includes(packet.t ?? '')
|
|
326
|
-
) {
|
|
327
|
-
this.packetQueue.push({ packet, shard });
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (this.packetQueue.length) {
|
|
332
|
-
const item = this.packetQueue.shift();
|
|
333
|
-
if (item) {
|
|
334
|
-
setImmediate(() => {
|
|
335
|
-
this.handlePacket(item.packet, item.shard);
|
|
336
|
-
}).unref();
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (packet?.t && PacketHandlers[packet.t]) {
|
|
341
|
-
PacketHandlers[packet.t](this.client, packet, shard);
|
|
342
|
-
} else if (packet) {
|
|
343
|
-
const actionName = String(packet.t || '')
|
|
344
|
-
.split('_')
|
|
345
|
-
.map((part) => part.charAt(0) + part.slice(1).toLowerCase())
|
|
346
|
-
.join('');
|
|
347
|
-
const action = Reflect.get(this.client.actions, actionName);
|
|
348
|
-
if (
|
|
349
|
-
action &&
|
|
350
|
-
typeof action === 'object' &&
|
|
351
|
-
'handle' in action &&
|
|
352
|
-
typeof action.handle === 'function'
|
|
353
|
-
) {
|
|
354
|
-
Reflect.apply(action.handle, action, [packet.d, shard]);
|
|
355
|
-
return true;
|
|
356
|
-
}
|
|
357
|
-
this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return true;
|
|
305
|
+
return this.dispatch.handle(packet, shard);
|
|
361
306
|
}
|
|
362
307
|
|
|
363
308
|
checkShardsReady() {
|
|
@@ -4,8 +4,9 @@ import _errorsModule from '../errors';
|
|
|
4
4
|
const { TypeError } = _errorsModule;
|
|
5
5
|
import MessagePayload from '../structures/MessagePayload';
|
|
6
6
|
import _UtilModule from '../util/Util';
|
|
7
|
+
import { uploadMessageAttachments } from '../util/OutboundMessagePipeline';
|
|
7
8
|
|
|
8
|
-
const { resolveAutoArchiveMaxLimit
|
|
9
|
+
const { resolveAutoArchiveMaxLimit } = _UtilModule;
|
|
9
10
|
import type { RESTPostAPIChannelThreadsResult } from 'discord-api-types/v10';
|
|
10
11
|
import type { GuildForumThreadCreateOptions } from '../../typings';
|
|
11
12
|
import type { MessagePayloadOptions } from '../structures/MessagePayload';
|
|
@@ -36,34 +37,10 @@ class GuildForumThreadManager extends ThreadManager {
|
|
|
36
37
|
: MessagePayload.create(this, message).resolveData();
|
|
37
38
|
|
|
38
39
|
const { data: body, files } = await messagePayload.resolveFiles();
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
// New API
|
|
42
|
-
const attachments = await getUploadURL(
|
|
40
|
+
const attachmentsData = await uploadMessageAttachments(
|
|
43
41
|
this.client,
|
|
44
42
|
this.channel.id,
|
|
45
|
-
|
|
46
|
-
);
|
|
47
|
-
const requestPromises = attachments.map(async (attachment) => {
|
|
48
|
-
const index = Number(attachment.id);
|
|
49
|
-
const file = fileList[index];
|
|
50
|
-
if (!file) {
|
|
51
|
-
throw new TypeError('GUILD_FORUM_ATTACHMENT_MISSING');
|
|
52
|
-
}
|
|
53
|
-
await uploadFile(file.file, attachment.upload_url);
|
|
54
|
-
return {
|
|
55
|
-
description: file.description,
|
|
56
|
-
duration_secs: file.duration_secs,
|
|
57
|
-
filename: file.name,
|
|
58
|
-
id: attachment.id,
|
|
59
|
-
uploaded_filename: attachment.upload_filename,
|
|
60
|
-
waveform: file.waveform,
|
|
61
|
-
};
|
|
62
|
-
});
|
|
63
|
-
const attachmentsData = await Promise.all(requestPromises);
|
|
64
|
-
attachmentsData.sort(
|
|
65
|
-
(left, right) =>
|
|
66
|
-
Math.trunc(Number(left.id)) - Math.trunc(Number(right.id))
|
|
43
|
+
files ?? []
|
|
67
44
|
);
|
|
68
45
|
|
|
69
46
|
let archiveDuration = autoArchiveDuration;
|
|
@@ -9,6 +9,7 @@ const { Message } = _MessageModule;
|
|
|
9
9
|
import MessagePayload from '../structures/MessagePayload';
|
|
10
10
|
import type { MessagePayloadOptions } from '../structures/MessagePayload';
|
|
11
11
|
import Util from '../util/Util';
|
|
12
|
+
import { uploadMessageAttachments } from '../util/OutboundMessagePipeline';
|
|
12
13
|
import type { Snowflake } from 'discord-api-types/v10';
|
|
13
14
|
import type {
|
|
14
15
|
BaseFetchOptions,
|
|
@@ -22,7 +23,6 @@ import type {
|
|
|
22
23
|
import type { RawMessageData, RawUserData } from '../../typings/rawDataTypes';
|
|
23
24
|
import type { Message as SourceMessage } from '../structures/Message';
|
|
24
25
|
import type { User } from '../structures/User';
|
|
25
|
-
import type { UploadAttachment } from '../types/uploads';
|
|
26
26
|
import type { Client } from '../client/Client';
|
|
27
27
|
import type { Guild } from '../structures/Guild';
|
|
28
28
|
|
|
@@ -184,35 +184,10 @@ class MessageManager extends CachedManager<SourceMessage> {
|
|
|
184
184
|
.resolveData()
|
|
185
185
|
.resolveFiles();
|
|
186
186
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
// New API
|
|
190
|
-
const attachments = await Util.getUploadURL(
|
|
187
|
+
const attachmentsData = await uploadMessageAttachments(
|
|
191
188
|
this.client,
|
|
192
189
|
this.channel.id,
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
const requestPromises = attachments.map(
|
|
196
|
-
async (attachment: UploadAttachment) => {
|
|
197
|
-
const file = fileList[Number(attachment.id)];
|
|
198
|
-
if (!file) {
|
|
199
|
-
throw new TypeError('INVALID_TYPE', 'attachment', 'FileOptions');
|
|
200
|
-
}
|
|
201
|
-
await Util.uploadFile(file.file, attachment.upload_url);
|
|
202
|
-
return {
|
|
203
|
-
description: file.description,
|
|
204
|
-
duration_secs: file.duration_secs,
|
|
205
|
-
filename: file.name,
|
|
206
|
-
id: attachment.id,
|
|
207
|
-
uploaded_filename: attachment.upload_filename,
|
|
208
|
-
waveform: file.waveform,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
);
|
|
212
|
-
const attachmentsData = await Promise.all(requestPromises);
|
|
213
|
-
attachmentsData.sort(
|
|
214
|
-
(left, right) =>
|
|
215
|
-
Math.trunc(Number(left.id)) - Math.trunc(Number(right.id))
|
|
190
|
+
files ?? []
|
|
216
191
|
);
|
|
217
192
|
if (data) {
|
|
218
193
|
Object.assign(data, { attachments: attachmentsData });
|
package/src/rest/APIRequest.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { setTimeout } from 'node:timers';
|
|
3
|
-
import { FormData
|
|
4
|
-
import _ConstantsModule from '../util/Constants';
|
|
5
|
-
|
|
6
|
-
const { ciphers } = _ConstantsModule;
|
|
7
|
-
import Util from '../util/Util';
|
|
3
|
+
import { FormData } from 'undici';
|
|
8
4
|
import type RESTManager from '../rest/RESTManager';
|
|
9
5
|
import type { BaseClient } from '../client/BaseClient';
|
|
10
6
|
|
|
11
|
-
let agent: unknown = null;
|
|
12
|
-
|
|
13
7
|
class APIRequest {
|
|
14
8
|
rest: RESTManager;
|
|
15
9
|
client: BaseClient;
|
|
@@ -37,10 +31,6 @@ class APIRequest {
|
|
|
37
31
|
this.client.options as { http: { headers: Record<string, string> } }
|
|
38
32
|
).http.headers['User-Agent'];
|
|
39
33
|
|
|
40
|
-
(
|
|
41
|
-
this.client.options as { ws: { properties: Record<string, string> } }
|
|
42
|
-
).ws.properties.browser_user_agent = this.fullUserAgent;
|
|
43
|
-
|
|
44
34
|
let queryString = '';
|
|
45
35
|
if (options.query) {
|
|
46
36
|
const queryOptions = options.query as Record<string, unknown>;
|
|
@@ -57,7 +47,7 @@ class APIRequest {
|
|
|
57
47
|
}
|
|
58
48
|
|
|
59
49
|
make(captchaKey?: unknown, captchaRqToken?: unknown): Promise<unknown> {
|
|
60
|
-
this.
|
|
50
|
+
const dispatcher = this.rest.getDispatcher() as never;
|
|
61
51
|
|
|
62
52
|
const API =
|
|
63
53
|
this.options.versioned === false
|
|
@@ -77,33 +67,15 @@ class APIRequest {
|
|
|
77
67
|
.fetch(url, {
|
|
78
68
|
body,
|
|
79
69
|
credentials: 'include',
|
|
80
|
-
dispatcher
|
|
70
|
+
dispatcher,
|
|
81
71
|
headers: headers as never,
|
|
82
72
|
method: this.method.toUpperCase(),
|
|
83
|
-
// Undici doesn't normalize "patch" into "PATCH" (which surprisingly follows the spec).
|
|
84
73
|
redirect: 'follow',
|
|
85
74
|
signal: controller.signal,
|
|
86
75
|
})
|
|
87
76
|
.finally(() => clearTimeout(timeout));
|
|
88
77
|
}
|
|
89
78
|
|
|
90
|
-
private ensureAgent(): void {
|
|
91
|
-
if (agent) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const proxyAgent = Util.checkUndiciProxyAgent(
|
|
95
|
-
this.client.options.http.agent
|
|
96
|
-
);
|
|
97
|
-
agent = proxyAgent
|
|
98
|
-
? new ProxyAgent({
|
|
99
|
-
...proxyAgent,
|
|
100
|
-
ciphers: ciphers.join(':'),
|
|
101
|
-
} as never)
|
|
102
|
-
: new Client('https://discord.com', {
|
|
103
|
-
connect: buildConnector({ ciphers: ciphers.join(':') }),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
79
|
private buildHeaders(
|
|
108
80
|
captchaKey?: unknown,
|
|
109
81
|
captchaRqToken?: unknown
|
package/src/rest/RESTManager.ts
CHANGED
|
@@ -2,7 +2,12 @@ import { setInterval } from 'node:timers';
|
|
|
2
2
|
import { Collection } from '@discordjs/collection';
|
|
3
3
|
import makeFetchCookie from 'fetch-cookie';
|
|
4
4
|
import { CookieJar } from 'tough-cookie';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Client as UndiciClient,
|
|
7
|
+
ProxyAgent,
|
|
8
|
+
buildConnector,
|
|
9
|
+
fetch as fetchOriginal,
|
|
10
|
+
} from 'undici';
|
|
6
11
|
import APIRequest from './APIRequest';
|
|
7
12
|
import type { ApiRouter } from './APIRouter';
|
|
8
13
|
import routeBuilder from './APIRouter';
|
|
@@ -11,9 +16,10 @@ import _errorsModule from '../errors';
|
|
|
11
16
|
|
|
12
17
|
const { Error } = _errorsModule;
|
|
13
18
|
import _ConstantsModule from '../util/Constants';
|
|
19
|
+
import Util from '../util/Util';
|
|
14
20
|
import type { BaseClient } from '../client/BaseClient';
|
|
15
21
|
|
|
16
|
-
const { Endpoints } = _ConstantsModule;
|
|
22
|
+
const { Endpoints, ciphers } = _ConstantsModule;
|
|
17
23
|
|
|
18
24
|
class RESTManager {
|
|
19
25
|
client: BaseClient;
|
|
@@ -26,6 +32,9 @@ class RESTManager {
|
|
|
26
32
|
cookieJar: CookieJar;
|
|
27
33
|
fetch: typeof fetchOriginal;
|
|
28
34
|
sweepInterval?: ReturnType<typeof setInterval>;
|
|
35
|
+
agent: unknown;
|
|
36
|
+
invalidCount: number;
|
|
37
|
+
invalidCountResetTime: number | null;
|
|
29
38
|
|
|
30
39
|
constructor(client: BaseClient) {
|
|
31
40
|
this.client = client;
|
|
@@ -40,6 +49,9 @@ class RESTManager {
|
|
|
40
49
|
this.globalDelay = null;
|
|
41
50
|
this.cookieJar = new CookieJar();
|
|
42
51
|
this.fetch = makeFetchCookie(fetchOriginal, this.cookieJar);
|
|
52
|
+
this.agent = null;
|
|
53
|
+
this.invalidCount = 0;
|
|
54
|
+
this.invalidCountResetTime = null;
|
|
43
55
|
if (client.options.restSweepInterval > 0) {
|
|
44
56
|
this.sweepInterval = setInterval(() => {
|
|
45
57
|
this.handlers.sweep((handler: RequestHandler) => handler._inactive);
|
|
@@ -47,6 +59,24 @@ class RESTManager {
|
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
getDispatcher(): unknown {
|
|
63
|
+
if (this.agent) {
|
|
64
|
+
return this.agent;
|
|
65
|
+
}
|
|
66
|
+
const proxyAgent = Util.checkUndiciProxyAgent(
|
|
67
|
+
this.client.options.http.agent
|
|
68
|
+
);
|
|
69
|
+
this.agent = proxyAgent
|
|
70
|
+
? new ProxyAgent({
|
|
71
|
+
...proxyAgent,
|
|
72
|
+
ciphers: ciphers.join(':'),
|
|
73
|
+
} as never)
|
|
74
|
+
: new UndiciClient('https://discord.com', {
|
|
75
|
+
connect: buildConnector({ ciphers: ciphers.join(':') }),
|
|
76
|
+
});
|
|
77
|
+
return this.agent;
|
|
78
|
+
}
|
|
79
|
+
|
|
50
80
|
get api(): ApiRouter {
|
|
51
81
|
return routeBuilder(this);
|
|
52
82
|
}
|
|
@@ -93,14 +93,7 @@ function calculateReset(
|
|
|
93
93
|
return new Date(Number(reset) * 1000).getTime() - getAPIOffset(serverDate);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/* Invalid request limiting is
|
|
97
|
-
* The best we can do is track invalid counts process-wide (on the theory that
|
|
98
|
-
* users could have multiple bots run from one process) rather than per-bot.
|
|
99
|
-
* Therefore, store these at file scope here rather than in the client's
|
|
100
|
-
* RESTManager object.
|
|
101
|
-
*/
|
|
102
|
-
let invalidCount = 0;
|
|
103
|
-
let invalidCountResetTime: number | null = null;
|
|
96
|
+
/* Invalid request limiting is tracked on the REST manager to keep state local to the client/module seam. */
|
|
104
97
|
|
|
105
98
|
class RequestHandler {
|
|
106
99
|
manager: RESTManager;
|
|
@@ -385,22 +378,26 @@ class RequestHandler {
|
|
|
385
378
|
|
|
386
379
|
private trackInvalidRequest(res: RestResponse): void {
|
|
387
380
|
if (res.status === 401 || res.status === 403 || res.status === 429) {
|
|
388
|
-
if (
|
|
389
|
-
invalidCountResetTime
|
|
390
|
-
|
|
381
|
+
if (
|
|
382
|
+
!this.manager.invalidCountResetTime ||
|
|
383
|
+
this.manager.invalidCountResetTime < Date.now()
|
|
384
|
+
) {
|
|
385
|
+
this.manager.invalidCountResetTime = Date.now() + 1000 * 60 * 10;
|
|
386
|
+
this.manager.invalidCount = 0;
|
|
391
387
|
}
|
|
392
|
-
invalidCount++;
|
|
388
|
+
this.manager.invalidCount++;
|
|
393
389
|
|
|
394
390
|
const emitInvalid =
|
|
395
391
|
this.manager.client.listenerCount(INVALID_REQUEST_WARNING) &&
|
|
396
392
|
this.manager.client.options.invalidRequestWarningInterval > 0 &&
|
|
397
|
-
invalidCount %
|
|
393
|
+
this.manager.invalidCount %
|
|
398
394
|
this.manager.client.options.invalidRequestWarningInterval ===
|
|
399
395
|
0;
|
|
400
396
|
if (emitInvalid) {
|
|
401
397
|
this.manager.client.emit(INVALID_REQUEST_WARNING, {
|
|
402
|
-
count: invalidCount,
|
|
403
|
-
remainingTime:
|
|
398
|
+
count: this.manager.invalidCount,
|
|
399
|
+
remainingTime:
|
|
400
|
+
(this.manager.invalidCountResetTime ?? Date.now()) - Date.now(),
|
|
404
401
|
});
|
|
405
402
|
}
|
|
406
403
|
}
|