@kevlid/discordmenus 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,3 +60,8 @@ client.on("interactionCreate", async (interaction) => {
60
60
 
61
61
  client.login(process.env.BOT_TOKEN);
62
62
  ```
63
+
64
+ ### Role / user / channel selects
65
+
66
+ - Persist IDs as **strings** (Discord snowflakes are too large for JS numbers; Mongo/JSON numbers will corrupt them and `default_values` won’t show on rerender).
67
+ - Select `custom_id`s include **category + page** so paging doesn’t reuse the same component identity in the same slot across pages.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevlid/discordmenus",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "some menu stuff for discord stuff",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
@@ -398,6 +398,15 @@ async function handleComponent(interaction, menu, menu_key, renderMenu) {
398
398
  const maxSel = Number(opt?.maxValues);
399
399
  const multi = Number.isFinite(maxSel) && maxSel > 1;
400
400
  let saveValue = multi ? [...selected] : selected[0] ?? null;
401
+ const snowflakeSelectTypes = new Set([
402
+ OptionTypes.UserSelect,
403
+ OptionTypes.RoleSelect,
404
+ OptionTypes.ChannelSelect,
405
+ OptionTypes.MentionableSelect,
406
+ ]);
407
+ if (opt && snowflakeSelectTypes.has(opt.type)) {
408
+ saveValue = multi ? selected.map(v => String(v)) : selected[0] != null ? String(selected[0]) : null;
409
+ }
401
410
  if (
402
411
  !multi &&
403
412
  saveValue == null &&
@@ -2,20 +2,48 @@ const { OptionTypes } = require("../utils/types");
2
2
  const { renderTextDisplay, renderActionRow, renderSeparator } = require("./utils");
3
3
  const { encodeId } = require("../utils/customIds");
4
4
 
5
- function toIdArray(currentValue) {
6
- if (Array.isArray(currentValue)) {
7
- return currentValue;
8
- }
9
- if (currentValue != null) {
10
- return [currentValue];
5
+ /**
6
+ * Discord snowflakes as JS numbers lose precision; DBs should store string IDs.
7
+ * Coerce to string and keep plausible snowflake digit strings for default_values.
8
+ */
9
+ function collectEntityIds(value, maxCount) {
10
+ const max = Math.max(0, Math.min(25, Number(maxCount) || 25));
11
+ const out = [];
12
+ const seen = new Set();
13
+ const push = v => {
14
+ if (out.length >= max) return;
15
+ if (v == null) return;
16
+ let s;
17
+ if (typeof v === "bigint") {
18
+ s = v.toString();
19
+ } else if (typeof v === "number" && Number.isFinite(v)) {
20
+ s = String(Math.trunc(v));
21
+ } else if (typeof v === "string") {
22
+ s = v.trim();
23
+ } else if (typeof v === "object" && v !== null && v.id != null) {
24
+ s = String(v.id).trim();
25
+ } else {
26
+ return;
27
+ }
28
+ if (!/^\d{17,22}$/.test(s)) return;
29
+ if (seen.has(s)) return;
30
+ seen.add(s);
31
+ out.push(s);
32
+ };
33
+ if (Array.isArray(value)) {
34
+ for (const item of value) {
35
+ push(item);
36
+ }
37
+ } else {
38
+ push(value);
11
39
  }
12
- return [];
40
+ return out;
13
41
  }
14
42
 
15
43
  function renderSelectMenuComponent(menuKey, opt, currentValue, userId, category, page) {
16
- // Stateless: do not depend on current rendered page in the custom id.
17
- // The handler can locate the option by key in the built menu.
18
- const ctx = { u: userId };
44
+ // Include cat/page so each physical select is unique when paging; avoids client mix-ups
45
+ // when the same row position shows a different option on another page.
46
+ const ctx = { u: userId, cat: category, page };
19
47
  const customId = encodeId(menuKey, [opt.key, opt.type], ctx);
20
48
 
21
49
  const component = {
@@ -29,8 +57,14 @@ function renderSelectMenuComponent(menuKey, opt, currentValue, userId, category,
29
57
  component.placeholder = opt.placeholder;
30
58
  }
31
59
 
60
+ const maxDefaults = Math.min(25, Number(opt.maxValues) || 1);
61
+
32
62
  if (opt.type === OptionTypes.StringSelect) {
33
- const selectedValues = toIdArray(currentValue);
63
+ const selectedValues = Array.isArray(currentValue)
64
+ ? currentValue.map(String)
65
+ : currentValue != null
66
+ ? [String(currentValue)]
67
+ : [];
34
68
  component.options = opt.choices.map(c => ({
35
69
  label: c.label,
36
70
  value: c.value,
@@ -40,14 +74,14 @@ function renderSelectMenuComponent(menuKey, opt, currentValue, userId, category,
40
74
  }
41
75
 
42
76
  if (opt.type === OptionTypes.UserSelect) {
43
- const ids = toIdArray(currentValue);
77
+ const ids = collectEntityIds(currentValue, maxDefaults);
44
78
  if (ids.length > 0) {
45
79
  component.default_values = ids.map(id => ({ id, type: "user" }));
46
80
  }
47
81
  }
48
82
 
49
83
  if (opt.type === OptionTypes.RoleSelect) {
50
- const ids = toIdArray(currentValue);
84
+ const ids = collectEntityIds(currentValue, maxDefaults);
51
85
  if (ids.length > 0) {
52
86
  component.default_values = ids.map(id => ({ id, type: "role" }));
53
87
  }
@@ -57,7 +91,7 @@ function renderSelectMenuComponent(menuKey, opt, currentValue, userId, category,
57
91
  if (opt.channelTypes && opt.channelTypes.length > 0) {
58
92
  component.channel_types = opt.channelTypes;
59
93
  }
60
- const ids = toIdArray(currentValue);
94
+ const ids = collectEntityIds(currentValue, maxDefaults);
61
95
  if (ids.length > 0) {
62
96
  component.default_values = ids.map(id => ({ id, type: "channel" }));
63
97
  }
@@ -80,4 +114,4 @@ function renderSelectMenuOption(menuKey, opt, currentValue, userId, category, pa
80
114
  return [textDisplay, actionRow, renderSeparator()];
81
115
  }
82
116
 
83
- module.exports = { renderSelectMenuOption };
117
+ module.exports = { renderSelectMenuOption, collectEntityIds };