discord-embed-router 0.2.0 → 0.2.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.js CHANGED
@@ -47,6 +47,20 @@ var import_path_to_regexp3 = require("path-to-regexp");
47
47
 
48
48
  // src/consts.ts
49
49
  var ID_PREFIX = "der";
50
+ var METHOD_TO_ENCODING = {
51
+ GET: "G",
52
+ POST: "P",
53
+ PUT: "U",
54
+ PATCH: "A",
55
+ DELETE: "D"
56
+ };
57
+ var ENCODING_TO_METHOD = {
58
+ G: "GET",
59
+ P: "POST",
60
+ U: "PUT",
61
+ A: "PATCH",
62
+ D: "DELETE"
63
+ };
50
64
  var BASE_URL = "discord://embed.router";
51
65
  var PUA_START = 57344;
52
66
  var PUA_END = 63743;
@@ -63,34 +77,38 @@ var pathToString = (path2, checkValidity = true) => {
63
77
 
64
78
  // src/helpers/decodePath.ts
65
79
  var import_path_to_regexp2 = require("path-to-regexp");
66
- var decodePath = (idPrefix, interaction) => {
80
+ var decodePath = ({
81
+ idPrefix,
82
+ interaction
83
+ }) => {
67
84
  const customId = interaction.customId;
68
85
  if (!customId.startsWith(idPrefix)) return false;
69
86
  if (interaction.isButton()) {
70
- return customId.slice(idPrefix.length);
71
- } else if (interaction.isStringSelectMenu()) {
72
- if (interaction.values.length === 0) return false;
73
- return fillParams(customId.slice(idPrefix.length), {
74
- to: interaction.values[0].split("/").slice(1)
75
- });
76
- } else if (interaction.isChannelSelectMenu()) {
77
- if (interaction.values.length === 0) return false;
78
- return fillParams(customId.slice(idPrefix.length), {
79
- channelId: interaction.values[0]
80
- });
81
- } else if (interaction.isRoleSelectMenu()) {
87
+ return parseMethodAndPath(customId.slice(idPrefix.length));
88
+ } else if (interaction.isAnySelectMenu()) {
82
89
  if (interaction.values.length === 0) return false;
83
- return fillParams(customId.slice(idPrefix.length), {
84
- roleId: interaction.values[0]
85
- });
86
- } else if (interaction.isUserSelectMenu()) {
87
- if (interaction.values.length === 0) return false;
88
- return fillParams(customId.slice(idPrefix.length), {
89
- userId: interaction.values[0]
90
- });
90
+ const res = parseMethodAndPath(customId.slice(idPrefix.length));
91
+ if (!res) return false;
92
+ const { method, path: path2 } = res;
93
+ return {
94
+ method,
95
+ path: fillParams(path2, {
96
+ [interaction.isStringSelectMenu() ? "to" : interaction.isChannelSelectMenu() ? "channelId" : interaction.isRoleSelectMenu() ? "roleId" : "userId"]: interaction.values[0].split("/").slice(1)
97
+ })
98
+ };
91
99
  }
92
100
  return false;
93
101
  };
102
+ var parseMethodAndPath = (pathWithMethod) => {
103
+ const firstSlash = pathWithMethod.indexOf("/");
104
+ if (firstSlash <= 0) return false;
105
+ const method = ENCODING_TO_METHOD[pathWithMethod.slice(0, firstSlash)];
106
+ if (method === void 0) return false;
107
+ return {
108
+ method,
109
+ path: pathWithMethod.slice(firstSlash)
110
+ };
111
+ };
94
112
  var fillParams = (path2, params = {}) => {
95
113
  const url = new URL(path2, BASE_URL);
96
114
  const toPath = (0, import_path_to_regexp2.compile)(url.pathname);
@@ -114,17 +132,17 @@ var fillParams = (path2, params = {}) => {
114
132
  // src/EmbedRouter.ts
115
133
  var EmbedRouter = class _EmbedRouter {
116
134
  // identifier -> embedRouter
117
- static usedIdentifiers = /* @__PURE__ */ new Map();
135
+ static #usedIdentifiers = /* @__PURE__ */ new Map();
118
136
  // Name used to generate prefixes
119
- name = "";
137
+ #name = "";
120
138
  // Prefix for customIds of RouteButtonBuilders
121
- idPrefix;
122
- identifier = "";
139
+ #idPrefix;
140
+ #identifier = "";
123
141
  getIdPrefix() {
124
- return `${this.idPrefix}${this.identifier}`;
142
+ return `${this.#idPrefix}${this.#identifier}`;
125
143
  }
126
144
  // All added routes
127
- routes = [];
145
+ #routes = /* @__PURE__ */ new Map();
128
146
  /**
129
147
  *
130
148
  * @param name the name to give the router. ensures buttons stay connected across restarts
@@ -134,40 +152,50 @@ var EmbedRouter = class _EmbedRouter {
134
152
  name = "",
135
153
  idPrefix = ID_PREFIX
136
154
  } = {}) {
137
- if (_EmbedRouter.usedIdentifiers.size >= PUA_RANGE) {
155
+ if (_EmbedRouter.#usedIdentifiers.size >= PUA_RANGE) {
138
156
  throw new Error(`You can not have more than ${PUA_RANGE} routers`);
139
157
  }
140
158
  if (idPrefix.includes("/")) {
141
159
  throw new Error(`Prefix can't contain "/": ${idPrefix}`);
142
160
  }
143
- this.idPrefix = idPrefix;
144
- this.name = name;
145
- this.updateIdentifier();
161
+ this.#idPrefix = idPrefix;
162
+ this.#name = name;
163
+ this.#updateIdentifier();
146
164
  }
147
- updateIdentifier() {
148
- const hash = (0, import_node_crypto.createHash)("sha256").update(this.name).digest();
165
+ #updateIdentifier() {
166
+ const hash = (0, import_node_crypto.createHash)("sha256").update(this.#name).digest();
149
167
  let raw = hash.readUint32BE(0);
150
168
  let char;
151
169
  const nameCollisions = [];
152
170
  while (true) {
153
171
  const codepoint = PUA_START + raw++ % PUA_RANGE;
154
172
  char = String.fromCodePoint(codepoint);
155
- const collisionRouter = _EmbedRouter.usedIdentifiers.get(char);
173
+ const collisionRouter = _EmbedRouter.#usedIdentifiers.get(char);
156
174
  if (collisionRouter === void 0) break;
157
- if (this.name.length > 0 && collisionRouter.name.length === 0) {
158
- collisionRouter.updateIdentifier();
175
+ if (this.#name.length > 0 && collisionRouter.#name.length === 0) {
176
+ collisionRouter.#updateIdentifier();
159
177
  break;
160
178
  }
161
179
  nameCollisions.push(collisionRouter);
162
180
  }
163
- _EmbedRouter.usedIdentifiers.set(char, this);
164
- if (nameCollisions.length > 0 && this.name.length > 0) {
181
+ _EmbedRouter.#usedIdentifiers.set(char, this);
182
+ if (nameCollisions.length > 0 && this.#name.length > 0) {
165
183
  process.emitWarning(
166
- `EmbedRouter identifier collision for name "${this.name}" with ${nameCollisions.map((c) => `"${c.name}"`).join(", ")}`,
184
+ `EmbedRouter identifier collision for name "${this.#name}" with ${nameCollisions.map((c) => `"${c.#name}"`).join(", ")}`,
167
185
  "EmbedRouterWarning"
168
186
  );
169
187
  }
170
- this.identifier = char;
188
+ this.#identifier = char;
189
+ }
190
+ #addRoute(method, routePath, handler) {
191
+ const methodRoutes = this.#routes.get(method) ?? [];
192
+ methodRoutes.push({
193
+ method,
194
+ path: Array.isArray(routePath) ? routePath : [routePath],
195
+ matchFunction: (0, import_path_to_regexp3.match)(routePath),
196
+ handler
197
+ });
198
+ this.#routes.set(method, methodRoutes);
171
199
  }
172
200
  /**
173
201
  * Registers a path with the router
@@ -176,11 +204,43 @@ var EmbedRouter = class _EmbedRouter {
176
204
  * @param handler function that generates the message when a path is matched
177
205
  */
178
206
  get(routePath, handler) {
179
- this.routes.push({
180
- path: Array.isArray(routePath) ? routePath : [routePath],
181
- matchFunction: (0, import_path_to_regexp3.match)(routePath),
182
- handler
183
- });
207
+ this.#addRoute("GET", routePath, handler);
208
+ }
209
+ /**
210
+ * Registers a path with the router
211
+ *
212
+ * @param routePath path to match with
213
+ * @param handler function that generates the message when a path is matched
214
+ */
215
+ post(routePath, handler) {
216
+ this.#addRoute("POST", routePath, handler);
217
+ }
218
+ /**
219
+ * Registers a path with the router
220
+ *
221
+ * @param routePath path to match with
222
+ * @param handler function that generates the message when a path is matched
223
+ */
224
+ put(routePath, handler) {
225
+ this.#addRoute("PUT", routePath, handler);
226
+ }
227
+ /**
228
+ * Registers a path with the router
229
+ *
230
+ * @param routePath path to match with
231
+ * @param handler function that generates the message when a path is matched
232
+ */
233
+ patch(routePath, handler) {
234
+ this.#addRoute("PATCH", routePath, handler);
235
+ }
236
+ /**
237
+ * Registers a path with the router
238
+ *
239
+ * @param routePath path to match with
240
+ * @param handler function that generates the message when a path is matched
241
+ */
242
+ delete(routePath, handler) {
243
+ this.#addRoute("DELETE", routePath, handler);
184
244
  }
185
245
  /**
186
246
  * Adds a subrouter to the router
@@ -190,11 +250,14 @@ var EmbedRouter = class _EmbedRouter {
190
250
  */
191
251
  use(routePath, embedRouter) {
192
252
  const pathString = pathToString(routePath);
193
- for (const route of embedRouter.routes) {
194
- this.get(
195
- route.path.map((p) => import_node_path.default.posix.join(pathString, pathToString(p))),
196
- route.handler
197
- );
253
+ for (const [method, routes] of embedRouter.#routes) {
254
+ for (const route of routes) {
255
+ this.#addRoute(
256
+ method,
257
+ route.path.map((p) => import_node_path.default.posix.join(pathString, pathToString(p))),
258
+ route.handler
259
+ );
260
+ }
198
261
  }
199
262
  }
200
263
  /**
@@ -203,22 +266,29 @@ var EmbedRouter = class _EmbedRouter {
203
266
  * @param interaction interactions from "interactionCreate" (filter for ButtonInteractions)
204
267
  */
205
268
  async listener(interaction, locals) {
206
- const path2 = decodePath(this.getIdPrefix(), interaction);
207
- if (!path2)
269
+ const res = decodePath({ idPrefix: this.getIdPrefix(), interaction });
270
+ if (!res)
208
271
  throw new Error(`Invalid component found: id ${interaction.customId}`);
209
- this.dispatch(interaction, path2, locals);
272
+ this.dispatch({ interaction, method: res.method, path: res.path, locals });
210
273
  }
211
274
  /**
212
275
  * Connect or update an interaction message to a path
213
276
  *
214
277
  * @param interaction interaction to connect to
215
278
  * @param path path to route the interaction to
279
+ * @param method method to send to route
216
280
  * @param flags optional discord flags to send with message (only allowed on first reply)
217
281
  */
218
- async dispatch(interaction, path2, locals, flags) {
282
+ async dispatch({
283
+ interaction,
284
+ method = "GET",
285
+ path: path2,
286
+ flags,
287
+ locals
288
+ }) {
219
289
  if (interaction.isAutocomplete())
220
290
  throw new Error("Autocomplete Interactions aren't supported");
221
- const resolvedRoute = this.resolve(pathToString(path2, false));
291
+ const resolvedRoute = this.#resolve(method, pathToString(path2, false));
222
292
  if (!resolvedRoute)
223
293
  throw new Error(`No route found for ${pathToString(path2, false)}`);
224
294
  const routeResponse = await resolvedRoute.handler(interaction, {
@@ -244,9 +314,9 @@ var EmbedRouter = class _EmbedRouter {
244
314
  });
245
315
  }
246
316
  }
247
- resolve(routePath) {
317
+ #resolve(method, routePath) {
248
318
  const url = new URL(routePath, BASE_URL);
249
- for (const route of this.routes) {
319
+ for (const route of this.#routes.get(method) ?? []) {
250
320
  const result = route.matchFunction(url.pathname);
251
321
  if (result) {
252
322
  return {
@@ -267,19 +337,29 @@ var EmbedRouter = class _EmbedRouter {
267
337
  var import_discord = require("discord.js");
268
338
 
269
339
  // src/helpers/encodePath.ts
270
- var encodePath = (idPrefix, path2, query) => {
340
+ var encodePath = ({
341
+ idPrefix,
342
+ method,
343
+ path: path2,
344
+ query
345
+ }) => {
271
346
  const url = new URL(pathToString(path2), BASE_URL);
272
347
  if (query) {
273
348
  for (const [key, value] of new URLSearchParams(query)) {
274
349
  url.searchParams.set(key, value);
275
350
  }
276
351
  }
277
- return `${idPrefix}${url.pathname}${url.search}`;
352
+ return `${idPrefix}${METHOD_TO_ENCODING[method]}${url.pathname}${url.search}`;
353
+ };
354
+
355
+ // src/types/componentBuilders.ts
356
+ var isSetOptions = (u) => {
357
+ return typeof u === "object" && u !== null && (!("method" in u) || "method" in u && typeof u.method === "string" || u.method === void 0) && "path" in u && typeof u.path === "string" && (!("query" in u) || "query" in u && typeof u.query in ["object", "string"] || u.query === void 0);
278
358
  };
279
359
 
280
360
  // src/componentBuilders/RouteButtonBuilder.ts
281
361
  var RouteButtonBuilder = class extends import_discord.ButtonBuilder {
282
- embedRouter;
362
+ #embedRouter;
283
363
  /**
284
364
  *
285
365
  * @param embedRouter the router you want to route with
@@ -287,7 +367,7 @@ var RouteButtonBuilder = class extends import_discord.ButtonBuilder {
287
367
  */
288
368
  constructor(embedRouter, data) {
289
369
  super(data);
290
- this.embedRouter = embedRouter;
370
+ this.#embedRouter = embedRouter;
291
371
  }
292
372
  /**
293
373
  * Not supported for RouteButtonBuilder
@@ -307,14 +387,20 @@ var RouteButtonBuilder = class extends import_discord.ButtonBuilder {
307
387
  setCustomId() {
308
388
  throw new Error("setCustomId is not supported on RouteButtonBuilder");
309
389
  }
310
- /**
311
- * Sets the path to route to when clicked
312
- *
313
- * @param path the path to route to
314
- * @param query any query parameters you want to add
315
- */
316
- setTo(path2, query) {
317
- super.setCustomId(encodePath(this.embedRouter.getIdPrefix(), path2, query));
390
+ setTo(arg) {
391
+ const {
392
+ method = "GET",
393
+ path: path2,
394
+ query
395
+ } = isSetOptions(arg) ? arg : { path: arg };
396
+ super.setCustomId(
397
+ encodePath({
398
+ idPrefix: this.#embedRouter.getIdPrefix(),
399
+ method,
400
+ path: path2,
401
+ query
402
+ })
403
+ );
318
404
  return this;
319
405
  }
320
406
  };
@@ -325,17 +411,6 @@ var import_discord3 = require("discord.js");
325
411
  // src/componentBuilders/RouteStringMenuOptionBuilder.ts
326
412
  var import_discord2 = require("discord.js");
327
413
  var RouteStringSelectMenuOptionBuilder = class extends import_discord2.StringSelectMenuOptionBuilder {
328
- /**
329
- *
330
- * @param data the data to construct a component out of
331
- */
332
- constructor(data) {
333
- const stringSelectData = data ? {
334
- ...data,
335
- value: encodePath("", data.to)
336
- } : void 0;
337
- super(stringSelectData);
338
- }
339
414
  /**
340
415
  * Not supported for RouteStringSelectMenuOptionBuilder (use setTo)
341
416
  *
@@ -347,21 +422,27 @@ var RouteStringSelectMenuOptionBuilder = class extends import_discord2.StringSel
347
422
  "setValue is not supported on RouteStringSelectMenuOptionBuilder"
348
423
  );
349
424
  }
350
- /**
351
- * Sets the path to route to when clicked
352
- *
353
- * @param path the path to route to
354
- * @param query any query parameters you want to add
355
- */
356
- setTo(path2, query) {
357
- super.setValue(encodePath("", path2, query));
425
+ setTo(arg) {
426
+ const {
427
+ method = "GET",
428
+ path: path2,
429
+ query
430
+ } = isSetOptions(arg) ? arg : { path: arg };
431
+ super.setValue(
432
+ encodePath({
433
+ idPrefix: "",
434
+ method,
435
+ path: path2,
436
+ query
437
+ })
438
+ );
358
439
  return this;
359
440
  }
360
441
  };
361
442
 
362
443
  // src/componentBuilders/RouteStringSelectMenuBuilder.ts
363
444
  var RouteStringSelectMenuBuilder = class extends import_discord3.StringSelectMenuBuilder {
364
- embedRouter;
445
+ #embedRouter;
365
446
  /**
366
447
  *
367
448
  * @param embedRouter the router you want to route with
@@ -369,15 +450,15 @@ var RouteStringSelectMenuBuilder = class extends import_discord3.StringSelectMen
369
450
  * @param query any query parameters you want to add, :to will be replaced with the selected user's id
370
451
  * @param data the data to construct a component out of
371
452
  */
372
- constructor(embedRouter, path2 = "/*to", query, data) {
453
+ constructor(embedRouter, data) {
373
454
  super(data);
374
- this.embedRouter = embedRouter;
455
+ this.#embedRouter = embedRouter;
375
456
  super.setCustomId(
376
- encodePath(
377
- this.embedRouter.getIdPrefix(),
378
- path2,
379
- new URLSearchParams(query)
380
- )
457
+ encodePath({
458
+ idPrefix: this.#embedRouter.getIdPrefix(),
459
+ method: "GET",
460
+ path: "/*to"
461
+ })
381
462
  );
382
463
  }
383
464
  /**
@@ -441,23 +522,36 @@ var RouteStringSelectMenuBuilder = class extends import_discord3.StringSelectMen
441
522
  );
442
523
  return this;
443
524
  }
525
+ setPattern(arg) {
526
+ const {
527
+ method = "GET",
528
+ path: path2,
529
+ query
530
+ } = isSetOptions(arg) ? arg : { path: arg };
531
+ super.setCustomId(
532
+ encodePath({
533
+ idPrefix: this.#embedRouter.getIdPrefix(),
534
+ method,
535
+ path: path2,
536
+ query
537
+ })
538
+ );
539
+ return this;
540
+ }
444
541
  };
445
542
 
446
543
  // src/componentBuilders/RouteChannelSelectMenuBuilder.ts
447
544
  var import_discord4 = require("discord.js");
448
545
  var RouteChannelSelectMenuBuilder = class extends import_discord4.ChannelSelectMenuBuilder {
449
- embedRouter;
546
+ #embedRouter;
450
547
  /**
451
548
  *
452
549
  * @param embedRouter the router you want to route with
453
- * @param path the path to redirect to, :channelId in path will be replaced with the selected user's id
454
- * @param query any query parameters you want to add, :channelId will be replaced with the selected user's id
455
550
  * @param data the data to construct a component out of
456
551
  */
457
- constructor(embedRouter, path2, query, data) {
552
+ constructor(embedRouter, data) {
458
553
  super(data);
459
- this.embedRouter = embedRouter;
460
- super.setCustomId(encodePath(this.embedRouter.getIdPrefix(), path2, query));
554
+ this.#embedRouter = embedRouter;
461
555
  }
462
556
  /**
463
557
  * Not supported for RouteChannelSelectMenuBuilder (customId set from embedRouter)
@@ -470,23 +564,36 @@ var RouteChannelSelectMenuBuilder = class extends import_discord4.ChannelSelectM
470
564
  "setCustomId is not supported on RouteChannelSelectMenuBuilder"
471
565
  );
472
566
  }
567
+ setPattern(arg) {
568
+ const {
569
+ method = "GET",
570
+ path: path2,
571
+ query
572
+ } = isSetOptions(arg) ? arg : { path: arg };
573
+ super.setCustomId(
574
+ encodePath({
575
+ idPrefix: this.#embedRouter.getIdPrefix(),
576
+ method,
577
+ path: path2,
578
+ query
579
+ })
580
+ );
581
+ return this;
582
+ }
473
583
  };
474
584
 
475
585
  // src/componentBuilders/RouteRoleSelectMenuBuilder.ts
476
586
  var import_discord5 = require("discord.js");
477
587
  var RouteRoleSelectMenuBuilder = class extends import_discord5.RoleSelectMenuBuilder {
478
- embedRouter;
588
+ #embedRouter;
479
589
  /**
480
590
  *
481
591
  * @param embedRouter the router you want to route with
482
- * @param path the path to redirect to, :roleId in path will be replaced with the selected user's id
483
- * @param query any query parameters you want to add, :roleId will be replaced with the selected user's id
484
592
  * @param data the data to construct a component out of
485
593
  */
486
- constructor(embedRouter, path2, query, data) {
594
+ constructor(embedRouter, data) {
487
595
  super(data);
488
- this.embedRouter = embedRouter;
489
- super.setCustomId(encodePath(this.embedRouter.getIdPrefix(), path2, query));
596
+ this.#embedRouter = embedRouter;
490
597
  }
491
598
  /**
492
599
  * Not supported for RouteRoleSelectMenuBuilder (customId set from embedRouter)
@@ -499,29 +606,36 @@ var RouteRoleSelectMenuBuilder = class extends import_discord5.RoleSelectMenuBui
499
606
  "setCustomId is not supported on RouteRoleSelectMenuBuilder"
500
607
  );
501
608
  }
609
+ setPattern(arg) {
610
+ const {
611
+ method = "GET",
612
+ path: path2,
613
+ query
614
+ } = isSetOptions(arg) ? arg : { path: arg };
615
+ super.setCustomId(
616
+ encodePath({
617
+ idPrefix: this.#embedRouter.getIdPrefix(),
618
+ method,
619
+ path: path2,
620
+ query
621
+ })
622
+ );
623
+ return this;
624
+ }
502
625
  };
503
626
 
504
627
  // src/componentBuilders/RouteUserSelectMenuBuilder.ts
505
628
  var import_discord6 = require("discord.js");
506
629
  var RouteUserSelectMenuBuilder = class extends import_discord6.UserSelectMenuBuilder {
507
- embedRouter;
630
+ #embedRouter;
508
631
  /**
509
632
  *
510
633
  * @param embedRouter the router you want to route with
511
- * @param path the path to redirect to, :userId in path will be replaced with the selected user's id
512
- * @param query any query parameters you want to add, :userId will be replaced with the selected user's id
513
634
  * @param data the data to construct a component out of
514
635
  */
515
- constructor(embedRouter, path2, query, data) {
636
+ constructor(embedRouter, data) {
516
637
  super(data);
517
- this.embedRouter = embedRouter;
518
- super.setCustomId(
519
- encodePath(
520
- this.embedRouter.getIdPrefix(),
521
- path2,
522
- new URLSearchParams(query)
523
- )
524
- );
638
+ this.#embedRouter = embedRouter;
525
639
  }
526
640
  /**
527
641
  * Not supported for RouteUserSelectMenuBuilder (customId set from embedRouter)
@@ -534,6 +648,22 @@ var RouteUserSelectMenuBuilder = class extends import_discord6.UserSelectMenuBui
534
648
  "setCustomId is not supported on RouteUserSelectMenuBuilder"
535
649
  );
536
650
  }
651
+ setPattern(arg) {
652
+ const {
653
+ method = "GET",
654
+ path: path2,
655
+ query
656
+ } = isSetOptions(arg) ? arg : { path: arg };
657
+ super.setCustomId(
658
+ encodePath({
659
+ idPrefix: this.#embedRouter.getIdPrefix(),
660
+ method,
661
+ path: path2,
662
+ query
663
+ })
664
+ );
665
+ return this;
666
+ }
537
667
  };
538
668
  // Annotate the CommonJS export names for ESM import in node:
539
669
  0 && (module.exports = {