@raven_app/raven.js 0.0.0-stage → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3076 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/index.js
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ AuthenticationError: () => AuthenticationError,
33
+ BOT_STATUSES: () => BOT_STATUSES,
34
+ CHANNEL_TYPES: () => CHANNEL_TYPES,
35
+ CommandHandler: () => CommandHandler,
36
+ DEFAULT_ADMIN_PERMS: () => DEFAULT_ADMIN_PERMS,
37
+ DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
38
+ DEFAULT_EVERYONE_PERMS: () => DEFAULT_EVERYONE_PERMS,
39
+ ETHAN_KEY_PREFIX: () => ETHAN_KEY_PREFIX,
40
+ ETHAN_MESSAGE_MAX: () => ETHAN_MESSAGE_MAX,
41
+ ETHAN_MODEL: () => ETHAN_MODEL,
42
+ ETHAN_PERSONALITIES: () => ETHAN_PERSONALITIES,
43
+ EthanClient: () => EthanClient,
44
+ GATEWAY_EVENTS: () => GATEWAY_EVENTS,
45
+ GatewayNotAvailableError: () => GatewayNotAvailableError,
46
+ MemoryCooldownStore: () => MemoryCooldownStore,
47
+ MessagePoller: () => MessagePoller,
48
+ MissingScopeError: () => MissingScopeError,
49
+ NotFoundError: () => NotFoundError,
50
+ NotInstalledError: () => NotInstalledError,
51
+ PERMISSIONS: () => PERMISSIONS,
52
+ PERMISSION_MASK: () => PERMISSION_MASK,
53
+ RateLimitedError: () => RateLimitedError,
54
+ RavenAPIError: () => RavenAPIError,
55
+ RavenAuthError: () => RavenAuthError,
56
+ RavenBot: () => RavenBot,
57
+ RavenClient: () => RavenClient,
58
+ RavenError: () => RavenError,
59
+ RavenGateway: () => RavenGateway,
60
+ RavenNotFoundError: () => RavenNotFoundError,
61
+ RavenPermissionError: () => RavenPermissionError,
62
+ RavenRateLimitError: () => RavenRateLimitError,
63
+ RavenValidationError: () => RavenValidationError,
64
+ ValidationError: () => ValidationError,
65
+ assertChannelName: () => assertChannelName,
66
+ assertChannelType: () => assertChannelType,
67
+ assertEthanMessage: () => assertEthanMessage,
68
+ assertPersonality: () => assertPersonality,
69
+ assertRoleColor: () => assertRoleColor,
70
+ assertRoleName: () => assertRoleName,
71
+ assertRolePermissions: () => assertRolePermissions,
72
+ assertTemperature: () => assertTemperature,
73
+ canAssignRole: () => canAssignRole,
74
+ canManageRole: () => canManageRole,
75
+ canModerate: () => canModerate,
76
+ createBot: () => createBot,
77
+ createClient: () => createClient,
78
+ createCommandHandler: () => createCommandHandler,
79
+ createEthanClient: () => createEthanClient,
80
+ editDistance: () => editDistance,
81
+ escapeContent: () => escapeContent,
82
+ formatError: () => formatError,
83
+ getHighestRole: () => getHighestRole,
84
+ getMemberPermissions: () => getMemberPermissions,
85
+ hasPermission: () => hasPermission,
86
+ invalidChoice: () => invalidChoice,
87
+ isBotAuthorId: () => isBotAuthorId,
88
+ markDiff: () => markDiff,
89
+ missingArgument: () => missingArgument,
90
+ normalizeRole: () => normalizeRole,
91
+ parseMentions: () => parseMentions,
92
+ sanitizeContent: () => sanitizeContent,
93
+ sortRoles: () => sortRoles,
94
+ suggest: () => suggest,
95
+ suggestAll: () => suggestAll,
96
+ toCamel: () => toCamel,
97
+ unknownOption: () => unknownOption,
98
+ withSuggestions: () => withSuggestions
99
+ });
100
+ module.exports = __toCommonJS(index_exports);
101
+
102
+ // src/errors.js
103
+ var RavenError = class extends Error {
104
+ constructor(message, { status = 0, code = "RAVEN_ERROR", details = null, retryAfter = null } = {}) {
105
+ super(message);
106
+ this.name = "RavenError";
107
+ this.status = status;
108
+ this.code = code;
109
+ this.details = details;
110
+ this.retryAfter = retryAfter;
111
+ }
112
+ };
113
+ var RavenAPIError = class extends RavenError {
114
+ constructor(message, { status = 0, code = "API_ERROR", endpoint = null, details = null, retryAfter = null } = {}) {
115
+ super(message, { status, code, details, retryAfter });
116
+ this.name = "RavenAPIError";
117
+ this.endpoint = endpoint;
118
+ }
119
+ };
120
+ var RavenAuthError = class extends RavenAPIError {
121
+ constructor(message = "Invalid or missing bot token. Use Authorization: Bot <rvb_...>.", { endpoint = null, details = null } = {}) {
122
+ super(message, { status: 401, code: "AUTHENTICATION_ERROR", endpoint, details });
123
+ this.name = "RavenAuthError";
124
+ }
125
+ };
126
+ var RavenPermissionError = class extends RavenAPIError {
127
+ constructor(message, { status = 403, code = "PERMISSION_ERROR", endpoint = null, details = null } = {}) {
128
+ super(message, { status, code, endpoint, details });
129
+ this.name = "RavenPermissionError";
130
+ }
131
+ };
132
+ var RavenRateLimitError = class extends RavenAPIError {
133
+ constructor(retryAfter = null, message = "Bot rate limit exceeded. Slow down.", { endpoint = null, details = null } = {}) {
134
+ super(message, { status: 429, code: "RATE_LIMITED", endpoint, details, retryAfter });
135
+ this.name = "RavenRateLimitError";
136
+ }
137
+ };
138
+ var RavenValidationError = class extends RavenAPIError {
139
+ constructor(message, { endpoint = null, details = null } = {}) {
140
+ super(message, { status: 400, code: "VALIDATION_ERROR", endpoint, details });
141
+ this.name = "RavenValidationError";
142
+ }
143
+ };
144
+ var RavenNotFoundError = class extends RavenAPIError {
145
+ constructor(message = "Not found.", { code = "NOT_FOUND", endpoint = null, details = null } = {}) {
146
+ super(message, { status: 404, code, endpoint, details });
147
+ this.name = "RavenNotFoundError";
148
+ }
149
+ };
150
+ var AuthenticationError = class extends RavenAuthError {
151
+ constructor(message = "Invalid or missing bot token. Use Authorization: Bot <rvb_...>.", details = null, endpoint = null) {
152
+ super(message, { endpoint, details });
153
+ this.name = "AuthenticationError";
154
+ }
155
+ };
156
+ var MissingScopeError = class extends RavenPermissionError {
157
+ constructor(scope, details = null, endpoint = null) {
158
+ super(`Missing scope: ${scope}`, { status: 403, code: "MISSING_SCOPE", endpoint, details });
159
+ this.name = "MissingScopeError";
160
+ this.scope = scope;
161
+ }
162
+ };
163
+ var NotInstalledError = class extends RavenAPIError {
164
+ constructor(message = "Guild not found or bot not installed.", details = null, endpoint = null) {
165
+ super(message, { status: 404, code: "NOT_INSTALLED", endpoint, details });
166
+ this.name = "NotInstalledError";
167
+ }
168
+ };
169
+ var NotFoundError = class extends RavenNotFoundError {
170
+ constructor(message = "Not found.", details = null, endpoint = null) {
171
+ super(message, { endpoint, details });
172
+ this.name = "NotFoundError";
173
+ }
174
+ };
175
+ var ValidationError = class extends RavenValidationError {
176
+ constructor(message, details = null, endpoint = null) {
177
+ super(message, { endpoint, details });
178
+ this.name = "ValidationError";
179
+ }
180
+ };
181
+ var RateLimitedError = class extends RavenRateLimitError {
182
+ constructor(retryAfter = null, message = "Bot rate limit exceeded. Slow down.", details = null, endpoint = null) {
183
+ super(retryAfter, message, { endpoint, details });
184
+ this.name = "RateLimitedError";
185
+ }
186
+ };
187
+ var GatewayNotAvailableError = class extends RavenError {
188
+ constructor(message = "Bot gateway is not available on this server version. Use client.onMessage() (REST polling) instead.") {
189
+ super(message, { status: 501, code: "GATEWAY_NOT_AVAILABLE" });
190
+ this.name = "GatewayNotAvailableError";
191
+ }
192
+ };
193
+ function toRavenError(status, body, { endpoint = null } = {}) {
194
+ const msg = body?.error || `Request failed with status ${status}`;
195
+ const retryAfter = body?.retry_after ?? null;
196
+ if (status === 401) return new AuthenticationError(msg, body, endpoint);
197
+ if (status === 429) return new RateLimitedError(retryAfter, msg, body, endpoint);
198
+ if (status === 400) return new ValidationError(msg, body, endpoint);
199
+ if (status === 403) {
200
+ if (/missing scope/i.test(msg)) {
201
+ const m = msg.match(/missing scope:\s*(\S+)/i);
202
+ return new MissingScopeError(m ? m[1] : "unknown", body, endpoint);
203
+ }
204
+ if (/not installed/i.test(msg)) return new NotInstalledError(msg, body, endpoint);
205
+ return new RavenPermissionError(msg, { status, code: "FORBIDDEN", endpoint, details: body });
206
+ }
207
+ if (status === 404) {
208
+ if (/not installed|guild not found/i.test(msg)) return new NotInstalledError(msg, body, endpoint);
209
+ return new NotFoundError(msg, body, endpoint);
210
+ }
211
+ return new RavenAPIError(msg, { status, code: "API_ERROR", endpoint, details: body, retryAfter });
212
+ }
213
+
214
+ // src/http.js
215
+ var TOKEN_PREFIX = "rvb_";
216
+ var ETHAN_KEY_PREFIX = "rve_";
217
+ var DEFAULT_BASE_URL = "https://ravenapp.xyz";
218
+ function assertToken(token, prefix = TOKEN_PREFIX) {
219
+ if (!token || typeof token !== "string" || !token.startsWith(prefix)) {
220
+ throw new Error(`Invalid token: expected a string starting with "${prefix}".`);
221
+ }
222
+ if (token.length < 20 || token.length > 120) {
223
+ throw new Error("Invalid token: unexpected token length.");
224
+ }
225
+ }
226
+ function assertEthanKey(key) {
227
+ assertToken(key, ETHAN_KEY_PREFIX);
228
+ }
229
+ function buildUrl(path2, query = {}) {
230
+ const base = DEFAULT_BASE_URL.replace(/\/+$/, "");
231
+ const url = new URL(base + path2);
232
+ for (const [k, v] of Object.entries(query)) {
233
+ if (v === void 0 || v === null || v === "") continue;
234
+ url.searchParams.set(k, String(v));
235
+ }
236
+ return url.toString();
237
+ }
238
+ function sleep(ms) {
239
+ return new Promise((r) => setTimeout(r, ms));
240
+ }
241
+ async function ravenRequest({
242
+ token,
243
+ path: path2,
244
+ method = "GET",
245
+ query = {},
246
+ body,
247
+ timeoutMs = 15e3,
248
+ retryOn429 = true,
249
+ maxRetries = 1,
250
+ // Bot API: scheme "Bot" + prefix "rvb_".
251
+ // Ethan API: scheme "Bearer" + prefix "rve_" (Plus/Pro key).
252
+ authScheme = "Bot",
253
+ tokenPrefix = TOKEN_PREFIX
254
+ }) {
255
+ assertToken(token, tokenPrefix);
256
+ const url = buildUrl(path2, query);
257
+ const controller = new AbortController();
258
+ const t = setTimeout(() => controller.abort(), timeoutMs);
259
+ let res;
260
+ try {
261
+ res = await fetch(url, {
262
+ method,
263
+ signal: controller.signal,
264
+ headers: {
265
+ Authorization: `${authScheme} ${token}`,
266
+ ...body !== void 0 ? { "Content-Type": "application/json" } : {}
267
+ },
268
+ body: body !== void 0 ? JSON.stringify(body) : void 0
269
+ });
270
+ } catch (err) {
271
+ clearTimeout(t);
272
+ if (err?.name === "AbortError") throw new Error(`Request timed out after ${timeoutMs}ms: ${method} ${path2}`);
273
+ throw err;
274
+ }
275
+ clearTimeout(t);
276
+ let json = null;
277
+ try {
278
+ json = await res.json();
279
+ } catch {
280
+ json = null;
281
+ }
282
+ if (res.status === 429 && retryOn429 && maxRetries > 0) {
283
+ const retryAfter = json?.retry_after ?? Number(res.headers.get("retry-after")) ?? 1;
284
+ await sleep(Math.min(Math.max(Number(retryAfter) || 1, 1), 10) * 1e3);
285
+ return ravenRequest({ token, path: path2, method, query, body, timeoutMs, retryOn429: false, maxRetries: 0, authScheme, tokenPrefix });
286
+ }
287
+ if (!res.ok) throw toRavenError(res.status, json, { endpoint: `${method} ${path2}` });
288
+ void RateLimitedError;
289
+ return json;
290
+ }
291
+
292
+ // src/errors/format.js
293
+ var import_node_fs = require("node:fs");
294
+ var import_node_path = __toESM(require("node:path"), 1);
295
+ var import_node_url = require("node:url");
296
+
297
+ // src/suggest.js
298
+ function editDistance(a, b) {
299
+ const s = String(a);
300
+ const t = String(b);
301
+ if (s === t) return 0;
302
+ if (!s.length) return t.length;
303
+ if (!t.length) return s.length;
304
+ let prev2 = new Array(t.length + 1);
305
+ let prev = new Array(t.length + 1);
306
+ let curr = new Array(t.length + 1);
307
+ for (let j = 0; j <= t.length; j++) prev[j] = j;
308
+ for (let i = 1; i <= s.length; i++) {
309
+ curr[0] = i;
310
+ for (let j = 1; j <= t.length; j++) {
311
+ const cost = s[i - 1] === t[j - 1] ? 0 : 1;
312
+ curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
313
+ if (i > 1 && j > 1 && s[i - 1] === t[j - 2] && s[i - 2] === t[j - 1]) {
314
+ curr[j] = Math.min(curr[j], prev2[j - 2] + 1);
315
+ }
316
+ }
317
+ const tmp = prev2;
318
+ prev2 = prev;
319
+ prev = curr;
320
+ curr = tmp;
321
+ }
322
+ return prev[t.length];
323
+ }
324
+ function threshold(input, candidate) {
325
+ return Math.max(1, Math.floor(Math.max(String(input).length, String(candidate).length) * 0.4));
326
+ }
327
+ var LEET = { 0: "o", 1: "l", 3: "e", 4: "a", 5: "s", 7: "t" };
328
+ function fold(s) {
329
+ return String(s).toLowerCase().replace(/[013457]/g, (ch) => LEET[ch]);
330
+ }
331
+ function suggestAll(input, candidates = [], { limit = 3 } = {}) {
332
+ const text = String(input ?? "");
333
+ if (!text) return [];
334
+ const out = [];
335
+ for (const c of candidates ?? []) {
336
+ const value = String(c);
337
+ if (!value || value === text) continue;
338
+ const distance = editDistance(fold(text), fold(value));
339
+ if (distance > threshold(text, value)) continue;
340
+ const confidence = Math.max(0, Math.round((1 - distance / Math.max(text.length, value.length)) * 100) / 100);
341
+ out.push({ value, distance, confidence });
342
+ }
343
+ out.sort((a, b) => a.distance - b.distance || a.value.localeCompare(b.value));
344
+ return out.slice(0, Math.max(0, limit));
345
+ }
346
+ function suggest(input, candidates, options) {
347
+ return suggestAll(input, candidates, { ...options, limit: 1 })[0] ?? null;
348
+ }
349
+ var PROTOCOL_PROPS = /* @__PURE__ */ new Set(["then", "toJSON"]);
350
+ function unknownMethod(label, received, candidates, display) {
351
+ const top = suggestAll(received, candidates, { limit: 3 });
352
+ const err = new RavenError(`Unknown method: ${label}.${received}()`, { status: 0, code: "UNKNOWN_METHOD" });
353
+ err.received = `${label}.${received}()`;
354
+ if (top.length) {
355
+ err.suggestion = `${label}.${top[0].value}()`;
356
+ err.suggestions = top.map((s) => `${label}.${s.value}()`);
357
+ }
358
+ err.suggestionsHint = "Use the full method name to continue.";
359
+ err.validTitle = "Available methods";
360
+ err.valid = [...display ?? candidates].sort();
361
+ return err;
362
+ }
363
+ function withSuggestions(target, label) {
364
+ const proto = Object.getPrototypeOf(target);
365
+ const methods = proto ? Object.getOwnPropertyNames(proto).filter((m) => m !== "constructor") : [];
366
+ const candidates = [.../* @__PURE__ */ new Set([...methods, ...Object.keys(target)])];
367
+ const display = [...new Set(methods)];
368
+ return new Proxy(target, {
369
+ get(t, prop, recv) {
370
+ if (typeof prop === "symbol") return Reflect.get(t, prop, recv);
371
+ if (PROTOCOL_PROPS.has(prop)) return void 0;
372
+ if (prop in t) return Reflect.get(t, prop, recv);
373
+ throw unknownMethod(label, String(prop), candidates, display);
374
+ }
375
+ });
376
+ }
377
+
378
+ // src/errors/format.js
379
+ var import_meta = {};
380
+ var SRC_DIR = (() => {
381
+ try {
382
+ if (typeof import_meta !== "undefined" && import_meta.url) {
383
+ return import_node_path.default.resolve(import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)), "..");
384
+ }
385
+ } catch {
386
+ }
387
+ try {
388
+ if (typeof __dirname !== "undefined" && __dirname) {
389
+ return import_node_path.default.resolve(__dirname, "..");
390
+ }
391
+ } catch {
392
+ }
393
+ return "";
394
+ })();
395
+ var HTTP_REASON = {
396
+ 400: "Bad Request",
397
+ 401: "Unauthorized",
398
+ 403: "Forbidden",
399
+ 404: "Not Found",
400
+ 429: "Too Many Requests",
401
+ 500: "Internal Server Error",
402
+ 502: "Bad Gateway",
403
+ 503: "Service Unavailable"
404
+ };
405
+ var RUNTIME_WITH_COLUMNS = /* @__PURE__ */ new Set(["ReferenceError", "TypeError", "SyntaxError", "RangeError"]);
406
+ var IDENT_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
407
+ var STOPWORDS = /* @__PURE__ */ new Set([
408
+ "const",
409
+ "let",
410
+ "var",
411
+ "function",
412
+ "return",
413
+ "await",
414
+ "async",
415
+ "new",
416
+ "true",
417
+ "false",
418
+ "null",
419
+ "undefined",
420
+ "this",
421
+ "typeof",
422
+ "require",
423
+ "import",
424
+ "export",
425
+ "from",
426
+ "if",
427
+ "else"
428
+ ]);
429
+ var FRAME_RE = /at (?:.+?\()?(.+?):(\d+):(\d+)\)?$/;
430
+ function normalizeFile(file) {
431
+ if (file.startsWith("file://")) {
432
+ try {
433
+ return (0, import_node_url.fileURLToPath)(file);
434
+ } catch {
435
+ return file;
436
+ }
437
+ }
438
+ return file;
439
+ }
440
+ function parseStack(stack) {
441
+ const frames = [];
442
+ for (const line of String(stack ?? "").split("\n").slice(1)) {
443
+ const m = FRAME_RE.exec(line.trim());
444
+ if (m) frames.push({ file: normalizeFile(m[1]), line: Number(m[2]), column: Number(m[3]) });
445
+ }
446
+ return frames;
447
+ }
448
+ function isSkipped(file) {
449
+ if (!file || file.startsWith("node:")) return true;
450
+ if (!SRC_DIR) return false;
451
+ return file === SRC_DIR || file.startsWith(SRC_DIR + import_node_path.default.sep);
452
+ }
453
+ function pickFrame(err) {
454
+ const frames = parseStack(err?.stack);
455
+ return frames.find((f) => !isSkipped(f.file)) ?? frames.find((f) => !f.file.startsWith("node:")) ?? null;
456
+ }
457
+ function readWindow(file, line, radius = 2) {
458
+ try {
459
+ const text = (0, import_node_fs.readFileSync)(file, "utf8");
460
+ if (text.length > 512 * 1024) return null;
461
+ const lines = text.split("\n");
462
+ if (line < 1 || line > lines.length) return null;
463
+ return { lines, start: Math.max(1, line - radius), end: Math.min(lines.length, line + radius) };
464
+ } catch {
465
+ return null;
466
+ }
467
+ }
468
+ function harvestIdentifiers(win, exclude) {
469
+ const found = /* @__PURE__ */ new Set();
470
+ for (let i = win.start; i <= win.end; i++) {
471
+ for (const m of win.lines[i - 1].matchAll(IDENT_RE)) {
472
+ const id = m[0];
473
+ if (id.length < 2 || id === exclude || STOPWORDS.has(id)) continue;
474
+ found.add(id);
475
+ }
476
+ }
477
+ return [...found];
478
+ }
479
+ function misspelledOf(err) {
480
+ if (err?.name !== "ReferenceError") return null;
481
+ const msg = String(err.message ?? "");
482
+ return /(.+?) is not defined/.exec(msg)?.[1] ?? /Cannot access '(.+?)' before/.exec(msg)?.[1] ?? null;
483
+ }
484
+ function rel(file, baseDir) {
485
+ try {
486
+ const r = import_node_path.default.relative(baseDir, file);
487
+ return r && !r.startsWith("..") ? r : file;
488
+ } catch {
489
+ return file;
490
+ }
491
+ }
492
+ function makeColors(enabled) {
493
+ if (!enabled) {
494
+ const id = (s) => s;
495
+ return { bold: id, red: id, dim: id, yellow: id };
496
+ }
497
+ const wrap = (code) => (s) => `\x1B[${code}m${s}\x1B[0m`;
498
+ return { bold: wrap(1), red: wrap(31), dim: wrap(2), yellow: wrap(33) };
499
+ }
500
+ function markDiff(received, suggested) {
501
+ const a = String(received);
502
+ const b = String(suggested);
503
+ if (a === b) return null;
504
+ let p = 0;
505
+ while (p < a.length && p < b.length && a[p] === b[p]) p++;
506
+ let s = 0;
507
+ while (s < b.length - p && s < a.length - p && a[a.length - 1 - s] === b[b.length - 1 - s]) s++;
508
+ const start = p;
509
+ const end = b.length - s;
510
+ if (end <= start) return "^".repeat(Math.max(1, b.length));
511
+ return " ".repeat(start) + "^".repeat(end - start);
512
+ }
513
+ function unknownOption({ option, valid, label = "command option" }) {
514
+ const s = suggest(option, valid);
515
+ const err = new RavenError(`Unknown ${label} "${option}"`, { status: 0, code: "UNKNOWN_OPTION" });
516
+ err.received = option;
517
+ if (s) err.suggestion = s.value;
518
+ err.validTitle = "Available options";
519
+ err.valid = [...valid].sort();
520
+ return err;
521
+ }
522
+ function invalidChoice({ label, received, valid, validTitle }) {
523
+ const s = suggest(received, valid);
524
+ const err = new RavenError(`Unknown ${label} "${received}"`, { status: 0, code: "INVALID_VALUE" });
525
+ err.received = String(received);
526
+ if (s) err.suggestion = s.value;
527
+ err.validTitle = validTitle ?? `Valid ${label}s`;
528
+ err.valid = [...valid];
529
+ return err;
530
+ }
531
+ function missingArgument({ label, param, example }) {
532
+ const err = new RavenError(`Missing required argument: ${param}`, { status: 0, code: "MISSING_ARGUMENT" });
533
+ err.received = `${label}()`;
534
+ err.expected = `${label}(${param})`;
535
+ if (example) err.example = example;
536
+ return err;
537
+ }
538
+ function formatError(err, options = {}) {
539
+ try {
540
+ const baseDir = options.baseDir ?? process.cwd();
541
+ const useColor = options.color ?? (process.stdout?.isTTY === true && !process.env.NO_COLOR);
542
+ const c = makeColors(useColor);
543
+ const name = err?.name || "Error";
544
+ const message = String(err?.message ?? err);
545
+ const [first, ...rest] = message.split("\n");
546
+ const out = [c.bold(c.red(`\u2716 ${name}: ${first}`))];
547
+ if (rest.join("\n").trim()) out.push("", ...rest);
548
+ const frame = options.file ? { file: options.file, line: options.line ?? 1, column: options.column ?? 1 } : pickFrame(err);
549
+ let win = null;
550
+ if (frame) {
551
+ out.push("", ` ${c.dim("at")} ${rel(frame.file, baseDir)}:${frame.line}:${frame.column}`);
552
+ win = readWindow(frame.file, frame.line);
553
+ if (win) {
554
+ out.push("");
555
+ for (let i = win.start; i <= win.end; i++) {
556
+ const no = String(i).padStart(5, " ");
557
+ const mark = i === frame.line ? c.red("\u203A") : " ";
558
+ out.push(` ${mark}${c.dim(no + " \u2502")} ${win.lines[i - 1]}`);
559
+ if (i === frame.line && RUNTIME_WITH_COLUMNS.has(name) && frame.column >= 1) {
560
+ out.push(` ${" ".repeat(9 + Math.max(0, frame.column - 1))}${c.red("^".repeat(3))}`);
561
+ }
562
+ }
563
+ }
564
+ }
565
+ let suggestion = err?.suggestion ?? null;
566
+ let receivedRaw = err?.received ?? null;
567
+ if (!suggestion && win) {
568
+ const miss = misspelledOf(err);
569
+ if (miss) {
570
+ const wide = readWindow(frame.file, frame.line, 8) ?? win;
571
+ suggestion = suggest(miss, harvestIdentifiers(wide, miss))?.value ?? null;
572
+ if (suggestion) receivedRaw = miss;
573
+ }
574
+ }
575
+ if (err instanceof RavenAPIError) {
576
+ const reason = HTTP_REASON[err.status];
577
+ out.push("", ` Status: ${err.status}${reason ? ` ${reason}` : ""}`);
578
+ if (err.endpoint) out.push(` Endpoint: ${err.endpoint}`);
579
+ if (err.code) out.push(` Code: ${err.code}`);
580
+ }
581
+ const suggestions = err?.suggestions ?? (suggestion ? [suggestion] : []);
582
+ if (suggestions.length > 1) {
583
+ out.push("", ` ${c.yellow("Did you mean one of these?")}`);
584
+ suggestions.slice(0, 5).forEach((s, i) => out.push(` ${i + 1}. ${s}`));
585
+ out.push("", ` ${err.suggestionsHint ?? "Use the full method name to continue."}`);
586
+ } else if (suggestions.length === 1) {
587
+ out.push("", ` ${c.yellow("Did you mean:")}`, ` ${suggestions[0]}`);
588
+ if (receivedRaw) {
589
+ const marks = markDiff(receivedRaw, suggestions[0]);
590
+ if (marks) out.push(` ${marks}`);
591
+ }
592
+ }
593
+ if (err?.scope) out.push("", " Missing scope:", ` ${err.scope}`);
594
+ if (err?.received) out.push("", " Received:", ` ${err.received}`);
595
+ if (err?.expected) out.push("", " Expected:", ` ${err.expected}`);
596
+ if (err?.example) out.push("", " Example:", ` ${err.example}`);
597
+ if (Array.isArray(err?.valid) && err.valid.length) {
598
+ out.push("", ` ${err.validTitle ?? "Available options:"}`);
599
+ for (const v of err.valid.slice(0, 12)) out.push(` ${v}`);
600
+ }
601
+ if (Array.isArray(err?.hints) && err.hints.length) {
602
+ out.push("");
603
+ for (const h of err.hints.slice(0, 5)) out.push(` ${c.dim(h)}`);
604
+ }
605
+ if (err?.docs) out.push("", " Learn more:", ` ${err.docs}`);
606
+ return out.join("\n");
607
+ } catch {
608
+ try {
609
+ return `${err?.name || "Error"}: ${err?.message || err}`;
610
+ } catch {
611
+ return "Unknown error";
612
+ }
613
+ }
614
+ }
615
+
616
+ // src/resources/me.js
617
+ var BOT_STATUSES = ["online", "idle", "dnd"];
618
+ function assertStatus(status) {
619
+ const s = String(status || "").toLowerCase();
620
+ if (!BOT_STATUSES.includes(s)) {
621
+ throw invalidChoice({ label: "status", received: String(status), valid: BOT_STATUSES, validTitle: "Valid statuses" });
622
+ }
623
+ return s;
624
+ }
625
+ var MeResource = class {
626
+ constructor(ctx) {
627
+ this.ctx = ctx;
628
+ }
629
+ async get() {
630
+ const json = await this.ctx.request("GET", "/api/v1/users/@me");
631
+ return {
632
+ id: json.id,
633
+ username: json.username,
634
+ avatar: json.avatar,
635
+ applicationId: json.application_id,
636
+ scopes: json.scopes ?? [],
637
+ bot: json.bot ?? true,
638
+ status: json.status ?? "offline",
639
+ statusLocked: json.status_locked ?? false,
640
+ raw: json
641
+ };
642
+ }
643
+ /**
644
+ * Set the bot status (online, idle, dnd). Pass null/undefined/"" to
645
+ * release the manual lock and let the server manage by activity again.
646
+ * Returns { status, locked, shown }.
647
+ */
648
+ async setStatus(status) {
649
+ let body;
650
+ if (status === void 0 || status === null || status === "") {
651
+ body = {};
652
+ } else {
653
+ body = { status: assertStatus(status) };
654
+ }
655
+ const json = await this.ctx.request("PATCH", "/api/v1/users/@me", { body });
656
+ return { status: json.status, locked: json.locked ?? true, shown: json.shown ?? json.status, raw: json };
657
+ }
658
+ };
659
+
660
+ // src/resources/guilds.js
661
+ var GuildsResource = class {
662
+ constructor(ctx) {
663
+ this.ctx = ctx;
664
+ }
665
+ async list() {
666
+ const json = await this.ctx.request("GET", "/api/v1/guilds");
667
+ return (json.guilds ?? []).map((g) => ({
668
+ id: g.id,
669
+ name: g.name,
670
+ icon: g.icon ?? null,
671
+ ownerId: g.owner_id,
672
+ memberCount: g.member_count ?? 0,
673
+ raw: g
674
+ }));
675
+ }
676
+ async get(guildId) {
677
+ if (!guildId) {
678
+ throw missingArgument({
679
+ label: "bot.client.guilds.get",
680
+ param: "guildId",
681
+ example: `bot.client.guilds.get("123456789")`
682
+ });
683
+ }
684
+ const json = await this.ctx.request("GET", `/api/v1/guilds/${encodeURIComponent(guildId)}`);
685
+ return {
686
+ id: json.id,
687
+ name: json.name,
688
+ description: json.description ?? "",
689
+ icon: json.icon ?? null,
690
+ ownerId: json.owner_id,
691
+ memberCount: json.member_count ?? 0,
692
+ verified: json.verified ?? false,
693
+ raw: json
694
+ };
695
+ }
696
+ async stats(guildId) {
697
+ if (!guildId) {
698
+ throw missingArgument({
699
+ label: "bot.client.guilds.stats",
700
+ param: "guildId",
701
+ example: `bot.client.guilds.stats("123456789")`
702
+ });
703
+ }
704
+ const json = await this.ctx.request("GET", `/api/v1/guilds/${encodeURIComponent(guildId)}/stats`);
705
+ return {
706
+ guildId: json.guild_id,
707
+ memberCount: json.member_count ?? 0,
708
+ ...json
709
+ };
710
+ }
711
+ };
712
+
713
+ // src/resources/channels.js
714
+ var CHANNEL_TYPES = ["text", "voice"];
715
+ function assertChannelType(type) {
716
+ const t = String(type || "").toLowerCase();
717
+ if (!CHANNEL_TYPES.includes(t)) {
718
+ throw invalidChoice({ label: "type", received: String(type), valid: CHANNEL_TYPES, validTitle: "Valid channel types" });
719
+ }
720
+ return t;
721
+ }
722
+ function assertChannelName(name) {
723
+ const s = String(name ?? "").replace(/<\/?script/gi, "").trim().replace(/^\s*(?:#\s*)+/, "");
724
+ if (!s) throw new Error("Channel name is required (1-100 chars).");
725
+ if (s.length > 100) throw new Error("Channel name must be 100 characters or less.");
726
+ return s;
727
+ }
728
+ function toCamelChannel(c) {
729
+ return {
730
+ id: c.id,
731
+ guildId: c.guild_id ?? c.guildId,
732
+ name: c.name,
733
+ type: c.type,
734
+ topic: c.topic ?? "",
735
+ position: c.position ?? 0,
736
+ parentId: c.parent_id ?? c.parentId ?? null,
737
+ icon: c.icon ?? "",
738
+ raw: c
739
+ };
740
+ }
741
+ var ChannelsResource = class {
742
+ constructor(ctx) {
743
+ this.ctx = ctx;
744
+ }
745
+ async list(guildId) {
746
+ if (!guildId) {
747
+ throw missingArgument({
748
+ label: "bot.client.channels.list",
749
+ param: "guildId",
750
+ example: `bot.client.channels.list("123456789")`
751
+ });
752
+ }
753
+ const json = await this.ctx.request(
754
+ "GET",
755
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/channels`
756
+ );
757
+ return (json.channels ?? []).map((c) => ({
758
+ id: c.id,
759
+ name: c.name,
760
+ type: c.type,
761
+ position: c.position ?? 0,
762
+ parentId: c.parent_id ?? null,
763
+ raw: c
764
+ }));
765
+ }
766
+ /** Single channel by id (guild membership implied by install). */
767
+ async get(channelId) {
768
+ if (!channelId) {
769
+ throw missingArgument({
770
+ label: "bot.client.channels.get",
771
+ param: "channelId",
772
+ example: `bot.client.channels.get("CHANNEL_ID")`
773
+ });
774
+ }
775
+ const json = await this.ctx.request("GET", `/api/v1/channels/${encodeURIComponent(channelId)}`);
776
+ return toCamelChannel(json.channel ?? json);
777
+ }
778
+ /**
779
+ * Create a text/voice channel.
780
+ * opts: { name, type = "text", parentId?, topic? }
781
+ */
782
+ async create(guildId, opts = {}) {
783
+ if (!guildId) {
784
+ throw missingArgument({
785
+ label: "bot.client.channels.create",
786
+ param: "guildId",
787
+ example: `bot.client.channels.create("123456789", { name: "announcements" })`
788
+ });
789
+ }
790
+ const body = { name: assertChannelName(opts.name), type: assertChannelType(opts.type ?? "text") };
791
+ if (opts.parentId !== void 0 && opts.parentId !== null && opts.parentId !== "") {
792
+ body.parent_id = String(opts.parentId);
793
+ }
794
+ if (opts.topic !== void 0) body.topic = String(opts.topic ?? "").slice(0, 1024);
795
+ const json = await this.ctx.request("POST", `/api/v1/guilds/${encodeURIComponent(guildId)}/channels`, { body });
796
+ return toCamelChannel(json.channel ?? json);
797
+ }
798
+ /**
799
+ * Edit a channel. patch: { name?, topic?, parentId? ("" = no category) }
800
+ */
801
+ async update(channelId, patch = {}) {
802
+ if (!channelId) {
803
+ throw missingArgument({
804
+ label: "bot.client.channels.update",
805
+ param: "channelId",
806
+ example: `bot.client.channels.update("CHANNEL_ID", { topic: "hello" })`
807
+ });
808
+ }
809
+ const body = {};
810
+ if (patch.name !== void 0) body.name = assertChannelName(patch.name);
811
+ if (patch.type !== void 0) body.type = assertChannelType(patch.type);
812
+ if (patch.topic !== void 0) body.topic = String(patch.topic ?? "").slice(0, 1024);
813
+ if (patch.parentId !== void 0) body.parent_id = patch.parentId ? String(patch.parentId) : null;
814
+ if (patch.position !== void 0) {
815
+ if (!Number.isInteger(Number(patch.position))) throw new Error("position must be an integer.");
816
+ body.position = Number(patch.position);
817
+ }
818
+ const json = await this.ctx.request("PATCH", `/api/v1/channels/${encodeURIComponent(channelId)}`, { body });
819
+ return toCamelChannel(json.channel ?? json);
820
+ }
821
+ /** Delete a channel (messages go with it). Returns { id }. */
822
+ async delete(channelId) {
823
+ if (!channelId) {
824
+ throw missingArgument({
825
+ label: "bot.client.channels.delete",
826
+ param: "channelId",
827
+ example: `bot.client.channels.delete("CHANNEL_ID")`
828
+ });
829
+ }
830
+ const json = await this.ctx.request("DELETE", `/api/v1/channels/${encodeURIComponent(channelId)}`);
831
+ return { id: json.id ?? json.channel_id ?? channelId, raw: json };
832
+ }
833
+ /**
834
+ * Reorder/move channels: items = [{ id, position?, parentId? }].
835
+ * Returns the fresh channel list.
836
+ */
837
+ async reorder(guildId, items) {
838
+ if (!guildId) {
839
+ throw missingArgument({
840
+ label: "bot.client.channels.reorder",
841
+ param: "guildId",
842
+ example: `bot.client.channels.reorder("123456789", [{ id: "c1", position: 0 }])`
843
+ });
844
+ }
845
+ if (!Array.isArray(items) || !items.length) {
846
+ throw missingArgument({
847
+ label: "bot.client.channels.reorder",
848
+ param: "items",
849
+ example: `bot.client.channels.reorder("123456789", [{ id: "c1", position: 0 }])`
850
+ });
851
+ }
852
+ const json = await this.ctx.request("POST", `/api/v1/guilds/${encodeURIComponent(guildId)}/channels/reorder`, {
853
+ body: {
854
+ items: items.map((it) => ({
855
+ id: String(it.id ?? it.channelId ?? ""),
856
+ ...it.position !== void 0 ? { position: Number(it.position) } : {},
857
+ ...it.parentId !== void 0 ? { parent_id: it.parentId ? String(it.parentId) : null } : {}
858
+ }))
859
+ }
860
+ });
861
+ return (json.channels ?? []).map(toCamelChannel);
862
+ }
863
+ };
864
+
865
+ // src/utils.js
866
+ function sanitizeContent(s) {
867
+ return String(s ?? "").replace(/<\/?script/gi, "").trim().slice(0, 2e3);
868
+ }
869
+ function escapeContent(s) {
870
+ return String(s ?? "").replace(/[<>&"]/g, (c) => ({ "<": "&lt", ">": "&gt", "&": "&amp", '"': "&quot" })[c]);
871
+ }
872
+ function isBotAuthorId(id) {
873
+ return typeof id === "string" && /^bot_\d{16}$/.test(id);
874
+ }
875
+ var KEY_MAP = {
876
+ channel_id: "channelId",
877
+ guild_id: "guildId",
878
+ author_id: "authorId",
879
+ user_id: "userId",
880
+ is_bot: "isBot",
881
+ is_owner: "isOwner",
882
+ bot_id: "botId",
883
+ created_at: "createdAt",
884
+ edited_at: "editedAt",
885
+ joined_at: "joinedAt",
886
+ banned_at: "bannedAt",
887
+ banned_by: "bannedBy",
888
+ expires_at: "expiresAt",
889
+ display_name: "displayName",
890
+ role_ids: "roleIds",
891
+ ordered_ids: "orderedIds",
892
+ member_count: "memberCount",
893
+ owner_id: "ownerId",
894
+ parent_id: "parentId",
895
+ application_id: "applicationId",
896
+ guild_id_hint: "guildId",
897
+ has_more: "hasMore"
898
+ };
899
+ function toCamel(obj) {
900
+ if (!obj || typeof obj !== "object") return obj;
901
+ if (Array.isArray(obj)) return obj.map(toCamel);
902
+ const out = {};
903
+ for (const [k, v] of Object.entries(obj)) out[KEY_MAP[k] ?? k] = v;
904
+ return out;
905
+ }
906
+ function toCamelMessage(m) {
907
+ const c = toCamel(m);
908
+ for (const k of ["createdAt", "editedAt"]) {
909
+ if (c[k] && !(c[k] instanceof Date)) {
910
+ const d = new Date(c[k]);
911
+ if (!Number.isNaN(d.getTime())) c[k] = d;
912
+ }
913
+ }
914
+ return c;
915
+ }
916
+ function parseMentions(content) {
917
+ const text = String(content ?? "");
918
+ const userIds = [...text.matchAll(/<@!?([^>\s]+)>/g)].map((m) => m[1]);
919
+ const everyone = /@everyone/.test(text);
920
+ const here = /@here/.test(text);
921
+ return { userIds, everyone, here };
922
+ }
923
+
924
+ // src/resources/messages.js
925
+ var MessagesResource = class {
926
+ constructor(ctx) {
927
+ this.ctx = ctx;
928
+ }
929
+ normalizeSendArgs(channelId, contentOrOpts) {
930
+ if (!channelId) {
931
+ throw missingArgument({
932
+ label: "bot.client.messages.send",
933
+ param: "channelId",
934
+ example: `bot.client.messages.send("CHANNEL_ID", "hello")`
935
+ });
936
+ }
937
+ let content = "";
938
+ let replyTo;
939
+ if (typeof contentOrOpts === "string") content = contentOrOpts;
940
+ else if (contentOrOpts && typeof contentOrOpts === "object") {
941
+ content = contentOrOpts.content ?? "";
942
+ replyTo = contentOrOpts.replyTo ?? contentOrOpts.reply_to;
943
+ }
944
+ content = sanitizeContent(content);
945
+ if (!content) throw new Error("Content is required (1-2000 chars after sanitizing).");
946
+ return { content, reply_to: replyTo };
947
+ }
948
+ async send(channelId, contentOrOpts) {
949
+ const { content, reply_to } = this.normalizeSendArgs(channelId, contentOrOpts);
950
+ const json = await this.ctx.request("POST", `/api/v1/channels/${encodeURIComponent(channelId)}/messages`, {
951
+ body: reply_to ? { content, reply_to } : { content }
952
+ });
953
+ const m = json.message ?? json;
954
+ return toCamelMessage(m);
955
+ }
956
+ /** Short reply helper: send with reply_to preset. */
957
+ async reply(channelId, messageId, content) {
958
+ return this.send(channelId, { content, reply_to: messageId });
959
+ }
960
+ normalizeEditArgs(channelId, messageId, content) {
961
+ if (!channelId) {
962
+ throw missingArgument({
963
+ label: "bot.client.messages.edit",
964
+ param: "channelId",
965
+ example: `bot.client.messages.edit("CHANNEL_ID", "MSG_ID", "new content")`
966
+ });
967
+ }
968
+ if (!messageId) {
969
+ throw missingArgument({
970
+ label: "bot.client.messages.edit",
971
+ param: "messageId",
972
+ example: `bot.client.messages.edit("CHANNEL_ID", "MSG_ID", "new content")`
973
+ });
974
+ }
975
+ content = sanitizeContent(content);
976
+ if (!content) throw new Error("Content is required (1-2000 chars after sanitizing).");
977
+ return { content };
978
+ }
979
+ /** Edit your own message. Only the author can edit (server enforces). */
980
+ async edit(channelId, messageId, content) {
981
+ const { content: clean } = this.normalizeEditArgs(channelId, messageId, content);
982
+ const json = await this.ctx.request("PATCH", `/api/v1/channels/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}`, {
983
+ body: { content: clean }
984
+ });
985
+ const m = json.message ?? json;
986
+ return toCamelMessage(m);
987
+ }
988
+ /** Delete a message. Own messages always; others need MANAGE_MESSAGES (server enforces). */
989
+ async delete(channelId, messageId) {
990
+ if (!channelId) {
991
+ throw missingArgument({
992
+ label: "bot.client.messages.delete",
993
+ param: "channelId",
994
+ example: `bot.client.messages.delete("CHANNEL_ID", "MSG_ID")`
995
+ });
996
+ }
997
+ if (!messageId) {
998
+ throw missingArgument({
999
+ label: "bot.client.messages.delete",
1000
+ param: "messageId",
1001
+ example: `bot.client.messages.delete("CHANNEL_ID", "MSG_ID")`
1002
+ });
1003
+ }
1004
+ const json = await this.ctx.request("DELETE", `/api/v1/channels/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}`);
1005
+ return { id: json.id ?? messageId, raw: json };
1006
+ }
1007
+ async fetch(channelId, { limit = 50, before } = {}) {
1008
+ if (!channelId) {
1009
+ throw missingArgument({
1010
+ label: "bot.client.messages.fetch",
1011
+ param: "channelId",
1012
+ example: `bot.client.messages.fetch("CHANNEL_ID", { limit: 20 })`
1013
+ });
1014
+ }
1015
+ limit = Math.min(Math.max(Number(limit) || 50, 1), 100);
1016
+ const json = await this.ctx.request(
1017
+ "GET",
1018
+ `/api/v1/channels/${encodeURIComponent(channelId)}/messages`,
1019
+ { query: { limit, ...before ? { before } : {} } }
1020
+ );
1021
+ return {
1022
+ messages: (json.messages ?? []).map(toCamelMessage),
1023
+ hasMore: json.has_more ?? false,
1024
+ raw: json
1025
+ };
1026
+ }
1027
+ /** Auto-paginate history (newest-first pages, returns ascending list). */
1028
+ async fetchAll(channelId, { max = 200, pageSize = 100 } = {}) {
1029
+ let before;
1030
+ const acc = [];
1031
+ while (acc.length < max) {
1032
+ const page = await this.fetch(channelId, { limit: Math.min(pageSize, max - acc.length), before });
1033
+ if (!page.messages.length) break;
1034
+ acc.unshift(...page.messages);
1035
+ if (!page.hasMore) break;
1036
+ before = page.messages[0]?.id;
1037
+ if (!before) break;
1038
+ }
1039
+ return acc.slice(-max);
1040
+ }
1041
+ };
1042
+
1043
+ // src/permissions.js
1044
+ var PERMISSIONS = {
1045
+ VIEW_CHANNEL: 1 << 0,
1046
+ SEND_MESSAGES: 1 << 1,
1047
+ MANAGE_MESSAGES: 1 << 2,
1048
+ MANAGE_CHANNELS: 1 << 3,
1049
+ MANAGE_ROLES: 1 << 4,
1050
+ MANAGE_SERVER: 1 << 5,
1051
+ KICK_MEMBERS: 1 << 6,
1052
+ BAN_MEMBERS: 1 << 7,
1053
+ CREATE_INVITE: 1 << 8,
1054
+ CONNECT: 1 << 9,
1055
+ MUTE_MEMBERS: 1 << 10,
1056
+ ADMINISTRATOR: 1 << 11,
1057
+ VIEW_AUDIT_LOG: 1 << 12
1058
+ };
1059
+ var PERMISSION_MASK = 8191;
1060
+ var DEFAULT_EVERYONE_PERMS = PERMISSIONS.VIEW_CHANNEL | PERMISSIONS.SEND_MESSAGES | PERMISSIONS.CREATE_INVITE | PERMISSIONS.CONNECT;
1061
+ var DEFAULT_ADMIN_PERMS = Object.values(PERMISSIONS).reduce((a, b) => a | b, 0);
1062
+ function normalizeRole(r = {}) {
1063
+ return {
1064
+ id: r.id,
1065
+ name: r.name,
1066
+ color: r.color ?? "#99AAB5",
1067
+ hoist: !!r.hoist,
1068
+ mentionable: !!r.mentionable,
1069
+ position: Number(r.position ?? r.pos ?? 0) || 0,
1070
+ permissions: Number(r.permissions ?? 0) || 0,
1071
+ managed: !!r.managed,
1072
+ raw: r.raw ?? r
1073
+ };
1074
+ }
1075
+ function roleListOf(roles) {
1076
+ return (Array.isArray(roles) ? roles : []).map(normalizeRole);
1077
+ }
1078
+ function getHighestRole(roles, roleIds, isOwner = false) {
1079
+ if (isOwner) return { id: "@owner", name: "Owner", position: Infinity, permissions: DEFAULT_ADMIN_PERMS, managed: true };
1080
+ const all = roleListOf(roles);
1081
+ const ids = new Set(Array.isArray(roleIds) ? roleIds : []);
1082
+ const mine = all.filter((r) => ids.has(r.id));
1083
+ if (!mine.length) return null;
1084
+ return mine.reduce((a, b) => b.position > a.position ? b : a, mine[0]);
1085
+ }
1086
+ function getMemberPermissions(roles, roleIds, isOwner = false) {
1087
+ if (isOwner) return DEFAULT_ADMIN_PERMS;
1088
+ const all = roleListOf(roles);
1089
+ const ids = new Set(Array.isArray(roleIds) ? roleIds : []);
1090
+ let perms = 0;
1091
+ for (const r of all) if (ids.has(r.id)) perms |= r.permissions;
1092
+ if (perms & PERMISSIONS.ADMINISTRATOR) return DEFAULT_ADMIN_PERMS;
1093
+ return perms;
1094
+ }
1095
+ function hasPermission(roles, roleIds, permission, isOwner = false) {
1096
+ const perms = getMemberPermissions(roles, roleIds, isOwner);
1097
+ if (perms & PERMISSIONS.ADMINISTRATOR) return true;
1098
+ return (perms & permission) === permission;
1099
+ }
1100
+ function canManageRole(actorRoles, actorRoleIds, targetRole, { actorIsOwner = false } = {}) {
1101
+ const target = normalizeRole(targetRole);
1102
+ if (actorIsOwner) {
1103
+ if (target.id === "everyone") return { ok: false, reason: "The @everyone role cannot be moved or deleted." };
1104
+ return { ok: true, reason: null };
1105
+ }
1106
+ if (target.id === "everyone") return { ok: false, reason: "The @everyone role cannot be managed." };
1107
+ if (target.managed) return { ok: false, reason: `Role "${target.name}" is managed and cannot be edited.` };
1108
+ const highest = getHighestRole(actorRoles, actorRoleIds, false);
1109
+ if (!highest) return { ok: false, reason: "Actor has no roles on this guild." };
1110
+ if (highest.permissions & PERMISSIONS.ADMINISTRATOR) return { ok: true, reason: null };
1111
+ if (!(highest.permissions & PERMISSIONS.MANAGE_ROLES)) {
1112
+ return { ok: false, reason: "Missing permission: MANAGE_ROLES." };
1113
+ }
1114
+ if (highest.position <= target.position) {
1115
+ return { ok: false, reason: `Role "${target.name}" is equal to or higher than your highest role.` };
1116
+ }
1117
+ return { ok: true, reason: null };
1118
+ }
1119
+ function canAssignRole(actorRoles, actorRoleIds, role, { actorIsOwner = false } = {}) {
1120
+ const target = normalizeRole(role);
1121
+ if (actorIsOwner) return { ok: true, reason: null };
1122
+ if (target.id === "everyone") return { ok: true, reason: null };
1123
+ if (target.managed) return { ok: false, reason: `Role "${target.name}" is managed and cannot be assigned.` };
1124
+ const highest = getHighestRole(actorRoles, actorRoleIds, false);
1125
+ if (!highest) return { ok: false, reason: "Actor has no roles on this guild." };
1126
+ if (highest.permissions & PERMISSIONS.ADMINISTRATOR) return { ok: true, reason: null };
1127
+ if (!(highest.permissions & PERMISSIONS.MANAGE_ROLES)) {
1128
+ return { ok: false, reason: "Missing permission: MANAGE_ROLES." };
1129
+ }
1130
+ if (highest.position <= target.position) {
1131
+ return { ok: false, reason: `Role "${target.name}" is equal to or higher than your highest role.` };
1132
+ }
1133
+ return { ok: true, reason: null };
1134
+ }
1135
+ function canModerate(actorRoles, actorRoleIds, targetRoleIds, { actorIsOwner = false, targetIsOwner = false, self = false } = {}) {
1136
+ if (self) return { ok: false, reason: "You cannot moderate yourself." };
1137
+ if (targetIsOwner) return { ok: false, reason: "You cannot moderate the server owner." };
1138
+ if (actorIsOwner) return { ok: true, reason: null };
1139
+ const actorHighest = getHighestRole(actorRoles, actorRoleIds, false);
1140
+ const targetHighest = getHighestRole(actorRoles, targetRoleIds, false);
1141
+ const actorPos = actorHighest?.position ?? 0;
1142
+ const targetPos = targetHighest?.position ?? 0;
1143
+ if (targetPos >= actorPos) {
1144
+ return { ok: false, reason: "Target has an equal or higher role." };
1145
+ }
1146
+ return { ok: true, reason: null };
1147
+ }
1148
+ function sortRoles(roles) {
1149
+ return roleListOf(roles).sort((a, b) => a.position - b.position);
1150
+ }
1151
+
1152
+ // src/resources/roles.js
1153
+ var COLOR_RE = /^#[0-9A-Fa-f]{6}$/;
1154
+ function needGuild(guildId, label) {
1155
+ if (!guildId) {
1156
+ throw missingArgument({
1157
+ label,
1158
+ param: "guildId",
1159
+ example: `bot.client.roles.list("123456789")`
1160
+ });
1161
+ }
1162
+ }
1163
+ function needRole(roleId, label) {
1164
+ if (!roleId) {
1165
+ throw missingArgument({
1166
+ label,
1167
+ param: "roleId",
1168
+ example: `bot.client.roles.get("123456789", "987654321")`
1169
+ });
1170
+ }
1171
+ }
1172
+ function needUser(userId, label) {
1173
+ if (!userId) {
1174
+ throw missingArgument({
1175
+ label,
1176
+ param: "userId",
1177
+ example: `bot.client.roles.setMemberRoles("123456789", "555", ["everyone"])`
1178
+ });
1179
+ }
1180
+ }
1181
+ function assertRoleName(name) {
1182
+ const s = String(name ?? "").trim().slice(0, 32);
1183
+ if (!s) throw new Error("Role name is required (1-32 chars).");
1184
+ return s;
1185
+ }
1186
+ function assertRoleColor(color) {
1187
+ if (color === void 0 || color === null || color === "") return void 0;
1188
+ if (!COLOR_RE.test(String(color))) {
1189
+ throw invalidChoice({
1190
+ label: "color",
1191
+ received: String(color),
1192
+ valid: ["#FF0000", "#00FF00", "#5865F2", "#99AAB5"],
1193
+ validTitle: "Valid colors look like"
1194
+ });
1195
+ }
1196
+ return String(color);
1197
+ }
1198
+ function assertRolePermissions(permissions) {
1199
+ if (permissions === void 0 || permissions === null) return void 0;
1200
+ const n = Number(permissions);
1201
+ if (!Number.isInteger(n) || n < 0) throw new Error("permissions must be a non-negative integer bitfield.");
1202
+ return n & PERMISSION_MASK;
1203
+ }
1204
+ function toSnakeCreate({ name, color, hoist, mentionable, permissions, position }) {
1205
+ const body = { name: assertRoleName(name) };
1206
+ const c = assertRoleColor(color);
1207
+ if (c !== void 0) body.color = c;
1208
+ if (hoist !== void 0) body.hoist = !!hoist;
1209
+ if (mentionable !== void 0) body.mentionable = !!mentionable;
1210
+ const p = assertRolePermissions(permissions);
1211
+ if (p !== void 0) body.permissions = p;
1212
+ if (position !== void 0) {
1213
+ if (!Number.isInteger(Number(position))) throw new Error("position must be an integer.");
1214
+ body.position = Number(position);
1215
+ }
1216
+ return body;
1217
+ }
1218
+ function toSnakePatch(patch = {}) {
1219
+ const body = {};
1220
+ if (patch.name !== void 0) body.name = assertRoleName(patch.name);
1221
+ if (patch.color !== void 0) {
1222
+ if (!COLOR_RE.test(String(patch.color))) {
1223
+ throw invalidChoice({
1224
+ label: "color",
1225
+ received: String(patch.color),
1226
+ valid: ["#FF0000", "#00FF00", "#5865F2", "#99AAB5"],
1227
+ validTitle: "Valid colors look like"
1228
+ });
1229
+ }
1230
+ body.color = String(patch.color);
1231
+ }
1232
+ if (patch.hoist !== void 0) body.hoist = !!patch.hoist;
1233
+ if (patch.mentionable !== void 0) body.mentionable = !!patch.mentionable;
1234
+ if (patch.permissions !== void 0) {
1235
+ const p = assertRolePermissions(patch.permissions);
1236
+ if (p === void 0) throw new Error("Invalid permissions value.");
1237
+ body.permissions = p;
1238
+ }
1239
+ if (patch.position !== void 0) {
1240
+ if (!Number.isInteger(Number(patch.position))) throw new Error("position must be an integer.");
1241
+ body.position = Number(patch.position);
1242
+ }
1243
+ return body;
1244
+ }
1245
+ function toCamelRole(r) {
1246
+ const n = normalizeRole({
1247
+ id: r.id,
1248
+ name: r.name,
1249
+ color: r.color,
1250
+ hoist: r.hoist,
1251
+ mentionable: r.mentionable,
1252
+ position: r.position,
1253
+ permissions: r.permissions,
1254
+ managed: r.managed
1255
+ });
1256
+ n.raw = r;
1257
+ return n;
1258
+ }
1259
+ var RolesResource = class {
1260
+ constructor(ctx) {
1261
+ this.ctx = ctx;
1262
+ }
1263
+ /** All roles of a guild, ascending by position. */
1264
+ async list(guildId) {
1265
+ needGuild(guildId, "bot.client.roles.list");
1266
+ const json = await this.ctx.request("GET", `/api/v1/guilds/${encodeURIComponent(guildId)}/roles`);
1267
+ return sortRoles(json.roles ?? []).map((r) => {
1268
+ const full = (json.roles ?? []).find((x) => x.id === r.id) ?? r;
1269
+ return toCamelRole(full);
1270
+ });
1271
+ }
1272
+ async get(guildId, roleId) {
1273
+ needGuild(guildId, "bot.client.roles.get");
1274
+ needRole(roleId, "bot.client.roles.get");
1275
+ const json = await this.ctx.request(
1276
+ "GET",
1277
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/roles/${encodeURIComponent(roleId)}`
1278
+ );
1279
+ return toCamelRole(json.role ?? json);
1280
+ }
1281
+ /**
1282
+ * Create a role. Position defaults server-side to just below your highest
1283
+ * role (owner: on top). Throws when the new role would land above you.
1284
+ */
1285
+ async create(guildId, opts = {}) {
1286
+ needGuild(guildId, "bot.client.roles.create");
1287
+ const body = toSnakeCreate(opts);
1288
+ const json = await this.ctx.request("POST", `/api/v1/guilds/${encodeURIComponent(guildId)}/roles`, { body });
1289
+ return toCamelRole(json.role ?? json);
1290
+ }
1291
+ /** Edit name/color/hoist/mentionable/permissions/position. */
1292
+ async update(guildId, roleId, patch = {}) {
1293
+ needGuild(guildId, "bot.client.roles.update");
1294
+ needRole(roleId, "bot.client.roles.update");
1295
+ const body = toSnakePatch(patch);
1296
+ const json = await this.ctx.request(
1297
+ "PATCH",
1298
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/roles/${encodeURIComponent(roleId)}`,
1299
+ { body }
1300
+ );
1301
+ const role = json.role ?? json.roles?.find((r) => r.id === roleId) ?? json;
1302
+ return Array.isArray(json.roles) ? sortRoles(json.roles).map(toCamelRole) : toCamelRole(role);
1303
+ }
1304
+ /** Delete a role (removes it from all members + bots). Returns { id }. */
1305
+ async delete(guildId, roleId) {
1306
+ needGuild(guildId, "bot.client.roles.delete");
1307
+ needRole(roleId, "bot.client.roles.delete");
1308
+ const json = await this.ctx.request(
1309
+ "DELETE",
1310
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/roles/${encodeURIComponent(roleId)}`
1311
+ );
1312
+ return { id: json.id ?? roleId, raw: json };
1313
+ }
1314
+ /** Reorder roles by id array (ascending = low to high). Returns sorted roles. */
1315
+ async reorder(guildId, orderedIds) {
1316
+ needGuild(guildId, "bot.client.roles.reorder");
1317
+ if (!Array.isArray(orderedIds) || !orderedIds.length) {
1318
+ throw missingArgument({
1319
+ label: "bot.client.roles.reorder",
1320
+ param: "orderedIds",
1321
+ example: `bot.client.roles.reorder("123456789", ["everyone", "roleA", "roleB"])`
1322
+ });
1323
+ }
1324
+ const json = await this.ctx.request("POST", `/api/v1/guilds/${encodeURIComponent(guildId)}/roles/reorder`, {
1325
+ body: { ordered_ids: orderedIds.map(String) }
1326
+ });
1327
+ return sortRoles(json.roles ?? []).map(toCamelRole);
1328
+ }
1329
+ /** Role ids of a member (user id or `bot_<botId>`). Always includes "everyone". */
1330
+ async memberRoles(guildId, userId) {
1331
+ needGuild(guildId, "bot.client.roles.memberRoles");
1332
+ needUser(userId, "bot.client.roles.memberRoles");
1333
+ const json = await this.ctx.request(
1334
+ "GET",
1335
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/members/${encodeURIComponent(userId)}/roles`
1336
+ );
1337
+ return { userId: json.user_id ?? userId, roleIds: json.role_ids ?? json.roleIds ?? ["everyone"], raw: json };
1338
+ }
1339
+ /**
1340
+ * Replace a member's roles (full set, "everyone" auto-added).
1341
+ * Fails server-side when a role is above your highest role or the target
1342
+ * outranks you.
1343
+ */
1344
+ async setMemberRoles(guildId, userId, roleIds) {
1345
+ needGuild(guildId, "bot.client.roles.setMemberRoles");
1346
+ needUser(userId, "bot.client.roles.setMemberRoles");
1347
+ if (!Array.isArray(roleIds)) {
1348
+ throw missingArgument({
1349
+ label: "bot.client.roles.setMemberRoles",
1350
+ param: "roleIds",
1351
+ example: `bot.client.roles.setMemberRoles("123456789", "555", ["everyone", "roleA"])`
1352
+ });
1353
+ }
1354
+ const json = await this.ctx.request(
1355
+ "PUT",
1356
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/members/${encodeURIComponent(userId)}/roles`,
1357
+ { body: { role_ids: roleIds.map(String) } }
1358
+ );
1359
+ return { userId: json.user_id ?? json.member?.user_id ?? userId, roleIds: json.role_ids ?? json.member?.role_ids ?? [], raw: json };
1360
+ }
1361
+ /** Convenience: add one role (reads current set first). */
1362
+ async addMemberRole(guildId, userId, roleId) {
1363
+ needRole(roleId, "bot.client.roles.addMemberRole");
1364
+ const cur = await this.memberRoles(guildId, userId);
1365
+ if (cur.roleIds.includes(String(roleId))) return cur;
1366
+ return this.setMemberRoles(guildId, userId, [...cur.roleIds, String(roleId)]);
1367
+ }
1368
+ /** Convenience: remove one role (never removes "everyone"). */
1369
+ async removeMemberRole(guildId, userId, roleId) {
1370
+ needRole(roleId, "bot.client.roles.removeMemberRole");
1371
+ if (String(roleId) === "everyone") throw new Error('The "everyone" role cannot be removed.');
1372
+ const cur = await this.memberRoles(guildId, userId);
1373
+ return this.setMemberRoles(
1374
+ guildId,
1375
+ userId,
1376
+ cur.roleIds.filter((id) => id !== String(roleId))
1377
+ );
1378
+ }
1379
+ // ---- offline hierarchy helpers (same rules the server enforces) ----
1380
+ /** Highest role object of `roleIds` within `roles` (null when none). */
1381
+ highestOf(roles, roleIds, isOwner = false) {
1382
+ return getHighestRole(roles, roleIds, isOwner);
1383
+ }
1384
+ highestPosition(roles, roleIds, isOwner = false) {
1385
+ if (isOwner) return Infinity;
1386
+ return getHighestRole(roles, roleIds, false)?.position ?? 0;
1387
+ }
1388
+ memberPermissions(roles, roleIds, isOwner = false) {
1389
+ return getMemberPermissions(roles, roleIds, isOwner);
1390
+ }
1391
+ hasPermission(roles, roleIds, permission, isOwner = false) {
1392
+ return hasPermission(roles, roleIds, permission, isOwner);
1393
+ }
1394
+ /**
1395
+ * Pre-check: may these `actorRoleIds` manage `targetRole`?
1396
+ * Returns { ok, reason } — the server re-checks authoritatively.
1397
+ */
1398
+ canManage(roles, actorRoleIds, targetRole, opts) {
1399
+ return canManageRole(roles, actorRoleIds, targetRole, opts);
1400
+ }
1401
+ /** Pre-check: may these `actorRoleIds` grant `role` to someone? */
1402
+ canAssign(roles, actorRoleIds, role, opts) {
1403
+ return canAssignRole(roles, actorRoleIds, role, opts);
1404
+ }
1405
+ /** Pre-check: may actor moderate target (position compare)? */
1406
+ canModerate(roles, actorRoleIds, targetRoleIds, opts) {
1407
+ return canModerate(roles, actorRoleIds, targetRoleIds, opts);
1408
+ }
1409
+ };
1410
+
1411
+ // src/resources/members.js
1412
+ function needGuild2(guildId, label) {
1413
+ if (!guildId) {
1414
+ throw missingArgument({
1415
+ label,
1416
+ param: "guildId",
1417
+ example: `bot.client.members.list("123456789")`
1418
+ });
1419
+ }
1420
+ }
1421
+ function needUser2(userId, label) {
1422
+ if (!userId) {
1423
+ throw missingArgument({
1424
+ label,
1425
+ param: "userId",
1426
+ example: `bot.client.members.get("123456789", "555")`
1427
+ });
1428
+ }
1429
+ }
1430
+ function toCamelMember(m) {
1431
+ const c = toCamel(m);
1432
+ if (c.joinedAt && !(c.joinedAt instanceof Date)) {
1433
+ const d = new Date(c.joinedAt);
1434
+ if (!Number.isNaN(d.getTime())) c.joinedAt = d;
1435
+ }
1436
+ if (c.createdAt && !(c.createdAt instanceof Date)) {
1437
+ const d = new Date(c.createdAt);
1438
+ if (!Number.isNaN(d.getTime())) c.createdAt = d;
1439
+ }
1440
+ return c;
1441
+ }
1442
+ function toCamelBan(b) {
1443
+ const c = toCamel(b);
1444
+ for (const k of ["bannedAt", "expiresAt"]) {
1445
+ if (c[k] && !(c[k] instanceof Date)) {
1446
+ const d = new Date(c[k]);
1447
+ if (!Number.isNaN(d.getTime())) c[k] = d;
1448
+ }
1449
+ }
1450
+ return c;
1451
+ }
1452
+ var MembersResource = class {
1453
+ constructor(ctx) {
1454
+ this.ctx = ctx;
1455
+ }
1456
+ /**
1457
+ * List members (users first, then installed bots). Cursor pagination:
1458
+ * `{ limit: 1-100 (default 50), after: "<userId>" }`.
1459
+ */
1460
+ async list(guildId, { limit = 50, after } = {}) {
1461
+ needGuild2(guildId, "bot.client.members.list");
1462
+ limit = Math.min(Math.max(Number(limit) || 50, 1), 100);
1463
+ const json = await this.ctx.request("GET", `/api/v1/guilds/${encodeURIComponent(guildId)}/members`, {
1464
+ query: { limit, ...after ? { after } : {} }
1465
+ });
1466
+ return {
1467
+ members: (json.members ?? []).map(toCamelMember),
1468
+ hasMore: json.has_more ?? false,
1469
+ raw: json
1470
+ };
1471
+ }
1472
+ /** Single member (`userId` or `bot_<botId>`). */
1473
+ async get(guildId, userId) {
1474
+ needGuild2(guildId, "bot.client.members.get");
1475
+ needUser2(userId, "bot.client.members.get");
1476
+ const json = await this.ctx.request(
1477
+ "GET",
1478
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/members/${encodeURIComponent(userId)}`
1479
+ );
1480
+ return toCamelMember(json.member ?? json);
1481
+ }
1482
+ /**
1483
+ * Kick a member (needs KICK_MEMBERS + higher top role, server enforces).
1484
+ * Returns { id }.
1485
+ */
1486
+ async kick(guildId, userId, reason) {
1487
+ needGuild2(guildId, "bot.client.members.kick");
1488
+ needUser2(userId, "bot.client.members.kick");
1489
+ const body = {};
1490
+ if (typeof reason === "string" && reason.trim()) body.reason = reason.trim().slice(0, 256);
1491
+ else if (reason && typeof reason === "object" && reason.reason) {
1492
+ body.reason = String(reason.reason).trim().slice(0, 256);
1493
+ }
1494
+ const json = await this.ctx.request(
1495
+ "POST",
1496
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/members/${encodeURIComponent(userId)}/kick`,
1497
+ { body }
1498
+ );
1499
+ return { id: json.id ?? json.user_id ?? userId, raw: json };
1500
+ }
1501
+ /**
1502
+ * Ban a member. `duration` accepts seconds (number) or strings like
1503
+ * "10m", "1h", "7d", "1w", "permanent" (default). Returns
1504
+ * { userId, reason, expiresAt, duration }.
1505
+ */
1506
+ async ban(guildId, userId, opts = {}) {
1507
+ needGuild2(guildId, "bot.client.members.ban");
1508
+ needUser2(userId, "bot.client.members.ban");
1509
+ const body = {};
1510
+ const o = typeof opts === "string" ? { reason: opts } : opts || {};
1511
+ if (o.reason !== void 0) body.reason = String(o.reason).slice(0, 256);
1512
+ if (o.duration !== void 0) body.duration = o.duration;
1513
+ if (o.durationSec !== void 0) body.duration = o.durationSec;
1514
+ const json = await this.ctx.request(
1515
+ "POST",
1516
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/members/${encodeURIComponent(userId)}/ban`,
1517
+ { body }
1518
+ );
1519
+ return toCamel(json);
1520
+ }
1521
+ /** Unban a user id. Returns { id }. */
1522
+ async unban(guildId, userId) {
1523
+ needGuild2(guildId, "bot.client.members.unban");
1524
+ needUser2(userId, "bot.client.members.unban");
1525
+ const json = await this.ctx.request(
1526
+ "POST",
1527
+ `/api/v1/guilds/${encodeURIComponent(guildId)}/members/${encodeURIComponent(userId)}/unban`
1528
+ );
1529
+ return { id: json.id ?? json.user_id ?? userId, raw: json };
1530
+ }
1531
+ /** Active bans (prunes expired server-side). */
1532
+ async bans(guildId) {
1533
+ needGuild2(guildId, "bot.client.members.bans");
1534
+ const json = await this.ctx.request("GET", `/api/v1/guilds/${encodeURIComponent(guildId)}/bans`);
1535
+ return {
1536
+ bans: (json.bans ?? []).map(toCamelBan),
1537
+ bannedUserIds: json.banned_user_ids ?? json.bannedMemberIds ?? [],
1538
+ raw: json
1539
+ };
1540
+ }
1541
+ };
1542
+
1543
+ // src/resources/dms.js
1544
+ function needChannel(channelId, label) {
1545
+ if (!channelId) {
1546
+ throw missingArgument({
1547
+ label,
1548
+ param: "channelId",
1549
+ example: `bot.client.dms.send("DM_CHANNEL_ID", "hello")`
1550
+ });
1551
+ }
1552
+ }
1553
+ function needUser3(userId, label) {
1554
+ if (!userId) {
1555
+ throw missingArgument({
1556
+ label,
1557
+ param: "userId",
1558
+ example: `bot.client.dms.open("555")`
1559
+ });
1560
+ }
1561
+ }
1562
+ function toCamelDm(m) {
1563
+ const c = toCamel(m);
1564
+ if (c.createdAt && !(c.createdAt instanceof Date)) {
1565
+ const d = new Date(c.createdAt);
1566
+ if (!Number.isNaN(d.getTime())) c.createdAt = d;
1567
+ }
1568
+ if (c.editedAt && !(c.editedAt instanceof Date)) {
1569
+ const d = new Date(c.editedAt);
1570
+ if (!Number.isNaN(d.getTime())) c.editedAt = d;
1571
+ }
1572
+ return c;
1573
+ }
1574
+ var DmsResource = class {
1575
+ constructor(ctx) {
1576
+ this.ctx = ctx;
1577
+ }
1578
+ /** All DM conversations of this bot (newest first). */
1579
+ async list() {
1580
+ const json = await this.ctx.request("GET", "/api/v1/dms");
1581
+ return {
1582
+ conversations: (json.conversations ?? []).map(toCamelDm),
1583
+ raw: json
1584
+ };
1585
+ }
1586
+ /**
1587
+ * Find-or-create the DM with a user. Fails when there is no shared
1588
+ * server, the user blocks DMs, or the target is a bot/system account.
1589
+ */
1590
+ async open(userId) {
1591
+ needUser3(userId, "bot.client.dms.open");
1592
+ const json = await this.ctx.request("POST", "/api/v1/dms", { body: { user_id: String(userId) } });
1593
+ return toCamelDm(json.conversation ?? json);
1594
+ }
1595
+ /** Fetch messages (counter pagination, newest page first -> ascending list). */
1596
+ async fetch(channelId, { limit = 50, before } = {}) {
1597
+ needChannel(channelId, "bot.client.dms.fetch");
1598
+ limit = Math.min(Math.max(Number(limit) || 50, 1), 100);
1599
+ const json = await this.ctx.request("GET", `/api/v1/dms/${encodeURIComponent(channelId)}/messages`, {
1600
+ query: { limit, ...before !== void 0 && before !== null ? { before } : {} }
1601
+ });
1602
+ return {
1603
+ messages: (json.messages ?? []).map(toCamelDm),
1604
+ hasMore: json.has_more ?? false,
1605
+ raw: json
1606
+ };
1607
+ }
1608
+ /** Auto-paginate DM history (returns ascending list, up to max). */
1609
+ async fetchAll(channelId, { max = 200, pageSize = 100 } = {}) {
1610
+ let before;
1611
+ const acc = [];
1612
+ while (acc.length < max) {
1613
+ const page = await this.fetch(channelId, { limit: Math.min(pageSize, max - acc.length), before });
1614
+ if (!page.messages.length) break;
1615
+ acc.unshift(...page.messages);
1616
+ if (!page.hasMore) break;
1617
+ before = page.messages[0]?.counter ?? page.messages[0]?.enc?.counter;
1618
+ if (before === void 0) break;
1619
+ }
1620
+ return acc.slice(-max);
1621
+ }
1622
+ /** Send a DM (1-4000 chars after sanitizing). */
1623
+ async send(channelId, content) {
1624
+ needChannel(channelId, "bot.client.dms.send");
1625
+ const clean = sanitizeContent(content).slice(0, 4e3);
1626
+ if (!clean) throw new Error("Content is required (1-4000 chars after sanitizing).");
1627
+ const json = await this.ctx.request("POST", `/api/v1/dms/${encodeURIComponent(channelId)}/messages`, {
1628
+ body: { content: clean }
1629
+ });
1630
+ return toCamelDm(json.message ?? json);
1631
+ }
1632
+ /** Edit your own DM. Only the author can edit (server enforces). */
1633
+ async edit(channelId, messageId, content) {
1634
+ needChannel(channelId, "bot.client.dms.edit");
1635
+ if (!messageId) {
1636
+ throw missingArgument({
1637
+ label: "bot.client.dms.edit",
1638
+ param: "messageId",
1639
+ example: `bot.client.dms.edit("DM_ID", "MSG_ID", "new text")`
1640
+ });
1641
+ }
1642
+ const clean = sanitizeContent(content).slice(0, 4e3);
1643
+ if (!clean) throw new Error("Content is required (1-4000 chars after sanitizing).");
1644
+ const json = await this.ctx.request(
1645
+ "PATCH",
1646
+ `/api/v1/dms/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}`,
1647
+ { body: { content: clean } }
1648
+ );
1649
+ return toCamelDm(json.message ?? json);
1650
+ }
1651
+ /** Delete your own DM. Returns { id }. */
1652
+ async delete(channelId, messageId) {
1653
+ needChannel(channelId, "bot.client.dms.delete");
1654
+ if (!messageId) {
1655
+ throw missingArgument({
1656
+ label: "bot.client.dms.delete",
1657
+ param: "messageId",
1658
+ example: `bot.client.dms.delete("DM_ID", "MSG_ID")`
1659
+ });
1660
+ }
1661
+ const json = await this.ctx.request(
1662
+ "DELETE",
1663
+ `/api/v1/dms/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}`
1664
+ );
1665
+ return { id: json.id ?? messageId, raw: json };
1666
+ }
1667
+ /** Mark a DM as read (clears the unread counter). */
1668
+ async read(channelId) {
1669
+ needChannel(channelId, "bot.client.dms.read");
1670
+ const json = await this.ctx.request("POST", `/api/v1/dms/${encodeURIComponent(channelId)}/read`);
1671
+ return { ok: json.success ?? true, raw: json };
1672
+ }
1673
+ };
1674
+
1675
+ // src/resources/ethan.js
1676
+ var ETHAN_MODEL = "e3n-v1-beta";
1677
+ var ETHAN_PERSONALITIES = ["friendly", "objective", "humorous", "neutral", "grumpy"];
1678
+ var ETHAN_MESSAGE_MAX = 5e3;
1679
+ function assertPersonality(personality) {
1680
+ const p = String(personality ?? "neutral").toLowerCase();
1681
+ if (!ETHAN_PERSONALITIES.includes(p)) {
1682
+ throw invalidChoice({ label: "personality", received: String(personality), valid: ETHAN_PERSONALITIES, validTitle: "Valid personalities" });
1683
+ }
1684
+ return p;
1685
+ }
1686
+ function assertEthanMessage(message) {
1687
+ const text = String(message ?? "").trim();
1688
+ if (!text) {
1689
+ throw missingArgument({
1690
+ label: "bot.client.ethan.chat",
1691
+ param: "message",
1692
+ example: `bot.client.ethan.chat("Explain recursion in one sentence")`
1693
+ });
1694
+ }
1695
+ if (text.length > ETHAN_MESSAGE_MAX) {
1696
+ throw new Error(`message too long (max ${ETHAN_MESSAGE_MAX} chars, got ${text.length}).`);
1697
+ }
1698
+ return text;
1699
+ }
1700
+ function assertTemperature(temperature) {
1701
+ if (temperature === void 0 || temperature === null) return void 0;
1702
+ if (typeof temperature !== "number" || Number.isNaN(temperature) || temperature < 0 || temperature > 100) {
1703
+ throw new Error(`temperature must be a number between 0 and 100 (got ${String(temperature)}).`);
1704
+ }
1705
+ return temperature;
1706
+ }
1707
+ function normalizeChatArgs(messageOrOpts, opts = {}) {
1708
+ let o = {};
1709
+ if (typeof messageOrOpts === "string") o = { ...opts, message: messageOrOpts };
1710
+ else if (messageOrOpts && typeof messageOrOpts === "object") o = { ...messageOrOpts, ...opts };
1711
+ const message = assertEthanMessage(o.message ?? o.input ?? o.prompt);
1712
+ const body = { message, personality: assertPersonality(o.personality ?? "neutral") };
1713
+ if (o.reasoning !== void 0) body.reasoning = o.reasoning === true;
1714
+ const temperature = assertTemperature(o.temperature);
1715
+ if (temperature !== void 0) body.temperature = temperature;
1716
+ return body;
1717
+ }
1718
+ var EthanClient = class {
1719
+ constructor({ key, timeoutMs = 3e4, retryOn429 = true } = {}) {
1720
+ if (!key) {
1721
+ throw new Error(
1722
+ `Ethan API key required (rve_...). Pass { key } to EthanClient, or { ethanKey } to RavenClient. Keys need an active EthanPlus/EthanPro plan.`
1723
+ );
1724
+ }
1725
+ assertEthanKey(key);
1726
+ this.key = key;
1727
+ this.timeoutMs = timeoutMs;
1728
+ this.retryOn429 = retryOn429;
1729
+ return withSuggestions(this, "bot.ethan");
1730
+ }
1731
+ /** Low-level request (Bearer rve_... auth). */
1732
+ request(method, path2, { query, body } = {}) {
1733
+ return ravenRequest({
1734
+ token: this.key,
1735
+ authScheme: "Bearer",
1736
+ tokenPrefix: ETHAN_KEY_PREFIX,
1737
+ path: path2,
1738
+ method,
1739
+ query,
1740
+ body,
1741
+ timeoutMs: this.timeoutMs,
1742
+ retryOn429: this.retryOn429
1743
+ });
1744
+ }
1745
+ /** Which model the Ethan API serves (no key needed server-side, cheap). */
1746
+ async models() {
1747
+ const json = await this.request("GET", "/api/v1/ethan/models");
1748
+ return {
1749
+ models: (json.models ?? []).map((m) => ({ id: m.id, name: m.name, via: m.via ?? null, raw: m })),
1750
+ raw: json
1751
+ };
1752
+ }
1753
+ /**
1754
+ * Ask Ethan something.
1755
+ * `chat("Explain recursion")` or `chat({ message, personality, reasoning, temperature })`.
1756
+ * personality: friendly|objective|humorous|neutral|grumpy (default neutral).
1757
+ * Returns { reply, thinking, model, plan }.
1758
+ */
1759
+ async chat(messageOrOpts, opts) {
1760
+ const body = normalizeChatArgs(messageOrOpts, opts);
1761
+ const json = await this.request("POST", "/api/v1/ethan/chat", { body });
1762
+ return {
1763
+ reply: json.reply ?? "",
1764
+ thinking: json.thinking ?? "",
1765
+ model: json.model ?? ETHAN_MODEL,
1766
+ plan: json.plan ?? null,
1767
+ raw: json
1768
+ };
1769
+ }
1770
+ /** Alias for chat(). */
1771
+ async ask(messageOrOpts, opts) {
1772
+ return this.chat(messageOrOpts, opts);
1773
+ }
1774
+ };
1775
+ function createEthanClient(options) {
1776
+ return new EthanClient(options);
1777
+ }
1778
+
1779
+ // src/resources/oauth.js
1780
+ var OAuthResource = class {
1781
+ constructor(ctx) {
1782
+ this.ctx = ctx;
1783
+ }
1784
+ async info(guildId) {
1785
+ const json = await this.ctx.request("GET", "/api/v1/oauth2/authorize", {
1786
+ query: guildId ? { guild_id: guildId } : {}
1787
+ });
1788
+ return {
1789
+ clientId: json.client_id,
1790
+ username: json.username,
1791
+ scopes: json.scopes ?? [],
1792
+ authorizeUrl: json.authorize_url,
1793
+ installed: json.installed ?? null,
1794
+ raw: json
1795
+ };
1796
+ }
1797
+ };
1798
+
1799
+ // src/realtime/poller.js
1800
+ var import_node_events = require("node:events");
1801
+ function toTime(m) {
1802
+ const t = m?.createdAt ? new Date(m.createdAt).getTime() : 0;
1803
+ return Number.isNaN(t) ? 0 : t;
1804
+ }
1805
+ var MessagePoller = class extends import_node_events.EventEmitter {
1806
+ constructor(client, channelIds, options = {}) {
1807
+ super();
1808
+ this.client = client;
1809
+ this.channelIds = [...new Set((Array.isArray(channelIds) ? channelIds : [channelIds]).filter(Boolean))];
1810
+ if (!this.channelIds.length) throw new Error("At least one channelId is required for polling.");
1811
+ this.pollInterval = Math.max(Number(options.pollInterval) || 3e3, 1e3);
1812
+ this.limit = Math.min(Math.max(Number(options.limit) || 25, 1), 100);
1813
+ this.emitHistory = options.emitHistory ?? false;
1814
+ this.ignoreBots = options.ignoreBots ?? true;
1815
+ this.selfAuthorId = options.selfAuthorId ?? null;
1816
+ this.signal = options.signal ?? null;
1817
+ this.timers = /* @__PURE__ */ new Map();
1818
+ this.seen = /* @__PURE__ */ new Map();
1819
+ this.lastAt = /* @__PURE__ */ new Map();
1820
+ this.running = false;
1821
+ this.onError = options.onError ?? null;
1822
+ if (this.signal) {
1823
+ if (this.signal.aborted) throw new Error("AbortSignal already aborted.");
1824
+ this._onAbort = () => this.stop();
1825
+ this.signal.addEventListener("abort", this._onAbort, { once: true });
1826
+ }
1827
+ }
1828
+ async _primeChannel(channelId) {
1829
+ try {
1830
+ const { messages } = await this.client.messages.fetch(channelId, { limit: this.limit });
1831
+ const ids = new Set(messages.map((m) => m.id));
1832
+ this.seen.set(channelId, ids);
1833
+ const latest = messages[messages.length - 1];
1834
+ if (latest) this.lastAt.set(channelId, toTime(latest));
1835
+ if (this.emitHistory) for (const m of messages) this.emit("message", m);
1836
+ } catch (err) {
1837
+ this.emit("error", err);
1838
+ if (typeof this.onError === "function") this.onError(err);
1839
+ }
1840
+ }
1841
+ async _pollChannel(channelId) {
1842
+ if (!this.running) return;
1843
+ try {
1844
+ const { messages } = await this.client.messages.fetch(channelId, { limit: this.limit });
1845
+ let known = this.seen.get(channelId);
1846
+ if (!known) {
1847
+ known = /* @__PURE__ */ new Set();
1848
+ this.seen.set(channelId, known);
1849
+ }
1850
+ const fresh = messages.filter((m) => !known.has(m.id));
1851
+ for (const m of fresh) known.add(m.id);
1852
+ if (known.size > 500) {
1853
+ const keep = new Set([...known].slice(-300));
1854
+ this.seen.set(channelId, keep);
1855
+ }
1856
+ const cutoff = this.lastAt.get(channelId) ?? 0;
1857
+ const ordered = fresh.filter((m) => toTime(m) >= cutoff).sort((a, b) => toTime(a) - toTime(b));
1858
+ for (const m of ordered) {
1859
+ if (this.ignoreBots && m.isBot) {
1860
+ if (!this.selfAuthorId || m.authorId !== this.selfAuthorId) {
1861
+ continue;
1862
+ }
1863
+ }
1864
+ if (this.selfAuthorId && m.authorId === this.selfAuthorId) continue;
1865
+ this.emit("message", m);
1866
+ }
1867
+ const latest = messages[messages.length - 1];
1868
+ if (latest) this.lastAt.set(channelId, Math.max(cutoff, toTime(latest)));
1869
+ } catch (err) {
1870
+ this.emit("error", err);
1871
+ if (typeof this.onError === "function") {
1872
+ try {
1873
+ this.onError(err);
1874
+ } catch {
1875
+ }
1876
+ }
1877
+ }
1878
+ }
1879
+ async start() {
1880
+ if (this.running) return this;
1881
+ this.running = true;
1882
+ for (const ch of this.channelIds) {
1883
+ await this._primeChannel(ch);
1884
+ }
1885
+ for (const ch of this.channelIds) {
1886
+ const tick = () => {
1887
+ if (!this.running) return;
1888
+ this._pollChannel(ch).finally(() => {
1889
+ if (this.running) this.timers.set(ch, setTimeout(tick, this.pollInterval));
1890
+ });
1891
+ };
1892
+ this.timers.set(ch, setTimeout(tick, this.pollInterval));
1893
+ }
1894
+ return this;
1895
+ }
1896
+ stop() {
1897
+ this.running = false;
1898
+ for (const t of this.timers.values()) clearTimeout(t);
1899
+ this.timers.clear();
1900
+ if (this.signal && this._onAbort) {
1901
+ try {
1902
+ this.signal.removeEventListener("abort", this._onAbort);
1903
+ } catch {
1904
+ }
1905
+ }
1906
+ this.emit("stopped");
1907
+ return this;
1908
+ }
1909
+ };
1910
+
1911
+ // src/realtime/gateway.js
1912
+ var import_node_events2 = require("node:events");
1913
+ var import_socket = require("socket.io-client");
1914
+ var GATEWAY_EVENTS = [
1915
+ "ready",
1916
+ "dispatch",
1917
+ "message:new",
1918
+ "message:update",
1919
+ "message:delete",
1920
+ "guildCreate",
1921
+ "guildRemove",
1922
+ "roleCreate",
1923
+ "roleUpdate",
1924
+ "roleDelete",
1925
+ "rolesReordered",
1926
+ "memberRolesUpdate",
1927
+ "memberJoin",
1928
+ "memberLeave",
1929
+ "memberBan",
1930
+ "memberUnban",
1931
+ "channelCreate",
1932
+ "channelUpdate",
1933
+ "channelDelete",
1934
+ "channelsReordered",
1935
+ "dmCreate",
1936
+ "dmMessage",
1937
+ "dmMessageUpdate",
1938
+ "dmMessageDelete",
1939
+ "commandError",
1940
+ "heartbeat",
1941
+ "disconnect",
1942
+ "error"
1943
+ ];
1944
+ var IDENTIFY_TIMEOUT_MS = 15e3;
1945
+ var RavenGateway = class extends import_node_events2.EventEmitter {
1946
+ constructor(client, options = {}) {
1947
+ super();
1948
+ this.client = client;
1949
+ this.options = options;
1950
+ this.socket = null;
1951
+ this.connected = false;
1952
+ this.sessionId = null;
1953
+ this.lastSeq = 0;
1954
+ this.bot = null;
1955
+ this.guilds = [];
1956
+ this._identifyTimer = null;
1957
+ this._manualClose = false;
1958
+ }
1959
+ get token() {
1960
+ return this.options.token || this.client.token;
1961
+ }
1962
+ /** Open the socket and identify. Resolves with the `ready` payload. */
1963
+ async connect() {
1964
+ if (this.socket && this.connected) return this._readyInfo();
1965
+ if (this.socket) {
1966
+ this._manualClose = false;
1967
+ this.socket.connect();
1968
+ return this._waitForReady();
1969
+ }
1970
+ this._manualClose = false;
1971
+ this.socket = (0, import_socket.io)(`${DEFAULT_BASE_URL}/gateway`, {
1972
+ transports: ["websocket", "polling"],
1973
+ reconnection: true
1974
+ });
1975
+ this.socket.on("connect", () => this._identify(false));
1976
+ this.socket.on("ready", (msg) => this._onReady(msg));
1977
+ this.socket.on("dispatch", (env) => this._onDispatch(env));
1978
+ this.socket.on("heartbeat", () => this._onHeartbeat());
1979
+ this.socket.on("invalid_session", () => this._onInvalidSession());
1980
+ this.socket.on("message:error", (err) => this.emit("error", new RavenError(err?.error || "Gateway error")));
1981
+ this.socket.on("disconnect", (reason) => {
1982
+ this.connected = false;
1983
+ this._clearIdentifyTimer();
1984
+ this.emit("disconnect", reason);
1985
+ });
1986
+ this.socket.on("connect_error", (err) => {
1987
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
1988
+ });
1989
+ return this._waitForReady();
1990
+ }
1991
+ /** Send a message over the gateway (same rules as REST send). */
1992
+ async send(channelId, contentOrOpts) {
1993
+ const { content, reply_to } = this.client.messages.normalizeSendArgs(channelId, contentOrOpts);
1994
+ const ack = await this._emitAck("message:create", reply_to ? { channel_id: channelId, content, reply_to } : { channel_id: channelId, content });
1995
+ if (!ack || ack.ok !== true) {
1996
+ throw new RavenError(ack?.error || "Send failed.", { status: 0, code: "GATEWAY_SEND_FAILED" });
1997
+ }
1998
+ return toCamelMessage(ack.message);
1999
+ }
2000
+ /** Reply to a message over the gateway. */
2001
+ async reply(channelId, messageId, content) {
2002
+ return this.send(channelId, { content, reply_to: messageId });
2003
+ }
2004
+ /** Edit your own message over the gateway. */
2005
+ async edit(channelId, messageId, content) {
2006
+ const { content: clean } = this.client.messages.normalizeEditArgs(channelId, messageId, content);
2007
+ const ack = await this._emitAck("message:update", { channel_id: channelId, message_id: messageId, content: clean });
2008
+ if (!ack || ack.ok !== true) {
2009
+ throw new RavenError(ack?.error || "Edit failed.", { status: 0, code: "GATEWAY_EDIT_FAILED" });
2010
+ }
2011
+ return toCamelMessage(ack.message);
2012
+ }
2013
+ /** Delete a message over the gateway (own always, others need MANAGE_MESSAGES). */
2014
+ async delete(channelId, messageId) {
2015
+ if (!channelId || !messageId) {
2016
+ throw new Error("channelId and messageId are required.");
2017
+ }
2018
+ const ack = await this._emitAck("message:delete", { channel_id: channelId, message_id: messageId });
2019
+ if (!ack || ack.ok !== true) {
2020
+ throw new RavenError(ack?.error || "Delete failed.", { status: 0, code: "GATEWAY_DELETE_FAILED" });
2021
+ }
2022
+ return { id: ack.id ?? messageId };
2023
+ }
2024
+ /**
2025
+ * Set the bot status over the gateway (online, idle, dnd).
2026
+ * Pass null/undefined/"" to release the manual lock.
2027
+ */
2028
+ async setStatus(status) {
2029
+ let payload;
2030
+ if (status === void 0 || status === null || status === "") {
2031
+ payload = {};
2032
+ } else {
2033
+ payload = { status: assertStatus(status) };
2034
+ }
2035
+ const ack = await this._emitAck("status:set", payload);
2036
+ if (!ack || ack.ok !== true) {
2037
+ throw new RavenError(ack?.error || "Status update failed.", { status: 0, code: "GATEWAY_STATUS_FAILED" });
2038
+ }
2039
+ return { status: ack.status, locked: ack.locked ?? true };
2040
+ }
2041
+ // ---- roles (same rules as REST: MANAGE_ROLES + strictly higher position) ----
2042
+ /** All roles of a guild, ascending by position. */
2043
+ async listRoles(guildId) {
2044
+ if (!guildId) throw new Error("guildId is required.");
2045
+ const ack = await this._emitAck("role:list", { guild_id: guildId });
2046
+ if (!ack || ack.ok !== true) {
2047
+ throw new RavenError(ack?.error || "Role list failed.", { status: 0, code: "GATEWAY_ROLES_FAILED" });
2048
+ }
2049
+ return (ack.roles || []).map((r) => toCamel(r));
2050
+ }
2051
+ async getRole(guildId, roleId) {
2052
+ if (!guildId || !roleId) throw new Error("guildId and roleId are required.");
2053
+ const ack = await this._emitAck("role:get", { guild_id: guildId, role_id: roleId });
2054
+ if (!ack || ack.ok !== true) {
2055
+ throw new RavenError(ack?.error || "Role fetch failed.", { status: 0, code: "GATEWAY_ROLES_FAILED" });
2056
+ }
2057
+ return toCamel(ack.role);
2058
+ }
2059
+ /** Create a role (position defaults to just below your highest role). */
2060
+ async createRole(guildId, opts = {}) {
2061
+ if (!guildId) throw new Error("guildId is required.");
2062
+ const ack = await this._emitAck("role:create", { guild_id: guildId, ...opts });
2063
+ if (!ack || ack.ok !== true) {
2064
+ throw new RavenError(ack?.error || "Role create failed.", { status: 0, code: "GATEWAY_ROLES_FAILED" });
2065
+ }
2066
+ return toCamel(ack.role);
2067
+ }
2068
+ /** Edit a role (name/color/hoist/mentionable/permissions/position). */
2069
+ async updateRole(guildId, roleId, patch = {}) {
2070
+ if (!guildId || !roleId) throw new Error("guildId and roleId are required.");
2071
+ const ack = await this._emitAck("role:update", { guild_id: guildId, role_id: roleId, ...patch });
2072
+ if (!ack || ack.ok !== true) {
2073
+ throw new RavenError(ack?.error || "Role update failed.", { status: 0, code: "GATEWAY_ROLES_FAILED" });
2074
+ }
2075
+ return ack.roles ? ack.roles.map((r) => toCamel(r)) : toCamel(ack.role);
2076
+ }
2077
+ /** Delete a role. Returns { id }. */
2078
+ async deleteRole(guildId, roleId) {
2079
+ if (!guildId || !roleId) throw new Error("guildId and roleId are required.");
2080
+ const ack = await this._emitAck("role:delete", { guild_id: guildId, role_id: roleId });
2081
+ if (!ack || ack.ok !== true) {
2082
+ throw new RavenError(ack?.error || "Role delete failed.", { status: 0, code: "GATEWAY_ROLES_FAILED" });
2083
+ }
2084
+ return { id: ack.id ?? roleId };
2085
+ }
2086
+ /** Reorder roles by id array (low to high). Returns sorted roles. */
2087
+ async reorderRoles(guildId, orderedIds) {
2088
+ if (!guildId || !Array.isArray(orderedIds) || !orderedIds.length) {
2089
+ throw new Error("guildId and orderedIds (string[]) are required.");
2090
+ }
2091
+ const ack = await this._emitAck("roles:reorder", { guild_id: guildId, ordered_ids: orderedIds.map(String) });
2092
+ if (!ack || ack.ok !== true) {
2093
+ throw new RavenError(ack?.error || "Role reorder failed.", { status: 0, code: "GATEWAY_ROLES_FAILED" });
2094
+ }
2095
+ return (ack.roles || []).map((r) => toCamel(r));
2096
+ }
2097
+ /** Role ids of a member (user id or `bot_<botId>`). */
2098
+ async memberRoles(guildId, userId) {
2099
+ if (!guildId || !userId) throw new Error("guildId and userId are required.");
2100
+ const ack = await this._emitAck("member:roles-get", { guild_id: guildId, user_id: userId });
2101
+ if (!ack || ack.ok !== true) {
2102
+ throw new RavenError(ack?.error || "Member roles fetch failed.", { status: 0, code: "GATEWAY_ROLES_FAILED" });
2103
+ }
2104
+ return { userId: ack.user_id ?? userId, roleIds: ack.role_ids ?? [] };
2105
+ }
2106
+ /** Replace a member's roles (full set, "everyone" auto-added server-side). */
2107
+ async setMemberRoles(guildId, userId, roleIds) {
2108
+ if (!guildId || !userId || !Array.isArray(roleIds)) {
2109
+ throw new Error("guildId, userId and roleIds (string[]) are required.");
2110
+ }
2111
+ const ack = await this._emitAck("member:roles-set", {
2112
+ guild_id: guildId,
2113
+ user_id: userId,
2114
+ role_ids: roleIds.map(String)
2115
+ });
2116
+ if (!ack || ack.ok !== true) {
2117
+ throw new RavenError(ack?.error || "Member roles update failed.", { status: 0, code: "GATEWAY_ROLES_FAILED" });
2118
+ }
2119
+ return { userId: ack.user_id ?? userId, roleIds: ack.role_ids ?? [] };
2120
+ }
2121
+ // ---- members & moderation (KICK_MEMBERS/BAN_MEMBERS + hierarchy) ----
2122
+ /** List members (users first, then installed bots). */
2123
+ async listMembers(guildId, { limit = 50, after } = {}) {
2124
+ if (!guildId) throw new Error("guildId is required.");
2125
+ const ack = await this._emitAck("member:list", { guild_id: guildId, limit, ...after ? { after } : {} });
2126
+ if (!ack || ack.ok !== true) {
2127
+ throw new RavenError(ack?.error || "Member list failed.", { status: 0, code: "GATEWAY_MEMBERS_FAILED" });
2128
+ }
2129
+ return { members: (ack.members || []).map((m) => toCamel(m)), hasMore: ack.has_more ?? false };
2130
+ }
2131
+ /** Single member (`userId` or `bot_<botId>`). */
2132
+ async getMember(guildId, userId) {
2133
+ if (!guildId || !userId) throw new Error("guildId and userId are required.");
2134
+ const ack = await this._emitAck("member:get", { guild_id: guildId, user_id: userId });
2135
+ if (!ack || ack.ok !== true) {
2136
+ throw new RavenError(ack?.error || "Member fetch failed.", { status: 0, code: "GATEWAY_MEMBERS_FAILED" });
2137
+ }
2138
+ return toCamel(ack.member);
2139
+ }
2140
+ /** Kick a member. Returns { id }. */
2141
+ async kick(guildId, userId, reason) {
2142
+ if (!guildId || !userId) throw new Error("guildId and userId are required.");
2143
+ const body = { guild_id: guildId, user_id: userId };
2144
+ const r = typeof reason === "string" ? reason : reason?.reason;
2145
+ if (r) body.reason = String(r).slice(0, 256);
2146
+ const ack = await this._emitAck("member:kick", body);
2147
+ if (!ack || ack.ok !== true) {
2148
+ throw new RavenError(ack?.error || "Kick failed.", { status: 0, code: "GATEWAY_MEMBERS_FAILED" });
2149
+ }
2150
+ return { id: ack.id ?? ack.user_id ?? userId };
2151
+ }
2152
+ /**
2153
+ * Ban a member. `duration` accepts seconds or "10m"/"1h"/"7d"/"1w"/
2154
+ * "permanent" (default). Returns { userId, reason, expiresAt, duration }.
2155
+ */
2156
+ async ban(guildId, userId, opts = {}) {
2157
+ if (!guildId || !userId) throw new Error("guildId and userId are required.");
2158
+ const o = typeof opts === "string" ? { reason: opts } : opts || {};
2159
+ const ack = await this._emitAck("member:ban", {
2160
+ guild_id: guildId,
2161
+ user_id: userId,
2162
+ ...o.reason !== void 0 ? { reason: String(o.reason).slice(0, 256) } : {},
2163
+ ...o.duration !== void 0 ? { duration: o.duration } : {}
2164
+ });
2165
+ if (!ack || ack.ok !== true) {
2166
+ throw new RavenError(ack?.error || "Ban failed.", { status: 0, code: "GATEWAY_MEMBERS_FAILED" });
2167
+ }
2168
+ return toCamel(ack);
2169
+ }
2170
+ /** Unban a user id. Returns { id }. */
2171
+ async unban(guildId, userId) {
2172
+ if (!guildId || !userId) throw new Error("guildId and userId are required.");
2173
+ const ack = await this._emitAck("member:unban", { guild_id: guildId, user_id: userId });
2174
+ if (!ack || ack.ok !== true) {
2175
+ throw new RavenError(ack?.error || "Unban failed.", { status: 0, code: "GATEWAY_MEMBERS_FAILED" });
2176
+ }
2177
+ return { id: ack.id ?? ack.user_id ?? userId };
2178
+ }
2179
+ /** Active bans. */
2180
+ async bans(guildId) {
2181
+ if (!guildId) throw new Error("guildId is required.");
2182
+ const ack = await this._emitAck("ban:list", { guild_id: guildId });
2183
+ if (!ack || ack.ok !== true) {
2184
+ throw new RavenError(ack?.error || "Ban list failed.", { status: 0, code: "GATEWAY_MEMBERS_FAILED" });
2185
+ }
2186
+ return { bans: (ack.bans || []).map((b) => toCamel(b)), bannedUserIds: ack.banned_user_ids ?? [] };
2187
+ }
2188
+ // ---- channels (needs MANAGE_CHANNELS + channels.write) ----
2189
+ /** Single channel by id. */
2190
+ async getChannel(channelId) {
2191
+ if (!channelId) throw new Error("channelId is required.");
2192
+ const ack = await this._emitAck("channel:get", { channel_id: channelId });
2193
+ if (!ack || ack.ok !== true) {
2194
+ throw new RavenError(ack?.error || "Channel fetch failed.", { status: 0, code: "GATEWAY_CHANNELS_FAILED" });
2195
+ }
2196
+ return toCamel(ack.channel);
2197
+ }
2198
+ /** Create a text/voice channel. opts: { name, type?, parentId?, topic? } */
2199
+ async createChannel(guildId, opts = {}) {
2200
+ if (!guildId) throw new Error("guildId is required.");
2201
+ const ack = await this._emitAck("channel:create", { guild_id: guildId, ...opts });
2202
+ if (!ack || ack.ok !== true) {
2203
+ throw new RavenError(ack?.error || "Channel create failed.", { status: 0, code: "GATEWAY_CHANNELS_FAILED" });
2204
+ }
2205
+ return toCamel(ack.channel);
2206
+ }
2207
+ /** Edit a channel. patch: { name?, topic?, parentId?, position? } */
2208
+ async updateChannel(channelId, patch = {}) {
2209
+ if (!channelId) throw new Error("channelId is required.");
2210
+ const ack = await this._emitAck("channel:update", { channel_id: channelId, ...patch });
2211
+ if (!ack || ack.ok !== true) {
2212
+ throw new RavenError(ack?.error || "Channel update failed.", { status: 0, code: "GATEWAY_CHANNELS_FAILED" });
2213
+ }
2214
+ return toCamel(ack.channel);
2215
+ }
2216
+ /** Delete a channel (messages go with it). Returns { id }. */
2217
+ async deleteChannel(channelId) {
2218
+ if (!channelId) throw new Error("channelId is required.");
2219
+ const ack = await this._emitAck("channel:delete", { channel_id: channelId });
2220
+ if (!ack || ack.ok !== true) {
2221
+ throw new RavenError(ack?.error || "Channel delete failed.", { status: 0, code: "GATEWAY_CHANNELS_FAILED" });
2222
+ }
2223
+ return { id: ack.id ?? channelId };
2224
+ }
2225
+ /** Reorder/move channels: items = [{ id, position?, parentId? }]. */
2226
+ async reorderChannels(guildId, items) {
2227
+ if (!guildId || !Array.isArray(items) || !items.length) {
2228
+ throw new Error("guildId and items ([{ id, position?, parentId? }]) are required.");
2229
+ }
2230
+ const ack = await this._emitAck("channels:reorder", { guild_id: guildId, items });
2231
+ if (!ack || ack.ok !== true) {
2232
+ throw new RavenError(ack?.error || "Channel reorder failed.", { status: 0, code: "GATEWAY_CHANNELS_FAILED" });
2233
+ }
2234
+ return (ack.channels || []).map((c) => toCamel(c));
2235
+ }
2236
+ // ---- direct messages (dms.read / dms.write, shared server required) ----
2237
+ /** All DM conversations of this bot (newest first). */
2238
+ async listDms() {
2239
+ const ack = await this._emitAck("dm:list", {});
2240
+ if (!ack || ack.ok !== true) {
2241
+ throw new RavenError(ack?.error || "DM list failed.", { status: 0, code: "GATEWAY_DMS_FAILED" });
2242
+ }
2243
+ return { conversations: (ack.conversations || []).map((c) => toCamel(c)) };
2244
+ }
2245
+ /** Find-or-create the DM with a user. */
2246
+ async openDm(userId) {
2247
+ if (!userId) throw new Error("userId is required.");
2248
+ const ack = await this._emitAck("dm:open", { user_id: userId });
2249
+ if (!ack || ack.ok !== true) {
2250
+ throw new RavenError(ack?.error || "DM open failed.", { status: 0, code: "GATEWAY_DMS_FAILED" });
2251
+ }
2252
+ return toCamel(ack.conversation);
2253
+ }
2254
+ /** Fetch DM messages (counter pagination). */
2255
+ async fetchDm(channelId, { limit = 50, before } = {}) {
2256
+ if (!channelId) throw new Error("channelId is required.");
2257
+ const ack = await this._emitAck("dm:fetch", {
2258
+ channel_id: channelId,
2259
+ limit,
2260
+ ...before !== void 0 && before !== null ? { before } : {}
2261
+ });
2262
+ if (!ack || ack.ok !== true) {
2263
+ throw new RavenError(ack?.error || "DM fetch failed.", { status: 0, code: "GATEWAY_DMS_FAILED" });
2264
+ }
2265
+ return { messages: (ack.messages || []).map((m) => toCamel(m)), hasMore: ack.has_more ?? false };
2266
+ }
2267
+ /** Send a DM (1-4000 chars). */
2268
+ async sendDm(channelId, content) {
2269
+ if (!channelId || !content) throw new Error("channelId and content are required.");
2270
+ const ack = await this._emitAck("dm:send", { channel_id: channelId, content: String(content).slice(0, 4e3) });
2271
+ if (!ack || ack.ok !== true) {
2272
+ throw new RavenError(ack?.error || "DM send failed.", { status: 0, code: "GATEWAY_DMS_FAILED" });
2273
+ }
2274
+ return toCamel(ack.message);
2275
+ }
2276
+ /** Edit your own DM. */
2277
+ async editDm(channelId, messageId, content) {
2278
+ if (!channelId || !messageId || !content) throw new Error("channelId, messageId and content are required.");
2279
+ const ack = await this._emitAck("dm:update", {
2280
+ channel_id: channelId,
2281
+ message_id: messageId,
2282
+ content: String(content).slice(0, 4e3)
2283
+ });
2284
+ if (!ack || ack.ok !== true) {
2285
+ throw new RavenError(ack?.error || "DM edit failed.", { status: 0, code: "GATEWAY_DMS_FAILED" });
2286
+ }
2287
+ return toCamel(ack.message);
2288
+ }
2289
+ /** Delete your own DM. Returns { id }. */
2290
+ async deleteDm(channelId, messageId) {
2291
+ if (!channelId || !messageId) throw new Error("channelId and messageId are required.");
2292
+ const ack = await this._emitAck("dm:delete", { channel_id: channelId, message_id: messageId });
2293
+ if (!ack || ack.ok !== true) {
2294
+ throw new RavenError(ack?.error || "DM delete failed.", { status: 0, code: "GATEWAY_DMS_FAILED" });
2295
+ }
2296
+ return { id: ack.id ?? messageId };
2297
+ }
2298
+ /** Mark a DM as read. */
2299
+ async readDm(channelId) {
2300
+ if (!channelId) throw new Error("channelId is required.");
2301
+ const ack = await this._emitAck("dm:read", { channel_id: channelId });
2302
+ if (!ack || ack.ok !== true) {
2303
+ throw new RavenError(ack?.error || "DM read failed.", { status: 0, code: "GATEWAY_DMS_FAILED" });
2304
+ }
2305
+ return { ok: true };
2306
+ }
2307
+ /**
2308
+ * Re-sync server-side subscriptions: picks up channels created after
2309
+ * connect and drops rooms of removed installs. The gateway joins every
2310
+ * text channel of every installed guild, so after sync the bot hears the
2311
+ * whole server. Returns { guilds, channels }.
2312
+ */
2313
+ async sync() {
2314
+ const ack = await this._emitAck("sync", {});
2315
+ if (!ack || ack.ok !== true) {
2316
+ throw new RavenError(ack?.error || "Sync failed.", { status: 0, code: "GATEWAY_SYNC_FAILED" });
2317
+ }
2318
+ return { guilds: ack.guilds || [], channels: ack.channels || 0 };
2319
+ }
2320
+ disconnect() {
2321
+ this._manualClose = true;
2322
+ this._clearIdentifyTimer();
2323
+ if (this.socket) this.socket.disconnect();
2324
+ this.socket = null;
2325
+ this.connected = false;
2326
+ this.emit("disconnect", "client");
2327
+ return this;
2328
+ }
2329
+ /** Compat helper: forward poller messages as gateway `message:new` events. */
2330
+ attachPoller(poller) {
2331
+ poller.on("message", (m) => this.emit("message:new", m));
2332
+ poller.on("error", (e) => this.emit("error", e));
2333
+ return this;
2334
+ }
2335
+ /**
2336
+ * Emit an event. Listeners on "*" receive every event as (event, ...args),
2337
+ * e.g. `gateway.on("*", (event, data) => console.log(event, data))`.
2338
+ */
2339
+ emit(event, ...args) {
2340
+ if (event === "*") return super.emit(event, ...args);
2341
+ super.emit(event, ...args);
2342
+ super.emit("*", event, ...args);
2343
+ return true;
2344
+ }
2345
+ _readyInfo() {
2346
+ return { sessionId: this.sessionId, bot: this.bot, guilds: this.guilds };
2347
+ }
2348
+ _waitForReady() {
2349
+ return new Promise((resolve, reject) => {
2350
+ const onReady = (info) => {
2351
+ cleanup();
2352
+ resolve(info);
2353
+ };
2354
+ const onError = (err) => {
2355
+ cleanup();
2356
+ reject(err instanceof Error ? err : new Error(String(err)));
2357
+ };
2358
+ const onTimeout = () => {
2359
+ cleanup();
2360
+ reject(new GatewayNotAvailableError("Gateway identify timed out. Is the server reachable?"));
2361
+ };
2362
+ const cleanup = () => {
2363
+ this._clearIdentifyTimer();
2364
+ this.off("ready", onReady);
2365
+ this.off("error", onError);
2366
+ };
2367
+ this.on("ready", onReady);
2368
+ this.on("error", onError);
2369
+ this._clearIdentifyTimer();
2370
+ this._identifyTimer = setTimeout(onTimeout, this.options.identifyTimeoutMs || IDENTIFY_TIMEOUT_MS);
2371
+ this._identifyTimer.unref?.();
2372
+ });
2373
+ }
2374
+ _clearIdentifyTimer() {
2375
+ if (this._identifyTimer) {
2376
+ clearTimeout(this._identifyTimer);
2377
+ this._identifyTimer = null;
2378
+ }
2379
+ }
2380
+ _identify(isRetry) {
2381
+ if (!this.socket || this._manualClose) return;
2382
+ const payload = this.sessionId ? { token: this.token, session_id: this.sessionId, seq: this.lastSeq } : { token: this.token };
2383
+ this.socket.emit("identify", payload, (ack) => {
2384
+ if (!ack || ack.ok !== true) {
2385
+ if (ack?.error && /session/i.test(ack.error) && !isRetry) {
2386
+ this.sessionId = null;
2387
+ this.lastSeq = 0;
2388
+ this._identify(true);
2389
+ return;
2390
+ }
2391
+ this.emit("error", new RavenError(ack?.error || "Identify failed.", { status: 401, code: "AUTHENTICATION_ERROR" }));
2392
+ return;
2393
+ }
2394
+ if (ack.session_id) this.sessionId = ack.session_id;
2395
+ });
2396
+ }
2397
+ _onReady(msg) {
2398
+ const d = msg?.d || {};
2399
+ this.connected = true;
2400
+ if (d.session_id) this.sessionId = d.session_id;
2401
+ this.bot = d.bot || null;
2402
+ this.guilds = d.guilds || [];
2403
+ this._clearIdentifyTimer();
2404
+ this.emit("ready", this._readyInfo());
2405
+ }
2406
+ _onDispatch(env) {
2407
+ if (env && typeof env.s === "number" && env.s > this.lastSeq) this.lastSeq = env.s;
2408
+ this.emit("dispatch", env);
2409
+ const type = env?.t;
2410
+ if (!type) return;
2411
+ if (type === "message:new" && env.d) {
2412
+ this.emit("message:new", toCamelMessage(env.d));
2413
+ return;
2414
+ }
2415
+ if (type === "message:update" && env.d) {
2416
+ this.emit("message:update", toCamelMessage(env.d));
2417
+ return;
2418
+ }
2419
+ if (type === "message:delete" && env.d) {
2420
+ this.emit("message:delete", toCamel(env.d));
2421
+ return;
2422
+ }
2423
+ if (type === "guildCreate" || type === "install:added") {
2424
+ this.emit("guildCreate", toCamel(env.d));
2425
+ return;
2426
+ }
2427
+ if (type === "guildRemove" || type === "install:removed") {
2428
+ this.emit("guildRemove", toCamel(env.d));
2429
+ return;
2430
+ }
2431
+ if (type === "role:create") {
2432
+ this.emit("roleCreate", toCamel(env.d));
2433
+ return;
2434
+ }
2435
+ if (type === "role:update") {
2436
+ this.emit("roleUpdate", toCamel(env.d));
2437
+ return;
2438
+ }
2439
+ if (type === "role:delete") {
2440
+ this.emit("roleDelete", toCamel(env.d));
2441
+ return;
2442
+ }
2443
+ if (type === "roles:reordered" || type === "roles:updated") {
2444
+ const d = toCamel(env.d);
2445
+ this.emit("rolesReordered", d);
2446
+ return;
2447
+ }
2448
+ if (type === "member:roles-updated" || type === "members:roles-updated") {
2449
+ this.emit("memberRolesUpdate", toCamel(env.d));
2450
+ return;
2451
+ }
2452
+ if (type === "member:join" || type === "members:member-joined") {
2453
+ this.emit("memberJoin", toCamel(env.d));
2454
+ return;
2455
+ }
2456
+ if (type === "member:leave" || type === "members:member-left") {
2457
+ this.emit("memberLeave", toCamel(env.d));
2458
+ return;
2459
+ }
2460
+ if (type === "member:ban") {
2461
+ this.emit("memberBan", toCamel(env.d));
2462
+ return;
2463
+ }
2464
+ if (type === "member:unban") {
2465
+ this.emit("memberUnban", toCamel(env.d));
2466
+ return;
2467
+ }
2468
+ if (type === "channel:create") {
2469
+ this.emit("channelCreate", toCamel(env.d));
2470
+ return;
2471
+ }
2472
+ if (type === "channel:update") {
2473
+ this.emit("channelUpdate", toCamel(env.d));
2474
+ return;
2475
+ }
2476
+ if (type === "channel:delete") {
2477
+ this.emit("channelDelete", toCamel(env.d));
2478
+ return;
2479
+ }
2480
+ if (type === "channels:reordered") {
2481
+ this.emit("channelsReordered", toCamel(env.d));
2482
+ return;
2483
+ }
2484
+ if (type === "dm:create") {
2485
+ this.emit("dmCreate", toCamel(env.d));
2486
+ return;
2487
+ }
2488
+ if (type === "dm:message:new" || type === "dm:new") {
2489
+ this.emit("dmMessage", toCamel(env.d));
2490
+ return;
2491
+ }
2492
+ if (type === "dm:message:update") {
2493
+ this.emit("dmMessageUpdate", toCamel(env.d));
2494
+ return;
2495
+ }
2496
+ if (type === "dm:message:delete") {
2497
+ this.emit("dmMessageDelete", toCamel(env.d));
2498
+ return;
2499
+ }
2500
+ if (type === "auth:revoked") {
2501
+ this.emit("error", new RavenError("Gateway session revoked. Send identify again.", { status: 401, code: "AUTHENTICATION_ERROR" }));
2502
+ return;
2503
+ }
2504
+ this.emit(type, env.d);
2505
+ }
2506
+ _onHeartbeat() {
2507
+ this.emit("heartbeat", { seq: this.lastSeq });
2508
+ if (this.socket) this.socket.emit("heartbeat_ack");
2509
+ }
2510
+ _onInvalidSession() {
2511
+ this.sessionId = null;
2512
+ this.lastSeq = 0;
2513
+ this.emit("error", new RavenError("Gateway session invalid. Re-identifying.", { status: 401, code: "GATEWAY_SESSION_INVALID" }));
2514
+ this._identify(true);
2515
+ }
2516
+ _emitAck(event, payload) {
2517
+ return new Promise((resolve, reject) => {
2518
+ if (!this.socket || !this.connected) {
2519
+ reject(new RavenError("Gateway not connected. Call gateway.connect() first.", { status: 0, code: "GATEWAY_NOT_CONNECTED" }));
2520
+ return;
2521
+ }
2522
+ this.socket.timeout(15e3).emit(event, payload, (err, ack) => {
2523
+ if (err) reject(err instanceof Error ? err : new Error(String(err)));
2524
+ else resolve(ack);
2525
+ });
2526
+ });
2527
+ }
2528
+ };
2529
+
2530
+ // src/commands/cooldown-store.js
2531
+ var MemoryCooldownStore = class {
2532
+ constructor({ maxKeys = 2e3 } = {}) {
2533
+ this.map = /* @__PURE__ */ new Map();
2534
+ this.maxKeys = maxKeys;
2535
+ }
2536
+ get(key) {
2537
+ const until = this.map.get(key);
2538
+ if (until === void 0) return null;
2539
+ if (until <= Date.now()) {
2540
+ this.map.delete(key);
2541
+ return null;
2542
+ }
2543
+ return until;
2544
+ }
2545
+ set(key, expiresAt) {
2546
+ this.map.set(key, expiresAt);
2547
+ if (this.map.size > this.maxKeys) this._prune();
2548
+ return null;
2549
+ }
2550
+ get size() {
2551
+ return this.map.size;
2552
+ }
2553
+ _prune(now = Date.now()) {
2554
+ for (const [k, until] of this.map) {
2555
+ if (until <= now) this.map.delete(k);
2556
+ }
2557
+ }
2558
+ };
2559
+
2560
+ // src/commands/handler.js
2561
+ var COMMAND_OPTS = ["alias", "aliases", "cooldown", "description", "usage"];
2562
+ var CommandHandler = class {
2563
+ constructor(options = {}) {
2564
+ this.prefix = options.prefix ?? "!";
2565
+ this.commands = /* @__PURE__ */ new Map();
2566
+ this.aliases = /* @__PURE__ */ new Map();
2567
+ this.cooldownStore = options.cooldownStore ?? new MemoryCooldownStore();
2568
+ this.onUnknown = options.onUnknown ?? null;
2569
+ this.onError = options.onError ?? null;
2570
+ this.onCooldown = options.onCooldown ?? null;
2571
+ this.ignoreBots = options.ignoreBots ?? true;
2572
+ return withSuggestions(this, "bot.commands");
2573
+ }
2574
+ command(name, fn, opts = {}) {
2575
+ if (!name || typeof fn !== "function") throw new Error("command(name, fn) required.");
2576
+ const bad = Object.keys(opts).find((k) => !COMMAND_OPTS.includes(k));
2577
+ if (bad) throw unknownOption({ option: bad, valid: COMMAND_OPTS });
2578
+ const key = String(name).toLowerCase();
2579
+ for (const [alias, target] of this.aliases) {
2580
+ if (target === key) this.aliases.delete(alias);
2581
+ }
2582
+ const raw = opts.aliases ?? opts.alias ?? [];
2583
+ const list = (Array.isArray(raw) ? raw : [raw]).map((a) => String(a).toLowerCase()).filter((a) => a && a !== key);
2584
+ const cooldown = Number(opts.cooldown ?? 0) || 0;
2585
+ this.commands.set(key, {
2586
+ fn,
2587
+ description: opts.description ?? "",
2588
+ usage: opts.usage ?? "",
2589
+ aliases: list,
2590
+ cooldown: cooldown > 0 ? cooldown : 0
2591
+ });
2592
+ for (const a of list) this.aliases.set(a, key);
2593
+ return this;
2594
+ }
2595
+ /** Resolve a name or alias to the canonical command name (or null). */
2596
+ resolve(name) {
2597
+ const key = String(name ?? "").toLowerCase();
2598
+ if (this.commands.has(key)) return key;
2599
+ return this.aliases.get(key) ?? null;
2600
+ }
2601
+ /** Closest command name (or alias target) for a typo, or null. */
2602
+ suggestCommand(name) {
2603
+ const s = suggest(name, [...this.commands.keys(), ...this.aliases.keys()]);
2604
+ if (!s) return null;
2605
+ return this.resolve(s.value) ?? s.value;
2606
+ }
2607
+ parse(content) {
2608
+ const text = String(content ?? "");
2609
+ if (!text.startsWith(this.prefix)) return null;
2610
+ const rest = text.slice(this.prefix.length).trim();
2611
+ if (!rest) return null;
2612
+ const [name, ...args] = rest.split(/\s+/);
2613
+ return { name: name.toLowerCase(), args, raw: rest.slice(name.length).trim() };
2614
+ }
2615
+ makeCtx(client, message) {
2616
+ const channelId = message.channelId ?? message.channel_id;
2617
+ const authorId = message.authorId ?? message.author_id;
2618
+ const guildId = message.guildId ?? message.guild_id;
2619
+ return {
2620
+ message,
2621
+ content: message.content ?? "",
2622
+ args: [],
2623
+ rawArgs: "",
2624
+ command: "",
2625
+ authorId,
2626
+ channelId,
2627
+ guildId,
2628
+ user: message.author ?? message.user ?? { id: authorId },
2629
+ guild: guildId ? { id: guildId } : null,
2630
+ channel: channelId ? { id: channelId } : null,
2631
+ reply: (content) => client.messages.reply(channelId, message.id, content),
2632
+ send: (content) => client.messages.send(channelId, content),
2633
+ delete: () => client.messages.delete(channelId, message.id)
2634
+ };
2635
+ }
2636
+ async handle(client, message) {
2637
+ if (this.ignoreBots && (message.isBot ?? message.is_bot)) return false;
2638
+ const parsed = this.parse(message.content ?? "");
2639
+ if (!parsed) return false;
2640
+ const key = this.resolve(parsed.name);
2641
+ const entry = key ? this.commands.get(key) : null;
2642
+ const ctx = this.makeCtx(client, message);
2643
+ ctx.args = parsed.args;
2644
+ ctx.rawArgs = parsed.raw;
2645
+ ctx.command = key ?? parsed.name;
2646
+ if (!entry) {
2647
+ if (typeof this.onUnknown === "function") {
2648
+ try {
2649
+ await this.onUnknown(ctx);
2650
+ } catch (err) {
2651
+ if (typeof this.onError === "function") await this.onError(err, ctx);
2652
+ }
2653
+ } else {
2654
+ const similar = this.suggestCommand(parsed.name);
2655
+ if (similar) {
2656
+ try {
2657
+ await ctx.reply(
2658
+ `Unknown command "${parsed.name}". Did you mean "${this.prefix}${similar}"?`
2659
+ );
2660
+ } catch {
2661
+ }
2662
+ }
2663
+ }
2664
+ return false;
2665
+ }
2666
+ if (entry.cooldown > 0) {
2667
+ const now = Date.now();
2668
+ const id = ctx.authorId ?? ctx.channelId ?? "unknown";
2669
+ const cdKey = `${key}:${id}`;
2670
+ const until = Number(await this.cooldownStore.get(cdKey)) || 0;
2671
+ if (until > now) {
2672
+ const wait = (until - now) / 1e3;
2673
+ if (typeof this.onCooldown === "function") {
2674
+ await this.onCooldown(ctx, wait);
2675
+ } else {
2676
+ try {
2677
+ await ctx.reply(`Slow down \u2014 try again in ${Math.ceil(wait)}s.`);
2678
+ } catch {
2679
+ }
2680
+ }
2681
+ return true;
2682
+ }
2683
+ await this.cooldownStore.set(cdKey, now + entry.cooldown * 1e3, entry.cooldown * 1e3);
2684
+ }
2685
+ try {
2686
+ await entry.fn(ctx);
2687
+ } catch (err) {
2688
+ if (typeof this.onError === "function") await this.onError(err, ctx);
2689
+ else throw err;
2690
+ }
2691
+ return true;
2692
+ }
2693
+ /** Wire handler to one poller over given channels. Returns { poller, stop }. */
2694
+ async attach(client, channelIds, pollOptions = {}) {
2695
+ const poller = new MessagePoller(client, channelIds, pollOptions);
2696
+ poller.on("message", (m) => {
2697
+ this.handle(client, m).catch((err) => {
2698
+ if (typeof this.onError === "function") this.onError(err, this.makeCtx(client, m));
2699
+ });
2700
+ });
2701
+ await poller.start();
2702
+ return { poller, stop: () => poller.stop() };
2703
+ }
2704
+ /**
2705
+ * Render help text. `command` selects a detail view: description, usage,
2706
+ * aliases and cooldown (`!help stats`). `prefix: true` (default) prepends
2707
+ * the command prefix, `descriptions: true` (default) appends descriptions.
2708
+ */
2709
+ help(options = {}) {
2710
+ const { prefix = true, descriptions = true, command = null } = options;
2711
+ const pre = prefix === true ? this.prefix : prefix || "";
2712
+ if (command) {
2713
+ const key = this.resolve(command);
2714
+ const entry = key ? this.commands.get(key) : null;
2715
+ if (!entry) return `Unknown command "${command}". Try ${this.prefix}help.`;
2716
+ const lines = [`${pre}${key}`];
2717
+ if (entry.aliases.length) lines.push(`Aliases: ${entry.aliases.map((a) => `${pre}${a}`).join(", ")}`);
2718
+ if (entry.description) lines.push(entry.description);
2719
+ if (entry.usage) lines.push(`Usage: ${pre}${key} ${entry.usage}`);
2720
+ if (entry.cooldown > 0) lines.push(`Cooldown: ${entry.cooldown}s`);
2721
+ return lines.join("\n");
2722
+ }
2723
+ const names = [...this.commands.entries()].map(([n, e]) => {
2724
+ let line = `${pre}${n}`;
2725
+ if (descriptions && e.description) line += `: ${e.description}`;
2726
+ return line;
2727
+ });
2728
+ return names.length ? `Commands:
2729
+ ${names.join("\n")}` : "No commands registered.";
2730
+ }
2731
+ helpText() {
2732
+ return this.help();
2733
+ }
2734
+ };
2735
+ function createCommandHandler(options) {
2736
+ return new CommandHandler(options);
2737
+ }
2738
+
2739
+ // src/client.js
2740
+ var RavenClient = class {
2741
+ constructor({ token, timeoutMs = 15e3, retryOn429 = true, ethanKey = null } = {}) {
2742
+ assertToken(token);
2743
+ this.token = token;
2744
+ this.timeoutMs = timeoutMs;
2745
+ this.retryOn429 = retryOn429;
2746
+ this.ethanKey = ethanKey || null;
2747
+ const ctx = {
2748
+ request: (method, path2, { query, body } = {}) => ravenRequest({
2749
+ token: this.token,
2750
+ path: path2,
2751
+ method,
2752
+ query,
2753
+ body,
2754
+ timeoutMs: this.timeoutMs,
2755
+ retryOn429: this.retryOn429
2756
+ })
2757
+ };
2758
+ let ethanClient = null;
2759
+ const ethanProxy = new Proxy(
2760
+ {},
2761
+ {
2762
+ get: (_, prop) => {
2763
+ if (typeof prop === "symbol" || prop === "then" || prop === "toJSON" || prop === "__ethanProxy") {
2764
+ return prop === "__ethanProxy" ? true : void 0;
2765
+ }
2766
+ if (!ethanClient) {
2767
+ if (!this.ethanKey) {
2768
+ throw new Error(
2769
+ "Ethan API key required (rve_...). Pass { ethanKey } to RavenClient or use EthanClient directly. Keys need an active EthanPlus/EthanPro plan."
2770
+ );
2771
+ }
2772
+ ethanClient = withSuggestions(
2773
+ new EthanClient({ key: this.ethanKey, timeoutMs: Math.max(this.timeoutMs, 3e4) }),
2774
+ "bot.client.ethan"
2775
+ );
2776
+ }
2777
+ const value = ethanClient[prop];
2778
+ return typeof value === "function" ? value.bind(ethanClient) : value;
2779
+ }
2780
+ }
2781
+ );
2782
+ this.me = withSuggestions(new MeResource(ctx), "bot.client.me");
2783
+ this.ethan = ethanProxy;
2784
+ this.guilds = withSuggestions(new GuildsResource(ctx), "bot.client.guilds");
2785
+ this.channels = withSuggestions(new ChannelsResource(ctx), "bot.client.channels");
2786
+ this.messages = withSuggestions(new MessagesResource(ctx), "bot.client.messages");
2787
+ this.roles = withSuggestions(new RolesResource(ctx), "bot.client.roles");
2788
+ this.members = withSuggestions(new MembersResource(ctx), "bot.client.members");
2789
+ this.dms = withSuggestions(new DmsResource(ctx), "bot.client.dms");
2790
+ this.oauth = withSuggestions(new OAuthResource(ctx), "bot.client.oauth");
2791
+ this.gateway = withSuggestions(new RavenGateway(this), "bot.client.gateway");
2792
+ return withSuggestions(this, "bot.client");
2793
+ }
2794
+ /** Low-level request escape hatch (e.g. future endpoints). */
2795
+ request(method, path2, opts) {
2796
+ return ravenRequest({
2797
+ token: this.token,
2798
+ path: path2,
2799
+ method,
2800
+ query: opts?.query,
2801
+ body: opts?.body,
2802
+ timeoutMs: opts?.timeoutMs ?? this.timeoutMs,
2803
+ retryOn429: opts?.retryOn429 ?? this.retryOn429
2804
+ });
2805
+ }
2806
+ /**
2807
+ * Poll one or more channels for new messages.
2808
+ * @param {Function} callback async (message) => void
2809
+ * @param {Object} opts { channelId | channelIds, pollInterval, limit, ignoreBots, selfAuthorId, signal }
2810
+ * @returns {Promise<MessagePoller>} call .stop() to end
2811
+ */
2812
+ async onMessage(callback, opts = {}) {
2813
+ const ids = opts.channelIds ?? opts.channelId ?? opts.channels;
2814
+ if (!ids) throw new Error("onMessage requires opts.channelId (string or string[]).");
2815
+ const poller = new MessagePoller(this, ids, {
2816
+ pollInterval: opts.pollInterval,
2817
+ limit: opts.limit,
2818
+ emitHistory: opts.emitHistory ?? false,
2819
+ ignoreBots: opts.ignoreBots ?? true,
2820
+ selfAuthorId: opts.selfAuthorId ?? null,
2821
+ signal: opts.signal ?? null,
2822
+ onError: opts.onError ?? null
2823
+ });
2824
+ poller.on("message", (m) => {
2825
+ Promise.resolve().then(() => callback(m)).catch((err) => poller.emit("error", err));
2826
+ });
2827
+ await poller.start();
2828
+ return poller;
2829
+ }
2830
+ /** Discover all text channels of all installed guilds (helper for polling setups). */
2831
+ async discoverTextChannels() {
2832
+ const guilds = await this.guilds.list();
2833
+ const out = [];
2834
+ for (const g of guilds) {
2835
+ const channels = await this.channels.list(g.id);
2836
+ for (const c of channels) if (c.type === "text") out.push({ guild: g, channel: c });
2837
+ }
2838
+ return out;
2839
+ }
2840
+ createCommands(options) {
2841
+ return new CommandHandler(options);
2842
+ }
2843
+ };
2844
+ function createClient(options) {
2845
+ return new RavenClient(options);
2846
+ }
2847
+
2848
+ // src/bot.js
2849
+ var RESYNC_MS = 5 * 60 * 1e3;
2850
+ var RavenBot = class {
2851
+ constructor({ token, prefix = "!", status = null, testGuild = null, testChannel = null, timeoutMs, retryOn429, log = true, debug = false, cooldownStore } = {}) {
2852
+ this.client = new RavenClient({ token, ...timeoutMs ? { timeoutMs } : {}, ...retryOn429 !== void 0 ? { retryOn429 } : {} });
2853
+ this.commands = new CommandHandler({ prefix, ...cooldownStore ? { cooldownStore } : {} });
2854
+ this.commands.onError = (err, ctx) => this._reportError(ctx?.message ?? null, err, ctx);
2855
+ this.status = status;
2856
+ this.debug = debug || process.env.RAVEN_DEBUG === "1" || false;
2857
+ this.testGuild = testGuild || process.env.RAVEN_TEST_GUILD || null;
2858
+ this.testChannel = testChannel || process.env.RAVEN_TEST_CHANNEL || null;
2859
+ this.testChannelId = null;
2860
+ this.log = log === true ? console.log.bind(console) : log || (() => {
2861
+ });
2862
+ this.errors = { format: (err, opts) => formatError(err, opts), suggest };
2863
+ this.started = false;
2864
+ this._resyncTimer = null;
2865
+ this._onMessage = null;
2866
+ this._onSigint = null;
2867
+ return withSuggestions(this, "bot");
2868
+ }
2869
+ _debug(...args) {
2870
+ if (this.debug) console.log("[Raven]", ...args);
2871
+ }
2872
+ /** Register a prefix command. Chainable. */
2873
+ command(name, fn, opts) {
2874
+ this.commands.command(name, fn, opts);
2875
+ return this;
2876
+ }
2877
+ /** Forward gateway events (message:new, ready, disconnect, guildCreate, ...). Chainable. */
2878
+ on(event, fn) {
2879
+ this.client.gateway.on(event, fn);
2880
+ return this;
2881
+ }
2882
+ /** Forward once-listeners, e.g. `bot.once("ready", (info) => ...)`. Chainable. */
2883
+ once(event, fn) {
2884
+ this.client.gateway.once(event, fn);
2885
+ return this;
2886
+ }
2887
+ /** Remove a forwarded listener. Chainable. */
2888
+ off(event, fn) {
2889
+ this.client.gateway.off(event, fn);
2890
+ return this;
2891
+ }
2892
+ /** Connect, sync subscriptions, wire commands. Resolves with { bot, guilds, channels }. */
2893
+ async start() {
2894
+ if (this.started) return this.info();
2895
+ this._debug("Connecting...");
2896
+ const info = await this.client.gateway.connect();
2897
+ this.started = true;
2898
+ this._debug("Gateway connected");
2899
+ this._debug(`Authenticated as ${info.bot?.username ?? "unknown"}`);
2900
+ this._debug(`Loaded ${this.commands.commands.size} command(s)`);
2901
+ this._onMessage = (m) => {
2902
+ this.commands.handle(this.client, m).catch((err) => this._reportError(m, err));
2903
+ };
2904
+ this.client.gateway.on("message:new", this._onMessage);
2905
+ let synced = { guilds: info.guilds || [], channels: 0 };
2906
+ try {
2907
+ synced = await this.client.gateway.sync();
2908
+ } catch (err) {
2909
+ this.log("sync failed:", err?.message || err);
2910
+ }
2911
+ this._debug(`Guilds: ${synced.guilds.length}`);
2912
+ if (this.status !== void 0 && this.status !== null) {
2913
+ try {
2914
+ const res = await this.client.gateway.setStatus(this.status);
2915
+ this.log(`status: ${res.status}${res.locked ? " (locked)" : ""}`);
2916
+ } catch (err) {
2917
+ this.log("status failed:", err?.message || err);
2918
+ }
2919
+ }
2920
+ this._resyncTimer = setInterval(() => {
2921
+ this.client.gateway.sync().catch(() => {
2922
+ });
2923
+ }, RESYNC_MS);
2924
+ this._resyncTimer.unref?.();
2925
+ await this._resolveTestChannel();
2926
+ this._onSigint = () => this.stop();
2927
+ process.on("SIGINT", this._onSigint);
2928
+ this.log(`online as ${info.bot.username}: prefix "${this.commands.prefix}": ${synced.channels} channel(s) on ${synced.guilds.length} guild(s)`);
2929
+ return { bot: info.bot, guilds: synced.guilds, channels: synced.channels };
2930
+ }
2931
+ /** Disconnect and clean up timers/listeners. */
2932
+ stop() {
2933
+ if (this._resyncTimer) {
2934
+ clearInterval(this._resyncTimer);
2935
+ this._resyncTimer = null;
2936
+ }
2937
+ if (this._onMessage) {
2938
+ this.client.gateway.off("message:new", this._onMessage);
2939
+ this._onMessage = null;
2940
+ }
2941
+ if (this._onSigint) {
2942
+ process.off("SIGINT", this._onSigint);
2943
+ this._onSigint = null;
2944
+ }
2945
+ this.client.gateway.disconnect();
2946
+ this.started = false;
2947
+ return this;
2948
+ }
2949
+ info() {
2950
+ return { bot: this.client.gateway.bot, guilds: this.client.gateway.guilds };
2951
+ }
2952
+ /** Find the bug channel: testChannel wins, else #bot-test / #test / first text channel of testGuild. */
2953
+ async _resolveTestChannel() {
2954
+ this.testChannelId = null;
2955
+ if (this.testChannel) {
2956
+ this.testChannelId = this.testChannel;
2957
+ this.log(`test area: bugs go to channel ${this.testChannelId}`);
2958
+ return this.testChannelId;
2959
+ }
2960
+ if (!this.testGuild) return null;
2961
+ try {
2962
+ const channels = await this.client.channels.list(this.testGuild);
2963
+ const texts = channels.filter((c) => c.type === "text");
2964
+ const pick = texts.find((c) => c.name === "bot-test") || texts.find((c) => c.name === "test") || texts[0] || null;
2965
+ if (pick) {
2966
+ this.testChannelId = pick.id;
2967
+ this.log(`test area: #${pick.name} on ${this.testGuild}: errors land here`);
2968
+ } else {
2969
+ this.log(`test area: no text channel found on ${this.testGuild}`);
2970
+ }
2971
+ } catch (err) {
2972
+ this.log("test area failed:", err?.message || err);
2973
+ }
2974
+ return this.testChannelId;
2975
+ }
2976
+ /** Emit commandError, log when unhandled, post to the test channel (if set). Never throws. */
2977
+ async _reportError(m, err, ctx = null) {
2978
+ let handled = false;
2979
+ try {
2980
+ const gw = this.client.gateway;
2981
+ if (typeof gw?.listenerCount === "function") {
2982
+ handled = gw.listenerCount("commandError") > 0 || gw.listenerCount("*") > 0;
2983
+ }
2984
+ const c = ctx ?? this.commands.makeCtx(this.client, m ?? {});
2985
+ gw.emit("commandError", err, c);
2986
+ } catch {
2987
+ }
2988
+ if (!handled) {
2989
+ try {
2990
+ this.log(formatError(err));
2991
+ } catch {
2992
+ }
2993
+ }
2994
+ if (!this.testChannelId) return;
2995
+ try {
2996
+ const cmd = m?.content ? String(m.content).split(/\s+/)[0] : "?";
2997
+ const pretty = formatError(err, { color: false });
2998
+ await this.client.messages.send(this.testChannelId, `Error in ${cmd}:
2999
+ ${pretty}`.slice(0, 1500));
3000
+ } catch {
3001
+ }
3002
+ }
3003
+ };
3004
+ function createBot(options) {
3005
+ return new RavenBot(options);
3006
+ }
3007
+ // Annotate the CommonJS export names for ESM import in node:
3008
+ 0 && (module.exports = {
3009
+ AuthenticationError,
3010
+ BOT_STATUSES,
3011
+ CHANNEL_TYPES,
3012
+ CommandHandler,
3013
+ DEFAULT_ADMIN_PERMS,
3014
+ DEFAULT_BASE_URL,
3015
+ DEFAULT_EVERYONE_PERMS,
3016
+ ETHAN_KEY_PREFIX,
3017
+ ETHAN_MESSAGE_MAX,
3018
+ ETHAN_MODEL,
3019
+ ETHAN_PERSONALITIES,
3020
+ EthanClient,
3021
+ GATEWAY_EVENTS,
3022
+ GatewayNotAvailableError,
3023
+ MemoryCooldownStore,
3024
+ MessagePoller,
3025
+ MissingScopeError,
3026
+ NotFoundError,
3027
+ NotInstalledError,
3028
+ PERMISSIONS,
3029
+ PERMISSION_MASK,
3030
+ RateLimitedError,
3031
+ RavenAPIError,
3032
+ RavenAuthError,
3033
+ RavenBot,
3034
+ RavenClient,
3035
+ RavenError,
3036
+ RavenGateway,
3037
+ RavenNotFoundError,
3038
+ RavenPermissionError,
3039
+ RavenRateLimitError,
3040
+ RavenValidationError,
3041
+ ValidationError,
3042
+ assertChannelName,
3043
+ assertChannelType,
3044
+ assertEthanMessage,
3045
+ assertPersonality,
3046
+ assertRoleColor,
3047
+ assertRoleName,
3048
+ assertRolePermissions,
3049
+ assertTemperature,
3050
+ canAssignRole,
3051
+ canManageRole,
3052
+ canModerate,
3053
+ createBot,
3054
+ createClient,
3055
+ createCommandHandler,
3056
+ createEthanClient,
3057
+ editDistance,
3058
+ escapeContent,
3059
+ formatError,
3060
+ getHighestRole,
3061
+ getMemberPermissions,
3062
+ hasPermission,
3063
+ invalidChoice,
3064
+ isBotAuthorId,
3065
+ markDiff,
3066
+ missingArgument,
3067
+ normalizeRole,
3068
+ parseMentions,
3069
+ sanitizeContent,
3070
+ sortRoles,
3071
+ suggest,
3072
+ suggestAll,
3073
+ toCamel,
3074
+ unknownOption,
3075
+ withSuggestions
3076
+ });