lunibee 0.3.0 → 0.3.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.
Files changed (87) hide show
  1. package/dist/builders/attachment.d.ts +1 -0
  2. package/dist/builders/clone.d.ts +10 -0
  3. package/dist/builders/commands.d.ts +1 -1
  4. package/dist/builders/components.d.ts +24 -5
  5. package/dist/builders/embed.d.ts +1 -0
  6. package/dist/builders/index.js +68 -17
  7. package/dist/builders/index.js.map +10 -9
  8. package/dist/collection/index.d.ts +1 -0
  9. package/dist/collection/index.js +297 -19
  10. package/dist/collection/index.js.map +5 -4
  11. package/dist/collection/redis.d.ts +98 -0
  12. package/dist/core/collector.d.ts +8 -0
  13. package/dist/core/events.d.ts +1 -0
  14. package/dist/core/index.d.ts +3 -0
  15. package/dist/core/index.js +6252 -4704
  16. package/dist/core/index.js.map +47 -40
  17. package/dist/core/permissions.d.ts +3 -402
  18. package/dist/core/redis-cache.d.ts +85 -0
  19. package/dist/formatters/index.d.ts +5 -1
  20. package/dist/formatters/index.js +7 -3
  21. package/dist/formatters/index.js.map +3 -3
  22. package/dist/handlers/index.js +11 -2
  23. package/dist/handlers/index.js.map +3 -3
  24. package/dist/index.js +10481 -6148
  25. package/dist/index.js.map +70 -56
  26. package/dist/managers/application.d.ts +10 -6
  27. package/dist/managers/base.d.ts +4 -1
  28. package/dist/managers/guild.d.ts +3 -1
  29. package/dist/managers/index.d.ts +6 -1
  30. package/dist/managers/index.js +365 -103
  31. package/dist/managers/index.js.map +19 -22
  32. package/dist/managers/member.d.ts +3 -1
  33. package/dist/managers/message.d.ts +13 -4
  34. package/dist/managers/thread.d.ts +3 -3
  35. package/dist/rest/decoder.d.ts +2 -0
  36. package/dist/rest/errors.d.ts +13 -2
  37. package/dist/rest/headers.d.ts +31 -0
  38. package/dist/rest/identity.d.ts +31 -0
  39. package/dist/rest/index.d.ts +99 -6
  40. package/dist/rest/index.js +1412 -231
  41. package/dist/rest/index.js.map +16 -13
  42. package/dist/rest/limiter.d.ts +75 -10
  43. package/dist/rest/redis.d.ts +103 -22
  44. package/dist/rest/scheduler.d.ts +13 -0
  45. package/dist/rest/store.d.ts +135 -12
  46. package/dist/rest/telemetry.d.ts +151 -0
  47. package/dist/rest/transport.d.ts +9 -0
  48. package/dist/rest/webhook.d.ts +18 -6
  49. package/dist/sharding/bus.d.ts +12 -2
  50. package/dist/sharding/cluster.d.ts +225 -15
  51. package/dist/sharding/context.d.ts +41 -0
  52. package/dist/sharding/gateway-bot.d.ts +20 -0
  53. package/dist/sharding/health.d.ts +94 -0
  54. package/dist/sharding/host.d.ts +76 -0
  55. package/dist/sharding/identify.d.ts +64 -0
  56. package/dist/sharding/index.d.ts +62 -12
  57. package/dist/sharding/index.js +2459 -202
  58. package/dist/sharding/index.js.map +21 -13
  59. package/dist/sharding/protocol.d.ts +47 -0
  60. package/dist/sharding/remote-protocol.d.ts +100 -0
  61. package/dist/sharding/remote.d.ts +134 -0
  62. package/dist/sharding/requests.d.ts +54 -0
  63. package/dist/sharding/supervisor.d.ts +1 -1
  64. package/dist/sharding/transport.d.ts +6 -5
  65. package/dist/sharding/worker.d.ts +55 -0
  66. package/dist/structures/base.d.ts +16 -5
  67. package/dist/structures/index.d.ts +1 -1
  68. package/dist/structures/index.js +57 -91
  69. package/dist/structures/index.js.map +10 -18
  70. package/dist/structures/options.d.ts +3 -2
  71. package/dist/structures/permissions.d.ts +409 -0
  72. package/dist/structures/resources.d.ts +4 -1
  73. package/dist/types/gateway.d.ts +7 -0
  74. package/dist/types/index.d.ts +14 -4
  75. package/dist/types/index.js.map +3 -3
  76. package/dist/utils/args.d.ts +3 -1
  77. package/dist/utils/index.d.ts +6 -1
  78. package/dist/utils/index.js +17 -10
  79. package/dist/utils/index.js.map +4 -4
  80. package/dist/voice/index.d.ts +7 -1
  81. package/dist/voice/index.js +33 -10
  82. package/dist/voice/index.js.map +3 -3
  83. package/dist/ws/index.d.ts +3 -1
  84. package/dist/ws/index.js +33 -5
  85. package/dist/ws/index.js.map +6 -6
  86. package/dist/ws/send-budget.d.ts +1 -1
  87. package/package.json +22 -17
@@ -1,3 +1,4 @@
1
+ /** Options for `CreateAttachment`: file name, alt-text description, the contents or a file path, and an optional root folder the path must stay inside. */
1
2
  export interface AttachmentData {
2
3
  name: string;
3
4
  description?: string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A deep copy of builder data, for `toJSON()`.
3
+ *
4
+ * Builder payloads are plain JSON: objects, arrays and primitives. Copying
5
+ * those directly is several times faster than `structuredClone` on the small
6
+ * payloads builders hold. Anything else (a Date, a Map, a class instance) and
7
+ * anything nested deeper than 64 levels (a cycle) goes to `structuredClone`,
8
+ * so the result is what `structuredClone` would give.
9
+ */
10
+ export declare function cloneData<T>(value: T, depth?: number): T;
@@ -69,7 +69,7 @@ export declare class CreateCommandOption {
69
69
  setDescription(description: string): this;
70
70
  /** Makes the option required or optional. */
71
71
  setRequired(required?: boolean): this;
72
- /** Sets autocomplete. */
72
+ /** Sets autocomplete; options with choices cannot use it. */
73
73
  setAutocomplete(enabled?: boolean): this;
74
74
  /** Serializes the option payload. */
75
75
  toJSON(): Record<string, unknown>;
@@ -6,11 +6,13 @@ export declare const TextInputType: {
6
6
  };
7
7
  /** One {@link TextInputType} value, usable as a type. */
8
8
  export type TextInputType = (typeof TextInputType)[keyof typeof TextInputType];
9
+ /** An emoji on a button or select option: a custom emoji `id`, or a Unicode `name`. */
9
10
  export interface APIComponentEmoji {
10
11
  id?: string | null;
11
12
  name?: string | null;
12
13
  animated?: boolean;
13
14
  }
15
+ /** A button as a builder sends it. Exported from `lunibee` as `APIButtonPayload`. */
14
16
  export interface APIButtonComponent {
15
17
  type: typeof ComponentEnum.Button;
16
18
  style: 1 | 2 | 3 | 4 | 5 | 6;
@@ -22,6 +24,7 @@ export interface APIButtonComponent {
22
24
  sku_id?: string;
23
25
  disabled?: boolean;
24
26
  }
27
+ /** One option of a string select menu. */
25
28
  export interface APISelectOption {
26
29
  label: string;
27
30
  value: string;
@@ -29,6 +32,7 @@ export interface APISelectOption {
29
32
  emoji?: APIComponentEmoji;
30
33
  default?: boolean;
31
34
  }
35
+ /** A string select menu (component type 3) as Discord receives it. */
32
36
  export interface APIStringSelectComponent {
33
37
  type: typeof ComponentEnum.StringSelect;
34
38
  custom_id?: string;
@@ -39,6 +43,7 @@ export interface APIStringSelectComponent {
39
43
  disabled?: boolean;
40
44
  options?: APISelectOption[];
41
45
  }
46
+ /** A user, role, mentionable or channel select menu as Discord receives it. */
42
47
  export interface APIEntitySelectComponent {
43
48
  type: typeof ComponentEnum.UserSelect | typeof ComponentEnum.RoleSelect | typeof ComponentEnum.MentionableSelect | typeof ComponentEnum.ChannelSelect;
44
49
  custom_id?: string;
@@ -49,6 +54,7 @@ export interface APIEntitySelectComponent {
49
54
  disabled?: boolean;
50
55
  default_values?: APISelectDefaultValue[];
51
56
  }
57
+ /** A modal text input as a builder sends it. Exported from `lunibee` as `APITextInputPayload`. */
52
58
  export interface APITextInputComponent {
53
59
  type: typeof ComponentEnum.TextInput;
54
60
  style: typeof TextInputType.Short | typeof TextInputType.Paragraph;
@@ -60,33 +66,40 @@ export interface APITextInputComponent {
60
66
  required?: boolean;
61
67
  value?: string;
62
68
  }
69
+ /** A modal (callback type 9 data): title, custom ID and its components. */
63
70
  export interface APIModalComponent {
64
71
  type: 9;
65
72
  custom_id: string;
66
73
  title: string;
67
74
  components: APIActionRowComponent[];
68
75
  }
76
+ /** Any component an action row can hold: a button, a select menu or a text input. */
69
77
  export type APIActionRowChild = APIButtonComponent | APIStringSelectComponent | APIEntitySelectComponent | APITextInputComponent;
78
+ /** An action row as a builder sends it: up to five buttons, or one select menu or text input. Exported from `lunibee` as `APIActionRowPayload`. */
70
79
  export interface APIActionRowComponent {
71
80
  type: typeof ComponentEnum.ActionRow;
72
81
  components: APIActionRowChild[];
73
82
  }
83
+ /** A Components V2 section: up to three text displays with an accessory. */
74
84
  export interface APISectionComponent {
75
85
  type: typeof ComponentEnum.Section;
76
86
  components?: APIComponent[];
77
87
  accessory?: APIComponent;
78
88
  }
89
+ /** A Components V2 text display (Markdown content). */
79
90
  export interface APITextDisplayComponent {
80
91
  type: typeof ComponentEnum.TextDisplay;
81
92
  content: string;
82
93
  }
94
+ /** A Components V2 media gallery; each item is `{ media: { url }, description?, spoiler? }`. */
83
95
  export interface APIMediaGalleryComponent {
84
96
  type: typeof ComponentEnum.MediaGallery;
85
97
  items: {
86
98
  media: {
87
99
  url: string;
88
- description?: string;
89
100
  };
101
+ description?: string;
102
+ spoiler?: boolean;
90
103
  }[];
91
104
  }
92
105
  /** Default values accepted by an auto-populated entity select. */
@@ -94,6 +107,7 @@ export interface APISelectDefaultValue {
94
107
  id: string;
95
108
  type: "user" | "role" | "channel";
96
109
  }
110
+ /** A Components V2 file display for an uploaded attachment. */
97
111
  export interface APIFileComponent {
98
112
  type: typeof ComponentEnum.File;
99
113
  file: {
@@ -101,30 +115,34 @@ export interface APIFileComponent {
101
115
  };
102
116
  spoiler?: boolean;
103
117
  }
118
+ /** A Components V2 separator, with optional divider line and spacing. */
104
119
  export interface APISeparatorComponent {
105
120
  type: typeof ComponentEnum.Separator;
106
121
  spacing?: 1 | 2;
107
122
  divider?: boolean;
108
123
  }
124
+ /** A Components V2 thumbnail: `{ type: 11, media: { url }, description?, spoiler? }`. */
109
125
  export interface APIThumbnailComponent {
110
126
  type: typeof ComponentEnum.Thumbnail;
111
- url: string;
112
- proxy_url?: string;
113
- width?: number;
114
- height?: number;
127
+ media: {
128
+ url: string;
129
+ };
115
130
  description?: string;
116
131
  spoiler?: boolean;
117
132
  }
133
+ /** A Components V2 content inventory entry. */
118
134
  export interface APIContentInventoryEntryComponent {
119
135
  type: typeof ComponentEnum.ContentInventoryEntry;
120
136
  id: string;
121
137
  }
138
+ /** A Components V2 container: a box around other components, with an optional accent colour. */
122
139
  export interface APIContainerComponent {
123
140
  type: typeof ComponentEnum.Container;
124
141
  components: APIComponent[];
125
142
  accent_color?: number;
126
143
  spoiler?: boolean;
127
144
  }
145
+ /** Any top-level message component: an action row or a Components V2 layout component. */
128
146
  export type APIComponent = APIActionRowComponent | APIButtonComponent | APIStringSelectComponent | APIEntitySelectComponent | APITextInputComponent | APISectionComponent | APITextDisplayComponent | APIThumbnailComponent | APIMediaGalleryComponent | APIFileComponent | APISeparatorComponent | APIContentInventoryEntryComponent | APIContainerComponent;
129
147
  export declare class CreateActionRow<T extends {
130
148
  toJSON(): APIActionRowChild;
@@ -225,6 +243,7 @@ export declare class CreateMediaGallery {
225
243
  addItems(...items: {
226
244
  url: string;
227
245
  description?: string;
246
+ spoiler?: boolean;
228
247
  }[]): this;
229
248
  toJSON(): APIMediaGalleryComponent;
230
249
  }
@@ -90,5 +90,6 @@ export declare class CreateEmbed {
90
90
  clearDescription(): this;
91
91
  /** Returns a deep-cloned Discord API payload. @returns Strict embed payload. */
92
92
  get data(): APIEmbed;
93
+ /** Returns a deep-cloned Discord API payload. @returns Strict embed payload. @throws {RangeError} If the text in the embed adds up to more than Discord's 6000-character limit. */
93
94
  toJSON(): APIEmbed;
94
95
  }
@@ -1,4 +1,26 @@
1
1
  // @bun
2
+ // packages/builders/src/clone.ts
3
+ function cloneData(value, depth = 0) {
4
+ if (value === null || typeof value !== "object")
5
+ return value;
6
+ if (depth > 64)
7
+ return structuredClone(value);
8
+ if (Array.isArray(value)) {
9
+ const out = new Array(value.length);
10
+ for (let i = 0;i < value.length; i++)
11
+ out[i] = cloneData(value[i], depth + 1);
12
+ return out;
13
+ }
14
+ const proto = Object.getPrototypeOf(value);
15
+ if (proto !== Object.prototype && proto !== null)
16
+ return structuredClone(value);
17
+ const from = value;
18
+ const out = {};
19
+ for (const key of Object.keys(from))
20
+ out[key] = cloneData(from[key], depth + 1);
21
+ return out;
22
+ }
23
+
2
24
  // packages/builders/src/embed.ts
3
25
  class CreateEmbed {
4
26
  #data = {};
@@ -94,7 +116,11 @@ class CreateEmbed {
94
116
  const current = [...this.#data.fields ?? []];
95
117
  if (!Number.isInteger(index) || !Number.isInteger(deleteCount) || index < 0 || deleteCount < 0)
96
118
  throw new RangeError("Field index and delete count must be non-negative integers.");
97
- current.splice(index, deleteCount, ...fields);
119
+ for (const field of fields) {
120
+ validate(field.name, 256, "Embed field name");
121
+ validate(field.value, 1024, "Embed field value");
122
+ }
123
+ current.splice(index, deleteCount, ...fields.map((f) => ({ ...f })));
98
124
  if (current.length > 25)
99
125
  throw new RangeError("An embed cannot contain more than 25 fields.");
100
126
  this.#data.fields = current;
@@ -116,7 +142,11 @@ class CreateEmbed {
116
142
  return this.toJSON();
117
143
  }
118
144
  toJSON() {
119
- return structuredClone(this.#data);
145
+ const data = this.#data;
146
+ const total = (data.title?.length ?? 0) + (data.description?.length ?? 0) + (data.footer?.text.length ?? 0) + (data.author?.name.length ?? 0) + (data.fields ?? []).reduce((sum, field) => sum + field.name.length + field.value.length, 0);
147
+ if (total > 6000)
148
+ throw new RangeError(`An embed cannot contain more than 6000 characters in total (this one has ${total}).`);
149
+ return cloneData(data);
120
150
  }
121
151
  }
122
152
  function validate(value, max, field) {
@@ -125,11 +155,15 @@ function validate(value, max, field) {
125
155
  return value;
126
156
  }
127
157
  function url(value, field) {
158
+ let parsed;
128
159
  try {
129
- return new URL(value).toString();
160
+ parsed = new URL(value);
130
161
  } catch (error) {
131
162
  throw new TypeError(`${field} must be a valid URL.`, { cause: error });
132
163
  }
164
+ if (!["http:", "https:", "attachment:"].includes(parsed.protocol))
165
+ throw new TypeError(`${field} must use http, https or attachment.`);
166
+ return parsed.toString();
133
167
  }
134
168
  // packages/types/src/channel-kind.ts
135
169
  var ChannelKinds = {
@@ -323,7 +357,7 @@ class CreateStringSelect extends CreateSelectMenu {
323
357
  throw new RangeError("A string select needs at least one option.");
324
358
  if ((this.#data.max_values ?? 1) > count)
325
359
  throw new RangeError("max_values cannot exceed the number of options.");
326
- return structuredClone(this.#data);
360
+ return cloneData(this.#data);
327
361
  }
328
362
  }
329
363
 
@@ -371,6 +405,8 @@ class CreateButton {
371
405
  cause: error
372
406
  });
373
407
  }
408
+ if (!["http:", "https:", "discord:"].includes(url.protocol))
409
+ throw new TypeError("Button URL must use http, https or discord.");
374
410
  this.setStyle(ButtonType.Link);
375
411
  this.#data.url = url.toString();
376
412
  return this;
@@ -409,7 +445,7 @@ class CreateButton {
409
445
  }
410
446
  if (data.style !== ButtonType.Premium && !data.label && !data.emoji)
411
447
  throw new TypeError("Buttons require a label or an emoji.");
412
- return structuredClone(data);
448
+ return cloneData(data);
413
449
  }
414
450
  }
415
451
 
@@ -428,7 +464,7 @@ class CreateEntitySelect extends CreateSelectMenu {
428
464
  }
429
465
  toJSON() {
430
466
  assertSelect(this.#data);
431
- return structuredClone(this.#data);
467
+ return cloneData(this.#data);
432
468
  }
433
469
  }
434
470
 
@@ -520,7 +556,7 @@ class CreateTextInput {
520
556
  throw new TypeError("Text inputs require a custom ID.");
521
557
  if (data.min_length !== undefined && data.max_length !== undefined && data.min_length > data.max_length)
522
558
  throw new RangeError("min_length cannot exceed max_length.");
523
- return structuredClone(data);
559
+ return cloneData(data);
524
560
  }
525
561
  }
526
562
 
@@ -604,13 +640,17 @@ class CreateMediaGallery {
604
640
  if (this.#items.length + items.length > 10) {
605
641
  throw new RangeError("MediaGallery can only contain up to 10 items.");
606
642
  }
607
- this.#items.push(...items.map((item) => ({ media: item })));
643
+ this.#items.push(...items.map(({ url, description, spoiler }) => ({
644
+ media: { url },
645
+ ...description !== undefined ? { description } : {},
646
+ ...spoiler !== undefined ? { spoiler } : {}
647
+ })));
608
648
  return this;
609
649
  }
610
650
  toJSON() {
611
651
  return {
612
652
  type: ComponentEnum.MediaGallery,
613
- items: structuredClone(this.#items)
653
+ items: cloneData(this.#items)
614
654
  };
615
655
  }
616
656
  }
@@ -684,7 +724,7 @@ class CreateThumbnail {
684
724
  toJSON() {
685
725
  const data = {
686
726
  type: ComponentEnum.Thumbnail,
687
- url: this.#url
727
+ media: { url: this.#url }
688
728
  };
689
729
  if (this.#description !== undefined)
690
730
  data.description = this.#description;
@@ -849,7 +889,7 @@ class CreateSlashCommand {
849
889
  return this.addOption(configure(new CreateSubcommandGroup));
850
890
  }
851
891
  toJSON() {
852
- return structuredClone(this.#data);
892
+ return cloneData(this.#data);
853
893
  }
854
894
  addOption(option) {
855
895
  this.#data.options = appendOption(this.#data.options, option.toJSON(), "application command");
@@ -877,11 +917,13 @@ class CreateCommandOption {
877
917
  return this;
878
918
  }
879
919
  setAutocomplete(enabled = true) {
920
+ if (enabled && Array.isArray(this.data.choices) && this.data.choices.length)
921
+ throw new RangeError("Autocomplete options cannot define choices.");
880
922
  this.data.autocomplete = enabled;
881
923
  return this;
882
924
  }
883
925
  toJSON() {
884
- return structuredClone(this.data);
926
+ return cloneData(this.data);
885
927
  }
886
928
  }
887
929
 
@@ -917,6 +959,9 @@ class CreateNumericOption extends CreateCommandOption {
917
959
  }
918
960
  setMinValue(value) {
919
961
  validateNumberRange(value, -MAX_SAFE, MAX_SAFE, "min_value", this.#integer);
962
+ const max = this.data.max_value;
963
+ if (max !== undefined && value > max)
964
+ throw new RangeError("min_value cannot exceed max_value.");
920
965
  this.data.min_value = value;
921
966
  return this;
922
967
  }
@@ -1071,8 +1116,12 @@ function appendChoices(data, choices) {
1071
1116
  throw new RangeError("Autocomplete options cannot define choices.");
1072
1117
  if (current.length + choices.length > 25)
1073
1118
  throw new RangeError("An option cannot contain more than 25 choices.");
1074
- for (const choice of choices)
1119
+ for (const choice of choices) {
1075
1120
  validateText2(choice.name, "Choice name", 100);
1121
+ const value = choice.value;
1122
+ if (typeof value === "string" ? value.length === 0 || value.length > 100 : typeof value !== "number" || !Number.isFinite(value))
1123
+ throw new TypeError("Choice value must be a string of 1-100 characters or a finite number.");
1124
+ }
1076
1125
  data.choices = [...current, ...choices.map((choice) => ({ ...choice }))];
1077
1126
  }
1078
1127
  function validateName(value, field) {
@@ -1127,7 +1176,7 @@ class CreateContextMenuCommand {
1127
1176
  return this;
1128
1177
  }
1129
1178
  toJSON() {
1130
- return structuredClone(this.data);
1179
+ return cloneData(this.data);
1131
1180
  }
1132
1181
  }
1133
1182
 
@@ -1194,12 +1243,14 @@ class CreateAttachment {
1194
1243
  }
1195
1244
  }
1196
1245
  async function insideRoot(root, file) {
1197
- const base = await realpath(root);
1246
+ const base = await realpath(root).catch(() => {
1247
+ throw new Error("Attachment root folder was not found.");
1248
+ });
1198
1249
  const target = await realpath(resolve(base, file)).catch(() => {
1199
1250
  throw new Error("Attachment file was not found inside its root.");
1200
1251
  });
1201
1252
  const rel = relative(base, target);
1202
- if (rel === "" || rel.startsWith("..") || isAbsolute(rel))
1253
+ if (rel === "" || rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute(rel))
1203
1254
  throw new Error("Attachment file is outside its root folder.");
1204
1255
  return target;
1205
1256
  }
@@ -1248,4 +1299,4 @@ export {
1248
1299
  componentsV2Message
1249
1300
  };
1250
1301
 
1251
- //# debugId=C9A7870D27CD731F64756E2164756E21
1302
+ //# debugId=53A00A51A367F67A64756E2164756E21