@kevlid/discordmenus 0.1.2 → 0.1.3

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/index.d.ts CHANGED
@@ -257,3 +257,8 @@ export declare function normalizeInteraction(
257
257
  ): DiscordInteraction;
258
258
  export declare function fromEris(interaction: DiscordInteraction): DiscordInteraction;
259
259
  export declare function fromDiscordJS(interaction: DiscordInteraction): DiscordInteraction;
260
+
261
+ /** Encode option/menu keys for custom IDs (uppercase → _lowercase, spaces → _). */
262
+ export declare function formatKey(key: string): string;
263
+ /** Restore original key before get/save callbacks (inverse of formatKey). */
264
+ export declare function decodeKey(encoded: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevlid/discordmenus",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Components v2 settings menus for Discord bots",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
@@ -365,7 +365,20 @@ async function handleComponent(interaction, menu, menu_key, renderMenu) {
365
365
  const opt = menu.getOption(category, page, optionKey);
366
366
  const maxSel = Number(opt?.maxValues);
367
367
  const multi = Number.isFinite(maxSel) && maxSel > 1;
368
- const saveValue = multi ? [...selected] : selected[0] ?? null;
368
+ let saveValue = multi ? [...selected] : selected[0] ?? null;
369
+ if (
370
+ !multi &&
371
+ saveValue == null &&
372
+ opt?.type === OptionTypes.StringSelect &&
373
+ Number(opt.minValues ?? 1) >= 1 &&
374
+ Array.isArray(opt.choices) &&
375
+ opt.choices.length === 1
376
+ ) {
377
+ const only = opt.choices[0]?.value;
378
+ if (only != null && only !== "") {
379
+ saveValue = only;
380
+ }
381
+ }
369
382
  await menu.save(optionKey, saveValue, { guildId, userId });
370
383
  result = updateMessage(
371
384
  await renderMenu(menu_key, { category, index: page, userId, guildId }),
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ const { OptionTypes } = require("./utils/types");
4
4
  const { CustomIdPrefix, EphemeralMessageFlag } = require("./utils/constants");
5
5
  const { MenuStorage, MemoryMenuStorage } = require("./utils/storage");
6
6
  const { normalizeInteraction, fromEris, fromDiscordJS } = require("./utils/adapters");
7
+ const { formatKey, decodeKey } = require("./utils/formatKey");
7
8
 
8
9
  module.exports = {
9
10
  MenuManager,
@@ -16,4 +17,6 @@ module.exports = {
16
17
  normalizeInteraction,
17
18
  fromEris,
18
19
  fromDiscordJS,
19
- }
20
+ formatKey,
21
+ decodeKey,
22
+ };
@@ -6,6 +6,7 @@ const { OptionTypes } = require("./utils/types");
6
6
  const { PageRenderOverhead, OptionRenderCost, MaxComponents, ComponentsV2MessageFlags } = require("./utils/constants");
7
7
  const { validateComponents } = require("./utils/componentsValidate");
8
8
  const { arrayClone, normalizeObjectListItems, objectListItemsNeedReshape } = require("./utils/helpers");
9
+ const { decodeKey } = require("./utils/formatKey");
9
10
 
10
11
  class MenuInstance {
11
12
  constructor(builder, options = {}) {
@@ -41,11 +42,11 @@ class MenuInstance {
41
42
  }
42
43
 
43
44
  async get(key, ctx = {}) {
44
- return this.getData(key, ctx);
45
+ return this.getData(decodeKey(key), ctx);
45
46
  }
46
47
 
47
48
  async save(key, value, ctx = {}) {
48
- return this.saveData(key, value, ctx);
49
+ return this.saveData(decodeKey(key), value, ctx);
49
50
  }
50
51
 
51
52
  ensureBuilt() {
@@ -37,7 +37,15 @@ function normalizeInteraction(interaction) {
37
37
  customId = source.id;
38
38
  }
39
39
 
40
- let values = source.data?.values ?? source.values ?? interaction?.data?.values ?? interaction?.values ?? [];
40
+ // Prefer live interaction: toJSON() often omits or strips data.values for select menus,
41
+ // which breaks saves (especially single-value selects).
42
+ let values = interaction?.data?.values;
43
+ if (!Array.isArray(values) && interaction != null && typeof interaction.values !== "undefined") {
44
+ values = interaction.values;
45
+ }
46
+ if (!Array.isArray(values)) {
47
+ values = source.data?.values ?? source.values ?? [];
48
+ }
41
49
  if (!Array.isArray(values)) {
42
50
  values = [];
43
51
  }
@@ -1 +1,46 @@
1
- function formatKey(key) {
2
1
  if (typeof key !== "string") throw new TypeError("Key must be a string");
3
2
  return key.toLowerCase().replace(" ", "_");
4
3
  formatKey
4
+ /**
5
+ * Encodes a key for custom IDs (Discord is case-insensitive in some paths; this keeps uniqueness).
6
+ * Each uppercase letter becomes "_" + its lowercase form (e.g. guildId → guild_id).
7
+ * Spaces become "_".
8
+ * Lowercase letters, digits, and underscores are kept as-is (underscores are not escaped).
9
+ */
10
+ function formatKey(key) {
11
+ if (typeof key !== "string") throw new TypeError("Key must be a string");
12
+ const s = String(key).replace(/ /g, "_");
13
+ let out = "";
14
+ for (const c of s) {
15
+ if (c >= "A" && c <= "Z") {
16
+ out += "_" + c.toLowerCase();
17
+ } else {
18
+ out += c;
19
+ }
20
+ }
21
+ return out;
22
+ }
23
+
24
+ /**
25
+ * Reverses formatKey: "_" followed by a-z becomes A–Z. Literal "_" + letter in keys that were
26
+ * not produced by an uppercase letter may decode incorrectly; prefer camelCase/PascalCase keys.
27
+ */
28
+ function decodeKey(encoded) {
29
+ if (typeof encoded !== "string") throw new TypeError("Key must be a string");
30
+ let out = "";
31
+ for (let i = 0; i < encoded.length; i++) {
32
+ const c = encoded[i];
33
+ if (c === "_" && i + 1 < encoded.length) {
34
+ const next = encoded[i + 1];
35
+ if (next >= "a" && next <= "z") {
36
+ out += next.toUpperCase();
37
+ i++;
38
+ continue;
39
+ }
40
+ }
41
+ out += c;
42
+ }
43
+ return out;
44
+ }
45
+
46
+ module.exports = {
47
+ formatKey,
48
+ decodeKey,
49
+ };