@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/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent docs
2
2
 
3
- Version-matched documentation for `@discord-selfbot/core@14.0.1`.
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 — Agent docs
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
 
@@ -1,6 +1,6 @@
1
1
  # API reference
2
2
 
3
- Public runtime surface of `@discord-selfbot/core@14.0.1`. Every page is generated from `typings/index.d.ts`.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@discord-selfbot/core",
3
- "version": "14.0.1",
3
+ "version": "14.0.2",
4
4
  "description": "An unofficial discord.js fork for creating selfbots, migrated to TypeScript and ESM",
5
5
  "keywords": [
6
6
  "api",
@@ -20,6 +20,8 @@ class BaseClient extends EventEmitter {
20
20
  }
21
21
 
22
22
  this.options = Util.mergeDefault(Options.createDefault(), options);
23
+ this.options.ws.properties.browser_user_agent =
24
+ this.options.http.headers['User-Agent'];
23
25
 
24
26
  this.rest = new RESTManager(this);
25
27
  }
@@ -347,6 +347,7 @@ class Client extends BaseClient {
347
347
  this.ws = new WebSocketManager(this);
348
348
 
349
349
  this.actions = new ActionsManager(this);
350
+ this.ws.dispatch.registerDefaults(this.actions);
350
351
 
351
352
  this.voice = new ClientVoiceManager(this);
352
353
 
@@ -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 PacketHandlers from './handlers';
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, WSEvents } = _ConstantsModule;
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 packetQueue: { packet: GatewayPacket; shard?: WebSocketShard }[];
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
- Object.defineProperty(this, 'packetQueue', { value: [] });
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
- if (
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, getUploadURL, uploadFile } = _UtilModule;
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 fileList = files ?? [];
40
-
41
- // New API
42
- const attachments = await getUploadURL(
40
+ const attachmentsData = await uploadMessageAttachments(
43
41
  this.client,
44
42
  this.channel.id,
45
- fileList
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 fileList = files ?? [];
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
- fileList
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 });
@@ -1,15 +1,9 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { setTimeout } from 'node:timers';
3
- import { FormData, buildConnector, Client, ProxyAgent } from 'undici';
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.ensureAgent();
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: agent as never,
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
@@ -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 { fetch as fetchOriginal } from 'undici';
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 done on a per-IP basis, not a per-token basis.
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 (!invalidCountResetTime || invalidCountResetTime < Date.now()) {
389
- invalidCountResetTime = Date.now() + 1000 * 60 * 10;
390
- invalidCount = 0;
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: invalidCountResetTime - Date.now(),
398
+ count: this.manager.invalidCount,
399
+ remainingTime:
400
+ (this.manager.invalidCountResetTime ?? Date.now()) - Date.now(),
404
401
  });
405
402
  }
406
403
  }