@squord/sdk 0.0.0-stage → 0.1.0
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 +544 -2
- package/dist/api-DbHoBFJY.d.cts +726 -0
- package/dist/api-DbHoBFJY.d.ts +726 -0
- package/dist/index.cjs +3881 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2340 -0
- package/dist/index.d.ts +2340 -0
- package/dist/index.js +3779 -0
- package/dist/index.js.map +1 -0
- package/dist/testing/index.cjs +789 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +261 -0
- package/dist/testing/index.d.ts +261 -0
- package/dist/testing/index.js +763 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +70 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3881 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ALL_PERMISSIONS: () => ALL_PERMISSIONS,
|
|
24
|
+
ActionRowBuilder: () => ActionRowBuilder,
|
|
25
|
+
ApplicationCommandsManager: () => ApplicationCommandsManager,
|
|
26
|
+
AuthenticationError: () => AuthenticationError,
|
|
27
|
+
BotManager: () => BotManager,
|
|
28
|
+
ButtonBuilder: () => ButtonBuilder,
|
|
29
|
+
Buttons: () => Buttons,
|
|
30
|
+
Cache: () => Cache,
|
|
31
|
+
Channel: () => Channel,
|
|
32
|
+
ChannelsManager: () => ChannelsManager,
|
|
33
|
+
CommandRegistry: () => CommandRegistry,
|
|
34
|
+
ComponentLimits: () => ComponentLimits,
|
|
35
|
+
DEFAULT_INTENTS: () => DEFAULT_INTENTS,
|
|
36
|
+
EmbedBuilder: () => EmbedBuilder,
|
|
37
|
+
EmbedLimits: () => EmbedLimits,
|
|
38
|
+
Gateway: () => Gateway,
|
|
39
|
+
GatewayError: () => GatewayError,
|
|
40
|
+
Guild: () => Guild,
|
|
41
|
+
GuildsManager: () => GuildsManager,
|
|
42
|
+
Intents: () => Intents,
|
|
43
|
+
Interaction: () => Interaction,
|
|
44
|
+
InteractionsManager: () => InteractionsManager,
|
|
45
|
+
InvalidTokenError: () => InvalidTokenError,
|
|
46
|
+
Member: () => Member,
|
|
47
|
+
MembersManager: () => MembersManager,
|
|
48
|
+
Message: () => Message,
|
|
49
|
+
MessagesManager: () => MessagesManager,
|
|
50
|
+
ModerationManager: () => ModerationManager,
|
|
51
|
+
NetworkError: () => NetworkError,
|
|
52
|
+
NotFoundError: () => NotFoundError,
|
|
53
|
+
PermissionError: () => PermissionError,
|
|
54
|
+
Permissions: () => Permissions,
|
|
55
|
+
RateLimitError: () => RateLimitError,
|
|
56
|
+
RequestTimeoutError: () => RequestTimeoutError,
|
|
57
|
+
RestClient: () => RestClient,
|
|
58
|
+
Role: () => Role,
|
|
59
|
+
RolesManager: () => RolesManager,
|
|
60
|
+
SUGGESTED_MODERATION_PERMISSIONS: () => SUGGESTED_MODERATION_PERMISSIONS,
|
|
61
|
+
SUGGESTED_TEXT_BOT_PERMISSIONS: () => SUGGESTED_TEXT_BOT_PERMISSIONS,
|
|
62
|
+
SelectMenuBuilder: () => SelectMenuBuilder,
|
|
63
|
+
SquordAPIError: () => SquordAPIError,
|
|
64
|
+
SquordClient: () => SquordClient,
|
|
65
|
+
SquordError: () => SquordError,
|
|
66
|
+
SquordTypeError: () => SquordTypeError,
|
|
67
|
+
Thread: () => Thread,
|
|
68
|
+
UploadsManager: () => UploadsManager,
|
|
69
|
+
User: () => User,
|
|
70
|
+
UsersManager: () => UsersManager,
|
|
71
|
+
ValidationError: () => ValidationError,
|
|
72
|
+
assertSnowflake: () => assertSnowflake,
|
|
73
|
+
attachWebSocket: () => attachWebSocket,
|
|
74
|
+
backoffDelay: () => backoffDelay,
|
|
75
|
+
channelMention: () => channelMention,
|
|
76
|
+
collectPagination: () => collectPagination,
|
|
77
|
+
compareSnowflakes: () => compareSnowflakes,
|
|
78
|
+
createConsoleLogger: () => createConsoleLogger,
|
|
79
|
+
hasAnyPermission: () => hasAnyPermission,
|
|
80
|
+
hasPermissions: () => hasPermissions,
|
|
81
|
+
intentsValue: () => intentsValue,
|
|
82
|
+
isValidSnowflake: () => isValidSnowflake,
|
|
83
|
+
listIntents: () => listIntents,
|
|
84
|
+
loggerFromEnv: () => loggerFromEnv,
|
|
85
|
+
makeAPIError: () => makeAPIError,
|
|
86
|
+
noopLogger: () => noopLogger,
|
|
87
|
+
normalizeEmbedColor: () => normalizeEmbedColor,
|
|
88
|
+
paginate: () => paginate,
|
|
89
|
+
parsePermissions: () => parsePermissions,
|
|
90
|
+
permissionsToString: () => permissionsToString,
|
|
91
|
+
redact: () => redact,
|
|
92
|
+
resolveChannelPermissions: () => resolveChannelPermissions,
|
|
93
|
+
resolveMessagePayload: () => resolveMessagePayload,
|
|
94
|
+
roleMention: () => roleMention,
|
|
95
|
+
routeKeyOf: () => routeKeyOf,
|
|
96
|
+
sleep: () => sleep,
|
|
97
|
+
splitArguments: () => splitArguments,
|
|
98
|
+
userMention: () => userMention
|
|
99
|
+
});
|
|
100
|
+
module.exports = __toCommonJS(index_exports);
|
|
101
|
+
|
|
102
|
+
// src/utils/TypedEmitter.ts
|
|
103
|
+
var TypedEmitter = class {
|
|
104
|
+
handlers = /* @__PURE__ */ new Map();
|
|
105
|
+
/** Registra um handler. Devolve uma função que o remove. */
|
|
106
|
+
on(event, handler) {
|
|
107
|
+
let set = this.handlers.get(event);
|
|
108
|
+
if (!set) {
|
|
109
|
+
set = /* @__PURE__ */ new Set();
|
|
110
|
+
this.handlers.set(event, set);
|
|
111
|
+
}
|
|
112
|
+
set.add(handler);
|
|
113
|
+
return () => this.off(event, handler);
|
|
114
|
+
}
|
|
115
|
+
/** Registra um handler que é removido após a primeira chamada. */
|
|
116
|
+
once(event, handler) {
|
|
117
|
+
const wrapper = ((...args) => {
|
|
118
|
+
this.off(event, wrapper);
|
|
119
|
+
void handler(...args);
|
|
120
|
+
});
|
|
121
|
+
return this.on(event, wrapper);
|
|
122
|
+
}
|
|
123
|
+
/** Remove um handler (ou todos do evento, se omitido). */
|
|
124
|
+
off(event, handler) {
|
|
125
|
+
if (!handler) {
|
|
126
|
+
this.handlers.delete(event);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.handlers.get(event)?.delete(handler);
|
|
130
|
+
}
|
|
131
|
+
/** Remove todos os handlers de todos os eventos. */
|
|
132
|
+
removeAllListeners() {
|
|
133
|
+
this.handlers.clear();
|
|
134
|
+
}
|
|
135
|
+
/** Emite um evento. Erros síncronos de handlers propagam para `error`. */
|
|
136
|
+
emit(event, ...args) {
|
|
137
|
+
const set = this.handlers.get(event);
|
|
138
|
+
if (!set || set.size === 0) return false;
|
|
139
|
+
for (const handler of [...set]) {
|
|
140
|
+
try {
|
|
141
|
+
const result = handler(...args);
|
|
142
|
+
if (result instanceof Promise) {
|
|
143
|
+
void result.catch((err) => this.handleAsyncError(event, err));
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
this.handleAsyncError(event, err);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
/** Quantos handlers estão registrados para um evento. */
|
|
152
|
+
listenerCount(event) {
|
|
153
|
+
return this.handlers.get(event)?.size ?? 0;
|
|
154
|
+
}
|
|
155
|
+
/** Nomes de eventos com pelo menos um handler. */
|
|
156
|
+
eventNames() {
|
|
157
|
+
return [...this.handlers.keys()].filter((k) => (this.handlers.get(k)?.size ?? 0) > 0);
|
|
158
|
+
}
|
|
159
|
+
handleAsyncError(event, err) {
|
|
160
|
+
if (event === "error") throw err;
|
|
161
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
162
|
+
this.emit("error", error);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// src/managers/appCommands.ts
|
|
167
|
+
var ApplicationCommandsManager = class {
|
|
168
|
+
constructor(rest) {
|
|
169
|
+
this.rest = rest;
|
|
170
|
+
}
|
|
171
|
+
rest;
|
|
172
|
+
/** Lista os comandos globais do bot. */
|
|
173
|
+
listGlobal(signal) {
|
|
174
|
+
return this.rest.get("/bots/@me/commands", { signal });
|
|
175
|
+
}
|
|
176
|
+
/** Sobrescreve os comandos globais (idempotente por ser PUT). */
|
|
177
|
+
setGlobal(commands, signal) {
|
|
178
|
+
return this.rest.put("/bots/@me/commands", commands, { signal });
|
|
179
|
+
}
|
|
180
|
+
/** Lista os comandos de um Space. */
|
|
181
|
+
listGuild(guildId, signal) {
|
|
182
|
+
return this.rest.get(`/bots/@me/guilds/${guildId}/commands`, { signal });
|
|
183
|
+
}
|
|
184
|
+
/** Sobrescreve os comandos de um Space (o bot precisa estar instalado nele). */
|
|
185
|
+
setGuild(guildId, commands, signal) {
|
|
186
|
+
return this.rest.put(`/bots/@me/guilds/${guildId}/commands`, commands, {
|
|
187
|
+
signal
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/managers/bots.ts
|
|
193
|
+
var BotManager = class {
|
|
194
|
+
constructor(rest) {
|
|
195
|
+
this.rest = rest;
|
|
196
|
+
}
|
|
197
|
+
rest;
|
|
198
|
+
/** `GET /bots/@me`: o bot na visão do dono (inclui as intents configuradas). */
|
|
199
|
+
fetchCurrent(signal) {
|
|
200
|
+
return this.rest.get("/bots/@me", { signal });
|
|
201
|
+
}
|
|
202
|
+
/** `GET /bots/{botId}`: o cartão público de um bot. */
|
|
203
|
+
fetch(botId, signal) {
|
|
204
|
+
return this.rest.get(`/bots/${botId}`, { signal });
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
var UsersManager = class {
|
|
208
|
+
constructor(rest) {
|
|
209
|
+
this.rest = rest;
|
|
210
|
+
}
|
|
211
|
+
rest;
|
|
212
|
+
/** `GET /users/@me`: a própria conta (em um bot, campos privados são padrão). */
|
|
213
|
+
fetchMe(signal) {
|
|
214
|
+
return this.rest.get("/users/@me", { signal });
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/managers/channels.ts
|
|
219
|
+
var ChannelsManager = class {
|
|
220
|
+
constructor(rest) {
|
|
221
|
+
this.rest = rest;
|
|
222
|
+
}
|
|
223
|
+
rest;
|
|
224
|
+
/** Busca um canal (ou uma thread, que também é canal) pelo id. */
|
|
225
|
+
fetch(channelId, signal) {
|
|
226
|
+
return this.rest.get(`/channels/${channelId}`, { signal });
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Lista a árvore de canais do Space: grupos por categoria.
|
|
230
|
+
* Requer que o bot veja os canais (`VIEW_CHANNEL` do cargo dele).
|
|
231
|
+
*/
|
|
232
|
+
fetchTree(guildId, signal) {
|
|
233
|
+
return this.rest.get(`/guilds/${guildId}/channels`, { signal });
|
|
234
|
+
}
|
|
235
|
+
/** Cria uma thread em um canal de texto ou de anúncios. */
|
|
236
|
+
createThread(channelId, body, options = {}) {
|
|
237
|
+
return this.rest.post(`/channels/${channelId}/threads`, body, {
|
|
238
|
+
signal: options.signal,
|
|
239
|
+
retryable: false
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/** Cria um post em um canal de fórum (a primeira mensagem vai no corpo). */
|
|
243
|
+
createForumPost(channelId, body, options = {}) {
|
|
244
|
+
return this.rest.post(`/channels/${channelId}/threads`, body, {
|
|
245
|
+
signal: options.signal,
|
|
246
|
+
retryable: false
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/** Cria uma thread a partir de uma mensagem de um canal de texto/anúncios. */
|
|
250
|
+
createThreadFromMessage(channelId, messageId, body, options = {}) {
|
|
251
|
+
return this.rest.post(
|
|
252
|
+
`/channels/${channelId}/messages/${messageId}/threads`,
|
|
253
|
+
body,
|
|
254
|
+
{ signal: options.signal, retryable: false }
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
/** Lista uma página de threads do canal (`cursor` = `nextCursor` anterior). */
|
|
258
|
+
listThreads(channelId, params = {}, signal) {
|
|
259
|
+
return this.rest.get(`/channels/${channelId}/threads`, {
|
|
260
|
+
query: {
|
|
261
|
+
limit: params.limit,
|
|
262
|
+
cursor: params.after ?? params.before,
|
|
263
|
+
archived: params.archived,
|
|
264
|
+
tagId: params.tagId
|
|
265
|
+
},
|
|
266
|
+
signal
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// src/errors/index.ts
|
|
272
|
+
var SquordError = class extends Error {
|
|
273
|
+
/** Código estável do erro, em `SCREAMING_SNAKE`. */
|
|
274
|
+
code;
|
|
275
|
+
constructor(code, message, options) {
|
|
276
|
+
super(message, options);
|
|
277
|
+
this.name = new.target.name;
|
|
278
|
+
this.code = code;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
var SquordAPIError = class extends SquordError {
|
|
282
|
+
/** Status HTTP da resposta. */
|
|
283
|
+
status;
|
|
284
|
+
/** O mesmo valor do header `x-request-id` da resposta. */
|
|
285
|
+
requestId;
|
|
286
|
+
/** Corpo bruto do erro (o envelope completo), quando disponível. */
|
|
287
|
+
responseBody;
|
|
288
|
+
/** `details` do envelope, quando existe. */
|
|
289
|
+
details;
|
|
290
|
+
constructor(body, status, requestId) {
|
|
291
|
+
super(body.code, body.message ?? `Erro da API do Squord (HTTP ${status})`);
|
|
292
|
+
this.status = status;
|
|
293
|
+
this.requestId = requestId ?? body.requestId ?? null;
|
|
294
|
+
this.details = body.details;
|
|
295
|
+
this.responseBody = body;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var AuthenticationError = class extends SquordAPIError {
|
|
299
|
+
};
|
|
300
|
+
var InvalidTokenError = class extends AuthenticationError {
|
|
301
|
+
};
|
|
302
|
+
var PermissionError = class extends SquordAPIError {
|
|
303
|
+
/** Nome da permissão que faltou, quando o erro é `MISSING_PERMISSION`. */
|
|
304
|
+
missingPermission;
|
|
305
|
+
constructor(body, status, requestId) {
|
|
306
|
+
super(body, status, requestId);
|
|
307
|
+
const details = body.details;
|
|
308
|
+
this.missingPermission = details?.permission ?? null;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
var NotFoundError = class extends SquordAPIError {
|
|
312
|
+
};
|
|
313
|
+
var ValidationError = class extends SquordAPIError {
|
|
314
|
+
/** Os itens de validação (campo, código do item, limite violado). */
|
|
315
|
+
issues;
|
|
316
|
+
constructor(body, status, requestId) {
|
|
317
|
+
super(body, status, requestId);
|
|
318
|
+
this.issues = Array.isArray(body.details) ? body.details : [];
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
var RateLimitError = class extends SquordAPIError {
|
|
322
|
+
/** Segundos de espera informados pelo servidor (mínimo 1). */
|
|
323
|
+
retryAfter;
|
|
324
|
+
/** O bucket que recusou (`MESSAGE_CREATE`, `BOT_GLOBAL`, ...). */
|
|
325
|
+
bucket;
|
|
326
|
+
/** `true` quando o SDK já esgotou as retentativas automáticas. */
|
|
327
|
+
exhaustedRetries;
|
|
328
|
+
constructor(body, status, options) {
|
|
329
|
+
super(body, status, options?.requestId);
|
|
330
|
+
const details = body.details;
|
|
331
|
+
this.retryAfter = details?.retryAfter ?? 1;
|
|
332
|
+
this.bucket = details?.bucket ?? null;
|
|
333
|
+
this.exhaustedRetries = options?.exhaustedRetries ?? false;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
var NetworkError = class extends SquordError {
|
|
337
|
+
constructor(message, options) {
|
|
338
|
+
super("NETWORK_ERROR", message, options);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var RequestTimeoutError = class extends SquordError {
|
|
342
|
+
/** Milissegundos configurados de timeout. */
|
|
343
|
+
timeoutMs;
|
|
344
|
+
/** Método HTTP da requisição que expirou. */
|
|
345
|
+
method;
|
|
346
|
+
/** Caminho da requisição que expirou. */
|
|
347
|
+
path;
|
|
348
|
+
constructor(method, path, timeoutMs, options) {
|
|
349
|
+
super(
|
|
350
|
+
"REQUEST_TIMEOUT",
|
|
351
|
+
`A requisi\xE7\xE3o ${method} ${path} excedeu o timeout de ${timeoutMs} ms`,
|
|
352
|
+
options
|
|
353
|
+
);
|
|
354
|
+
this.method = method;
|
|
355
|
+
this.path = path;
|
|
356
|
+
this.timeoutMs = timeoutMs;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
var GatewayError = class extends SquordError {
|
|
360
|
+
/** Código de fechamento (4000+) ou `null` em falhas de rede. */
|
|
361
|
+
closeCode;
|
|
362
|
+
/** Razão textual enviada pelo servidor (informativa; use `closeCode`). */
|
|
363
|
+
reason;
|
|
364
|
+
constructor(closeCode, reason, message) {
|
|
365
|
+
super(
|
|
366
|
+
closeCode === null ? "GATEWAY_NETWORK_ERROR" : "GATEWAY_CLOSE",
|
|
367
|
+
message ?? `Gateway fechou a conex\xE3o${closeCode !== null ? ` (c\xF3digo ${closeCode})` : ""}${reason ? `: ${reason}` : ""}`,
|
|
368
|
+
{ cause: void 0 }
|
|
369
|
+
);
|
|
370
|
+
this.closeCode = closeCode;
|
|
371
|
+
this.reason = reason;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
var SquordTypeError = class extends SquordError {
|
|
375
|
+
constructor(message) {
|
|
376
|
+
super("SDK_INVALID_ARGUMENT", message);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
function makeAPIError(body, status, requestId) {
|
|
380
|
+
switch (status) {
|
|
381
|
+
case 401:
|
|
382
|
+
return body.code === "INVALID_TOKEN" ? new InvalidTokenError(body, status, requestId) : new AuthenticationError(body, status, requestId);
|
|
383
|
+
case 403:
|
|
384
|
+
return new PermissionError(body, status, requestId);
|
|
385
|
+
case 404:
|
|
386
|
+
return new NotFoundError(body, status, requestId);
|
|
387
|
+
case 422:
|
|
388
|
+
return new ValidationError(body, status, requestId);
|
|
389
|
+
case 429:
|
|
390
|
+
return new RateLimitError(body, status, { requestId });
|
|
391
|
+
default:
|
|
392
|
+
return new SquordAPIError(body, status, requestId);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/builders/components.ts
|
|
397
|
+
var ComponentLimits = {
|
|
398
|
+
RowsPerMessage: 5,
|
|
399
|
+
ButtonsPerRow: 5,
|
|
400
|
+
CustomId: 100,
|
|
401
|
+
Label: 80,
|
|
402
|
+
OptionsPerMenu: 25
|
|
403
|
+
};
|
|
404
|
+
var ButtonBuilder = class {
|
|
405
|
+
constructor(style, labelValue) {
|
|
406
|
+
this.style = style;
|
|
407
|
+
this.labelValue = labelValue;
|
|
408
|
+
}
|
|
409
|
+
style;
|
|
410
|
+
labelValue;
|
|
411
|
+
customIdValue;
|
|
412
|
+
urlValue;
|
|
413
|
+
disabledValue = false;
|
|
414
|
+
/** Identificador da ação; obrigatório (e único na mensagem) fora do estilo `link`. */
|
|
415
|
+
setCustomId(customId) {
|
|
416
|
+
assertCustomId(customId);
|
|
417
|
+
this.customIdValue = customId;
|
|
418
|
+
return this;
|
|
419
|
+
}
|
|
420
|
+
/** URL aberta ao clicar; obrigatória e exclusiva do estilo `link`. */
|
|
421
|
+
setURL(url) {
|
|
422
|
+
if (!/^https:\/\//.test(url)) {
|
|
423
|
+
throw new SquordTypeError(`Bot\xE3o de link exige URL https:// (recebido: ${url})`);
|
|
424
|
+
}
|
|
425
|
+
this.urlValue = url;
|
|
426
|
+
return this;
|
|
427
|
+
}
|
|
428
|
+
setDisabled(disabled) {
|
|
429
|
+
this.disabledValue = disabled;
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
setLabel(label) {
|
|
433
|
+
this.labelValue = label;
|
|
434
|
+
return this;
|
|
435
|
+
}
|
|
436
|
+
build() {
|
|
437
|
+
if (this.style === "link" && !this.urlValue) {
|
|
438
|
+
throw new SquordTypeError("Bot\xE3o de link exige setURL(...)");
|
|
439
|
+
}
|
|
440
|
+
if (this.style !== "link" && !this.customIdValue) {
|
|
441
|
+
throw new SquordTypeError(`Bot\xE3o de estilo ${this.style} exige setCustomId(...)`);
|
|
442
|
+
}
|
|
443
|
+
if (this.labelValue.length > ComponentLimits.Label) {
|
|
444
|
+
throw new SquordTypeError(`Label de bot\xE3o excede ${ComponentLimits.Label} caracteres`);
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
type: "button",
|
|
448
|
+
style: this.style,
|
|
449
|
+
label: this.labelValue,
|
|
450
|
+
customId: this.style === "link" ? void 0 : this.customIdValue,
|
|
451
|
+
url: this.style === "link" ? this.urlValue : void 0,
|
|
452
|
+
disabled: this.disabledValue || void 0
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
var SelectMenuBuilder = class {
|
|
457
|
+
constructor(customId) {
|
|
458
|
+
this.customId = customId;
|
|
459
|
+
assertCustomId(customId);
|
|
460
|
+
}
|
|
461
|
+
customId;
|
|
462
|
+
placeholderValue;
|
|
463
|
+
minValuesValue;
|
|
464
|
+
maxValuesValue;
|
|
465
|
+
disabledValue = false;
|
|
466
|
+
optionsValue = [];
|
|
467
|
+
setPlaceholder(placeholder) {
|
|
468
|
+
if (placeholder.length > 150) {
|
|
469
|
+
throw new SquordTypeError("Placeholder de menu excede 150 caracteres");
|
|
470
|
+
}
|
|
471
|
+
this.placeholderValue = placeholder;
|
|
472
|
+
return this;
|
|
473
|
+
}
|
|
474
|
+
setMinValues(min) {
|
|
475
|
+
this.minValuesValue = min;
|
|
476
|
+
return this;
|
|
477
|
+
}
|
|
478
|
+
setMaxValues(max) {
|
|
479
|
+
this.maxValuesValue = max;
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
setDisabled(disabled) {
|
|
483
|
+
this.disabledValue = disabled;
|
|
484
|
+
return this;
|
|
485
|
+
}
|
|
486
|
+
addOption(option) {
|
|
487
|
+
if (this.optionsValue.length >= ComponentLimits.OptionsPerMenu) {
|
|
488
|
+
throw new SquordTypeError(`Menu aceita no m\xE1ximo ${ComponentLimits.OptionsPerMenu} op\xE7\xF5es`);
|
|
489
|
+
}
|
|
490
|
+
if (!option.label || !option.value) {
|
|
491
|
+
throw new SquordTypeError("Op\xE7\xE3o de menu exige label e value");
|
|
492
|
+
}
|
|
493
|
+
if (option.label.length > ComponentLimits.Label || option.value.length > 100) {
|
|
494
|
+
throw new SquordTypeError("Label de op\xE7\xE3o: at\xE9 80 caracteres; value: at\xE9 100");
|
|
495
|
+
}
|
|
496
|
+
this.optionsValue.push(option);
|
|
497
|
+
return this;
|
|
498
|
+
}
|
|
499
|
+
addOptions(...options) {
|
|
500
|
+
for (const option of options) this.addOption(option);
|
|
501
|
+
return this;
|
|
502
|
+
}
|
|
503
|
+
build() {
|
|
504
|
+
if (this.optionsValue.length === 0) {
|
|
505
|
+
throw new SquordTypeError("Menu de sele\xE7\xE3o precisa de ao menos uma op\xE7\xE3o");
|
|
506
|
+
}
|
|
507
|
+
const min = this.minValuesValue ?? 1;
|
|
508
|
+
const max = this.maxValuesValue ?? Math.max(1, this.optionsValue.length);
|
|
509
|
+
if (min > max) {
|
|
510
|
+
throw new SquordTypeError(`minValues (${min}) n\xE3o pode passar de maxValues (${max})`);
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
type: "select",
|
|
514
|
+
customId: this.customId,
|
|
515
|
+
placeholder: this.placeholderValue,
|
|
516
|
+
minValues: this.minValuesValue,
|
|
517
|
+
maxValues: this.maxValuesValue,
|
|
518
|
+
disabled: this.disabledValue || void 0,
|
|
519
|
+
options: [...this.optionsValue]
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
var ActionRowBuilder = class {
|
|
524
|
+
components = [];
|
|
525
|
+
/** Adiciona um botão (até 5 por linha). */
|
|
526
|
+
addButton(button) {
|
|
527
|
+
if (this.components.length >= ComponentLimits.ButtonsPerRow || this.components.some((c) => c.type === "select")) {
|
|
528
|
+
throw new SquordTypeError("Uma linha aceita at\xE9 5 bot\xF5es e n\xE3o mistura bot\xF5es com menus");
|
|
529
|
+
}
|
|
530
|
+
this.components.push(button instanceof ButtonBuilder ? button.build() : button);
|
|
531
|
+
return this;
|
|
532
|
+
}
|
|
533
|
+
/** Define o menu da linha (exatamente 1, sozinho). */
|
|
534
|
+
setSelectMenu(menu) {
|
|
535
|
+
if (this.components.length > 0) {
|
|
536
|
+
throw new SquordTypeError("Um menu ocupa a linha sozinho; n\xE3o misture com bot\xF5es");
|
|
537
|
+
}
|
|
538
|
+
this.components.push(menu instanceof SelectMenuBuilder ? menu.build() : menu);
|
|
539
|
+
return this;
|
|
540
|
+
}
|
|
541
|
+
addComponents(...components) {
|
|
542
|
+
for (const component of components) {
|
|
543
|
+
if (component instanceof ButtonBuilder || "type" in component && component.type === "button") {
|
|
544
|
+
this.addButton(component);
|
|
545
|
+
} else {
|
|
546
|
+
this.setSelectMenu(component);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return this;
|
|
550
|
+
}
|
|
551
|
+
build() {
|
|
552
|
+
if (this.components.length === 0) {
|
|
553
|
+
throw new SquordTypeError("Linha de componentes vazia");
|
|
554
|
+
}
|
|
555
|
+
return { type: "row", components: [...this.components] };
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
var Buttons = {
|
|
559
|
+
primary: (label) => new ButtonBuilder("primary", label),
|
|
560
|
+
secondary: (label) => new ButtonBuilder("secondary", label),
|
|
561
|
+
danger: (label) => new ButtonBuilder("danger", label),
|
|
562
|
+
link: (label) => new ButtonBuilder("link", label)
|
|
563
|
+
};
|
|
564
|
+
function assertCustomId(customId) {
|
|
565
|
+
if (typeof customId !== "string" || customId.length < 1 || customId.length > ComponentLimits.CustomId) {
|
|
566
|
+
throw new SquordTypeError(
|
|
567
|
+
`customId precisa ter de 1 a ${ComponentLimits.CustomId} caracteres`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/builders/embed.ts
|
|
573
|
+
var EmbedLimits = {
|
|
574
|
+
EmbedsPerMessage: 10,
|
|
575
|
+
FieldsPerEmbed: 25,
|
|
576
|
+
Title: 256,
|
|
577
|
+
Description: 4096,
|
|
578
|
+
FieldName: 256,
|
|
579
|
+
FieldValue: 1024,
|
|
580
|
+
FooterText: 2048,
|
|
581
|
+
AuthorName: 256,
|
|
582
|
+
TotalTextPerMessage: 6e3
|
|
583
|
+
};
|
|
584
|
+
function normalizeEmbedColor(color) {
|
|
585
|
+
if (typeof color === "number") {
|
|
586
|
+
if (!Number.isInteger(color) || color < 0 || color > 16777215) {
|
|
587
|
+
throw new SquordTypeError(
|
|
588
|
+
`Cor de embed inv\xE1lida: ${color} \u2014 use um inteiro de 0 a 16777215 ou "#rrggbb"`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
return color;
|
|
592
|
+
}
|
|
593
|
+
const trimmed = color.trim();
|
|
594
|
+
const hex = trimmed.startsWith("#") ? trimmed.slice(1) : trimmed;
|
|
595
|
+
if (!/^[0-9a-fA-F]{6}$/.test(hex)) {
|
|
596
|
+
throw new SquordTypeError(`Cor de embed inv\xE1lida: ${JSON.stringify(color)} \u2014 use "#rrggbb"`);
|
|
597
|
+
}
|
|
598
|
+
return Number.parseInt(hex, 16);
|
|
599
|
+
}
|
|
600
|
+
var EmbedBuilder = class _EmbedBuilder {
|
|
601
|
+
titleValue = null;
|
|
602
|
+
descriptionValue = null;
|
|
603
|
+
urlValue = null;
|
|
604
|
+
colorValue = null;
|
|
605
|
+
timestampValue = null;
|
|
606
|
+
authorValue = null;
|
|
607
|
+
footerValue = null;
|
|
608
|
+
imageValue = null;
|
|
609
|
+
thumbnailValue = null;
|
|
610
|
+
fieldsValue = [];
|
|
611
|
+
/** Título do embed (até 256 caracteres). */
|
|
612
|
+
setTitle(title) {
|
|
613
|
+
assertLength("title", title, EmbedLimits.Title);
|
|
614
|
+
this.titleValue = title;
|
|
615
|
+
return this;
|
|
616
|
+
}
|
|
617
|
+
/** Descrição (até 4096 caracteres). */
|
|
618
|
+
setDescription(description) {
|
|
619
|
+
assertLength("description", description, EmbedLimits.Description);
|
|
620
|
+
this.descriptionValue = description;
|
|
621
|
+
return this;
|
|
622
|
+
}
|
|
623
|
+
/** URL do título (só `http://` ou `https://`). */
|
|
624
|
+
setURL(url) {
|
|
625
|
+
assertHttpUrl("url", url);
|
|
626
|
+
this.urlValue = url;
|
|
627
|
+
return this;
|
|
628
|
+
}
|
|
629
|
+
/** Cor: inteiro de 0 a 16777215 ou `#rrggbb`. */
|
|
630
|
+
setColor(color) {
|
|
631
|
+
this.colorValue = normalizeEmbedColor(color);
|
|
632
|
+
return this;
|
|
633
|
+
}
|
|
634
|
+
/** Momento exibido no rodapé. Um `Date` ou string ISO 8601 com fuso. */
|
|
635
|
+
setTimestamp(timestamp = /* @__PURE__ */ new Date()) {
|
|
636
|
+
this.timestampValue = timestamp instanceof Date ? timestamp.toISOString() : timestamp;
|
|
637
|
+
return this;
|
|
638
|
+
}
|
|
639
|
+
/** Autor exibido no topo (nome até 256 caracteres). */
|
|
640
|
+
setAuthor(author) {
|
|
641
|
+
if (author) {
|
|
642
|
+
assertLength("author.name", author.name, EmbedLimits.AuthorName);
|
|
643
|
+
if (author.url) assertHttpUrl("author.url", author.url);
|
|
644
|
+
}
|
|
645
|
+
this.authorValue = author;
|
|
646
|
+
return this;
|
|
647
|
+
}
|
|
648
|
+
/** Rodapé (texto até 2048 caracteres). */
|
|
649
|
+
setFooter(footer) {
|
|
650
|
+
if (footer) {
|
|
651
|
+
assertLength("footer.text", footer.text, EmbedLimits.FooterText);
|
|
652
|
+
}
|
|
653
|
+
this.footerValue = footer;
|
|
654
|
+
return this;
|
|
655
|
+
}
|
|
656
|
+
/** Imagem principal (baixada e hospedada pelo Squord em segundo plano). */
|
|
657
|
+
setImage(url) {
|
|
658
|
+
this.imageValue = url;
|
|
659
|
+
return this;
|
|
660
|
+
}
|
|
661
|
+
/** Miniatura. */
|
|
662
|
+
setThumbnail(url) {
|
|
663
|
+
this.thumbnailValue = url;
|
|
664
|
+
return this;
|
|
665
|
+
}
|
|
666
|
+
/** Adiciona um campo (nome até 256, valor até 1024). */
|
|
667
|
+
addField(name, value, inline = false) {
|
|
668
|
+
return this.addFields({ name, value, inline });
|
|
669
|
+
}
|
|
670
|
+
/** Adiciona vários campos. */
|
|
671
|
+
addFields(...fields) {
|
|
672
|
+
if (this.fieldsValue.length + fields.length > EmbedLimits.FieldsPerEmbed) {
|
|
673
|
+
throw new SquordTypeError(
|
|
674
|
+
`Um embed aceita no m\xE1ximo ${EmbedLimits.FieldsPerEmbed} campos`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
for (const field of fields) {
|
|
678
|
+
assertLength("field.name", field.name, EmbedLimits.FieldName);
|
|
679
|
+
assertLength("field.value", field.value, EmbedLimits.FieldValue);
|
|
680
|
+
this.fieldsValue.push(field);
|
|
681
|
+
}
|
|
682
|
+
return this;
|
|
683
|
+
}
|
|
684
|
+
/** Remove todos os campos. */
|
|
685
|
+
clearFields() {
|
|
686
|
+
this.fieldsValue = [];
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
689
|
+
/** Payload pronto para o campo `embeds` (só campos preenchidos). */
|
|
690
|
+
build() {
|
|
691
|
+
if (!this.titleValue && !this.descriptionValue && !this.imageValue && !this.thumbnailValue && this.fieldsValue.length === 0 && !this.footerValue && !this.authorValue) {
|
|
692
|
+
throw new SquordTypeError(
|
|
693
|
+
"O embed n\xE3o tem nada vis\xEDvel: defina t\xEDtulo, descri\xE7\xE3o, campos, imagem ou rodap\xE9 (o Squord recusa embeds vazios com BLANK)"
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
const embed = {};
|
|
697
|
+
if (this.titleValue) embed.title = this.titleValue;
|
|
698
|
+
if (this.descriptionValue) embed.description = this.descriptionValue;
|
|
699
|
+
if (this.urlValue) embed.url = this.urlValue;
|
|
700
|
+
if (this.colorValue !== null) embed.color = this.colorValue;
|
|
701
|
+
if (this.timestampValue) embed.timestamp = this.timestampValue;
|
|
702
|
+
if (this.authorValue) {
|
|
703
|
+
const author = { name: this.authorValue.name };
|
|
704
|
+
if (this.authorValue.url) author.url = this.authorValue.url;
|
|
705
|
+
if (this.authorValue.iconUrl) author.icon_url = this.authorValue.iconUrl;
|
|
706
|
+
embed.author = author;
|
|
707
|
+
}
|
|
708
|
+
if (this.footerValue) {
|
|
709
|
+
const footer = { text: this.footerValue.text };
|
|
710
|
+
if (this.footerValue.iconUrl) footer.icon_url = this.footerValue.iconUrl;
|
|
711
|
+
embed.footer = footer;
|
|
712
|
+
}
|
|
713
|
+
if (this.imageValue) embed.image = { url: this.imageValue };
|
|
714
|
+
if (this.thumbnailValue) embed.thumbnail = { url: this.thumbnailValue };
|
|
715
|
+
if (this.fieldsValue.length > 0) embed.fields = [...this.fieldsValue];
|
|
716
|
+
return embed;
|
|
717
|
+
}
|
|
718
|
+
/** Soma dos textos do embed (para conferir o limite de 6000 por mensagem). */
|
|
719
|
+
get textLength() {
|
|
720
|
+
let total = 0;
|
|
721
|
+
if (this.titleValue) total += this.titleValue.length;
|
|
722
|
+
if (this.descriptionValue) total += this.descriptionValue.length;
|
|
723
|
+
if (this.footerValue) total += this.footerValue.text.length;
|
|
724
|
+
if (this.authorValue) total += this.authorValue.name.length;
|
|
725
|
+
for (const field of this.fieldsValue) {
|
|
726
|
+
total += field.name.length + field.value.length;
|
|
727
|
+
}
|
|
728
|
+
return total;
|
|
729
|
+
}
|
|
730
|
+
/** Cria um builder a partir de um payload existente. */
|
|
731
|
+
static from(embed) {
|
|
732
|
+
const builder = new _EmbedBuilder();
|
|
733
|
+
if (embed.title) builder.setTitle(embed.title);
|
|
734
|
+
if (embed.description) builder.setDescription(embed.description);
|
|
735
|
+
if (embed.url) builder.setURL(embed.url);
|
|
736
|
+
if (embed.color != null) builder.setColor(embed.color);
|
|
737
|
+
if (embed.timestamp) builder.setTimestamp(embed.timestamp);
|
|
738
|
+
if ("author" in embed && embed.author) {
|
|
739
|
+
builder.setAuthor({
|
|
740
|
+
name: embed.author.name,
|
|
741
|
+
url: "url" in embed.author ? embed.author.url ?? void 0 : void 0,
|
|
742
|
+
iconUrl: "iconUrl" in embed.author ? embed.author.iconUrl ?? void 0 : void 0
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
if ("footer" in embed && embed.footer) {
|
|
746
|
+
builder.setFooter({
|
|
747
|
+
text: embed.footer.text,
|
|
748
|
+
iconUrl: "iconUrl" in embed.footer ? embed.footer.iconUrl ?? void 0 : void 0
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
if ("fields" in embed && Array.isArray(embed.fields) && embed.fields.length > 0) {
|
|
752
|
+
builder.addFields(...embed.fields);
|
|
753
|
+
}
|
|
754
|
+
const image = "image" in embed ? embed.image : null;
|
|
755
|
+
if (image && "url" in image) builder.setImage(image.url);
|
|
756
|
+
const thumbnail = "thumbnail" in embed ? embed.thumbnail : null;
|
|
757
|
+
if (thumbnail && "url" in thumbnail) builder.setThumbnail(thumbnail.url);
|
|
758
|
+
return builder;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
function assertLength(field, value, max) {
|
|
762
|
+
if (value.length > max) {
|
|
763
|
+
throw new SquordTypeError(`Embed.${field} excede ${max} caracteres (${value.length})`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function assertHttpUrl(field, url) {
|
|
767
|
+
if (!/^https?:\/\//.test(url)) {
|
|
768
|
+
throw new SquordTypeError(`Embed.${field} precisa ser http:// ou https:// (recebido: ${url})`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// src/builders/message.ts
|
|
773
|
+
var SAFE_ALLOWED_MENTIONS = { users: true, roles: false, everyone: false };
|
|
774
|
+
function resolveMessagePayload(payload) {
|
|
775
|
+
const source = typeof payload === "string" ? { content: payload } : payload;
|
|
776
|
+
const body = {};
|
|
777
|
+
let hasVisibleContent = false;
|
|
778
|
+
if (source.content !== void 0) {
|
|
779
|
+
if (typeof source.content !== "string") {
|
|
780
|
+
throw new SquordTypeError("content precisa ser string");
|
|
781
|
+
}
|
|
782
|
+
if (source.content.length > 4e3) {
|
|
783
|
+
throw new SquordTypeError(`content excede 4000 caracteres (${source.content.length})`);
|
|
784
|
+
}
|
|
785
|
+
body.content = source.content;
|
|
786
|
+
if (source.content.length > 0) hasVisibleContent = true;
|
|
787
|
+
}
|
|
788
|
+
if (source.embeds !== void 0) {
|
|
789
|
+
if (!Array.isArray(source.embeds) || source.embeds.length > 10) {
|
|
790
|
+
throw new SquordTypeError("embeds aceita at\xE9 10 itens");
|
|
791
|
+
}
|
|
792
|
+
body.embeds = source.embeds.map(
|
|
793
|
+
(embed) => embed instanceof EmbedBuilder ? embed.build() : embed
|
|
794
|
+
);
|
|
795
|
+
if (body.embeds.length > 0) hasVisibleContent = true;
|
|
796
|
+
}
|
|
797
|
+
if (source.components !== void 0) {
|
|
798
|
+
if (!Array.isArray(source.components) || source.components.length > 5) {
|
|
799
|
+
throw new SquordTypeError("components aceita at\xE9 5 linhas");
|
|
800
|
+
}
|
|
801
|
+
body.components = source.components.map(
|
|
802
|
+
(row) => row instanceof ActionRowBuilder ? row.build() : row
|
|
803
|
+
);
|
|
804
|
+
if (body.components.length > 0) hasVisibleContent = true;
|
|
805
|
+
}
|
|
806
|
+
body.allowedMentions = source.allowedMentions ?? SAFE_ALLOWED_MENTIONS;
|
|
807
|
+
if (source.replyToId !== void 0) {
|
|
808
|
+
body.replyToId = source.replyToId;
|
|
809
|
+
}
|
|
810
|
+
if (source.attachmentIds !== void 0) {
|
|
811
|
+
if (!Array.isArray(source.attachmentIds) || source.attachmentIds.length > 10) {
|
|
812
|
+
throw new SquordTypeError("attachmentIds aceita at\xE9 10 itens");
|
|
813
|
+
}
|
|
814
|
+
body.attachmentIds = source.attachmentIds;
|
|
815
|
+
if (body.attachmentIds.length > 0) hasVisibleContent = true;
|
|
816
|
+
}
|
|
817
|
+
if (source.nonce !== void 0) {
|
|
818
|
+
if (typeof source.nonce !== "string" || source.nonce.length > 64) {
|
|
819
|
+
throw new SquordTypeError("nonce precisa ser string de at\xE9 64 caracteres");
|
|
820
|
+
}
|
|
821
|
+
body.nonce = source.nonce;
|
|
822
|
+
}
|
|
823
|
+
if (source.ephemeral !== void 0) {
|
|
824
|
+
body.ephemeral = source.ephemeral;
|
|
825
|
+
}
|
|
826
|
+
if (!hasVisibleContent) {
|
|
827
|
+
throw new SquordTypeError(
|
|
828
|
+
"A mensagem precisa de ao menos um entre content (n\xE3o vazio), embeds, components e attachmentIds"
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
return body;
|
|
832
|
+
}
|
|
833
|
+
function resolveMessageEditPayload(payload) {
|
|
834
|
+
const source = typeof payload === "string" ? { content: payload } : payload;
|
|
835
|
+
const body = {};
|
|
836
|
+
let hasField = false;
|
|
837
|
+
if (source.content !== void 0) {
|
|
838
|
+
if (source.content.length > 4e3) {
|
|
839
|
+
throw new SquordTypeError(`content excede 4000 caracteres (${source.content.length})`);
|
|
840
|
+
}
|
|
841
|
+
body.content = source.content;
|
|
842
|
+
hasField = true;
|
|
843
|
+
}
|
|
844
|
+
if (source.embeds !== void 0) {
|
|
845
|
+
body.embeds = source.embeds.map(
|
|
846
|
+
(embed) => embed instanceof EmbedBuilder ? embed.build() : embed
|
|
847
|
+
);
|
|
848
|
+
hasField = true;
|
|
849
|
+
}
|
|
850
|
+
if (source.components !== void 0) {
|
|
851
|
+
body.components = source.components.map(
|
|
852
|
+
(row) => row instanceof ActionRowBuilder ? row.build() : row
|
|
853
|
+
);
|
|
854
|
+
hasField = true;
|
|
855
|
+
}
|
|
856
|
+
if (source.allowedMentions !== void 0) {
|
|
857
|
+
body.allowedMentions = source.allowedMentions;
|
|
858
|
+
}
|
|
859
|
+
if (!hasField) {
|
|
860
|
+
throw new SquordTypeError(
|
|
861
|
+
"Edi\xE7\xE3o precisa de ao menos um entre content, embeds e components (EMPTY_UPDATE)"
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
return body;
|
|
865
|
+
}
|
|
866
|
+
function safeAllowedMentions(allowed) {
|
|
867
|
+
return allowed ?? SAFE_ALLOWED_MENTIONS;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/managers/interactions.ts
|
|
871
|
+
var InteractionsManager = class {
|
|
872
|
+
constructor(rest) {
|
|
873
|
+
this.rest = rest;
|
|
874
|
+
}
|
|
875
|
+
rest;
|
|
876
|
+
/**
|
|
877
|
+
* Primeira resposta a uma interação (`type`: `MESSAGE`, `DEFERRED`,
|
|
878
|
+
* `UPDATE` ou `DEFERRED_UPDATE`). Resolve em `204` (sem corpo).
|
|
879
|
+
*
|
|
880
|
+
* Não é retentável automaticamente: uma repetição pode encontrar
|
|
881
|
+
* `409 INTERACTION_ALREADY_ACKNOWLEDGED`. O SDK trata esse 409 como
|
|
882
|
+
* "resposta já entregue" e resolve sem erro (veja `reply`).
|
|
883
|
+
*/
|
|
884
|
+
callback(interactionId, interactionToken, body, signal) {
|
|
885
|
+
return this.rest.post(`/interactions/${interactionId}/${interactionToken}/callback`, body, {
|
|
886
|
+
auth: false,
|
|
887
|
+
signal,
|
|
888
|
+
retryable: false
|
|
889
|
+
}).then(() => void 0);
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Follow-up depois da primeira resposta. Age como `MESSAGE`: cria uma
|
|
893
|
+
* mensagem (ou resposta efêmera). Responde `204`.
|
|
894
|
+
*/
|
|
895
|
+
followup(interactionId, interactionToken, data, signal) {
|
|
896
|
+
return this.rest.post(`/interactions/${interactionId}/${interactionToken}/followups`, data, {
|
|
897
|
+
auth: false,
|
|
898
|
+
signal,
|
|
899
|
+
retryable: false
|
|
900
|
+
}).then(() => void 0);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Resposta `MESSAGE` com payload conveniente (string ou objeto).
|
|
904
|
+
* Um `409 INTERACTION_ALREADY_ACKNOWLEDGED` é tratado como sucesso:
|
|
905
|
+
* significa que a resposta já foi entregue (por exemplo, após um timeout
|
|
906
|
+
* de rede em que a primeira tentativa chegou ao servidor).
|
|
907
|
+
*/
|
|
908
|
+
async reply(interactionId, interactionToken, payload, options = {}) {
|
|
909
|
+
const data = resolveMessagePayload(payload);
|
|
910
|
+
if (options.ephemeral) data.ephemeral = true;
|
|
911
|
+
try {
|
|
912
|
+
await this.callback(interactionId, interactionToken, { type: "MESSAGE", data }, options.signal);
|
|
913
|
+
} catch (err) {
|
|
914
|
+
if (isAlreadyAcknowledged(err)) return;
|
|
915
|
+
throw err;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/** `DEFERRED`: avisa que o bot está processando ("pensando"). Depois, use follow-ups. */
|
|
919
|
+
async defer(interactionId, interactionToken, options = {}) {
|
|
920
|
+
try {
|
|
921
|
+
await this.callback(
|
|
922
|
+
interactionId,
|
|
923
|
+
interactionToken,
|
|
924
|
+
{ type: "DEFERRED" },
|
|
925
|
+
options.signal
|
|
926
|
+
);
|
|
927
|
+
} catch (err) {
|
|
928
|
+
if (isAlreadyAcknowledged(err)) return;
|
|
929
|
+
throw err;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
/** `UPDATE` (só componente): edita a mensagem que tinha o componente clicado. */
|
|
933
|
+
async update(interactionId, interactionToken, payload, options = {}) {
|
|
934
|
+
const data = resolveMessagePayload(payload);
|
|
935
|
+
try {
|
|
936
|
+
await this.callback(
|
|
937
|
+
interactionId,
|
|
938
|
+
interactionToken,
|
|
939
|
+
{ type: "UPDATE", data },
|
|
940
|
+
options.signal
|
|
941
|
+
);
|
|
942
|
+
} catch (err) {
|
|
943
|
+
if (isAlreadyAcknowledged(err)) return;
|
|
944
|
+
throw err;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
/** `DEFERRED_UPDATE` (só componente): confirma o clique sem mudar nada. */
|
|
948
|
+
async acknowledgeComponent(interactionId, interactionToken, options = {}) {
|
|
949
|
+
try {
|
|
950
|
+
await this.callback(
|
|
951
|
+
interactionId,
|
|
952
|
+
interactionToken,
|
|
953
|
+
{ type: "DEFERRED_UPDATE" },
|
|
954
|
+
options.signal
|
|
955
|
+
);
|
|
956
|
+
} catch (err) {
|
|
957
|
+
if (isAlreadyAcknowledged(err)) return;
|
|
958
|
+
throw err;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
function isAlreadyAcknowledged(err) {
|
|
963
|
+
return typeof err === "object" && err !== null && err.status === 409 && err.code === "INTERACTION_ALREADY_ACKNOWLEDGED";
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// src/managers/members.ts
|
|
967
|
+
var GuildsManager = class {
|
|
968
|
+
constructor(rest) {
|
|
969
|
+
this.rest = rest;
|
|
970
|
+
}
|
|
971
|
+
rest;
|
|
972
|
+
/** Busca um Space pelo id, com o bloco `self` do bot nele. */
|
|
973
|
+
fetch(guildId, signal) {
|
|
974
|
+
return this.rest.get(`/guilds/${guildId}`, { signal });
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
var MembersManager = class {
|
|
978
|
+
constructor(rest) {
|
|
979
|
+
this.rest = rest;
|
|
980
|
+
}
|
|
981
|
+
rest;
|
|
982
|
+
/** Busca um membro de um Space. */
|
|
983
|
+
fetch(guildId, userId, signal) {
|
|
984
|
+
return this.rest.get(`/guilds/${guildId}/members/${userId}`, { signal });
|
|
985
|
+
}
|
|
986
|
+
/** Lista membros com paginação por cursor e filtros. */
|
|
987
|
+
list(guildId, params = {}, signal) {
|
|
988
|
+
return this.rest.get(`/guilds/${guildId}/members`, {
|
|
989
|
+
query: {
|
|
990
|
+
limit: params.limit,
|
|
991
|
+
before: params.before,
|
|
992
|
+
after: params.after,
|
|
993
|
+
roleId: params.roleId,
|
|
994
|
+
search: params.search,
|
|
995
|
+
channelId: params.channelId
|
|
996
|
+
},
|
|
997
|
+
signal
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Edita um membro: apelido, cargos (substituem o conjunto), timeout,
|
|
1002
|
+
* silenciamento. O bot precisa das permissões correspondentes.
|
|
1003
|
+
*/
|
|
1004
|
+
edit(guildId, userId, body, options = {}) {
|
|
1005
|
+
return this.rest.patch(`/guilds/${guildId}/members/${userId}`, body, {
|
|
1006
|
+
signal: options.signal,
|
|
1007
|
+
// PATCH de membro é substituição declarativa; seguro repetir.
|
|
1008
|
+
retryable: true
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
/** Aplica um timeout até a data informada (`TIMEOUT_MEMBERS`). */
|
|
1012
|
+
timeout(guildId, userId, until, options) {
|
|
1013
|
+
return this.edit(guildId, userId, { timeoutUntil: until }, options);
|
|
1014
|
+
}
|
|
1015
|
+
/** Remove o timeout de um membro. */
|
|
1016
|
+
removeTimeout(guildId, userId, options) {
|
|
1017
|
+
return this.edit(guildId, userId, { timeoutUntil: null }, options);
|
|
1018
|
+
}
|
|
1019
|
+
/** Expulsa um membro (`KICK_MEMBERS`). Ele pode voltar com um novo convite. */
|
|
1020
|
+
kick(guildId, userId, reason, options = {}) {
|
|
1021
|
+
return this.rest.delete(`/guilds/${guildId}/members/${userId}`, {
|
|
1022
|
+
body: reason ? { reason } : {},
|
|
1023
|
+
signal: options.signal
|
|
1024
|
+
}).then(() => void 0);
|
|
1025
|
+
}
|
|
1026
|
+
/** Dá um cargo a um membro (`MANAGE_ROLES`, hierarquia abaixo do bot). */
|
|
1027
|
+
addRole(guildId, userId, roleId, options = {}) {
|
|
1028
|
+
return this.rest.put(`/guilds/${guildId}/members/${userId}/roles/${roleId}`, void 0, {
|
|
1029
|
+
signal: options.signal
|
|
1030
|
+
}).then(() => void 0);
|
|
1031
|
+
}
|
|
1032
|
+
/** Tira um cargo de um membro. */
|
|
1033
|
+
removeRole(guildId, userId, roleId, options = {}) {
|
|
1034
|
+
return this.rest.delete(`/guilds/${guildId}/members/${userId}/roles/${roleId}`, {
|
|
1035
|
+
signal: options.signal
|
|
1036
|
+
}).then(() => void 0);
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
var RolesManager = class {
|
|
1040
|
+
constructor(rest) {
|
|
1041
|
+
this.rest = rest;
|
|
1042
|
+
}
|
|
1043
|
+
rest;
|
|
1044
|
+
/** Lista os cargos de um Space. */
|
|
1045
|
+
list(guildId, signal) {
|
|
1046
|
+
return this.rest.get(`/guilds/${guildId}/roles`, { signal });
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
var ModerationManager = class {
|
|
1050
|
+
constructor(rest) {
|
|
1051
|
+
this.rest = rest;
|
|
1052
|
+
}
|
|
1053
|
+
rest;
|
|
1054
|
+
/**
|
|
1055
|
+
* Ban um membro (`BAN_MEMBERS`). Pode apagar até 7 dias de histórico
|
|
1056
|
+
* recente com `deleteMessageSeconds`. Não é retentável por padrão.
|
|
1057
|
+
*/
|
|
1058
|
+
ban(guildId, userId, body = {}, options = {}) {
|
|
1059
|
+
return this.rest.put(`/guilds/${guildId}/bans/${userId}`, body, {
|
|
1060
|
+
signal: options.signal,
|
|
1061
|
+
retryable: false
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
/** Desbane (`BAN_MEMBERS`). Banimentos da plataforma não podem ser desfeitos. */
|
|
1065
|
+
unban(guildId, userId, options = {}) {
|
|
1066
|
+
return this.rest.delete(`/guilds/${guildId}/bans/${userId}`, { signal: options.signal }).then(() => void 0);
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
// src/utils/misc.ts
|
|
1071
|
+
var import_node_crypto = require("crypto");
|
|
1072
|
+
function newRequestId() {
|
|
1073
|
+
return (0, import_node_crypto.randomUUID)();
|
|
1074
|
+
}
|
|
1075
|
+
function newNonce() {
|
|
1076
|
+
return (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 32);
|
|
1077
|
+
}
|
|
1078
|
+
function isBotToken(token) {
|
|
1079
|
+
return typeof token === "string" && /^sqb_.+\..+/.test(token);
|
|
1080
|
+
}
|
|
1081
|
+
function stripTokenPrefix(token) {
|
|
1082
|
+
return token.startsWith("Bot ") ? token.slice(4) : token;
|
|
1083
|
+
}
|
|
1084
|
+
function splitArguments(input) {
|
|
1085
|
+
const args = [];
|
|
1086
|
+
let current = "";
|
|
1087
|
+
let inQuotes = null;
|
|
1088
|
+
for (const char of input) {
|
|
1089
|
+
if (inQuotes) {
|
|
1090
|
+
if (char === inQuotes) {
|
|
1091
|
+
inQuotes = null;
|
|
1092
|
+
} else {
|
|
1093
|
+
current += char;
|
|
1094
|
+
}
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (char === '"' || char === "'") {
|
|
1098
|
+
inQuotes = char;
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (/\s/.test(char)) {
|
|
1102
|
+
if (current) {
|
|
1103
|
+
args.push(current);
|
|
1104
|
+
current = "";
|
|
1105
|
+
}
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
current += char;
|
|
1109
|
+
}
|
|
1110
|
+
if (current) args.push(current);
|
|
1111
|
+
return args;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/managers/messages.ts
|
|
1115
|
+
var MessagesManager = class {
|
|
1116
|
+
constructor(rest) {
|
|
1117
|
+
this.rest = rest;
|
|
1118
|
+
}
|
|
1119
|
+
rest;
|
|
1120
|
+
/** Busca uma mensagem pelo id. */
|
|
1121
|
+
fetch(channelId, messageId) {
|
|
1122
|
+
return this.rest.get(
|
|
1123
|
+
`/channels/${channelId}/messages/${messageId}`
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
/** Lista uma página do histórico do canal (padrão: 50, máximo 100). */
|
|
1127
|
+
list(channelId, params = {}, signal) {
|
|
1128
|
+
return this.rest.get(`/channels/${channelId}/messages`, {
|
|
1129
|
+
query: { limit: params.limit, before: params.before, after: params.after },
|
|
1130
|
+
signal
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Envia uma mensagem. O SDK anexa um `nonce` automático (idempotência),
|
|
1135
|
+
* então a operação pode ser repetida com segurança pelo cliente REST em
|
|
1136
|
+
* falhas transitórias.
|
|
1137
|
+
*/
|
|
1138
|
+
send(channelId, payload, options = {}) {
|
|
1139
|
+
const body = resolveMessagePayload(payload);
|
|
1140
|
+
const nonce = body.nonce ?? options.nonce ?? newNonce();
|
|
1141
|
+
const createBody = {
|
|
1142
|
+
...body,
|
|
1143
|
+
nonce,
|
|
1144
|
+
allowedMentions: safeAllowedMentions(body.allowedMentions)
|
|
1145
|
+
};
|
|
1146
|
+
return this.rest.post(`/channels/${channelId}/messages`, createBody, {
|
|
1147
|
+
signal: options.signal,
|
|
1148
|
+
// Protegido por nonce: seguro repetir.
|
|
1149
|
+
retryable: true
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
/** Edita uma mensagem (do próprio bot). `embeds`/`components` substituem a lista inteira. */
|
|
1153
|
+
edit(channelId, messageId, payload, options = {}) {
|
|
1154
|
+
const body = resolveMessageEditPayload(payload);
|
|
1155
|
+
return this.rest.patch(
|
|
1156
|
+
`/channels/${channelId}/messages/${messageId}`,
|
|
1157
|
+
body,
|
|
1158
|
+
{ signal: options.signal }
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
/** Apaga uma mensagem (própria ou de outros, com `MANAGE_MESSAGES`). Responde 204. */
|
|
1162
|
+
delete(channelId, messageId, options = {}) {
|
|
1163
|
+
return this.rest.delete(`/channels/${channelId}/messages/${messageId}`, { signal: options.signal }).then(() => void 0);
|
|
1164
|
+
}
|
|
1165
|
+
/** Reage a uma mensagem (`ADD_REACTIONS`). `emoji` é o caractere ou `<:nome:id>`. */
|
|
1166
|
+
react(channelId, messageId, emoji, options = {}) {
|
|
1167
|
+
return this.rest.put(
|
|
1168
|
+
`/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`,
|
|
1169
|
+
void 0,
|
|
1170
|
+
{ signal: options.signal }
|
|
1171
|
+
).then(() => void 0);
|
|
1172
|
+
}
|
|
1173
|
+
/** Remove a reação do bot. */
|
|
1174
|
+
removeReaction(channelId, messageId, emoji, options = {}) {
|
|
1175
|
+
return this.rest.delete(
|
|
1176
|
+
`/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`,
|
|
1177
|
+
{ signal: options.signal }
|
|
1178
|
+
).then(() => void 0);
|
|
1179
|
+
}
|
|
1180
|
+
/** Lista as reações de uma mensagem. */
|
|
1181
|
+
listReactions(channelId, messageId, signal) {
|
|
1182
|
+
return this.rest.get(
|
|
1183
|
+
`/channels/${channelId}/messages/${messageId}/reactions`,
|
|
1184
|
+
{ signal }
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
/** Fixa uma mensagem no canal (`PIN_MESSAGES`). */
|
|
1188
|
+
pin(channelId, messageId, options = {}) {
|
|
1189
|
+
return this.rest.put(`/channels/${channelId}/pins/${messageId}`, void 0, { signal: options.signal }).then(() => void 0);
|
|
1190
|
+
}
|
|
1191
|
+
/** Desafixa uma mensagem. */
|
|
1192
|
+
unpin(channelId, messageId, options = {}) {
|
|
1193
|
+
return this.rest.delete(`/channels/${channelId}/pins/${messageId}`, { signal: options.signal }).then(() => void 0);
|
|
1194
|
+
}
|
|
1195
|
+
/** Lista as mensagens fixadas do canal. */
|
|
1196
|
+
listPins(channelId, signal) {
|
|
1197
|
+
return this.rest.get(`/channels/${channelId}/pins`, { signal });
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
// src/managers/uploads.ts
|
|
1202
|
+
var import_promises = require("fs/promises");
|
|
1203
|
+
|
|
1204
|
+
// src/logger.ts
|
|
1205
|
+
var LEVEL_ORDER = {
|
|
1206
|
+
debug: 10,
|
|
1207
|
+
info: 20,
|
|
1208
|
+
warn: 30,
|
|
1209
|
+
error: 40,
|
|
1210
|
+
silent: 100
|
|
1211
|
+
};
|
|
1212
|
+
function redact(input) {
|
|
1213
|
+
return input.replace(/sqb_[A-Za-z0-9._-]+/g, "sqb_[REDACTED]").replace(/Bot\s+sqb_[A-Za-z0-9._-]+/gi, "Bot [REDACTED]").replace(/"token"\s*:\s*"[^"]+"/g, '"token":"[REDACTED]"');
|
|
1214
|
+
}
|
|
1215
|
+
function createConsoleLogger(options = {}) {
|
|
1216
|
+
const level = options.level ?? "silent";
|
|
1217
|
+
const write = options.write ?? ((line) => console.log(line));
|
|
1218
|
+
const log = (lvl, prefix) => (message, meta) => {
|
|
1219
|
+
if (LEVEL_ORDER[lvl] < LEVEL_ORDER[level]) return;
|
|
1220
|
+
const entry = {
|
|
1221
|
+
t: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1222
|
+
level: lvl,
|
|
1223
|
+
scope: prefix || void 0,
|
|
1224
|
+
msg: redact(message),
|
|
1225
|
+
...meta
|
|
1226
|
+
};
|
|
1227
|
+
write(redact(JSON.stringify(entry)));
|
|
1228
|
+
};
|
|
1229
|
+
const make = (prefix) => ({
|
|
1230
|
+
debug: log("debug", prefix),
|
|
1231
|
+
info: log("info", prefix),
|
|
1232
|
+
warn: log("warn", prefix),
|
|
1233
|
+
error: log("error", prefix),
|
|
1234
|
+
child: (childPrefix) => make(prefix ? `${prefix}:${childPrefix}` : childPrefix)
|
|
1235
|
+
});
|
|
1236
|
+
return make("");
|
|
1237
|
+
}
|
|
1238
|
+
var noopLogger = {
|
|
1239
|
+
debug() {
|
|
1240
|
+
},
|
|
1241
|
+
info() {
|
|
1242
|
+
},
|
|
1243
|
+
warn() {
|
|
1244
|
+
},
|
|
1245
|
+
error() {
|
|
1246
|
+
},
|
|
1247
|
+
child: () => noopLogger
|
|
1248
|
+
};
|
|
1249
|
+
function loggerFromEnv() {
|
|
1250
|
+
const fromEnv = (process.env.SQUORD_LOG ?? "").toLowerCase();
|
|
1251
|
+
if (fromEnv in LEVEL_ORDER) {
|
|
1252
|
+
return createConsoleLogger({ level: fromEnv });
|
|
1253
|
+
}
|
|
1254
|
+
if (process.env.SQUORD_DEBUG === "1" || process.env.SQUORD_DEBUG === "true") {
|
|
1255
|
+
return createConsoleLogger({ level: "debug" });
|
|
1256
|
+
}
|
|
1257
|
+
return noopLogger;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/managers/uploads.ts
|
|
1261
|
+
var UploadsManager = class {
|
|
1262
|
+
constructor(rest, logger) {
|
|
1263
|
+
this.rest = rest;
|
|
1264
|
+
this.logger = (logger ?? noopLogger).child("uploads");
|
|
1265
|
+
}
|
|
1266
|
+
rest;
|
|
1267
|
+
logger;
|
|
1268
|
+
/** Reserva um upload (`POST /uploads`). Não é idempotente. */
|
|
1269
|
+
reserve(body, signal) {
|
|
1270
|
+
return this.rest.post("/uploads", body, {
|
|
1271
|
+
signal,
|
|
1272
|
+
retryable: false
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
/** Confirma um upload (`POST /uploads/{id}/confirm`). Idempotente. */
|
|
1276
|
+
confirm(uploadId, body = {}, signal) {
|
|
1277
|
+
return this.rest.post(`/uploads/${uploadId}/confirm`, body, {
|
|
1278
|
+
signal,
|
|
1279
|
+
retryable: true
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Fluxo completo: reserva, envia os bytes e confirma. Devolve o upload em
|
|
1284
|
+
* `status: "READY"`, pronto para `attachmentIds`.
|
|
1285
|
+
*
|
|
1286
|
+
* Aceita caminho local (`path`), `Buffer`/`Uint8Array` ou `Readable`
|
|
1287
|
+
* (informe `size`). Streams são sempre encerrados, mesmo em falha.
|
|
1288
|
+
*/
|
|
1289
|
+
async upload(options) {
|
|
1290
|
+
const { path, data, signal } = options;
|
|
1291
|
+
let bytes;
|
|
1292
|
+
let filename = options.filename;
|
|
1293
|
+
let size = options.size;
|
|
1294
|
+
if (path !== void 0) {
|
|
1295
|
+
bytes = await (0, import_promises.readFile)(path).catch(() => {
|
|
1296
|
+
throw new SquordTypeError(`N\xE3o foi poss\xEDvel ler o arquivo: ${path}`);
|
|
1297
|
+
});
|
|
1298
|
+
filename ??= basename(path);
|
|
1299
|
+
size ??= bytes.length;
|
|
1300
|
+
} else if (data !== void 0) {
|
|
1301
|
+
if (typeof data === "string") {
|
|
1302
|
+
bytes = Buffer.from(data, "utf8");
|
|
1303
|
+
} else if (Buffer.isBuffer(data) || data instanceof Uint8Array) {
|
|
1304
|
+
bytes = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
1305
|
+
} else if (typeof data.read === "function") {
|
|
1306
|
+
bytes = await this.collectStream(data, signal);
|
|
1307
|
+
} else {
|
|
1308
|
+
throw new SquordTypeError("data precisa ser Buffer, Uint8Array, string ou Readable");
|
|
1309
|
+
}
|
|
1310
|
+
filename ??= "arquivo";
|
|
1311
|
+
size ??= bytes.length;
|
|
1312
|
+
} else {
|
|
1313
|
+
throw new SquordTypeError("Informe path ou data para o upload");
|
|
1314
|
+
}
|
|
1315
|
+
if (size === void 0 || Number.isNaN(size)) {
|
|
1316
|
+
throw new SquordTypeError("Informe o size ao enviar um stream");
|
|
1317
|
+
}
|
|
1318
|
+
if (size <= 0) {
|
|
1319
|
+
throw new SquordTypeError("O arquivo est\xE1 vazio (size 0)");
|
|
1320
|
+
}
|
|
1321
|
+
if (size > 524288e3) {
|
|
1322
|
+
throw new SquordTypeError(
|
|
1323
|
+
`O arquivo passa de 500 MB (${size} bytes); o limite real vem do plano do Space`
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
const contentType = options.contentType ?? guessContentType(filename ?? "arquivo");
|
|
1327
|
+
const reservation = await this.reserve(
|
|
1328
|
+
{ scope: "attachments", filename: filename ?? "arquivo", contentType, size, guildId: options.guildId },
|
|
1329
|
+
signal
|
|
1330
|
+
);
|
|
1331
|
+
const target = reservation.target;
|
|
1332
|
+
const response = await this.rest.fetch(target.uploadUrl, {
|
|
1333
|
+
method: "PUT",
|
|
1334
|
+
headers: { ...target.headers },
|
|
1335
|
+
body: new Uint8Array(bytes),
|
|
1336
|
+
signal
|
|
1337
|
+
});
|
|
1338
|
+
if (!response.ok) {
|
|
1339
|
+
const text = await response.text().catch(() => "");
|
|
1340
|
+
throw new NetworkError(
|
|
1341
|
+
`O envio dos bytes falhou (HTTP ${response.status})${text ? `: ${text.slice(0, 200)}` : ""}`
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
const upload = await this.confirm(reservation.upload.id, {}, signal);
|
|
1345
|
+
if (upload.status !== "READY") {
|
|
1346
|
+
this.logger.warn("upload confirmado fora do esperado", {
|
|
1347
|
+
id: upload.id,
|
|
1348
|
+
status: upload.status,
|
|
1349
|
+
scanStatus: upload.scanStatus
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
return upload;
|
|
1353
|
+
}
|
|
1354
|
+
/** Atalho: envia um arquivo e devolve o `id` pronto para `attachmentIds`. */
|
|
1355
|
+
async uploadForAttachment(options) {
|
|
1356
|
+
const upload = await this.upload(options);
|
|
1357
|
+
return upload.id;
|
|
1358
|
+
}
|
|
1359
|
+
async collectStream(stream, signal) {
|
|
1360
|
+
const chunks = [];
|
|
1361
|
+
try {
|
|
1362
|
+
for await (const chunk of stream) {
|
|
1363
|
+
if (signal?.aborted) throw new DOMException("Upload cancelado", "AbortError");
|
|
1364
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1365
|
+
}
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
stream.destroy();
|
|
1368
|
+
throw err;
|
|
1369
|
+
}
|
|
1370
|
+
return Buffer.concat(chunks);
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
function basename(path) {
|
|
1374
|
+
const parts = path.split(/[\\/]/);
|
|
1375
|
+
return parts[parts.length - 1] || "arquivo";
|
|
1376
|
+
}
|
|
1377
|
+
function guessContentType(filename) {
|
|
1378
|
+
const ext = filename.toLowerCase().split(".").pop() ?? "";
|
|
1379
|
+
const table = {
|
|
1380
|
+
png: "image/png",
|
|
1381
|
+
jpg: "image/jpeg",
|
|
1382
|
+
jpeg: "image/jpeg",
|
|
1383
|
+
gif: "image/gif",
|
|
1384
|
+
webp: "image/webp",
|
|
1385
|
+
mp4: "video/mp4",
|
|
1386
|
+
webm: "video/webm",
|
|
1387
|
+
mp3: "audio/mpeg",
|
|
1388
|
+
ogg: "audio/ogg",
|
|
1389
|
+
wav: "audio/wav",
|
|
1390
|
+
pdf: "application/pdf",
|
|
1391
|
+
txt: "text/plain",
|
|
1392
|
+
json: "application/json",
|
|
1393
|
+
csv: "text/csv",
|
|
1394
|
+
md: "text/markdown",
|
|
1395
|
+
zip: "application/zip"
|
|
1396
|
+
};
|
|
1397
|
+
return table[ext] ?? "application/octet-stream";
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/utils/backoff.ts
|
|
1401
|
+
function backoffDelay(attempt, options = {}) {
|
|
1402
|
+
const { baseMs = 1e3, factor = 2, maxMs = 6e4, jitter = 0.25 } = options;
|
|
1403
|
+
const bounded = Math.min(attempt, 16);
|
|
1404
|
+
const exp = Math.min(baseMs * factor ** bounded, maxMs);
|
|
1405
|
+
const jitterAmount = exp * jitter * (Math.random() * 2 - 1);
|
|
1406
|
+
return Math.max(250, Math.round(exp + jitterAmount));
|
|
1407
|
+
}
|
|
1408
|
+
function sleep(ms, signal) {
|
|
1409
|
+
if (ms <= 0) return Promise.resolve();
|
|
1410
|
+
return new Promise((resolve, reject) => {
|
|
1411
|
+
const timer = setTimeout(() => {
|
|
1412
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1413
|
+
resolve();
|
|
1414
|
+
}, ms);
|
|
1415
|
+
const onAbort = () => {
|
|
1416
|
+
clearTimeout(timer);
|
|
1417
|
+
reject(new DOMException("Sleep aborted", "AbortError"));
|
|
1418
|
+
};
|
|
1419
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// src/rest/RestClient.ts
|
|
1424
|
+
var DEFAULT_BASE_URL = "https://api.squord.com.br/api/v1";
|
|
1425
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
1426
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
1427
|
+
var RestClient = class {
|
|
1428
|
+
baseURL;
|
|
1429
|
+
token;
|
|
1430
|
+
timeoutMs;
|
|
1431
|
+
maxRetries;
|
|
1432
|
+
fetchImpl;
|
|
1433
|
+
logger;
|
|
1434
|
+
bucketStates = /* @__PURE__ */ new Map();
|
|
1435
|
+
bucketBlockedUntil = /* @__PURE__ */ new Map();
|
|
1436
|
+
globalWindow = [];
|
|
1437
|
+
globalLimit;
|
|
1438
|
+
destroyed = false;
|
|
1439
|
+
constructor(options) {
|
|
1440
|
+
this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1441
|
+
this.token = options.token;
|
|
1442
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1443
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1444
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
1445
|
+
this.logger = (options.logger ?? noopLogger).child("rest");
|
|
1446
|
+
const rps = options.globalRequestsPerSecond ?? 45;
|
|
1447
|
+
this.globalLimit = rps > 0 ? rps : Number.POSITIVE_INFINITY;
|
|
1448
|
+
}
|
|
1449
|
+
/** GET tipado. */
|
|
1450
|
+
get(path, options = {}) {
|
|
1451
|
+
return this.request("GET", path, options);
|
|
1452
|
+
}
|
|
1453
|
+
/** Implementação de fetch em uso (exposta para uploads e testes). */
|
|
1454
|
+
get fetch() {
|
|
1455
|
+
return this.fetchImpl;
|
|
1456
|
+
}
|
|
1457
|
+
/** POST tipado. Não é retentável por padrão. */
|
|
1458
|
+
post(path, body, options = {}) {
|
|
1459
|
+
return this.request("POST", path, { ...options, body });
|
|
1460
|
+
}
|
|
1461
|
+
/** PUT tipado (idempotente). */
|
|
1462
|
+
put(path, body, options = {}) {
|
|
1463
|
+
return this.request("PUT", path, { ...options, body });
|
|
1464
|
+
}
|
|
1465
|
+
/** PATCH tipado (retentável por padrão quando idempotente pelo caller). */
|
|
1466
|
+
patch(path, body, options = {}) {
|
|
1467
|
+
return this.request("PATCH", path, { ...options, body });
|
|
1468
|
+
}
|
|
1469
|
+
/** DELETE tipado. Resposta 204 devolve `null`. */
|
|
1470
|
+
delete(path, options = {}) {
|
|
1471
|
+
return this.request("DELETE", path, options);
|
|
1472
|
+
}
|
|
1473
|
+
/** Executa uma requisição com política completa de erros, retries e rate limits. */
|
|
1474
|
+
async request(method, path, options = {}) {
|
|
1475
|
+
const response = await this.requestRaw(method, path, options);
|
|
1476
|
+
return response.data;
|
|
1477
|
+
}
|
|
1478
|
+
/** Como `request`, mas devolve também status, headers e `x-request-id`. */
|
|
1479
|
+
async requestRaw(method, path, options = {}) {
|
|
1480
|
+
if (this.destroyed) {
|
|
1481
|
+
throw new SquordAPIError(
|
|
1482
|
+
{ statusCode: 0, code: "SDK_DESTROYED", message: "O cliente foi destru\xEDdo" },
|
|
1483
|
+
0
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
const retryable = options.retryable ?? method !== "POST";
|
|
1487
|
+
const url = this.buildUrl(path, options.query);
|
|
1488
|
+
const requestId = options.requestId ?? newRequestId();
|
|
1489
|
+
const routeKey = routeKeyOf(method, path);
|
|
1490
|
+
let lastError;
|
|
1491
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
1492
|
+
if (options.signal?.aborted) throw new DOMException("Requisi\xE7\xE3o cancelada", "AbortError");
|
|
1493
|
+
await this.waitForRouteBucket(routeKey, options.signal);
|
|
1494
|
+
await this.acquireGlobalSlot(options.signal);
|
|
1495
|
+
const headers = {
|
|
1496
|
+
"Content-Type": "application/json",
|
|
1497
|
+
"x-request-id": requestId
|
|
1498
|
+
};
|
|
1499
|
+
if (options.auth !== false) {
|
|
1500
|
+
headers.Authorization = `Bot ${this.token}`;
|
|
1501
|
+
}
|
|
1502
|
+
const request = new Request(url, {
|
|
1503
|
+
method,
|
|
1504
|
+
headers,
|
|
1505
|
+
body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
|
|
1506
|
+
});
|
|
1507
|
+
const signals = [];
|
|
1508
|
+
const timeoutController = new AbortController();
|
|
1509
|
+
const timer = setTimeout(() => {
|
|
1510
|
+
timeoutController.abort(new DOMException("Request timed out", "TimeoutError"));
|
|
1511
|
+
}, this.timeoutMs);
|
|
1512
|
+
signals.push(timeoutController.signal);
|
|
1513
|
+
if (options.signal) signals.push(options.signal);
|
|
1514
|
+
const compositeSignal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
1515
|
+
let response;
|
|
1516
|
+
try {
|
|
1517
|
+
response = await this.fetchImpl(request, { signal: compositeSignal });
|
|
1518
|
+
} catch (err) {
|
|
1519
|
+
lastError = this.toNetworkError(err, method, path);
|
|
1520
|
+
const canRetry = retryable && attempt < this.maxRetries;
|
|
1521
|
+
this.logger.debug("network failure", { method, path, attempt, retryable, canRetry });
|
|
1522
|
+
if (!canRetry) throw lastError;
|
|
1523
|
+
await sleep(backoffDelay(attempt, { baseMs: 500, maxMs: 8e3 }), options.signal);
|
|
1524
|
+
continue;
|
|
1525
|
+
} finally {
|
|
1526
|
+
clearTimeout(timer);
|
|
1527
|
+
}
|
|
1528
|
+
this.recordBucketState(routeKey, response.headers);
|
|
1529
|
+
if (response.ok) {
|
|
1530
|
+
const data = await this.parseBody(response);
|
|
1531
|
+
return {
|
|
1532
|
+
data,
|
|
1533
|
+
status: response.status,
|
|
1534
|
+
headers: response.headers,
|
|
1535
|
+
requestId: response.headers.get("x-request-id") ?? requestId,
|
|
1536
|
+
request
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
const apiError = await this.toAPIError(response);
|
|
1540
|
+
lastError = apiError;
|
|
1541
|
+
if (apiError.status === 429 && apiError instanceof RateLimitError) {
|
|
1542
|
+
if (attempt < this.maxRetries) {
|
|
1543
|
+
const waitMs = apiError.retryAfter * 1e3;
|
|
1544
|
+
this.logger.warn("rate limit, aguardando", {
|
|
1545
|
+
bucket: apiError.bucket,
|
|
1546
|
+
retryAfter: apiError.retryAfter
|
|
1547
|
+
});
|
|
1548
|
+
await sleep(waitMs, options.signal);
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
throw new RateLimitError(apiError.responseBody, 429, {
|
|
1552
|
+
requestId: apiError.requestId,
|
|
1553
|
+
exhaustedRetries: true
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
if (apiError.status >= 500 && retryable && attempt < this.maxRetries) {
|
|
1557
|
+
const waitMs = backoffDelay(attempt, { baseMs: 500, maxMs: 8e3 });
|
|
1558
|
+
this.logger.warn("erro 5xx, repetindo", { status: apiError.status, attempt, waitMs });
|
|
1559
|
+
await sleep(waitMs, options.signal);
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (apiError.status === 409 && (apiError.code === "COMMANDS_CONFLICT" || apiError.code === "THREAD_CHANGED") && attempt < this.maxRetries) {
|
|
1563
|
+
await sleep(250 + backoffDelay(attempt, { baseMs: 200, maxMs: 2e3 }), options.signal);
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
throw apiError;
|
|
1567
|
+
}
|
|
1568
|
+
throw lastError;
|
|
1569
|
+
}
|
|
1570
|
+
/** Encerra o cliente: novas requisições falham imediatamente. */
|
|
1571
|
+
destroy() {
|
|
1572
|
+
this.destroyed = true;
|
|
1573
|
+
this.bucketStates.clear();
|
|
1574
|
+
this.bucketBlockedUntil.clear();
|
|
1575
|
+
}
|
|
1576
|
+
/** Estado de rate limit conhecido para uma rota (diagnóstico). */
|
|
1577
|
+
bucketState(method, path) {
|
|
1578
|
+
return this.bucketStates.get(routeKeyOf(method, path));
|
|
1579
|
+
}
|
|
1580
|
+
// -----------------------------------------------------------------
|
|
1581
|
+
buildUrl(path, query) {
|
|
1582
|
+
let url = `${this.baseURL}${path.startsWith("/") ? path : `/${path}`}`;
|
|
1583
|
+
const params = new URLSearchParams();
|
|
1584
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
1585
|
+
if (value === void 0 || value === null) continue;
|
|
1586
|
+
params.set(key, String(value));
|
|
1587
|
+
}
|
|
1588
|
+
const qs = params.toString();
|
|
1589
|
+
if (qs) url += `?${qs}`;
|
|
1590
|
+
return url;
|
|
1591
|
+
}
|
|
1592
|
+
async parseBody(response) {
|
|
1593
|
+
if (response.status === 204) return null;
|
|
1594
|
+
const text = await response.text();
|
|
1595
|
+
if (!text) return null;
|
|
1596
|
+
try {
|
|
1597
|
+
return JSON.parse(text);
|
|
1598
|
+
} catch {
|
|
1599
|
+
return text;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
async toAPIError(response) {
|
|
1603
|
+
let body = {};
|
|
1604
|
+
try {
|
|
1605
|
+
body = await response.json();
|
|
1606
|
+
} catch {
|
|
1607
|
+
}
|
|
1608
|
+
const enriched = {
|
|
1609
|
+
statusCode: response.status,
|
|
1610
|
+
code: body.code ?? `HTTP_${response.status}`,
|
|
1611
|
+
message: body.message ?? response.statusText,
|
|
1612
|
+
details: body.details,
|
|
1613
|
+
requestId: body.requestId ?? response.headers.get("x-request-id") ?? void 0
|
|
1614
|
+
};
|
|
1615
|
+
return makeAPIError(enriched, response.status, enriched.requestId);
|
|
1616
|
+
}
|
|
1617
|
+
toNetworkError(err, method, path) {
|
|
1618
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
1619
|
+
return new RequestTimeoutError(method, path, this.timeoutMs, { cause: err });
|
|
1620
|
+
}
|
|
1621
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
1622
|
+
return err;
|
|
1623
|
+
}
|
|
1624
|
+
const reason = err?.reason;
|
|
1625
|
+
if (reason instanceof DOMException && reason.name === "TimeoutError") {
|
|
1626
|
+
return new RequestTimeoutError(method, path, this.timeoutMs, { cause: err });
|
|
1627
|
+
}
|
|
1628
|
+
return new NetworkError(`Falha de rede em ${method} ${path}: ${String(err?.message ?? err)}`, {
|
|
1629
|
+
cause: err
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
recordBucketState(routeKey, headers) {
|
|
1633
|
+
const limit = headers.get("x-ratelimit-limit");
|
|
1634
|
+
const remaining = headers.get("x-ratelimit-remaining");
|
|
1635
|
+
const reset = headers.get("x-ratelimit-reset");
|
|
1636
|
+
if (limit === null && remaining === null && reset === null) return;
|
|
1637
|
+
const state = {
|
|
1638
|
+
limit: limit !== null ? Number(limit) : null,
|
|
1639
|
+
remaining: remaining !== null ? Number(remaining) : null,
|
|
1640
|
+
reset: reset !== null ? Number(reset) : null
|
|
1641
|
+
};
|
|
1642
|
+
this.bucketStates.set(routeKey, state);
|
|
1643
|
+
if (state.remaining === 0 && state.reset !== null) {
|
|
1644
|
+
const blockedUntil = state.reset * 1e3;
|
|
1645
|
+
this.bucketBlockedUntil.set(routeKey, blockedUntil);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
async waitForRouteBucket(routeKey, signal) {
|
|
1649
|
+
for (; ; ) {
|
|
1650
|
+
const blockedUntil = this.bucketBlockedUntil.get(routeKey);
|
|
1651
|
+
if (blockedUntil === void 0) return;
|
|
1652
|
+
const waitMs = blockedUntil - Date.now();
|
|
1653
|
+
if (waitMs <= 0) {
|
|
1654
|
+
this.bucketBlockedUntil.delete(routeKey);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
await sleep(waitMs, signal).catch(() => {
|
|
1658
|
+
});
|
|
1659
|
+
if (signal?.aborted) return;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
async acquireGlobalSlot(signal) {
|
|
1663
|
+
if (this.globalLimit === Number.POSITIVE_INFINITY) return;
|
|
1664
|
+
for (; ; ) {
|
|
1665
|
+
const now = Date.now();
|
|
1666
|
+
while (this.globalWindow.length > 0 && now - this.globalWindow[0] >= 1e3) {
|
|
1667
|
+
this.globalWindow.shift();
|
|
1668
|
+
}
|
|
1669
|
+
if (this.globalWindow.length < this.globalLimit) {
|
|
1670
|
+
this.globalWindow.push(now);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const waitMs = 1e3 - (now - this.globalWindow[0]) + 10;
|
|
1674
|
+
await sleep(waitMs, signal);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
function routeKeyOf(method, path) {
|
|
1679
|
+
const normalized = path.replace(/\/\d{5,20}\b/g, "/{id}").replace(/\/[0-9a-f]{24}\b/gi, "/{id}");
|
|
1680
|
+
return `${method} ${normalized}`;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// src/gateway/WebSocketLike.ts
|
|
1684
|
+
function attachWebSocket(ws, handlers) {
|
|
1685
|
+
const target = ws;
|
|
1686
|
+
if (typeof target.addEventListener === "function") {
|
|
1687
|
+
const onOpen = () => handlers.open();
|
|
1688
|
+
const onMessage2 = (event) => {
|
|
1689
|
+
const data = event.data;
|
|
1690
|
+
if (typeof data === "string") handlers.message(data);
|
|
1691
|
+
else if (data instanceof ArrayBuffer) handlers.message(new TextDecoder().decode(data));
|
|
1692
|
+
else if (data && typeof data === "object" && "toString" in data) {
|
|
1693
|
+
handlers.message(String(data));
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
const onClose2 = (event) => {
|
|
1697
|
+
handlers.close({
|
|
1698
|
+
code: event.code ?? 1005,
|
|
1699
|
+
reason: event.reason ?? "",
|
|
1700
|
+
wasClean: event.wasClean ?? false
|
|
1701
|
+
});
|
|
1702
|
+
};
|
|
1703
|
+
const onError2 = (event) => handlers.error(event);
|
|
1704
|
+
target.addEventListener("open", onOpen);
|
|
1705
|
+
target.addEventListener("message", onMessage2);
|
|
1706
|
+
target.addEventListener("close", onClose2);
|
|
1707
|
+
target.addEventListener("error", onError2);
|
|
1708
|
+
return {
|
|
1709
|
+
send: (data) => target.send?.(data),
|
|
1710
|
+
close: (code, reason) => target.close?.(code, reason),
|
|
1711
|
+
get readyState() {
|
|
1712
|
+
return target.readyState ?? 0;
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
const onMessage = (data) => {
|
|
1717
|
+
if (typeof data === "string") handlers.message(data);
|
|
1718
|
+
else if (Buffer.isBuffer(data)) handlers.message(data.toString("utf8"));
|
|
1719
|
+
else handlers.message(String(data));
|
|
1720
|
+
};
|
|
1721
|
+
const onClose = (code, reason) => handlers.close({ code, reason, wasClean: true });
|
|
1722
|
+
const onError = (error) => handlers.error(error);
|
|
1723
|
+
target.on?.("open", handlers.open);
|
|
1724
|
+
target.on?.("message", onMessage);
|
|
1725
|
+
target.on?.("close", onClose);
|
|
1726
|
+
target.on?.("error", onError);
|
|
1727
|
+
return {
|
|
1728
|
+
send: (data) => target.send?.(data),
|
|
1729
|
+
close: (code, reason) => target.close?.(code, reason),
|
|
1730
|
+
get readyState() {
|
|
1731
|
+
return target.readyState ?? 0;
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
var globalWebSocketFactory = (url) => new WebSocket(url);
|
|
1736
|
+
|
|
1737
|
+
// src/types/gateway.ts
|
|
1738
|
+
var GatewayCloseCodes = {
|
|
1739
|
+
UnknownError: 4e3,
|
|
1740
|
+
UnknownOpcode: 4001,
|
|
1741
|
+
DecodeError: 4002,
|
|
1742
|
+
NotAuthenticated: 4003,
|
|
1743
|
+
AuthenticationFailed: 4004,
|
|
1744
|
+
AlreadyAuthenticated: 4005,
|
|
1745
|
+
InvalidSequence: 4007,
|
|
1746
|
+
RateLimited: 4008,
|
|
1747
|
+
SessionTimeout: 4009,
|
|
1748
|
+
InvalidProtocolVersion: 4012,
|
|
1749
|
+
AccountSuspended: 4013,
|
|
1750
|
+
BotSessionLimit: 4014
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
// src/gateway/Gateway.ts
|
|
1754
|
+
var DEFAULT_URL = "wss://gateway.squord.com.br/gateway";
|
|
1755
|
+
var PROTOCOL_VERSION = 1;
|
|
1756
|
+
var FATAL_CLOSE_CODES = /* @__PURE__ */ new Set([
|
|
1757
|
+
GatewayCloseCodes.AuthenticationFailed,
|
|
1758
|
+
// 4004
|
|
1759
|
+
GatewayCloseCodes.AccountSuspended,
|
|
1760
|
+
// 4013
|
|
1761
|
+
GatewayCloseCodes.BotSessionLimit
|
|
1762
|
+
// 4014
|
|
1763
|
+
]);
|
|
1764
|
+
var BOT_BUG_CLOSE_CODES = /* @__PURE__ */ new Set([
|
|
1765
|
+
GatewayCloseCodes.UnknownOpcode,
|
|
1766
|
+
// 4001
|
|
1767
|
+
GatewayCloseCodes.DecodeError,
|
|
1768
|
+
// 4002
|
|
1769
|
+
GatewayCloseCodes.AlreadyAuthenticated,
|
|
1770
|
+
// 4005
|
|
1771
|
+
GatewayCloseCodes.InvalidProtocolVersion
|
|
1772
|
+
// 4012
|
|
1773
|
+
]);
|
|
1774
|
+
var Gateway = class extends TypedEmitter {
|
|
1775
|
+
url;
|
|
1776
|
+
token;
|
|
1777
|
+
wsFactory;
|
|
1778
|
+
handshakeTimeoutMs;
|
|
1779
|
+
autoReconnect;
|
|
1780
|
+
reconnectBaseMs;
|
|
1781
|
+
logger;
|
|
1782
|
+
ws = null;
|
|
1783
|
+
statusValue = "idle";
|
|
1784
|
+
reconnectAttempts = 0;
|
|
1785
|
+
connectLoop = null;
|
|
1786
|
+
// Estado de sessão (para resume)
|
|
1787
|
+
sessionId = null;
|
|
1788
|
+
resumeUrl = null;
|
|
1789
|
+
lastSequence = null;
|
|
1790
|
+
established = false;
|
|
1791
|
+
// Handshake em curso
|
|
1792
|
+
handshake = null;
|
|
1793
|
+
handshakeTimer = null;
|
|
1794
|
+
// Heartbeat
|
|
1795
|
+
heartbeatIntervalMs = null;
|
|
1796
|
+
heartbeatTimer = null;
|
|
1797
|
+
awaitingAck = false;
|
|
1798
|
+
lastHeartbeatSentAt = null;
|
|
1799
|
+
destroyed = false;
|
|
1800
|
+
closingByClient = false;
|
|
1801
|
+
reconnectRequested = false;
|
|
1802
|
+
/** Latência do gateway: último heartbeat até o ack, em ms. */
|
|
1803
|
+
heartbeatLatencyMs = null;
|
|
1804
|
+
/** Quantas reconexões ocorreram desde a criação. */
|
|
1805
|
+
reconnectCount = 0;
|
|
1806
|
+
/** Quantos frames de evento foram recebidos (incluindo duplicados). */
|
|
1807
|
+
eventsReceived = 0;
|
|
1808
|
+
/** Quantos eventos foram descartados como duplicados. */
|
|
1809
|
+
eventsDropped = 0;
|
|
1810
|
+
get sequence() {
|
|
1811
|
+
return this.lastSequence;
|
|
1812
|
+
}
|
|
1813
|
+
get session() {
|
|
1814
|
+
return this.sessionId;
|
|
1815
|
+
}
|
|
1816
|
+
get status() {
|
|
1817
|
+
return this.statusValue;
|
|
1818
|
+
}
|
|
1819
|
+
get isReady() {
|
|
1820
|
+
return this.statusValue === "ready";
|
|
1821
|
+
}
|
|
1822
|
+
constructor(options) {
|
|
1823
|
+
super();
|
|
1824
|
+
this.url = options.url ?? DEFAULT_URL;
|
|
1825
|
+
this.token = options.token;
|
|
1826
|
+
this.wsFactory = options.ws ?? globalWebSocketFactory;
|
|
1827
|
+
this.handshakeTimeoutMs = options.handshakeTimeoutMs ?? 2e4;
|
|
1828
|
+
this.autoReconnect = options.autoReconnect ?? true;
|
|
1829
|
+
this.reconnectBaseMs = options.reconnectBaseMs ?? 1e3;
|
|
1830
|
+
this.logger = (options.logger ?? noopLogger).child("gateway");
|
|
1831
|
+
}
|
|
1832
|
+
// ------------------------------------------------------------------
|
|
1833
|
+
// API pública
|
|
1834
|
+
// ------------------------------------------------------------------
|
|
1835
|
+
/**
|
|
1836
|
+
* Conecta ao gateway e resolve quando a sessão está pronta
|
|
1837
|
+
* (`ready` ou `resumed`). Em quedas transitórias, segue tentando com
|
|
1838
|
+
* backoff — a promise só é rejeitada em erros não retomáveis
|
|
1839
|
+
* (token inválido/revogado, conta suspensa, sessões demais, erro de
|
|
1840
|
+
* protocolo) ou se o gateway for destruído.
|
|
1841
|
+
*/
|
|
1842
|
+
async connect() {
|
|
1843
|
+
if (this.destroyed) throw new GatewayError(null, null, "O gateway foi destru\xEDdo");
|
|
1844
|
+
if (this.isReady) {
|
|
1845
|
+
throw new GatewayError(null, null, "O gateway j\xE1 est\xE1 conectado");
|
|
1846
|
+
}
|
|
1847
|
+
if (this.connectLoop) return this.connectLoop;
|
|
1848
|
+
this.closingByClient = false;
|
|
1849
|
+
this.connectLoop = this.runConnectLoop().finally(() => {
|
|
1850
|
+
this.connectLoop = null;
|
|
1851
|
+
});
|
|
1852
|
+
return this.connectLoop;
|
|
1853
|
+
}
|
|
1854
|
+
/** Envia `presence.update`. O status escolhido vale também nas próximas conexões. */
|
|
1855
|
+
setPresence(presence) {
|
|
1856
|
+
if (!this.ws) {
|
|
1857
|
+
this.emit("error", new GatewayError(null, null, "Sem conex\xE3o para atualizar a presen\xE7a"));
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
this.sendFrame("presence.update", presence);
|
|
1861
|
+
}
|
|
1862
|
+
/** Fecha a conexão, encerra as reconexões e descarta o estado de sessão. */
|
|
1863
|
+
async destroy() {
|
|
1864
|
+
if (this.destroyed) return;
|
|
1865
|
+
this.destroyed = true;
|
|
1866
|
+
this.closingByClient = true;
|
|
1867
|
+
this.stopHeartbeat();
|
|
1868
|
+
this.failHandshake(new GatewayError(null, null, "Gateway destru\xEDdo"));
|
|
1869
|
+
this.setStatus("destroyed");
|
|
1870
|
+
const ws = this.ws;
|
|
1871
|
+
this.ws = null;
|
|
1872
|
+
if (ws && ws.readyState <= 1) {
|
|
1873
|
+
try {
|
|
1874
|
+
ws.close(1e3, "client shutdown");
|
|
1875
|
+
} catch {
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
/** Descarta o estado de sessão: a próxima conexão fará `identify` limpo. */
|
|
1880
|
+
invalidateSession() {
|
|
1881
|
+
this.sessionId = null;
|
|
1882
|
+
this.resumeUrl = null;
|
|
1883
|
+
this.lastSequence = null;
|
|
1884
|
+
}
|
|
1885
|
+
/** Força uma reconexão imediata (que tentará `resume`). */
|
|
1886
|
+
reconnectNow() {
|
|
1887
|
+
if (!this.ws) return;
|
|
1888
|
+
this.reconnectRequested = true;
|
|
1889
|
+
try {
|
|
1890
|
+
this.ws.close(4e3, "manual reconnect");
|
|
1891
|
+
} catch {
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
// ------------------------------------------------------------------
|
|
1895
|
+
// Loop de conexão
|
|
1896
|
+
// ------------------------------------------------------------------
|
|
1897
|
+
async runConnectLoop() {
|
|
1898
|
+
this.setStatus(this.sessionId ? "reconnecting" : "connecting");
|
|
1899
|
+
for (; ; ) {
|
|
1900
|
+
try {
|
|
1901
|
+
const ready = await this.openSocketOnce(this.resumeUrl ?? this.url);
|
|
1902
|
+
this.reconnectAttempts = 0;
|
|
1903
|
+
return ready;
|
|
1904
|
+
} catch (err) {
|
|
1905
|
+
if (this.destroyed) throw err;
|
|
1906
|
+
const gatewayErr = err;
|
|
1907
|
+
const code = gatewayErr?.closeCode ?? null;
|
|
1908
|
+
if (code !== null && (FATAL_CLOSE_CODES.has(code) || BOT_BUG_CLOSE_CODES.has(code))) {
|
|
1909
|
+
this.setStatus("disconnected");
|
|
1910
|
+
this.emit("error", gatewayErr);
|
|
1911
|
+
throw gatewayErr;
|
|
1912
|
+
}
|
|
1913
|
+
if (!this.autoReconnect) {
|
|
1914
|
+
this.setStatus("disconnected");
|
|
1915
|
+
throw err;
|
|
1916
|
+
}
|
|
1917
|
+
const attempt = this.reconnectAttempts++;
|
|
1918
|
+
const delayMs = backoffDelay(attempt, { baseMs: this.reconnectBaseMs });
|
|
1919
|
+
this.reconnectCount++;
|
|
1920
|
+
this.setStatus("reconnecting");
|
|
1921
|
+
this.emit("reconnecting", {
|
|
1922
|
+
attempt,
|
|
1923
|
+
delayMs,
|
|
1924
|
+
closeCode: code,
|
|
1925
|
+
reason: gatewayErr?.reason ?? String(err?.message ?? err)
|
|
1926
|
+
});
|
|
1927
|
+
this.logger.info("reconectando", { attempt, delayMs });
|
|
1928
|
+
await sleep(delayMs);
|
|
1929
|
+
this.setStatus(this.sessionId ? "reconnecting" : "connecting");
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
/** Abre um socket e resolve no `ready`/`resumed` (handshake completo). */
|
|
1934
|
+
openSocketOnce(url) {
|
|
1935
|
+
return new Promise((resolve, reject) => {
|
|
1936
|
+
let socket;
|
|
1937
|
+
try {
|
|
1938
|
+
socket = this.wsFactory(url);
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
reject(
|
|
1941
|
+
new GatewayError(
|
|
1942
|
+
null,
|
|
1943
|
+
null,
|
|
1944
|
+
`N\xE3o foi poss\xEDvel criar o WebSocket para ${url}: ${String(err?.message ?? err)}`
|
|
1945
|
+
)
|
|
1946
|
+
);
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
this.handshake = { resolve, reject };
|
|
1950
|
+
this.established = false;
|
|
1951
|
+
this.handshakeTimer = setTimeout(() => {
|
|
1952
|
+
this.failHandshake(
|
|
1953
|
+
new GatewayError(
|
|
1954
|
+
null,
|
|
1955
|
+
null,
|
|
1956
|
+
`Handshake do gateway excedeu ${this.handshakeTimeoutMs} ms sem ready/resumed`
|
|
1957
|
+
)
|
|
1958
|
+
);
|
|
1959
|
+
}, this.handshakeTimeoutMs);
|
|
1960
|
+
this.ws = attachWebSocket(socket, {
|
|
1961
|
+
open: () => this.logger.debug("socket aberto, aguardando hello"),
|
|
1962
|
+
message: (data) => this.handleMessage(data),
|
|
1963
|
+
close: (event) => this.handleClose(event.code, event.reason),
|
|
1964
|
+
error: (err) => this.logger.debug("erro de socket", {
|
|
1965
|
+
err: String(err?.message ?? err)
|
|
1966
|
+
})
|
|
1967
|
+
});
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
resolveHandshake(ready) {
|
|
1971
|
+
const handshake = this.handshake;
|
|
1972
|
+
if (!handshake) return;
|
|
1973
|
+
this.clearHandshakeTimer();
|
|
1974
|
+
this.handshake = null;
|
|
1975
|
+
handshake.resolve(ready);
|
|
1976
|
+
}
|
|
1977
|
+
failHandshake(error) {
|
|
1978
|
+
const handshake = this.handshake;
|
|
1979
|
+
this.clearHandshakeTimer();
|
|
1980
|
+
if (!handshake) return;
|
|
1981
|
+
this.handshake = null;
|
|
1982
|
+
handshake.reject(error);
|
|
1983
|
+
}
|
|
1984
|
+
clearHandshakeTimer() {
|
|
1985
|
+
if (this.handshakeTimer) {
|
|
1986
|
+
clearTimeout(this.handshakeTimer);
|
|
1987
|
+
this.handshakeTimer = null;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
// ------------------------------------------------------------------
|
|
1991
|
+
// Entrada de frames
|
|
1992
|
+
// ------------------------------------------------------------------
|
|
1993
|
+
handleMessage(data) {
|
|
1994
|
+
let frame;
|
|
1995
|
+
try {
|
|
1996
|
+
frame = JSON.parse(data);
|
|
1997
|
+
} catch {
|
|
1998
|
+
this.logger.warn("frame n\xE3o-JSON recebido; ignorado");
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
this.emit("raw", frame);
|
|
2002
|
+
switch (frame.op) {
|
|
2003
|
+
case "hello":
|
|
2004
|
+
this.handleHello(frame.d);
|
|
2005
|
+
break;
|
|
2006
|
+
case "ready":
|
|
2007
|
+
this.handleReady(frame.d);
|
|
2008
|
+
break;
|
|
2009
|
+
case "resumed":
|
|
2010
|
+
this.handleResumed(frame.d);
|
|
2011
|
+
break;
|
|
2012
|
+
case "heartbeat.ack":
|
|
2013
|
+
this.handleHeartbeatAck(frame.d);
|
|
2014
|
+
break;
|
|
2015
|
+
case "invalid_session":
|
|
2016
|
+
this.handleInvalidSession(frame.d);
|
|
2017
|
+
break;
|
|
2018
|
+
case "reconnect":
|
|
2019
|
+
this.handleReconnectRequest(frame.d);
|
|
2020
|
+
break;
|
|
2021
|
+
case "error":
|
|
2022
|
+
this.handleServerError(frame.d);
|
|
2023
|
+
break;
|
|
2024
|
+
case "event":
|
|
2025
|
+
this.handleEvent(frame);
|
|
2026
|
+
break;
|
|
2027
|
+
default:
|
|
2028
|
+
this.logger.debug("opcode desconhecido", {
|
|
2029
|
+
op: frame.op
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
handleHello(hello) {
|
|
2034
|
+
this.heartbeatIntervalMs = hello.heartbeatIntervalMs;
|
|
2035
|
+
this.logger.debug("hello", { heartbeatIntervalMs: hello.heartbeatIntervalMs });
|
|
2036
|
+
if (this.sessionId !== null) {
|
|
2037
|
+
this.sendFrame("resume", {
|
|
2038
|
+
token: this.token,
|
|
2039
|
+
sessionId: this.sessionId,
|
|
2040
|
+
lastSequence: this.lastSequence ?? 0
|
|
2041
|
+
});
|
|
2042
|
+
} else {
|
|
2043
|
+
this.sendFrame("identify", {
|
|
2044
|
+
token: this.token,
|
|
2045
|
+
protocolVersion: PROTOCOL_VERSION
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
this.startHeartbeat();
|
|
2049
|
+
}
|
|
2050
|
+
handleReady(ready) {
|
|
2051
|
+
this.sessionId = ready.sessionId;
|
|
2052
|
+
this.resumeUrl = ready.resumeGatewayUrl || this.url;
|
|
2053
|
+
this.lastSequence = null;
|
|
2054
|
+
this.established = true;
|
|
2055
|
+
this.setStatus("ready");
|
|
2056
|
+
this.logger.info("sess\xE3o pronta", {
|
|
2057
|
+
sessionId: ready.sessionId,
|
|
2058
|
+
guilds: ready.guilds.length
|
|
2059
|
+
});
|
|
2060
|
+
this.emit("ready", ready);
|
|
2061
|
+
this.resolveHandshake(ready);
|
|
2062
|
+
}
|
|
2063
|
+
handleResumed(resumed) {
|
|
2064
|
+
this.sessionId = resumed.sessionId;
|
|
2065
|
+
this.established = true;
|
|
2066
|
+
this.setStatus("ready");
|
|
2067
|
+
this.logger.info("sess\xE3o retomada", {
|
|
2068
|
+
replayed: resumed.replayed,
|
|
2069
|
+
sequence: resumed.sequence
|
|
2070
|
+
});
|
|
2071
|
+
this.emit("resumed", resumed);
|
|
2072
|
+
this.resolveHandshake({
|
|
2073
|
+
sessionId: resumed.sessionId,
|
|
2074
|
+
userId: "",
|
|
2075
|
+
guilds: [],
|
|
2076
|
+
resumeGatewayUrl: this.resumeUrl ?? this.url
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
handleHeartbeatAck(ack) {
|
|
2080
|
+
this.awaitingAck = false;
|
|
2081
|
+
if (this.lastHeartbeatSentAt !== null) {
|
|
2082
|
+
this.heartbeatLatencyMs = Date.now() - this.lastHeartbeatSentAt;
|
|
2083
|
+
}
|
|
2084
|
+
this.logger.debug("heartbeat.ack", { sequence: ack.sequence });
|
|
2085
|
+
}
|
|
2086
|
+
handleInvalidSession(payload) {
|
|
2087
|
+
this.logger.warn("resume recusado; fazendo identify", { payload });
|
|
2088
|
+
this.emit("invalidSession", payload);
|
|
2089
|
+
this.invalidateSession();
|
|
2090
|
+
this.sendFrame("identify", {
|
|
2091
|
+
token: this.token,
|
|
2092
|
+
protocolVersion: PROTOCOL_VERSION
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
handleReconnectRequest(payload) {
|
|
2096
|
+
this.logger.warn("servidor pediu reconex\xE3o", { payload });
|
|
2097
|
+
const watchdog = setTimeout(() => {
|
|
2098
|
+
if (!this.destroyed && this.ws) {
|
|
2099
|
+
this.logger.warn("watchdog: fechando para reconectar");
|
|
2100
|
+
try {
|
|
2101
|
+
this.ws.close(4e3, "reconnect watchdog");
|
|
2102
|
+
} catch {
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}, 5e3);
|
|
2106
|
+
watchdog.unref?.();
|
|
2107
|
+
}
|
|
2108
|
+
handleServerError(errorFrame) {
|
|
2109
|
+
this.emit("serverError", errorFrame);
|
|
2110
|
+
this.logger.warn("frame error do servidor", { frame: errorFrame });
|
|
2111
|
+
}
|
|
2112
|
+
handleEvent(frame) {
|
|
2113
|
+
this.eventsReceived++;
|
|
2114
|
+
if (typeof frame.s === "number") {
|
|
2115
|
+
if (this.lastSequence !== null && frame.s <= this.lastSequence) {
|
|
2116
|
+
this.eventsDropped++;
|
|
2117
|
+
this.logger.debug("evento duplicado descartado", {
|
|
2118
|
+
type: frame.type,
|
|
2119
|
+
s: frame.s
|
|
2120
|
+
});
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
this.lastSequence = frame.s;
|
|
2124
|
+
}
|
|
2125
|
+
this.emit("event", {
|
|
2126
|
+
type: frame.type,
|
|
2127
|
+
d: frame.d,
|
|
2128
|
+
s: frame.s,
|
|
2129
|
+
timestamp: frame.timestamp
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
// ------------------------------------------------------------------
|
|
2133
|
+
// Fechamento
|
|
2134
|
+
// ------------------------------------------------------------------
|
|
2135
|
+
handleClose(code, reason) {
|
|
2136
|
+
this.stopHeartbeat();
|
|
2137
|
+
this.ws = null;
|
|
2138
|
+
const wasEstablished = this.established;
|
|
2139
|
+
this.established = false;
|
|
2140
|
+
if (this.reconnectRequested) {
|
|
2141
|
+
this.reconnectRequested = false;
|
|
2142
|
+
this.emit("disconnect", { code, reason, fatal: false });
|
|
2143
|
+
void this.kickReconnectAfterDrop(code, reason);
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
if (this.destroyed || this.closingByClient) {
|
|
2147
|
+
this.closingByClient = false;
|
|
2148
|
+
this.emit("disconnect", { code, reason, fatal: false });
|
|
2149
|
+
this.failHandshake(new GatewayError(code, reason, "Conex\xE3o encerrada"));
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
const fatal = FATAL_CLOSE_CODES.has(code);
|
|
2153
|
+
const botBug = BOT_BUG_CLOSE_CODES.has(code);
|
|
2154
|
+
const error = new GatewayError(
|
|
2155
|
+
code,
|
|
2156
|
+
reason,
|
|
2157
|
+
describeClose(code, reason, fatal, botBug)
|
|
2158
|
+
);
|
|
2159
|
+
this.emit("disconnect", { code, reason, fatal: fatal || botBug });
|
|
2160
|
+
if (fatal || botBug) {
|
|
2161
|
+
this.logger.error("conex\xE3o encerrada sem retomada", { code, reason });
|
|
2162
|
+
this.setStatus("disconnected");
|
|
2163
|
+
this.failHandshake(error);
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
if (wasEstablished) {
|
|
2167
|
+
this.emit("error", error);
|
|
2168
|
+
void this.kickReconnectAfterDrop(code, reason);
|
|
2169
|
+
} else {
|
|
2170
|
+
this.failHandshake(error);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
async kickReconnectAfterDrop(code, reason) {
|
|
2174
|
+
if (!this.autoReconnect || this.destroyed) {
|
|
2175
|
+
this.setStatus("disconnected");
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
const attempt = this.reconnectAttempts++;
|
|
2179
|
+
const delayMs = backoffDelay(attempt, { baseMs: this.reconnectBaseMs });
|
|
2180
|
+
this.reconnectCount++;
|
|
2181
|
+
this.setStatus("reconnecting");
|
|
2182
|
+
this.emit("reconnecting", { attempt, delayMs, closeCode: code, reason });
|
|
2183
|
+
this.logger.info("reconectando ap\xF3s queda", { attempt, delayMs, code });
|
|
2184
|
+
await sleep(delayMs);
|
|
2185
|
+
if (this.destroyed) return;
|
|
2186
|
+
this.setStatus(this.sessionId ? "reconnecting" : "connecting");
|
|
2187
|
+
try {
|
|
2188
|
+
await this.openSocketOnce(this.resumeUrl ?? this.url);
|
|
2189
|
+
this.reconnectAttempts = 0;
|
|
2190
|
+
} catch {
|
|
2191
|
+
if (!this.destroyed) void this.kickReconnectAfterDrop(code, reason);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
// ------------------------------------------------------------------
|
|
2195
|
+
// Heartbeat
|
|
2196
|
+
// ------------------------------------------------------------------
|
|
2197
|
+
startHeartbeat() {
|
|
2198
|
+
this.stopHeartbeat();
|
|
2199
|
+
if (!this.heartbeatIntervalMs) return;
|
|
2200
|
+
const firstDelay = Math.max(250, Math.random() * this.heartbeatIntervalMs);
|
|
2201
|
+
this.heartbeatTimer = setTimeout(() => {
|
|
2202
|
+
this.sendHeartbeat();
|
|
2203
|
+
if (this.heartbeatIntervalMs) {
|
|
2204
|
+
this.heartbeatTimer = setInterval(
|
|
2205
|
+
() => this.sendHeartbeat(),
|
|
2206
|
+
this.heartbeatIntervalMs
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
}, firstDelay);
|
|
2210
|
+
}
|
|
2211
|
+
stopHeartbeat() {
|
|
2212
|
+
if (this.heartbeatTimer) {
|
|
2213
|
+
clearTimeout(this.heartbeatTimer);
|
|
2214
|
+
clearInterval(this.heartbeatTimer);
|
|
2215
|
+
this.heartbeatTimer = null;
|
|
2216
|
+
}
|
|
2217
|
+
this.awaitingAck = false;
|
|
2218
|
+
}
|
|
2219
|
+
sendHeartbeat() {
|
|
2220
|
+
if (this.destroyed || !this.ws) return;
|
|
2221
|
+
if (this.awaitingAck) {
|
|
2222
|
+
this.logger.warn("sem heartbeat.ack desde o \xFAltimo heartbeat; conex\xE3o zumbi, reconectando");
|
|
2223
|
+
try {
|
|
2224
|
+
this.ws.close(4e3, "zombie connection");
|
|
2225
|
+
} catch {
|
|
2226
|
+
}
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
this.lastHeartbeatSentAt = Date.now();
|
|
2230
|
+
this.sendFrame("heartbeat", { s: this.lastSequence ?? void 0 });
|
|
2231
|
+
this.awaitingAck = true;
|
|
2232
|
+
}
|
|
2233
|
+
// ------------------------------------------------------------------
|
|
2234
|
+
sendFrame(op, d) {
|
|
2235
|
+
const ws = this.ws;
|
|
2236
|
+
if (!ws) {
|
|
2237
|
+
this.logger.debug("frame descartado: sem socket", { op });
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
const payload = JSON.stringify({ op, d, requestId: newRequestId() });
|
|
2241
|
+
if (Buffer.byteLength(payload, "utf8") > 65536) {
|
|
2242
|
+
this.emit(
|
|
2243
|
+
"error",
|
|
2244
|
+
new GatewayError(null, null, `Frame ${op} excede 65536 bytes e n\xE3o foi enviado`)
|
|
2245
|
+
);
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
try {
|
|
2249
|
+
ws.send(payload);
|
|
2250
|
+
} catch (err) {
|
|
2251
|
+
this.emit(
|
|
2252
|
+
"error",
|
|
2253
|
+
new GatewayError(null, null, `Falha ao enviar frame ${op}: ${String(err?.message ?? err)}`)
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
setStatus(status) {
|
|
2258
|
+
if (this.statusValue === status) return;
|
|
2259
|
+
this.statusValue = status;
|
|
2260
|
+
this.emit("statusChange", status);
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
function describeClose(code, reason, fatal, botBug) {
|
|
2264
|
+
if (fatal) {
|
|
2265
|
+
if (code === GatewayCloseCodes.AuthenticationFailed) {
|
|
2266
|
+
return `O token foi recusado (4004${reason ? `: ${reason}` : ""}). Ele foi trocado, revogado, ou o bot/dono foi exclu\xEDdo ou suspenso. N\xE3o reconecte com o mesmo token.`;
|
|
2267
|
+
}
|
|
2268
|
+
if (code === GatewayCloseCodes.BotSessionLimit) {
|
|
2269
|
+
return `O bot j\xE1 tem 2 sess\xF5es simult\xE2neas (4014). Feche uma das sess\xF5es antes de reconectar.`;
|
|
2270
|
+
}
|
|
2271
|
+
return `A conta do bot est\xE1 suspensa (4013). N\xE3o reconecte.`;
|
|
2272
|
+
}
|
|
2273
|
+
if (botBug) {
|
|
2274
|
+
return `Erro de protocolo do bot (c\xF3digo ${code}${reason ? `: ${reason}` : ""}). Corrija o c\xF3digo antes de tentar de novo.`;
|
|
2275
|
+
}
|
|
2276
|
+
return `Conex\xE3o do gateway fechou (c\xF3digo ${code}${reason ? `: ${reason}` : ""}).`;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
// src/structures/base.ts
|
|
2280
|
+
var Structure = class {
|
|
2281
|
+
constructor(client, raw) {
|
|
2282
|
+
this.client = client;
|
|
2283
|
+
this.raw = raw;
|
|
2284
|
+
}
|
|
2285
|
+
client;
|
|
2286
|
+
raw;
|
|
2287
|
+
};
|
|
2288
|
+
|
|
2289
|
+
// src/permissions.ts
|
|
2290
|
+
var Permissions = {
|
|
2291
|
+
VIEW_CHANNEL: 1n << 0n,
|
|
2292
|
+
MANAGE_CHANNELS: 1n << 1n,
|
|
2293
|
+
MANAGE_GUILD: 1n << 2n,
|
|
2294
|
+
MANAGE_ROLES: 1n << 3n,
|
|
2295
|
+
MANAGE_INVITES: 1n << 4n,
|
|
2296
|
+
VIEW_AUDIT_LOG: 1n << 5n,
|
|
2297
|
+
MANAGE_EMOJIS: 1n << 6n,
|
|
2298
|
+
MANAGE_WEBHOOKS: 1n << 7n,
|
|
2299
|
+
KICK_MEMBERS: 1n << 8n,
|
|
2300
|
+
BAN_MEMBERS: 1n << 9n,
|
|
2301
|
+
TIMEOUT_MEMBERS: 1n << 10n,
|
|
2302
|
+
CHANGE_NICKNAME: 1n << 11n,
|
|
2303
|
+
MANAGE_NICKNAMES: 1n << 12n,
|
|
2304
|
+
SEND_MESSAGES: 1n << 13n,
|
|
2305
|
+
READ_MESSAGE_HISTORY: 1n << 14n,
|
|
2306
|
+
MANAGE_MESSAGES: 1n << 15n,
|
|
2307
|
+
ATTACH_FILES: 1n << 16n,
|
|
2308
|
+
EMBED_LINKS: 1n << 17n,
|
|
2309
|
+
ADD_REACTIONS: 1n << 18n,
|
|
2310
|
+
MENTION_EVERYONE: 1n << 19n,
|
|
2311
|
+
USE_EXTERNAL_EMOJIS: 1n << 20n,
|
|
2312
|
+
PIN_MESSAGES: 1n << 21n,
|
|
2313
|
+
CONNECT: 1n << 22n,
|
|
2314
|
+
SPEAK: 1n << 23n,
|
|
2315
|
+
VIDEO: 1n << 24n,
|
|
2316
|
+
STREAM: 1n << 25n,
|
|
2317
|
+
MUTE_MEMBERS: 1n << 26n,
|
|
2318
|
+
DEAFEN_MEMBERS: 1n << 27n,
|
|
2319
|
+
MOVE_MEMBERS: 1n << 28n,
|
|
2320
|
+
PRIORITY_SPEAKER: 1n << 29n,
|
|
2321
|
+
ADMINISTRATOR: 1n << 30n,
|
|
2322
|
+
SEND_POLLS: 1n << 31n,
|
|
2323
|
+
/** O bit 32 foi aposentado e nunca é reutilizado. */
|
|
2324
|
+
CREATE_INVITE: 1n << 33n,
|
|
2325
|
+
APPROVE_JOIN_REQUESTS: 1n << 34n,
|
|
2326
|
+
FORWARD_MESSAGES: 1n << 35n,
|
|
2327
|
+
HANDLE_REPORTS: 1n << 36n,
|
|
2328
|
+
PUBLISH_ANNOUNCEMENTS: 1n << 37n,
|
|
2329
|
+
BYPASS_SLOWMODE: 1n << 38n,
|
|
2330
|
+
CREATE_THREADS: 1n << 39n,
|
|
2331
|
+
SEND_MESSAGES_IN_THREADS: 1n << 40n,
|
|
2332
|
+
MANAGE_THREADS: 1n << 41n,
|
|
2333
|
+
MANAGE_EVENTS: 1n << 42n
|
|
2334
|
+
};
|
|
2335
|
+
var ALL_PERMISSIONS = Object.values(Permissions).reduce((a, b) => a | b, 0n);
|
|
2336
|
+
function permissionsToString(bits) {
|
|
2337
|
+
return bits.toString();
|
|
2338
|
+
}
|
|
2339
|
+
function parsePermissions(value) {
|
|
2340
|
+
return BigInt(value);
|
|
2341
|
+
}
|
|
2342
|
+
function hasPermissions(bits, ...flags) {
|
|
2343
|
+
const b = typeof bits === "string" ? BigInt(bits) : bits;
|
|
2344
|
+
return flags.every((flag) => (b & flag) === flag);
|
|
2345
|
+
}
|
|
2346
|
+
function hasAnyPermission(bits, ...flags) {
|
|
2347
|
+
const b = typeof bits === "string" ? BigInt(bits) : bits;
|
|
2348
|
+
return flags.some((flag) => (b & flag) === flag);
|
|
2349
|
+
}
|
|
2350
|
+
function resolveChannelPermissions(options) {
|
|
2351
|
+
let base = options.everyonePermissions;
|
|
2352
|
+
for (const role of options.rolePermissions) base |= role;
|
|
2353
|
+
if ((base & Permissions.ADMINISTRATOR) === Permissions.ADMINISTRATOR) {
|
|
2354
|
+
return ALL_PERMISSIONS;
|
|
2355
|
+
}
|
|
2356
|
+
const apply = (acc, overwrites) => {
|
|
2357
|
+
const everyone = overwrites.find((o) => o.targetId === options.everyoneRoleId);
|
|
2358
|
+
if (everyone) {
|
|
2359
|
+
acc &= ~BigInt(everyone.deny);
|
|
2360
|
+
acc |= BigInt(everyone.allow);
|
|
2361
|
+
}
|
|
2362
|
+
const roleOverwrites = overwrites.filter(
|
|
2363
|
+
(o) => o.targetType === "ROLE" && options.memberRoleIds.includes(o.targetId)
|
|
2364
|
+
);
|
|
2365
|
+
const deny = roleOverwrites.reduce((acc2, o) => acc2 | BigInt(o.deny), 0n);
|
|
2366
|
+
const allow = roleOverwrites.reduce((acc2, o) => acc2 | BigInt(o.allow), 0n);
|
|
2367
|
+
acc &= ~deny;
|
|
2368
|
+
acc |= allow;
|
|
2369
|
+
const member = overwrites.find((o) => o.targetType === "MEMBER" && o.targetId === options.memberId);
|
|
2370
|
+
if (member) {
|
|
2371
|
+
acc &= ~BigInt(member.deny);
|
|
2372
|
+
acc |= BigInt(member.allow);
|
|
2373
|
+
}
|
|
2374
|
+
return acc;
|
|
2375
|
+
};
|
|
2376
|
+
let result = apply(base, options.overwrites);
|
|
2377
|
+
if ((result & Permissions.VIEW_CHANNEL) !== Permissions.VIEW_CHANNEL) return 0n;
|
|
2378
|
+
return result;
|
|
2379
|
+
}
|
|
2380
|
+
var SUGGESTED_TEXT_BOT_PERMISSIONS = Permissions.VIEW_CHANNEL | Permissions.SEND_MESSAGES | Permissions.READ_MESSAGE_HISTORY | Permissions.EMBED_LINKS | Permissions.ATTACH_FILES | Permissions.ADD_REACTIONS;
|
|
2381
|
+
var SUGGESTED_MODERATION_PERMISSIONS = Permissions.KICK_MEMBERS | Permissions.BAN_MEMBERS | Permissions.TIMEOUT_MEMBERS | Permissions.MANAGE_MESSAGES;
|
|
2382
|
+
|
|
2383
|
+
// src/types/api.ts
|
|
2384
|
+
var UserFlags = {
|
|
2385
|
+
STAFF: 1 << 0,
|
|
2386
|
+
EARLY_SUPPORTER: 1 << 1,
|
|
2387
|
+
VERIFIED_BOT: 1 << 2,
|
|
2388
|
+
SYSTEM: 1 << 3,
|
|
2389
|
+
VERIFIED: 1 << 4,
|
|
2390
|
+
BOT: 1 << 5
|
|
2391
|
+
};
|
|
2392
|
+
var GuildFlags = {
|
|
2393
|
+
OFFICIAL: 1 << 0,
|
|
2394
|
+
PARTNER: 1 << 1,
|
|
2395
|
+
DISCOVERY_BLOCKED: 1 << 2,
|
|
2396
|
+
VERIFIED: 1 << 3,
|
|
2397
|
+
DISCOVERY_AGE_WAIVED: 1 << 4
|
|
2398
|
+
};
|
|
2399
|
+
function userMention(id) {
|
|
2400
|
+
return `<@${id}>`;
|
|
2401
|
+
}
|
|
2402
|
+
function roleMention(id) {
|
|
2403
|
+
return `<@&${id}>`;
|
|
2404
|
+
}
|
|
2405
|
+
function channelMention(id) {
|
|
2406
|
+
return `<#${id}>`;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// src/structures/User.ts
|
|
2410
|
+
var User = class extends Structure {
|
|
2411
|
+
constructor(client, raw) {
|
|
2412
|
+
super(client, raw);
|
|
2413
|
+
}
|
|
2414
|
+
get data() {
|
|
2415
|
+
return this.raw;
|
|
2416
|
+
}
|
|
2417
|
+
get id() {
|
|
2418
|
+
return this.data.id;
|
|
2419
|
+
}
|
|
2420
|
+
get username() {
|
|
2421
|
+
return this.data.username;
|
|
2422
|
+
}
|
|
2423
|
+
get displayName() {
|
|
2424
|
+
return this.data.displayName;
|
|
2425
|
+
}
|
|
2426
|
+
get avatarUrl() {
|
|
2427
|
+
return this.data.avatarUrl;
|
|
2428
|
+
}
|
|
2429
|
+
/** `true` para contas de bot (flag `BOT`). */
|
|
2430
|
+
get isBot() {
|
|
2431
|
+
return (this.data.flags & UserFlags.BOT) !== 0;
|
|
2432
|
+
}
|
|
2433
|
+
/** `true` para bots verificados pelo Squord. */
|
|
2434
|
+
get isVerifiedBot() {
|
|
2435
|
+
return (this.data.flags & UserFlags.VERIFIED_BOT) !== 0;
|
|
2436
|
+
}
|
|
2437
|
+
get deleted() {
|
|
2438
|
+
return this.data.deleted;
|
|
2439
|
+
}
|
|
2440
|
+
get suspended() {
|
|
2441
|
+
return this.data.suspended;
|
|
2442
|
+
}
|
|
2443
|
+
/** Menção `<@id>` para usar no conteúdo de mensagens. */
|
|
2444
|
+
get mention() {
|
|
2445
|
+
return `<@${this.id}>`;
|
|
2446
|
+
}
|
|
2447
|
+
/** Busca o membro correspondente em um Space. */
|
|
2448
|
+
member(guildId) {
|
|
2449
|
+
return this.client.members.fetch(guildId, this.id).then((m) => new Member(this.client, m)).catch(() => null);
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
var Member = class _Member extends Structure {
|
|
2453
|
+
constructor(client, raw) {
|
|
2454
|
+
super(client, raw);
|
|
2455
|
+
}
|
|
2456
|
+
get data() {
|
|
2457
|
+
return this.raw;
|
|
2458
|
+
}
|
|
2459
|
+
/** Id da participação (não confundir com o id da conta). */
|
|
2460
|
+
get id() {
|
|
2461
|
+
return this.data.id;
|
|
2462
|
+
}
|
|
2463
|
+
get guildId() {
|
|
2464
|
+
return this.data.guildId;
|
|
2465
|
+
}
|
|
2466
|
+
get userId() {
|
|
2467
|
+
return this.data.userId;
|
|
2468
|
+
}
|
|
2469
|
+
get user() {
|
|
2470
|
+
return new User(this.client, this.data.user);
|
|
2471
|
+
}
|
|
2472
|
+
get nickname() {
|
|
2473
|
+
return this.data.nickname;
|
|
2474
|
+
}
|
|
2475
|
+
/** Nome de exibição efetivo: apelido no Space ou `displayName`. */
|
|
2476
|
+
get displayName() {
|
|
2477
|
+
return this.data.nickname ?? this.data.user.displayName;
|
|
2478
|
+
}
|
|
2479
|
+
get roleIds() {
|
|
2480
|
+
return this.data.roleIds;
|
|
2481
|
+
}
|
|
2482
|
+
get joinedAt() {
|
|
2483
|
+
return this.data.joinedAt;
|
|
2484
|
+
}
|
|
2485
|
+
/** `null` ou data futura: enquanto futura, o membro está de castigo. */
|
|
2486
|
+
get timeoutUntil() {
|
|
2487
|
+
return this.data.timeoutUntil;
|
|
2488
|
+
}
|
|
2489
|
+
get isTimedOut() {
|
|
2490
|
+
return this.timeoutUntil !== null && Date.parse(this.timeoutUntil) > Date.now();
|
|
2491
|
+
}
|
|
2492
|
+
get isBot() {
|
|
2493
|
+
return this.data.type === "BOT";
|
|
2494
|
+
}
|
|
2495
|
+
get mention() {
|
|
2496
|
+
return `<@${this.userId}>`;
|
|
2497
|
+
}
|
|
2498
|
+
/** Menciona o cargo do membro? Cargos não mencionáveis não notificam. */
|
|
2499
|
+
get mentionNicknameOnly() {
|
|
2500
|
+
return `<@!${this.userId}>`;
|
|
2501
|
+
}
|
|
2502
|
+
/** Recarrega o membro da API. */
|
|
2503
|
+
fetch() {
|
|
2504
|
+
return this.client.members.fetch(this.guildId, this.userId).then((m) => new _Member(this.client, m));
|
|
2505
|
+
}
|
|
2506
|
+
/** Lista os cargos do membro no Space. */
|
|
2507
|
+
fetchRoles() {
|
|
2508
|
+
return this.client.roles.list(this.guildId).then(
|
|
2509
|
+
(roles) => roles.filter((r) => this.roleIds.includes(r.id) || r.id === this.guildId).map((r) => new Role(this.client, r))
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
/** Dá um cargo ao membro (respeita hierarquia; cargos gerenciados dão `400 ROLE_MANAGED`). */
|
|
2513
|
+
addRole(roleId) {
|
|
2514
|
+
return this.client.members.addRole(this.guildId, this.userId, roleId);
|
|
2515
|
+
}
|
|
2516
|
+
/** Tira um cargo do membro. */
|
|
2517
|
+
removeRole(roleId) {
|
|
2518
|
+
return this.client.members.removeRole(this.guildId, this.userId, roleId);
|
|
2519
|
+
}
|
|
2520
|
+
/** Muda o apelido (`MANAGE_NICKNAMES`, ou `CHANGE_NICKNAME` em si mesmo). */
|
|
2521
|
+
setNickname(nickname) {
|
|
2522
|
+
return this.client.members.edit(this.guildId, this.userId, { nickname }).then((m) => new _Member(this.client, m));
|
|
2523
|
+
}
|
|
2524
|
+
/** Aplica timeout até uma data (`TIMEOUT_MEMBERS`). */
|
|
2525
|
+
timeout(until) {
|
|
2526
|
+
const iso = until instanceof Date ? until.toISOString() : until;
|
|
2527
|
+
return this.client.members.edit(this.guildId, this.userId, { timeoutUntil: iso }).then((m) => new _Member(this.client, m));
|
|
2528
|
+
}
|
|
2529
|
+
/** Remove o timeout. */
|
|
2530
|
+
removeTimeout() {
|
|
2531
|
+
return this.client.members.edit(this.guildId, this.userId, { timeoutUntil: null }).then((m) => new _Member(this.client, m));
|
|
2532
|
+
}
|
|
2533
|
+
/** Expulsa o membro (`KICK_MEMBERS`). Ele pode voltar com novo convite. */
|
|
2534
|
+
kick(reason) {
|
|
2535
|
+
return this.client.members.kick(this.guildId, this.userId, reason);
|
|
2536
|
+
}
|
|
2537
|
+
/** Bane o membro (`BAN_MEMBERS`). */
|
|
2538
|
+
ban(options = {}) {
|
|
2539
|
+
return this.client.moderation.ban(this.guildId, this.userId, options).then(() => void 0);
|
|
2540
|
+
}
|
|
2541
|
+
};
|
|
2542
|
+
var Role = class extends Structure {
|
|
2543
|
+
constructor(client, raw) {
|
|
2544
|
+
super(client, raw);
|
|
2545
|
+
}
|
|
2546
|
+
get data() {
|
|
2547
|
+
return this.raw;
|
|
2548
|
+
}
|
|
2549
|
+
get id() {
|
|
2550
|
+
return this.data.id;
|
|
2551
|
+
}
|
|
2552
|
+
get guildId() {
|
|
2553
|
+
return this.data.guildId;
|
|
2554
|
+
}
|
|
2555
|
+
get name() {
|
|
2556
|
+
return this.data.name;
|
|
2557
|
+
}
|
|
2558
|
+
get color() {
|
|
2559
|
+
return this.data.color;
|
|
2560
|
+
}
|
|
2561
|
+
get position() {
|
|
2562
|
+
return this.data.position;
|
|
2563
|
+
}
|
|
2564
|
+
/** Bits como `BigInt` (permissões trafegam como string decimal). */
|
|
2565
|
+
get permissions() {
|
|
2566
|
+
return parsePermissions(this.data.permissions);
|
|
2567
|
+
}
|
|
2568
|
+
/** Testa se o cargo concede um conjunto de permissões. */
|
|
2569
|
+
has(...flags) {
|
|
2570
|
+
return hasPermissions(this.permissions, ...flags);
|
|
2571
|
+
}
|
|
2572
|
+
get managed() {
|
|
2573
|
+
return this.data.managed;
|
|
2574
|
+
}
|
|
2575
|
+
get managedKind() {
|
|
2576
|
+
return this.data.managedKind;
|
|
2577
|
+
}
|
|
2578
|
+
get mentionable() {
|
|
2579
|
+
return this.data.mentionable;
|
|
2580
|
+
}
|
|
2581
|
+
/** Menção `<@&id>`; só notifica se o cargo for mencionável. */
|
|
2582
|
+
get mention() {
|
|
2583
|
+
return `<@&${this.id}>`;
|
|
2584
|
+
}
|
|
2585
|
+
/** `true` para o cargo @everyone (o id é o próprio id do Space). */
|
|
2586
|
+
get isEveryone() {
|
|
2587
|
+
return this.id === this.guildId;
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
var Guild = class _Guild extends Structure {
|
|
2591
|
+
constructor(client, raw) {
|
|
2592
|
+
super(client, raw);
|
|
2593
|
+
}
|
|
2594
|
+
get data() {
|
|
2595
|
+
return this.raw;
|
|
2596
|
+
}
|
|
2597
|
+
get id() {
|
|
2598
|
+
return this.data.id;
|
|
2599
|
+
}
|
|
2600
|
+
get name() {
|
|
2601
|
+
return this.data.name;
|
|
2602
|
+
}
|
|
2603
|
+
get slug() {
|
|
2604
|
+
return this.data.slug;
|
|
2605
|
+
}
|
|
2606
|
+
get ownerId() {
|
|
2607
|
+
return this.data.ownerId;
|
|
2608
|
+
}
|
|
2609
|
+
get memberCount() {
|
|
2610
|
+
return this.data.memberCount;
|
|
2611
|
+
}
|
|
2612
|
+
get iconUrl() {
|
|
2613
|
+
return this.data.iconUrl;
|
|
2614
|
+
}
|
|
2615
|
+
get boostCount() {
|
|
2616
|
+
return this.data.boostCount;
|
|
2617
|
+
}
|
|
2618
|
+
/** O bot neste Space: id de participação, cargos e permissões no nível do Space. */
|
|
2619
|
+
get self() {
|
|
2620
|
+
const self = this.data.self;
|
|
2621
|
+
return {
|
|
2622
|
+
memberId: self.memberId,
|
|
2623
|
+
roleIds: self.roleIds,
|
|
2624
|
+
permissions: parsePermissions(self.permissions),
|
|
2625
|
+
isOwner: self.isOwner
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
/** Testa permissões do bot no nível do Space (overwrites de canal à parte). */
|
|
2629
|
+
botHas(...flags) {
|
|
2630
|
+
return hasPermissions(this.self.permissions, ...flags);
|
|
2631
|
+
}
|
|
2632
|
+
/** Recarrega o Space. */
|
|
2633
|
+
fetch() {
|
|
2634
|
+
return this.client.guilds.fetch(this.id).then((g) => new _Guild(this.client, g));
|
|
2635
|
+
}
|
|
2636
|
+
/** Árvore de canais do Space, agrupada por categorias. */
|
|
2637
|
+
fetchChannelTree() {
|
|
2638
|
+
return this.client.channels.fetchTree(this.id).then(
|
|
2639
|
+
(nodes) => nodes.map((node) => ({
|
|
2640
|
+
category: node.category ? new Channel(this.client, node.category) : null,
|
|
2641
|
+
channels: node.channels.map((c) => new Channel(this.client, c))
|
|
2642
|
+
}))
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
/** Lista os cargos do Space. */
|
|
2646
|
+
fetchRoles() {
|
|
2647
|
+
return this.client.roles.list(this.id).then((roles) => roles.map((r) => new Role(this.client, r)));
|
|
2648
|
+
}
|
|
2649
|
+
/** Busca um membro pelo id da conta. */
|
|
2650
|
+
fetchMember(userId) {
|
|
2651
|
+
return this.client.members.fetch(this.id, userId).then((m) => new Member(this.client, m));
|
|
2652
|
+
}
|
|
2653
|
+
};
|
|
2654
|
+
|
|
2655
|
+
// src/structures/Message.ts
|
|
2656
|
+
var Message = class _Message extends Structure {
|
|
2657
|
+
constructor(client, raw) {
|
|
2658
|
+
super(client, raw);
|
|
2659
|
+
}
|
|
2660
|
+
get data() {
|
|
2661
|
+
return this.raw;
|
|
2662
|
+
}
|
|
2663
|
+
get id() {
|
|
2664
|
+
return this.data.id;
|
|
2665
|
+
}
|
|
2666
|
+
get guildId() {
|
|
2667
|
+
return this.data.guildId;
|
|
2668
|
+
}
|
|
2669
|
+
get channelId() {
|
|
2670
|
+
return this.data.channelId;
|
|
2671
|
+
}
|
|
2672
|
+
get authorId() {
|
|
2673
|
+
return this.data.authorId;
|
|
2674
|
+
}
|
|
2675
|
+
get author() {
|
|
2676
|
+
return new User(this.client, this.data.author);
|
|
2677
|
+
}
|
|
2678
|
+
get type() {
|
|
2679
|
+
return this.data.type;
|
|
2680
|
+
}
|
|
2681
|
+
get content() {
|
|
2682
|
+
return this.data.content;
|
|
2683
|
+
}
|
|
2684
|
+
get replyToId() {
|
|
2685
|
+
return this.data.replyToId;
|
|
2686
|
+
}
|
|
2687
|
+
get pinned() {
|
|
2688
|
+
return this.data.pinned;
|
|
2689
|
+
}
|
|
2690
|
+
get editedAt() {
|
|
2691
|
+
return this.data.editedAt;
|
|
2692
|
+
}
|
|
2693
|
+
get moderated() {
|
|
2694
|
+
return this.data.moderated;
|
|
2695
|
+
}
|
|
2696
|
+
get attachments() {
|
|
2697
|
+
return this.data.attachments;
|
|
2698
|
+
}
|
|
2699
|
+
get embeds() {
|
|
2700
|
+
return this.data.embeds;
|
|
2701
|
+
}
|
|
2702
|
+
get components() {
|
|
2703
|
+
return this.data.components;
|
|
2704
|
+
}
|
|
2705
|
+
get reactions() {
|
|
2706
|
+
return this.data.reactions;
|
|
2707
|
+
}
|
|
2708
|
+
get createdAt() {
|
|
2709
|
+
return this.data.createdAt;
|
|
2710
|
+
}
|
|
2711
|
+
/** Apelido do autor no Space (quando houver). */
|
|
2712
|
+
get memberNickname() {
|
|
2713
|
+
return this.data.memberNickname;
|
|
2714
|
+
}
|
|
2715
|
+
/** Nome de exibição efetivo do autor (apelido ou displayName). */
|
|
2716
|
+
get authorDisplayName() {
|
|
2717
|
+
return this.data.memberNickname ?? this.data.author.displayName;
|
|
2718
|
+
}
|
|
2719
|
+
/** `true` se a mensagem veio do próprio bot. */
|
|
2720
|
+
get fromMe() {
|
|
2721
|
+
return this.client.userId !== null && this.authorId === this.client.userId;
|
|
2722
|
+
}
|
|
2723
|
+
/** `true` se o bot foi mencionado (`<@id>` no conteúdo). */
|
|
2724
|
+
get mentionsMe() {
|
|
2725
|
+
return this.client.userId !== null && this.data.mentions.userIds.includes(this.client.userId);
|
|
2726
|
+
}
|
|
2727
|
+
/** Ids mencionados na mensagem (só as menções que valeram). */
|
|
2728
|
+
get mentionedUserIds() {
|
|
2729
|
+
return this.data.mentions.userIds;
|
|
2730
|
+
}
|
|
2731
|
+
get mentionedRoleIds() {
|
|
2732
|
+
return this.data.mentions.roleIds;
|
|
2733
|
+
}
|
|
2734
|
+
get mentionsEveryone() {
|
|
2735
|
+
return this.data.mentions.everyone;
|
|
2736
|
+
}
|
|
2737
|
+
/** Recarrega a mensagem (ela pode ter sido apagada; isso lança 404). */
|
|
2738
|
+
fetch() {
|
|
2739
|
+
return this.client.messages.fetch(this.channelId, this.id).then((m) => new _Message(this.client, m));
|
|
2740
|
+
}
|
|
2741
|
+
/** Busca o canal da mensagem (uma thread também é canal). */
|
|
2742
|
+
fetchChannel() {
|
|
2743
|
+
return this.client.channels.fetch(this.channelId).then(
|
|
2744
|
+
(channel) => channel.type === "THREAD" ? new Thread(this.client, channel) : new Channel(this.client, channel)
|
|
2745
|
+
).catch(() => null);
|
|
2746
|
+
}
|
|
2747
|
+
/** Responde a mensagem (usa `replyToId`). Idempotente por `nonce`. */
|
|
2748
|
+
reply(payload, options) {
|
|
2749
|
+
const source = typeof payload === "string" ? { content: payload } : payload;
|
|
2750
|
+
return this.client.messages.send(this.channelId, { ...source, replyToId: this.id }, options).then((m) => new _Message(this.client, m));
|
|
2751
|
+
}
|
|
2752
|
+
/** Edita a mensagem (só mensagens do próprio bot). */
|
|
2753
|
+
edit(payload, options) {
|
|
2754
|
+
return this.client.messages.edit(this.channelId, this.id, payload, options).then((m) => new _Message(this.client, m));
|
|
2755
|
+
}
|
|
2756
|
+
/** Apaga a mensagem (a do bot sempre; de outros, com `MANAGE_MESSAGES`). */
|
|
2757
|
+
delete(options) {
|
|
2758
|
+
return this.client.messages.delete(this.channelId, this.id, options);
|
|
2759
|
+
}
|
|
2760
|
+
/** Reage com um emoji Unicode ou `<:nome:id>`. */
|
|
2761
|
+
react(emoji, options) {
|
|
2762
|
+
return this.client.messages.react(this.channelId, this.id, emoji, options);
|
|
2763
|
+
}
|
|
2764
|
+
/** Remove a reação do bot. */
|
|
2765
|
+
removeReaction(emoji, options) {
|
|
2766
|
+
return this.client.messages.removeReaction(this.channelId, this.id, emoji, options);
|
|
2767
|
+
}
|
|
2768
|
+
/** Fixa a mensagem no canal (`PIN_MESSAGES`). */
|
|
2769
|
+
pin(options) {
|
|
2770
|
+
return this.client.messages.pin(this.channelId, this.id, options);
|
|
2771
|
+
}
|
|
2772
|
+
/** Desafixa a mensagem. */
|
|
2773
|
+
unpin(options) {
|
|
2774
|
+
return this.client.messages.unpin(this.channelId, this.id, options);
|
|
2775
|
+
}
|
|
2776
|
+
/** Cria uma thread a partir desta mensagem (canais de texto e anúncios). */
|
|
2777
|
+
startThread(body) {
|
|
2778
|
+
return this.client.channels.createThreadFromMessage(this.channelId, this.id, body).then((t) => new Thread(this.client, t));
|
|
2779
|
+
}
|
|
2780
|
+
};
|
|
2781
|
+
|
|
2782
|
+
// src/structures/Channel.ts
|
|
2783
|
+
var Channel = class _Channel extends Structure {
|
|
2784
|
+
constructor(client, raw) {
|
|
2785
|
+
super(client, raw);
|
|
2786
|
+
}
|
|
2787
|
+
get data() {
|
|
2788
|
+
return this.raw;
|
|
2789
|
+
}
|
|
2790
|
+
get id() {
|
|
2791
|
+
return this.data.id;
|
|
2792
|
+
}
|
|
2793
|
+
get guildId() {
|
|
2794
|
+
return this.data.guildId;
|
|
2795
|
+
}
|
|
2796
|
+
get name() {
|
|
2797
|
+
return this.data.name;
|
|
2798
|
+
}
|
|
2799
|
+
get type() {
|
|
2800
|
+
return this.data.type;
|
|
2801
|
+
}
|
|
2802
|
+
get topic() {
|
|
2803
|
+
return this.data.topic;
|
|
2804
|
+
}
|
|
2805
|
+
get categoryId() {
|
|
2806
|
+
return this.data.categoryId;
|
|
2807
|
+
}
|
|
2808
|
+
get isPrivate() {
|
|
2809
|
+
return this.data.isPrivate;
|
|
2810
|
+
}
|
|
2811
|
+
/** Modo lento em segundos (0 desligado). */
|
|
2812
|
+
get rateLimitPerUser() {
|
|
2813
|
+
return this.data.rateLimitPerUser;
|
|
2814
|
+
}
|
|
2815
|
+
get chatEnabled() {
|
|
2816
|
+
return this.data.chatEnabled;
|
|
2817
|
+
}
|
|
2818
|
+
get lastMessageId() {
|
|
2819
|
+
return this.data.lastMessageId;
|
|
2820
|
+
}
|
|
2821
|
+
/** Menção `<#id>`. */
|
|
2822
|
+
get mention() {
|
|
2823
|
+
return `<#${this.id}>`;
|
|
2824
|
+
}
|
|
2825
|
+
get isText() {
|
|
2826
|
+
return this.data.type === "TEXT" || this.data.type === "ANNOUNCEMENT";
|
|
2827
|
+
}
|
|
2828
|
+
get isVoice() {
|
|
2829
|
+
return this.data.type === "VOICE" || this.data.type === "STAGE";
|
|
2830
|
+
}
|
|
2831
|
+
get isForum() {
|
|
2832
|
+
return this.data.type === "FORUM";
|
|
2833
|
+
}
|
|
2834
|
+
/** Recarrega o canal. */
|
|
2835
|
+
fetch() {
|
|
2836
|
+
return this.client.channels.fetch(this.id).then((c) => new _Channel(this.client, c));
|
|
2837
|
+
}
|
|
2838
|
+
/** Envia uma mensagem no canal (idempotente por `nonce`). */
|
|
2839
|
+
send(payload, options) {
|
|
2840
|
+
return this.client.messages.send(this.id, payload, options).then((m) => new Message(this.client, m));
|
|
2841
|
+
}
|
|
2842
|
+
/** Envia um arquivo como anexo do canal (fluxo completo de upload). */
|
|
2843
|
+
sendFile(options, content) {
|
|
2844
|
+
return this.client.uploads.uploadForAttachment({ guildId: this.guildId, ...options }).then(
|
|
2845
|
+
(attachmentId) => this.send({
|
|
2846
|
+
content: content || void 0,
|
|
2847
|
+
attachmentIds: [attachmentId]
|
|
2848
|
+
})
|
|
2849
|
+
);
|
|
2850
|
+
}
|
|
2851
|
+
/** Busca uma mensagem do canal pelo id. */
|
|
2852
|
+
fetchMessage(messageId) {
|
|
2853
|
+
return this.client.messages.fetch(this.id, messageId).then((m) => new Message(this.client, m));
|
|
2854
|
+
}
|
|
2855
|
+
/** Lista uma página do histórico (use `client.paginate` para iterar tudo). */
|
|
2856
|
+
fetchMessages(params) {
|
|
2857
|
+
return this.client.messages.list(this.id, params ?? {}).then((page) => ({
|
|
2858
|
+
...page,
|
|
2859
|
+
items: page.items.map((m) => new Message(this.client, m))
|
|
2860
|
+
}));
|
|
2861
|
+
}
|
|
2862
|
+
/** Mensagens fixadas do canal. */
|
|
2863
|
+
fetchPins() {
|
|
2864
|
+
return this.client.messages.listPins(this.id).then((msgs) => msgs.map((m) => new Message(this.client, m)));
|
|
2865
|
+
}
|
|
2866
|
+
/** Cria uma thread neste canal (texto/anúncios) ou um post (fórum). */
|
|
2867
|
+
createThread(body) {
|
|
2868
|
+
return this.client.channels.createThread(this.id, body).then((t) => new Thread(this.client, t));
|
|
2869
|
+
}
|
|
2870
|
+
/** Lista as threads do canal (`cursor` = `nextCursor` da página anterior). */
|
|
2871
|
+
fetchThreads(params) {
|
|
2872
|
+
return this.client.channels.listThreads(this.id, { limit: params?.limit, after: params?.cursor, archived: params?.archived, tagId: params?.tagId }).then((page) => ({
|
|
2873
|
+
...page,
|
|
2874
|
+
items: page.items.map((t) => new Thread(this.client, t))
|
|
2875
|
+
}));
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
var Thread = class extends Structure {
|
|
2879
|
+
constructor(client, raw) {
|
|
2880
|
+
super(client, raw);
|
|
2881
|
+
}
|
|
2882
|
+
get data() {
|
|
2883
|
+
return this.raw;
|
|
2884
|
+
}
|
|
2885
|
+
get id() {
|
|
2886
|
+
return this.data.id;
|
|
2887
|
+
}
|
|
2888
|
+
get guildId() {
|
|
2889
|
+
return this.data.guildId;
|
|
2890
|
+
}
|
|
2891
|
+
get parentId() {
|
|
2892
|
+
return this.data.parentId;
|
|
2893
|
+
}
|
|
2894
|
+
get name() {
|
|
2895
|
+
return this.data.name;
|
|
2896
|
+
}
|
|
2897
|
+
get ownerId() {
|
|
2898
|
+
return this.data.ownerId;
|
|
2899
|
+
}
|
|
2900
|
+
get isForumPost() {
|
|
2901
|
+
return this.data.isForumPost;
|
|
2902
|
+
}
|
|
2903
|
+
get archived() {
|
|
2904
|
+
return this.data.archived;
|
|
2905
|
+
}
|
|
2906
|
+
get locked() {
|
|
2907
|
+
return this.data.locked;
|
|
2908
|
+
}
|
|
2909
|
+
get autoArchiveMinutes() {
|
|
2910
|
+
return this.data.autoArchiveMinutes;
|
|
2911
|
+
}
|
|
2912
|
+
get messageCount() {
|
|
2913
|
+
return this.data.messageCount;
|
|
2914
|
+
}
|
|
2915
|
+
get lastMessageId() {
|
|
2916
|
+
return this.data.lastMessageId;
|
|
2917
|
+
}
|
|
2918
|
+
get lastMessageAt() {
|
|
2919
|
+
return this.data.lastMessageAt;
|
|
2920
|
+
}
|
|
2921
|
+
get appliedTagIds() {
|
|
2922
|
+
return this.data.appliedTagIds;
|
|
2923
|
+
}
|
|
2924
|
+
get mention() {
|
|
2925
|
+
return `<#${this.id}>`;
|
|
2926
|
+
}
|
|
2927
|
+
/** Envia uma mensagem na thread (usa o id dela como channelId). */
|
|
2928
|
+
send(payload, options) {
|
|
2929
|
+
return this.client.messages.send(this.id, payload, options).then((m) => new Message(this.client, m));
|
|
2930
|
+
}
|
|
2931
|
+
/** Busca uma mensagem da thread. */
|
|
2932
|
+
fetchMessage(messageId) {
|
|
2933
|
+
return this.client.messages.fetch(this.id, messageId).then((m) => new Message(this.client, m));
|
|
2934
|
+
}
|
|
2935
|
+
};
|
|
2936
|
+
|
|
2937
|
+
// src/structures/Interaction.ts
|
|
2938
|
+
var Interaction = class extends Structure {
|
|
2939
|
+
constructor(client, raw) {
|
|
2940
|
+
super(client, raw);
|
|
2941
|
+
}
|
|
2942
|
+
get data() {
|
|
2943
|
+
return this.raw;
|
|
2944
|
+
}
|
|
2945
|
+
get id() {
|
|
2946
|
+
return this.data.id;
|
|
2947
|
+
}
|
|
2948
|
+
/** Credencial da interação (15 minutos de validade). Trate como segredo. */
|
|
2949
|
+
get token() {
|
|
2950
|
+
return this.data.token;
|
|
2951
|
+
}
|
|
2952
|
+
get type() {
|
|
2953
|
+
return this.data.type;
|
|
2954
|
+
}
|
|
2955
|
+
get guildId() {
|
|
2956
|
+
return this.data.guildId;
|
|
2957
|
+
}
|
|
2958
|
+
get channelId() {
|
|
2959
|
+
return this.data.channelId;
|
|
2960
|
+
}
|
|
2961
|
+
/** Quem invocou o comando ou clicou no componente. */
|
|
2962
|
+
get user() {
|
|
2963
|
+
return new User(this.client, this.data.user);
|
|
2964
|
+
}
|
|
2965
|
+
/** O membro que invocou (com cargos do Space), quando disponível. */
|
|
2966
|
+
get member() {
|
|
2967
|
+
return this.data.member ? new Member(this.client, this.data.member) : null;
|
|
2968
|
+
}
|
|
2969
|
+
get createdAt() {
|
|
2970
|
+
return this.data.createdAt;
|
|
2971
|
+
}
|
|
2972
|
+
get isCommand() {
|
|
2973
|
+
return this.data.type === "COMMAND" && this.data.command !== void 0;
|
|
2974
|
+
}
|
|
2975
|
+
get isComponent() {
|
|
2976
|
+
return this.data.type === "COMPONENT" && this.data.component !== void 0;
|
|
2977
|
+
}
|
|
2978
|
+
/** Dados do comando (presentes quando `isCommand`). */
|
|
2979
|
+
get command() {
|
|
2980
|
+
return this.data.command ?? null;
|
|
2981
|
+
}
|
|
2982
|
+
/** Dados do componente (presentes quando `isComponent`). */
|
|
2983
|
+
get component() {
|
|
2984
|
+
return this.data.component ?? null;
|
|
2985
|
+
}
|
|
2986
|
+
/** O `customId` do componente clicado (ou `null`). */
|
|
2987
|
+
get customId() {
|
|
2988
|
+
return this.component?.customId ?? null;
|
|
2989
|
+
}
|
|
2990
|
+
/** Os valores escolhidos em um menu (vazio em botões). */
|
|
2991
|
+
get values() {
|
|
2992
|
+
return this.component?.values ?? [];
|
|
2993
|
+
}
|
|
2994
|
+
// ------------------------------------------------------------------
|
|
2995
|
+
// Acesso às opções do comando
|
|
2996
|
+
// ------------------------------------------------------------------
|
|
2997
|
+
/** Lista as opções preenchidas da invocação (já validadas pelo Squord). */
|
|
2998
|
+
get options() {
|
|
2999
|
+
return this.command?.options ?? [];
|
|
3000
|
+
}
|
|
3001
|
+
/** Valor bruto de uma opção pelo nome. */
|
|
3002
|
+
getOption(name) {
|
|
3003
|
+
return this.options.find((option) => option.name === name)?.value;
|
|
3004
|
+
}
|
|
3005
|
+
/** Opção `STRING`. */
|
|
3006
|
+
getString(name, required = false) {
|
|
3007
|
+
const value = this.getOption(name);
|
|
3008
|
+
if (typeof value === "string") return value;
|
|
3009
|
+
if (required) throw new Error(`A op\xE7\xE3o obrigat\xF3ria "${name}" n\xE3o veio como STRING`);
|
|
3010
|
+
return void 0;
|
|
3011
|
+
}
|
|
3012
|
+
/** Opção `INTEGER`. */
|
|
3013
|
+
getInteger(name, required = false) {
|
|
3014
|
+
const value = this.getOption(name);
|
|
3015
|
+
if (typeof value === "number") return value;
|
|
3016
|
+
if (required) throw new Error(`A op\xE7\xE3o obrigat\xF3ria "${name}" n\xE3o veio como INTEGER`);
|
|
3017
|
+
return void 0;
|
|
3018
|
+
}
|
|
3019
|
+
/** Opção `BOOLEAN`. */
|
|
3020
|
+
getBoolean(name, required = false) {
|
|
3021
|
+
const value = this.getOption(name);
|
|
3022
|
+
if (typeof value === "boolean") return value;
|
|
3023
|
+
if (required) throw new Error(`A op\xE7\xE3o obrigat\xF3ria "${name}" n\xE3o veio como BOOLEAN`);
|
|
3024
|
+
return void 0;
|
|
3025
|
+
}
|
|
3026
|
+
/** Opção `USER`/`CHANNEL`/`ROLE`: o valor é o id (string). */
|
|
3027
|
+
getId(name, required = false) {
|
|
3028
|
+
const value = this.getOption(name);
|
|
3029
|
+
if (typeof value === "string") return value;
|
|
3030
|
+
if (required) throw new Error(`A op\xE7\xE3o obrigat\xF3ria "${name}" n\xE3o veio como USER/CHANNEL/ROLE`);
|
|
3031
|
+
return void 0;
|
|
3032
|
+
}
|
|
3033
|
+
// ------------------------------------------------------------------
|
|
3034
|
+
// Respostas
|
|
3035
|
+
// ------------------------------------------------------------------
|
|
3036
|
+
/** Primeira resposta como mensagem. `ephemeral: true` mostra só para quem invocou. */
|
|
3037
|
+
reply(payload, options = {}) {
|
|
3038
|
+
return this.client.interactions.reply(this.id, this.token, payload, options);
|
|
3039
|
+
}
|
|
3040
|
+
/** Resposta efêmera: só quem invocou vê; não é salva; só botões de link. */
|
|
3041
|
+
replyEphemeral(payload, options) {
|
|
3042
|
+
return this.client.interactions.reply(this.id, this.token, payload, {
|
|
3043
|
+
...options,
|
|
3044
|
+
ephemeral: true
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
/** `DEFERRED`: mostra o bot "pensando" enquanto o trabalho roda. */
|
|
3048
|
+
defer(options) {
|
|
3049
|
+
return this.client.interactions.defer(this.id, this.token, options);
|
|
3050
|
+
}
|
|
3051
|
+
/**
|
|
3052
|
+
* `UPDATE` (só componente): edita a mensagem que tinha o componente.
|
|
3053
|
+
* O caso típico: marcar a escolha, mudar de página.
|
|
3054
|
+
*/
|
|
3055
|
+
update(payload, options) {
|
|
3056
|
+
return this.client.interactions.update(this.id, this.token, payload, options);
|
|
3057
|
+
}
|
|
3058
|
+
/** `DEFERRED_UPDATE` (só componente): confirma o clique sem mudar nada. */
|
|
3059
|
+
acknowledge(options) {
|
|
3060
|
+
return this.client.interactions.acknowledgeComponent(this.id, this.token, options);
|
|
3061
|
+
}
|
|
3062
|
+
/**
|
|
3063
|
+
* Follow-up: mensagem extra depois da primeira resposta
|
|
3064
|
+
* (o padrão para trabalho demorado é `defer()` + follow-up).
|
|
3065
|
+
*/
|
|
3066
|
+
followup(payload, options = {}) {
|
|
3067
|
+
const source = typeof payload === "string" ? { content: payload } : payload;
|
|
3068
|
+
if (options.ephemeral) source.ephemeral = true;
|
|
3069
|
+
return this.client.interactions.followup(
|
|
3070
|
+
this.id,
|
|
3071
|
+
this.token,
|
|
3072
|
+
source,
|
|
3073
|
+
options.signal
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
/** Busca a mensagem do componente clicado (quando `isComponent`). */
|
|
3077
|
+
fetchComponentMessage() {
|
|
3078
|
+
const messageId = this.component?.messageId;
|
|
3079
|
+
if (!messageId) return Promise.resolve(null);
|
|
3080
|
+
return this.client.messages.fetch(this.channelId, messageId).then((m) => new Message(this.client, m)).catch(() => null);
|
|
3081
|
+
}
|
|
3082
|
+
};
|
|
3083
|
+
|
|
3084
|
+
// src/utils/snowflake.ts
|
|
3085
|
+
var SNOWFLAKE_RE = /^\d{1,20}$/;
|
|
3086
|
+
var LEGACY_ID_RE = /^[0-9a-f]{24}$/i;
|
|
3087
|
+
function isValidSnowflake(id) {
|
|
3088
|
+
return typeof id === "string" && (SNOWFLAKE_RE.test(id) || LEGACY_ID_RE.test(id));
|
|
3089
|
+
}
|
|
3090
|
+
function assertSnowflake(id, label = "id") {
|
|
3091
|
+
if (!isValidSnowflake(id)) {
|
|
3092
|
+
throw new SquordTypeError(
|
|
3093
|
+
`${label} inv\xE1lido: ${JSON.stringify(id)} \u2014 ids do Squord s\xE3o strings (Snowflake decimal ou hex de 24 caracteres)`
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
return id;
|
|
3097
|
+
}
|
|
3098
|
+
function compareSnowflakes(a, b) {
|
|
3099
|
+
if (!SNOWFLAKE_RE.test(a) || !SNOWFLAKE_RE.test(b)) {
|
|
3100
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
3101
|
+
}
|
|
3102
|
+
try {
|
|
3103
|
+
const ba = BigInt(a);
|
|
3104
|
+
const bb = BigInt(b);
|
|
3105
|
+
return ba < bb ? -1 : ba > bb ? 1 : 0;
|
|
3106
|
+
} catch {
|
|
3107
|
+
return a.localeCompare(b);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// src/cache/Cache.ts
|
|
3112
|
+
var Cache = class {
|
|
3113
|
+
store = /* @__PURE__ */ new Map();
|
|
3114
|
+
ttlMs;
|
|
3115
|
+
maxSize;
|
|
3116
|
+
now;
|
|
3117
|
+
constructor(options = {}) {
|
|
3118
|
+
this.ttlMs = options.ttlMs ?? 3e5;
|
|
3119
|
+
this.maxSize = options.maxSize ?? 1e3;
|
|
3120
|
+
this.now = options.now ?? (() => Date.now());
|
|
3121
|
+
}
|
|
3122
|
+
get(key) {
|
|
3123
|
+
const entry = this.store.get(key);
|
|
3124
|
+
if (!entry) return void 0;
|
|
3125
|
+
if (this.now() >= entry.expiresAt) {
|
|
3126
|
+
this.store.delete(key);
|
|
3127
|
+
return void 0;
|
|
3128
|
+
}
|
|
3129
|
+
this.store.delete(key);
|
|
3130
|
+
this.store.set(key, entry);
|
|
3131
|
+
return entry.value;
|
|
3132
|
+
}
|
|
3133
|
+
set(key, value, ttlMs) {
|
|
3134
|
+
if (this.maxSize <= 0) return;
|
|
3135
|
+
const ttl = ttlMs ?? this.ttlMs;
|
|
3136
|
+
this.store.delete(key);
|
|
3137
|
+
this.store.set(key, { value, expiresAt: ttl === Infinity ? Number.MAX_SAFE_INTEGER : this.now() + ttl, insertedAt: this.now() });
|
|
3138
|
+
this.evictIfNeeded();
|
|
3139
|
+
}
|
|
3140
|
+
has(key) {
|
|
3141
|
+
return this.get(key) !== void 0 || this.peek(key) !== void 0;
|
|
3142
|
+
}
|
|
3143
|
+
delete(key) {
|
|
3144
|
+
return this.store.delete(key);
|
|
3145
|
+
}
|
|
3146
|
+
clear() {
|
|
3147
|
+
this.store.clear();
|
|
3148
|
+
}
|
|
3149
|
+
get size() {
|
|
3150
|
+
this.sweep();
|
|
3151
|
+
return this.store.size;
|
|
3152
|
+
}
|
|
3153
|
+
/** Itera itens vivos. */
|
|
3154
|
+
*entries() {
|
|
3155
|
+
for (const [key, entry] of [...this.store.entries()]) {
|
|
3156
|
+
if (this.now() < entry.expiresAt) yield [key, entry.value];
|
|
3157
|
+
else this.store.delete(key);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
peek(key) {
|
|
3161
|
+
return this.store.get(key)?.value;
|
|
3162
|
+
}
|
|
3163
|
+
evictIfNeeded() {
|
|
3164
|
+
if (this.store.size <= this.maxSize) return;
|
|
3165
|
+
const excess = this.store.size - this.maxSize;
|
|
3166
|
+
const iterator = this.store.keys();
|
|
3167
|
+
for (let i = 0; i < excess; i++) {
|
|
3168
|
+
const oldest = iterator.next();
|
|
3169
|
+
if (oldest.done) break;
|
|
3170
|
+
this.store.delete(oldest.value);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
sweep() {
|
|
3174
|
+
const now = this.now();
|
|
3175
|
+
for (const [key, entry] of this.store.entries()) {
|
|
3176
|
+
if (now >= entry.expiresAt) this.store.delete(key);
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
};
|
|
3180
|
+
|
|
3181
|
+
// src/client/SquordClient.ts
|
|
3182
|
+
var SquordClient = class extends TypedEmitter {
|
|
3183
|
+
rest;
|
|
3184
|
+
messages;
|
|
3185
|
+
channels;
|
|
3186
|
+
guilds;
|
|
3187
|
+
members;
|
|
3188
|
+
roles;
|
|
3189
|
+
moderation;
|
|
3190
|
+
uploads;
|
|
3191
|
+
interactions;
|
|
3192
|
+
commands;
|
|
3193
|
+
bot;
|
|
3194
|
+
users;
|
|
3195
|
+
logger;
|
|
3196
|
+
/** Caches opcionais, desativados por padrão (`cache.enabled`). */
|
|
3197
|
+
caches;
|
|
3198
|
+
options;
|
|
3199
|
+
gatewayInstance = null;
|
|
3200
|
+
botInfo = null;
|
|
3201
|
+
userIdValue = null;
|
|
3202
|
+
shutdownSignalsBound = false;
|
|
3203
|
+
destroyed = false;
|
|
3204
|
+
constructor(options) {
|
|
3205
|
+
super();
|
|
3206
|
+
if (!options?.token || typeof options.token !== "string") {
|
|
3207
|
+
throw new SquordTypeError("Informe o token do bot: new SquordClient({ token: process.env.SQUORD_BOT_TOKEN })");
|
|
3208
|
+
}
|
|
3209
|
+
const token = stripTokenPrefix(options.token);
|
|
3210
|
+
if (!isBotToken(token)) {
|
|
3211
|
+
throw new SquordTypeError(
|
|
3212
|
+
"O token do bot deve come\xE7ar com sqb_ (formato sqb_<id>.<segredo>), obtido em Configura\xE7\xF5es \u2192 Desenvolvedor"
|
|
3213
|
+
);
|
|
3214
|
+
}
|
|
3215
|
+
this.options = { ...options, token };
|
|
3216
|
+
this.logger = options.logger !== void 0 && options.logger !== null ? options.logger : options.logger === null ? noopLogger : options.logLevel ? createConsoleLogger({ level: options.logLevel }) : loggerFromEnv();
|
|
3217
|
+
this.rest = new RestClient({
|
|
3218
|
+
baseURL: options.apiBaseURL,
|
|
3219
|
+
token,
|
|
3220
|
+
timeoutMs: options.restTimeoutMs,
|
|
3221
|
+
maxRetries: options.restMaxRetries,
|
|
3222
|
+
globalRequestsPerSecond: options.restGlobalPerSecond,
|
|
3223
|
+
fetchImpl: options.fetchImpl,
|
|
3224
|
+
logger: this.logger
|
|
3225
|
+
});
|
|
3226
|
+
this.messages = new MessagesManager(this.rest);
|
|
3227
|
+
this.channels = new ChannelsManager(this.rest);
|
|
3228
|
+
this.guilds = new GuildsManager(this.rest);
|
|
3229
|
+
this.members = new MembersManager(this.rest);
|
|
3230
|
+
this.roles = new RolesManager(this.rest);
|
|
3231
|
+
this.moderation = new ModerationManager(this.rest);
|
|
3232
|
+
this.uploads = new UploadsManager(this.rest, this.logger);
|
|
3233
|
+
this.interactions = new InteractionsManager(this.rest);
|
|
3234
|
+
this.commands = new ApplicationCommandsManager(this.rest);
|
|
3235
|
+
this.bot = new BotManager(this.rest);
|
|
3236
|
+
this.users = new UsersManager(this.rest);
|
|
3237
|
+
const cacheOptions = options.cache?.enabled ? { ttlMs: options.cache.ttlMs, maxSize: options.cache.maxEntries } : null;
|
|
3238
|
+
this.caches = cacheOptions ? {
|
|
3239
|
+
guilds: new Cache(cacheOptions),
|
|
3240
|
+
channels: new Cache(cacheOptions),
|
|
3241
|
+
members: new Cache(cacheOptions),
|
|
3242
|
+
roles: new Cache(cacheOptions)
|
|
3243
|
+
} : null;
|
|
3244
|
+
}
|
|
3245
|
+
/** Opções com que o cliente foi criado (sem o token). */
|
|
3246
|
+
get optionsSafe() {
|
|
3247
|
+
const { token: _token, ...rest } = this.options;
|
|
3248
|
+
return rest;
|
|
3249
|
+
}
|
|
3250
|
+
/** Id da conta do bot (disponível após `login()`). */
|
|
3251
|
+
get userId() {
|
|
3252
|
+
return this.userIdValue;
|
|
3253
|
+
}
|
|
3254
|
+
/** A conta do bot como `User` (após `login()`). */
|
|
3255
|
+
get user() {
|
|
3256
|
+
return this.botInfo ? new User(this, this.botInfo.user) : null;
|
|
3257
|
+
}
|
|
3258
|
+
/** O bot na visão do dono (após `login()`). */
|
|
3259
|
+
get application() {
|
|
3260
|
+
return this.botInfo;
|
|
3261
|
+
}
|
|
3262
|
+
/** O gateway, se conectado (ou em conexão). */
|
|
3263
|
+
get gateway() {
|
|
3264
|
+
return this.gatewayInstance;
|
|
3265
|
+
}
|
|
3266
|
+
/** `true` quando o gateway está autenticado e recebendo eventos. */
|
|
3267
|
+
isReady() {
|
|
3268
|
+
return this.gatewayInstance?.isReady ?? false;
|
|
3269
|
+
}
|
|
3270
|
+
/** Latência do gateway (último heartbeat → ack), em ms. */
|
|
3271
|
+
get ping() {
|
|
3272
|
+
return this.gatewayInstance?.heartbeatLatencyMs ?? null;
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3275
|
+
* Autentica e conecta. Valida o token (`GET /bots/@me`), conecta ao
|
|
3276
|
+
* gateway (a menos que `restOnly`) e resolve no `ready`.
|
|
3277
|
+
*/
|
|
3278
|
+
async login() {
|
|
3279
|
+
if (this.destroyed) throw new SquordTypeError("O cliente foi destru\xEDdo");
|
|
3280
|
+
if (this.userIdValue) return;
|
|
3281
|
+
this.logger.info("validando token do bot");
|
|
3282
|
+
this.botInfo = await this.bot.fetchCurrent();
|
|
3283
|
+
this.userIdValue = this.botInfo.user.id;
|
|
3284
|
+
this.logger.info("token validado", { botId: this.botInfo.id, username: this.botInfo.user.username });
|
|
3285
|
+
if (this.options.restOnly) {
|
|
3286
|
+
this.logger.info("modo REST-only: sem gateway");
|
|
3287
|
+
this.emit("ready", {
|
|
3288
|
+
user: new User(this, this.botInfo.user),
|
|
3289
|
+
sessionId: "",
|
|
3290
|
+
guilds: [],
|
|
3291
|
+
ping: null
|
|
3292
|
+
});
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
if (this.options.gracefulShutdown && !this.shutdownSignalsBound) {
|
|
3296
|
+
this.shutdownSignalsBound = true;
|
|
3297
|
+
const shutdown = () => {
|
|
3298
|
+
void this.destroy().then(() => process.exit(0));
|
|
3299
|
+
};
|
|
3300
|
+
process.once("SIGINT", shutdown);
|
|
3301
|
+
process.once("SIGTERM", shutdown);
|
|
3302
|
+
}
|
|
3303
|
+
const gateway = this.ensureGateway();
|
|
3304
|
+
const ready = await gateway.connect();
|
|
3305
|
+
this.userIdValue = ready.userId || this.userIdValue;
|
|
3306
|
+
this.emit("ready", {
|
|
3307
|
+
user: this.user ?? new User(this, this.botInfo.user),
|
|
3308
|
+
sessionId: ready.sessionId,
|
|
3309
|
+
guilds: ready.guilds,
|
|
3310
|
+
ping: this.ping
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Encerra o cliente: fecha o gateway, cancela requisições pendentes e
|
|
3315
|
+
* remove listeners. Seguro chamar mais de uma vez.
|
|
3316
|
+
*/
|
|
3317
|
+
async destroy() {
|
|
3318
|
+
if (this.destroyed) return;
|
|
3319
|
+
this.destroyed = true;
|
|
3320
|
+
if (this.gatewayInstance) {
|
|
3321
|
+
await this.gatewayInstance.destroy();
|
|
3322
|
+
this.gatewayInstance = null;
|
|
3323
|
+
}
|
|
3324
|
+
this.rest.destroy();
|
|
3325
|
+
this.removeAllListeners();
|
|
3326
|
+
}
|
|
3327
|
+
/** Presença do bot: `ONLINE`, `IDLE`, `DND` ou `INVISIBLE`, com status personalizado. */
|
|
3328
|
+
setPresence(presence) {
|
|
3329
|
+
if (!this.gatewayInstance) {
|
|
3330
|
+
throw new SquordTypeError("Conecte o gateway antes de mudar a presen\xE7a (login())");
|
|
3331
|
+
}
|
|
3332
|
+
const update = {
|
|
3333
|
+
status: presence.status,
|
|
3334
|
+
customStatus: presence.customStatus === void 0 ? void 0 : presence.customStatus,
|
|
3335
|
+
afk: presence.afk
|
|
3336
|
+
};
|
|
3337
|
+
this.gatewayInstance.setPresence(update);
|
|
3338
|
+
}
|
|
3339
|
+
/** Força reconexão do gateway (tenta `resume`). */
|
|
3340
|
+
reconnect() {
|
|
3341
|
+
this.gatewayInstance?.reconnectNow();
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Permissões efetivas do bot em um canal, resolvidas localmente a partir
|
|
3345
|
+
* dos cargos e overwrites (com o cache, quando habilitado). O backend
|
|
3346
|
+
* continua sendo a autoridade final.
|
|
3347
|
+
*/
|
|
3348
|
+
async myPermissionsIn(channelId) {
|
|
3349
|
+
assertSnowflake(channelId, "channelId");
|
|
3350
|
+
const channel = this.caches?.channels.get(channelId) ?? await this.channels.fetch(channelId);
|
|
3351
|
+
this.caches?.channels.set(channelId, channel);
|
|
3352
|
+
const guildId = channel.guildId;
|
|
3353
|
+
const guild = this.caches?.guilds.get(guildId) ?? await this.guilds.fetch(guildId);
|
|
3354
|
+
this.caches?.guilds.set(guildId, guild);
|
|
3355
|
+
const roles = this.caches?.roles.get(guildId) ?? await this.roles.list(guildId);
|
|
3356
|
+
this.caches?.roles.set(guildId, roles);
|
|
3357
|
+
const memberKey = `${guildId}:${this.userIdValue}`;
|
|
3358
|
+
const me = this.caches?.members.get(memberKey) ?? await this.members.fetch(guildId, this.userIdValue);
|
|
3359
|
+
this.caches?.members.set(memberKey, me);
|
|
3360
|
+
const roleById = new Map(roles.map((role) => [role.id, role]));
|
|
3361
|
+
const everyonePermissions = BigInt(
|
|
3362
|
+
(roleById.get(guildId) ?? roles.find((r) => r.id === guildId))?.permissions ?? "0"
|
|
3363
|
+
);
|
|
3364
|
+
return resolveChannelPermissions({
|
|
3365
|
+
everyonePermissions,
|
|
3366
|
+
rolePermissions: me.roleIds.map((roleId) => roleById.get(roleId)).filter((role) => role !== void 0).map((role) => BigInt(role.permissions)),
|
|
3367
|
+
overwrites: channel.overwrites,
|
|
3368
|
+
memberId: me.id,
|
|
3369
|
+
everyoneRoleId: guildId,
|
|
3370
|
+
memberRoleIds: me.roleIds
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
/** Testa se o bot tem um conjunto de permissões em um canal. */
|
|
3374
|
+
async hasPermissionsIn(channelId, ...flags) {
|
|
3375
|
+
const bits = await this.myPermissionsIn(channelId);
|
|
3376
|
+
return flags.every((flag) => (bits & flag) === flag);
|
|
3377
|
+
}
|
|
3378
|
+
/** Atalho para `Permissions` (evita import extra em código de bot). */
|
|
3379
|
+
get Permissions() {
|
|
3380
|
+
return Permissions;
|
|
3381
|
+
}
|
|
3382
|
+
// ------------------------------------------------------------------
|
|
3383
|
+
ensureGateway() {
|
|
3384
|
+
if (this.gatewayInstance) return this.gatewayInstance;
|
|
3385
|
+
const gateway = new Gateway({
|
|
3386
|
+
url: this.options.gatewayURL,
|
|
3387
|
+
token: this.options.token,
|
|
3388
|
+
ws: this.options.ws,
|
|
3389
|
+
logger: this.logger,
|
|
3390
|
+
reconnectBaseMs: this.options.gatewayReconnectBaseMs
|
|
3391
|
+
});
|
|
3392
|
+
gateway.on("resumed", (resumed) => {
|
|
3393
|
+
this.emit("resumed", resumed);
|
|
3394
|
+
this.logger.info("sess\xE3o retomada", { replayed: resumed.replayed });
|
|
3395
|
+
});
|
|
3396
|
+
gateway.on("reconnecting", (info) => {
|
|
3397
|
+
this.emit("reconnecting", info);
|
|
3398
|
+
this.logger.warn("reconectando ao gateway", info);
|
|
3399
|
+
});
|
|
3400
|
+
gateway.on("disconnect", (info) => {
|
|
3401
|
+
this.emit("disconnect", info);
|
|
3402
|
+
if (!info.fatal) this.emit("warn", `Conex\xE3o do gateway caiu (c\xF3digo ${info.code})`);
|
|
3403
|
+
});
|
|
3404
|
+
gateway.on("error", (error) => {
|
|
3405
|
+
this.emit("error", error);
|
|
3406
|
+
});
|
|
3407
|
+
gateway.on("invalidSession", () => {
|
|
3408
|
+
this.emit("warn", "Sess\xE3o do gateway n\xE3o p\xF4de ser retomada; refazendo identify");
|
|
3409
|
+
});
|
|
3410
|
+
gateway.on("raw", (frame) => {
|
|
3411
|
+
this.emit("raw", frame);
|
|
3412
|
+
});
|
|
3413
|
+
gateway.on("event", (event) => {
|
|
3414
|
+
this.dispatchGatewayEvent(event);
|
|
3415
|
+
});
|
|
3416
|
+
this.gatewayInstance = gateway;
|
|
3417
|
+
return gateway;
|
|
3418
|
+
}
|
|
3419
|
+
dispatchGatewayEvent(event) {
|
|
3420
|
+
const { type, d } = event;
|
|
3421
|
+
const data = d;
|
|
3422
|
+
try {
|
|
3423
|
+
switch (type) {
|
|
3424
|
+
case "message.created":
|
|
3425
|
+
case "message.updated":
|
|
3426
|
+
this.emit(type, new Message(this, data.message));
|
|
3427
|
+
break;
|
|
3428
|
+
case "member.joined":
|
|
3429
|
+
case "member.updated":
|
|
3430
|
+
this.emit(type, {
|
|
3431
|
+
guildId: data.guildId,
|
|
3432
|
+
member: new Member(this, data.member)
|
|
3433
|
+
});
|
|
3434
|
+
break;
|
|
3435
|
+
case "guild.updated":
|
|
3436
|
+
this.emit(type, new Guild(this, data.guild));
|
|
3437
|
+
break;
|
|
3438
|
+
case "channel.created":
|
|
3439
|
+
case "channel.updated":
|
|
3440
|
+
this.emit(type, new Channel(this, data.channel));
|
|
3441
|
+
break;
|
|
3442
|
+
case "role.created":
|
|
3443
|
+
case "role.updated":
|
|
3444
|
+
this.emit(type, new Role(this, data.role));
|
|
3445
|
+
break;
|
|
3446
|
+
case "thread.created":
|
|
3447
|
+
case "thread.updated":
|
|
3448
|
+
this.emit(type, new Thread(this, data.thread));
|
|
3449
|
+
break;
|
|
3450
|
+
case "interaction.created":
|
|
3451
|
+
this.emit(type, new Interaction(this, d));
|
|
3452
|
+
break;
|
|
3453
|
+
default:
|
|
3454
|
+
if (this.listenerCount(type) > 0) {
|
|
3455
|
+
this.emit(type, d);
|
|
3456
|
+
} else if (isKnownEventType(type)) {
|
|
3457
|
+
this.emit(type, d);
|
|
3458
|
+
} else {
|
|
3459
|
+
this.emit("unknownEvent", event);
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
} catch (err) {
|
|
3463
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
var KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
3468
|
+
"message.deleted",
|
|
3469
|
+
"message.bulk_deleted",
|
|
3470
|
+
"poll.vote.cast",
|
|
3471
|
+
"poll.vote.removed",
|
|
3472
|
+
"reaction.added",
|
|
3473
|
+
"reaction.removed",
|
|
3474
|
+
"member.left",
|
|
3475
|
+
"member.banned",
|
|
3476
|
+
"member.unbanned",
|
|
3477
|
+
"guild.deleted",
|
|
3478
|
+
"guild.taken_down",
|
|
3479
|
+
"guild.boost.added",
|
|
3480
|
+
"guild.boost.removed",
|
|
3481
|
+
"channel.deleted",
|
|
3482
|
+
"channel.tree.invalidated",
|
|
3483
|
+
"role.deleted",
|
|
3484
|
+
"thread.deleted",
|
|
3485
|
+
"emoji.created",
|
|
3486
|
+
"emoji.updated",
|
|
3487
|
+
"emoji.deleted",
|
|
3488
|
+
"sticker.created",
|
|
3489
|
+
"sticker.updated",
|
|
3490
|
+
"sticker.deleted",
|
|
3491
|
+
"guild_event.created",
|
|
3492
|
+
"guild_event.updated",
|
|
3493
|
+
"guild_event.deleted",
|
|
3494
|
+
"announcement.follow.created",
|
|
3495
|
+
"announcement.follow.deleted",
|
|
3496
|
+
"voice.state.updated",
|
|
3497
|
+
"voice.room.updated",
|
|
3498
|
+
"voice.member.moved",
|
|
3499
|
+
"voice.e2ee.bot_key_requested",
|
|
3500
|
+
"application_command.updated"
|
|
3501
|
+
]);
|
|
3502
|
+
function isKnownEventType(type) {
|
|
3503
|
+
return KNOWN_EVENT_TYPES.has(type);
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// src/commands/CommandRegistry.ts
|
|
3507
|
+
var CommandRegistry = class {
|
|
3508
|
+
constructor(client, options = {}) {
|
|
3509
|
+
this.client = client;
|
|
3510
|
+
this.options = options;
|
|
3511
|
+
this.prefixResolver = options.prefix ?? "!";
|
|
3512
|
+
}
|
|
3513
|
+
client;
|
|
3514
|
+
options;
|
|
3515
|
+
textCommands = /* @__PURE__ */ new Map();
|
|
3516
|
+
slashCommands = /* @__PURE__ */ new Map();
|
|
3517
|
+
componentHandlers = /* @__PURE__ */ new Map();
|
|
3518
|
+
cooldowns = /* @__PURE__ */ new Map();
|
|
3519
|
+
prefixResolver;
|
|
3520
|
+
attached = false;
|
|
3521
|
+
/** Registra um comando de texto. */
|
|
3522
|
+
text(definition) {
|
|
3523
|
+
validateTextDefinition(definition);
|
|
3524
|
+
const names = [definition.name, ...definition.aliases ?? []];
|
|
3525
|
+
for (const name of names) {
|
|
3526
|
+
const key = name.toLowerCase();
|
|
3527
|
+
if (this.textCommands.has(key)) {
|
|
3528
|
+
throw new SquordTypeError(`Comando de texto duplicado: ${name}`);
|
|
3529
|
+
}
|
|
3530
|
+
this.textCommands.set(key, definition);
|
|
3531
|
+
}
|
|
3532
|
+
this.ensureAttached();
|
|
3533
|
+
return this;
|
|
3534
|
+
}
|
|
3535
|
+
/** Registra um comando slash. */
|
|
3536
|
+
slash(definition) {
|
|
3537
|
+
if (!/^[a-z0-9_-]{1,32}$/.test(definition.name)) {
|
|
3538
|
+
throw new SquordTypeError(
|
|
3539
|
+
`Nome de comando inv\xE1lido: ${definition.name} (use letras min\xFAsculas, n\xFAmeros, _ e -, at\xE9 32 caracteres)`
|
|
3540
|
+
);
|
|
3541
|
+
}
|
|
3542
|
+
if (this.slashCommands.has(definition.name)) {
|
|
3543
|
+
throw new SquordTypeError(`Comando slash duplicado: ${definition.name}`);
|
|
3544
|
+
}
|
|
3545
|
+
this.slashCommands.set(definition.name, definition);
|
|
3546
|
+
this.ensureAttached();
|
|
3547
|
+
return this;
|
|
3548
|
+
}
|
|
3549
|
+
/** Registra um handler de componente (botão ou menu). */
|
|
3550
|
+
component(definition) {
|
|
3551
|
+
if (!definition.customId) throw new SquordTypeError("customId \xE9 obrigat\xF3rio");
|
|
3552
|
+
if (this.componentHandlers.has(definition.customId)) {
|
|
3553
|
+
throw new SquordTypeError(`Handler de componente duplicado: ${definition.customId}`);
|
|
3554
|
+
}
|
|
3555
|
+
this.componentHandlers.set(definition.customId, definition);
|
|
3556
|
+
this.ensureAttached();
|
|
3557
|
+
return this;
|
|
3558
|
+
}
|
|
3559
|
+
/** Remove um handler de componente. */
|
|
3560
|
+
removeComponent(customId) {
|
|
3561
|
+
this.componentHandlers.delete(customId);
|
|
3562
|
+
return this;
|
|
3563
|
+
}
|
|
3564
|
+
/** Definições slash prontas para o `PUT` em bloco. */
|
|
3565
|
+
toCommandsInput() {
|
|
3566
|
+
return [...this.slashCommands.values()].map((command) => ({
|
|
3567
|
+
name: command.name,
|
|
3568
|
+
description: command.description,
|
|
3569
|
+
options: command.options ?? []
|
|
3570
|
+
}));
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* Sincroniza os slash commands com o Squord (PUT em bloco, sobrescrevendo
|
|
3574
|
+
* o que existir em cada escopo). Chame após `client.login()` — ou deixe o
|
|
3575
|
+
* registro fazer automaticamente no `ready` (`syncOnLogin`, o padrão).
|
|
3576
|
+
*/
|
|
3577
|
+
async sync() {
|
|
3578
|
+
const global = [];
|
|
3579
|
+
const guildScope = /* @__PURE__ */ new Map();
|
|
3580
|
+
for (const command of this.slashCommands.values()) {
|
|
3581
|
+
const input = {
|
|
3582
|
+
name: command.name,
|
|
3583
|
+
description: command.description,
|
|
3584
|
+
options: command.options ?? []
|
|
3585
|
+
};
|
|
3586
|
+
const scope = command.scope ?? { kind: "global" };
|
|
3587
|
+
if (scope.kind === "global") {
|
|
3588
|
+
global.push(input);
|
|
3589
|
+
} else {
|
|
3590
|
+
for (const guildId of scope.guildIds) {
|
|
3591
|
+
const list = guildScope.get(guildId) ?? [];
|
|
3592
|
+
list.push(input);
|
|
3593
|
+
guildScope.set(guildId, list);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
if (global.length > 0) {
|
|
3598
|
+
await this.client.commands.setGlobal(global);
|
|
3599
|
+
}
|
|
3600
|
+
for (const [guildId, commands] of guildScope) {
|
|
3601
|
+
await this.client.commands.setGuild(guildId, commands);
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
/** Limpa os cooldowns (útil em testes). */
|
|
3605
|
+
clearCooldowns() {
|
|
3606
|
+
this.cooldowns.clear();
|
|
3607
|
+
}
|
|
3608
|
+
/** Processa manualmente uma mensagem como possível comando de texto (para testes). */
|
|
3609
|
+
async handleTextMessage(message) {
|
|
3610
|
+
const prefix = this.resolvePrefix(message);
|
|
3611
|
+
if (prefix === null) return false;
|
|
3612
|
+
const content = message.content;
|
|
3613
|
+
if (!content.startsWith(prefix)) return false;
|
|
3614
|
+
const line = content.slice(prefix.length).trim();
|
|
3615
|
+
if (!line) return false;
|
|
3616
|
+
const spaceIndex = line.search(/\s/);
|
|
3617
|
+
const invokedName = (spaceIndex === -1 ? line : line.slice(0, spaceIndex)).toLowerCase();
|
|
3618
|
+
const rawArgs = spaceIndex === -1 ? "" : line.slice(spaceIndex).trim();
|
|
3619
|
+
const definition = this.textCommands.get(invokedName);
|
|
3620
|
+
if (!definition) return false;
|
|
3621
|
+
if (definition.guildIds && !definition.guildIds.includes(message.guildId ?? "")) {
|
|
3622
|
+
return false;
|
|
3623
|
+
}
|
|
3624
|
+
if ((definition.ignoreBots ?? this.options.ignoreBots ?? true) && message.author.isBot) {
|
|
3625
|
+
return false;
|
|
3626
|
+
}
|
|
3627
|
+
if (definition.cooldownMs && definition.cooldownMs > 0) {
|
|
3628
|
+
const key = `${message.authorId}:${definition.name}`;
|
|
3629
|
+
const until = this.cooldowns.get(key) ?? 0;
|
|
3630
|
+
const now = Date.now();
|
|
3631
|
+
if (until > now) return false;
|
|
3632
|
+
this.cooldowns.set(key, now + definition.cooldownMs);
|
|
3633
|
+
}
|
|
3634
|
+
const args = splitArguments(rawArgs);
|
|
3635
|
+
const context = {
|
|
3636
|
+
client: this.client,
|
|
3637
|
+
message,
|
|
3638
|
+
name: invokedName,
|
|
3639
|
+
command: definition,
|
|
3640
|
+
args,
|
|
3641
|
+
rawArgs,
|
|
3642
|
+
reply: (payload) => message.reply(payload),
|
|
3643
|
+
replyWithMention: (payload) => {
|
|
3644
|
+
const mention = `<@${message.authorId}>`;
|
|
3645
|
+
const body = typeof payload === "string" ? { content: `${mention} ${payload}` } : { ...payload };
|
|
3646
|
+
if (typeof payload !== "string" && body.content) {
|
|
3647
|
+
body.content = `${mention} ${body.content}`;
|
|
3648
|
+
}
|
|
3649
|
+
return message.reply(body);
|
|
3650
|
+
},
|
|
3651
|
+
react: (emoji) => message.react(emoji)
|
|
3652
|
+
};
|
|
3653
|
+
try {
|
|
3654
|
+
if (definition.beforeExecute) {
|
|
3655
|
+
const allowed = await definition.beforeExecute(context);
|
|
3656
|
+
if (allowed === false) return false;
|
|
3657
|
+
}
|
|
3658
|
+
await definition.execute(context);
|
|
3659
|
+
} catch (error) {
|
|
3660
|
+
this.reportError(error, { command: definition.name, message });
|
|
3661
|
+
}
|
|
3662
|
+
return true;
|
|
3663
|
+
}
|
|
3664
|
+
/** Processa manualmente uma interação (para testes). */
|
|
3665
|
+
async handleInteraction(interaction) {
|
|
3666
|
+
try {
|
|
3667
|
+
if (interaction.isCommand) {
|
|
3668
|
+
const definition = this.slashCommands.get(interaction.command?.name ?? "");
|
|
3669
|
+
if (!definition) return false;
|
|
3670
|
+
await definition.execute(interaction);
|
|
3671
|
+
return true;
|
|
3672
|
+
}
|
|
3673
|
+
if (interaction.isComponent) {
|
|
3674
|
+
const handler = this.matchComponent(interaction.customId ?? "");
|
|
3675
|
+
if (!handler) return false;
|
|
3676
|
+
await handler.execute(interaction);
|
|
3677
|
+
return true;
|
|
3678
|
+
}
|
|
3679
|
+
} catch (error) {
|
|
3680
|
+
this.reportError(error, { interaction, command: interaction.command?.name });
|
|
3681
|
+
}
|
|
3682
|
+
return false;
|
|
3683
|
+
}
|
|
3684
|
+
matchComponent(customId) {
|
|
3685
|
+
const exact = this.componentHandlers.get(customId);
|
|
3686
|
+
if (exact) return exact;
|
|
3687
|
+
for (const [pattern, handler] of this.componentHandlers) {
|
|
3688
|
+
if (pattern.endsWith("*") && customId.startsWith(pattern.slice(0, -1))) {
|
|
3689
|
+
return handler;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
return void 0;
|
|
3693
|
+
}
|
|
3694
|
+
resolvePrefix(message) {
|
|
3695
|
+
if (typeof this.prefixResolver === "function") {
|
|
3696
|
+
return this.prefixResolver(message);
|
|
3697
|
+
}
|
|
3698
|
+
return this.prefixResolver;
|
|
3699
|
+
}
|
|
3700
|
+
ensureAttached() {
|
|
3701
|
+
if (this.attached) return;
|
|
3702
|
+
this.attached = true;
|
|
3703
|
+
this.client.on("message.created", (message) => {
|
|
3704
|
+
void this.handleTextMessage(message);
|
|
3705
|
+
});
|
|
3706
|
+
this.client.on("interaction.created", (interaction) => {
|
|
3707
|
+
void this.handleInteraction(interaction);
|
|
3708
|
+
});
|
|
3709
|
+
if (this.options.syncOnLogin !== false) {
|
|
3710
|
+
const runSync = () => {
|
|
3711
|
+
void this.sync().catch((error) => {
|
|
3712
|
+
this.reportError(error, {});
|
|
3713
|
+
});
|
|
3714
|
+
};
|
|
3715
|
+
if (this.client.isReady()) {
|
|
3716
|
+
runSync();
|
|
3717
|
+
} else {
|
|
3718
|
+
this.client.once("ready", runSync);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
reportError(error, context) {
|
|
3723
|
+
if (this.options.onError) {
|
|
3724
|
+
this.options.onError(error, context);
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
this.client.emit("commandError", { error, ...context });
|
|
3728
|
+
}
|
|
3729
|
+
};
|
|
3730
|
+
function validateTextDefinition(definition) {
|
|
3731
|
+
if (!definition.name || typeof definition.name !== "string") {
|
|
3732
|
+
throw new SquordTypeError("Comando de texto precisa de name");
|
|
3733
|
+
}
|
|
3734
|
+
if (/\s/.test(definition.name)) {
|
|
3735
|
+
throw new SquordTypeError(`Nome de comando n\xE3o pode ter espa\xE7os: ${definition.name}`);
|
|
3736
|
+
}
|
|
3737
|
+
if (typeof definition.execute !== "function") {
|
|
3738
|
+
throw new SquordTypeError(`Comando ${definition.name} precisa de execute()`);
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
// src/rest/pagination.ts
|
|
3743
|
+
async function* paginate(fetchPage, firstParams = {}, direction = "before") {
|
|
3744
|
+
let params = { ...firstParams };
|
|
3745
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
3746
|
+
for (; ; ) {
|
|
3747
|
+
const page = await fetchPage(params);
|
|
3748
|
+
for (const item of page.items) {
|
|
3749
|
+
yield item;
|
|
3750
|
+
}
|
|
3751
|
+
if (!page.hasMore) return;
|
|
3752
|
+
const nextCursor = direction === "before" ? page.prevCursor : page.nextCursor;
|
|
3753
|
+
if (!nextCursor || seenCursors.has(nextCursor)) return;
|
|
3754
|
+
seenCursors.add(nextCursor);
|
|
3755
|
+
params = direction === "before" ? { ...params, limit: params.limit, before: nextCursor, after: void 0 } : { ...params, limit: params.limit, after: nextCursor, before: void 0 };
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
async function collectPagination(fetchPage, firstParams = {}, direction = "before", maxItems = 1e4) {
|
|
3759
|
+
const items = [];
|
|
3760
|
+
for await (const item of paginate(fetchPage, firstParams, direction)) {
|
|
3761
|
+
items.push(item);
|
|
3762
|
+
if (items.length >= maxItems) break;
|
|
3763
|
+
}
|
|
3764
|
+
return items;
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
// src/intents.ts
|
|
3768
|
+
var Intents = {
|
|
3769
|
+
/** Space, canais, cargos, threads, emojis, stickers, eventos do Space. */
|
|
3770
|
+
GUILDS: 1 << 0,
|
|
3771
|
+
/** Entrada, saída, edição e ban de membros. */
|
|
3772
|
+
GUILD_MEMBERS: 1 << 1,
|
|
3773
|
+
/** `message.*` e votos de enquete. */
|
|
3774
|
+
GUILD_MESSAGES: 1 << 2,
|
|
3775
|
+
/** Reações. */
|
|
3776
|
+
GUILD_MESSAGE_REACTIONS: 1 << 3,
|
|
3777
|
+
/** Estado e salas de voz. */
|
|
3778
|
+
GUILD_VOICE_STATES: 1 << 4,
|
|
3779
|
+
/**
|
|
3780
|
+
* **Privilegiada.** Não liga evento nenhum: devolve o conteúdo das
|
|
3781
|
+
* mensagens. Sem ela, `content`, `embeds`, `attachments`, `components`
|
|
3782
|
+
* e `poll` chegam esvaziados — exceto mensagens do próprio bot e as que
|
|
3783
|
+
* o mencionam.
|
|
3784
|
+
*/
|
|
3785
|
+
MESSAGE_CONTENT: 1 << 5
|
|
3786
|
+
};
|
|
3787
|
+
var DEFAULT_INTENTS = Intents.GUILDS | Intents.GUILD_MESSAGES | Intents.GUILD_MESSAGE_REACTIONS;
|
|
3788
|
+
function intentsValue(...flags) {
|
|
3789
|
+
const value = flags.reduce((a, b) => a | b, 0);
|
|
3790
|
+
if (value < 0 || value > 65535) {
|
|
3791
|
+
throw new RangeError("As intents somam um inteiro de 0 a 65535");
|
|
3792
|
+
}
|
|
3793
|
+
return value;
|
|
3794
|
+
}
|
|
3795
|
+
function listIntents(value) {
|
|
3796
|
+
const names = [];
|
|
3797
|
+
for (const [name, flag] of Object.entries(Intents)) {
|
|
3798
|
+
if ((value & flag) === flag) names.push(name);
|
|
3799
|
+
}
|
|
3800
|
+
return names;
|
|
3801
|
+
}
|
|
3802
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3803
|
+
0 && (module.exports = {
|
|
3804
|
+
ALL_PERMISSIONS,
|
|
3805
|
+
ActionRowBuilder,
|
|
3806
|
+
ApplicationCommandsManager,
|
|
3807
|
+
AuthenticationError,
|
|
3808
|
+
BotManager,
|
|
3809
|
+
ButtonBuilder,
|
|
3810
|
+
Buttons,
|
|
3811
|
+
Cache,
|
|
3812
|
+
Channel,
|
|
3813
|
+
ChannelsManager,
|
|
3814
|
+
CommandRegistry,
|
|
3815
|
+
ComponentLimits,
|
|
3816
|
+
DEFAULT_INTENTS,
|
|
3817
|
+
EmbedBuilder,
|
|
3818
|
+
EmbedLimits,
|
|
3819
|
+
Gateway,
|
|
3820
|
+
GatewayError,
|
|
3821
|
+
Guild,
|
|
3822
|
+
GuildsManager,
|
|
3823
|
+
Intents,
|
|
3824
|
+
Interaction,
|
|
3825
|
+
InteractionsManager,
|
|
3826
|
+
InvalidTokenError,
|
|
3827
|
+
Member,
|
|
3828
|
+
MembersManager,
|
|
3829
|
+
Message,
|
|
3830
|
+
MessagesManager,
|
|
3831
|
+
ModerationManager,
|
|
3832
|
+
NetworkError,
|
|
3833
|
+
NotFoundError,
|
|
3834
|
+
PermissionError,
|
|
3835
|
+
Permissions,
|
|
3836
|
+
RateLimitError,
|
|
3837
|
+
RequestTimeoutError,
|
|
3838
|
+
RestClient,
|
|
3839
|
+
Role,
|
|
3840
|
+
RolesManager,
|
|
3841
|
+
SUGGESTED_MODERATION_PERMISSIONS,
|
|
3842
|
+
SUGGESTED_TEXT_BOT_PERMISSIONS,
|
|
3843
|
+
SelectMenuBuilder,
|
|
3844
|
+
SquordAPIError,
|
|
3845
|
+
SquordClient,
|
|
3846
|
+
SquordError,
|
|
3847
|
+
SquordTypeError,
|
|
3848
|
+
Thread,
|
|
3849
|
+
UploadsManager,
|
|
3850
|
+
User,
|
|
3851
|
+
UsersManager,
|
|
3852
|
+
ValidationError,
|
|
3853
|
+
assertSnowflake,
|
|
3854
|
+
attachWebSocket,
|
|
3855
|
+
backoffDelay,
|
|
3856
|
+
channelMention,
|
|
3857
|
+
collectPagination,
|
|
3858
|
+
compareSnowflakes,
|
|
3859
|
+
createConsoleLogger,
|
|
3860
|
+
hasAnyPermission,
|
|
3861
|
+
hasPermissions,
|
|
3862
|
+
intentsValue,
|
|
3863
|
+
isValidSnowflake,
|
|
3864
|
+
listIntents,
|
|
3865
|
+
loggerFromEnv,
|
|
3866
|
+
makeAPIError,
|
|
3867
|
+
noopLogger,
|
|
3868
|
+
normalizeEmbedColor,
|
|
3869
|
+
paginate,
|
|
3870
|
+
parsePermissions,
|
|
3871
|
+
permissionsToString,
|
|
3872
|
+
redact,
|
|
3873
|
+
resolveChannelPermissions,
|
|
3874
|
+
resolveMessagePayload,
|
|
3875
|
+
roleMention,
|
|
3876
|
+
routeKeyOf,
|
|
3877
|
+
sleep,
|
|
3878
|
+
splitArguments,
|
|
3879
|
+
userMention
|
|
3880
|
+
});
|
|
3881
|
+
//# sourceMappingURL=index.cjs.map
|