@lunibee/builders 0.2.2 → 0.2.4
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 +69 -69
- package/package.json +4 -1
- package/src/attachment.ts +104 -79
- package/src/commands.ts +720 -736
- package/src/components.ts +880 -915
- package/src/embed.ts +227 -214
- package/src/index.ts +115 -114
package/README.md
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
# @lunibee/builders
|
|
2
|
-
|
|
3
|
-
> Fluent, validated builders for embeds, components, modals, commands and attachments.
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
bun add @lunibee/builders
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import {
|
|
11
|
-
CreateEmbed,
|
|
12
|
-
CreateActionRow,
|
|
13
|
-
CreateButton,
|
|
14
|
-
ButtonType,
|
|
15
|
-
CreateStringSelectMenu,
|
|
16
|
-
CreateSlashCommand,
|
|
17
|
-
} from "@lunibee/builders";
|
|
18
|
-
|
|
19
|
-
const embed = new CreateEmbed()
|
|
20
|
-
.setTitle("Welcome")
|
|
21
|
-
.setDescription("Pick a role below.")
|
|
22
|
-
.setColor(0xf5c542)
|
|
23
|
-
.addFields([{ name: "Server", value: "Lunibee" }]);
|
|
24
|
-
|
|
25
|
-
const buttons = new CreateActionRow().addComponents(
|
|
26
|
-
new CreateButton().setCustomId("yes").setLabel("Yes").setStyle(ButtonType.Success),
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const menu = new CreateActionRow().addComponents(
|
|
30
|
-
new CreateStringSelectMenu()
|
|
31
|
-
.setCustomId("role")
|
|
32
|
-
.addOptions({ label: "Developer", value: "dev" }),
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const ping = new CreateSlashCommand()
|
|
36
|
-
.setName("ping")
|
|
37
|
-
.setDescription("Replies with pong")
|
|
38
|
-
.addStringOption((option) => option.setName("message").setDescription("Echo text"));
|
|
39
|
-
|
|
40
|
-
console.log(embed.toJSON(), buttons.toJSON(), menu.toJSON(), ping.toJSON());
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Builders
|
|
44
|
-
|
|
45
|
-
| Area | Builders |
|
|
46
|
-
|---|---|
|
|
47
|
-
| Embeds | `CreateEmbed` |
|
|
48
|
-
| Buttons & rows | `CreateButton`, `CreateActionRow` |
|
|
49
|
-
| Select menus | `CreateStringSelect` (alias `CreateStringSelectMenu`), `CreateEntitySelect`, `CreateUserSelectMenu`, `CreateRoleSelectMenu`, `CreateChannelSelectMenu`, `CreateMentionableSelectMenu` |
|
|
50
|
-
| Modals | `CreateModal`, `CreateTextInput` |
|
|
51
|
-
| Slash commands | `CreateSlashCommand`, `CreateSubcommand`, `CreateSubcommandGroup`, option builders |
|
|
52
|
-
| Context menus | `CreateContextMenuCommand` (`setType(2 \| 3)`), `CreateUserCommand`, `CreateMessageCommand` |
|
|
53
|
-
| Components V2 | `CreateContainer`, `CreateSection`, `CreateTextDisplay`, `CreateMediaGallery`, `CreateFileComponent`, `CreateSeparator`, `CreateThumbnail` |
|
|
54
|
-
| Files | `CreateAttachment` |
|
|
55
|
-
|
|
56
|
-
Every builder validates Discord's limits as you set values and serialises with `toJSON()`.
|
|
57
|
-
|
|
58
|
-
## What's new in 0.2.1
|
|
59
|
-
|
|
60
|
-
- `CreateModal.addTextInputs(...inputs)` wraps each text input in its own row.
|
|
61
|
-
- `componentsV2Message(components, options?)` builds a Components V2 message with the required flag.
|
|
62
|
-
- `CreateEmbed` setters (`setTitle`, `setDescription`, `setURL`, `setThumbnail`, `setImage`) clear the field with `null`.
|
|
63
|
-
- `TextInputType` and `ComponentEnum` are usable as types; `CreateModal.toJSON()` is typed with a required `custom_id` and `title`.
|
|
64
|
-
|
|
65
|
-
## What's new in 0.2.0
|
|
66
|
-
|
|
67
|
-
`toJSON()` validates whole components (required fields, value bounds, action-row rules, button field exclusivity); `CreateButton.setSKUId()` for premium buttons.
|
|
68
|
-
|
|
69
|
-
Docs: https://lunibee.js.org/core-concepts/builders/
|
|
1
|
+
# @lunibee/builders
|
|
2
|
+
|
|
3
|
+
> Fluent, validated builders for embeds, components, modals, commands and attachments.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun add @lunibee/builders
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
CreateEmbed,
|
|
12
|
+
CreateActionRow,
|
|
13
|
+
CreateButton,
|
|
14
|
+
ButtonType,
|
|
15
|
+
CreateStringSelectMenu,
|
|
16
|
+
CreateSlashCommand,
|
|
17
|
+
} from "@lunibee/builders";
|
|
18
|
+
|
|
19
|
+
const embed = new CreateEmbed()
|
|
20
|
+
.setTitle("Welcome")
|
|
21
|
+
.setDescription("Pick a role below.")
|
|
22
|
+
.setColor(0xf5c542)
|
|
23
|
+
.addFields([{ name: "Server", value: "Lunibee" }]);
|
|
24
|
+
|
|
25
|
+
const buttons = new CreateActionRow().addComponents(
|
|
26
|
+
new CreateButton().setCustomId("yes").setLabel("Yes").setStyle(ButtonType.Success),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const menu = new CreateActionRow().addComponents(
|
|
30
|
+
new CreateStringSelectMenu()
|
|
31
|
+
.setCustomId("role")
|
|
32
|
+
.addOptions({ label: "Developer", value: "dev" }),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const ping = new CreateSlashCommand()
|
|
36
|
+
.setName("ping")
|
|
37
|
+
.setDescription("Replies with pong")
|
|
38
|
+
.addStringOption((option) => option.setName("message").setDescription("Echo text"));
|
|
39
|
+
|
|
40
|
+
console.log(embed.toJSON(), buttons.toJSON(), menu.toJSON(), ping.toJSON());
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Builders
|
|
44
|
+
|
|
45
|
+
| Area | Builders |
|
|
46
|
+
|---|---|
|
|
47
|
+
| Embeds | `CreateEmbed` |
|
|
48
|
+
| Buttons & rows | `CreateButton`, `CreateActionRow` |
|
|
49
|
+
| Select menus | `CreateStringSelect` (alias `CreateStringSelectMenu`), `CreateEntitySelect`, `CreateUserSelectMenu`, `CreateRoleSelectMenu`, `CreateChannelSelectMenu`, `CreateMentionableSelectMenu` |
|
|
50
|
+
| Modals | `CreateModal`, `CreateTextInput` |
|
|
51
|
+
| Slash commands | `CreateSlashCommand`, `CreateSubcommand`, `CreateSubcommandGroup`, option builders |
|
|
52
|
+
| Context menus | `CreateContextMenuCommand` (`setType(2 \| 3)`), `CreateUserCommand`, `CreateMessageCommand` |
|
|
53
|
+
| Components V2 | `CreateContainer`, `CreateSection`, `CreateTextDisplay`, `CreateMediaGallery`, `CreateFileComponent`, `CreateSeparator`, `CreateThumbnail` |
|
|
54
|
+
| Files | `CreateAttachment` |
|
|
55
|
+
|
|
56
|
+
Every builder validates Discord's limits as you set values and serialises with `toJSON()`.
|
|
57
|
+
|
|
58
|
+
## What's new in 0.2.1
|
|
59
|
+
|
|
60
|
+
- `CreateModal.addTextInputs(...inputs)` wraps each text input in its own row.
|
|
61
|
+
- `componentsV2Message(components, options?)` builds a Components V2 message with the required flag.
|
|
62
|
+
- `CreateEmbed` setters (`setTitle`, `setDescription`, `setURL`, `setThumbnail`, `setImage`) clear the field with `null`.
|
|
63
|
+
- `TextInputType` and `ComponentEnum` are usable as types; `CreateModal.toJSON()` is typed with a required `custom_id` and `title`.
|
|
64
|
+
|
|
65
|
+
## What's new in 0.2.0
|
|
66
|
+
|
|
67
|
+
`toJSON()` validates whole components (required fields, value bounds, action-row rules, button field exclusivity); `CreateButton.setSKUId()` for premium buttons.
|
|
68
|
+
|
|
69
|
+
Docs: https://lunibee.js.org/core-concepts/builders/
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunibee/builders",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@lunibee/types": "^0.2.4"
|
|
9
12
|
}
|
|
10
13
|
}
|
package/src/attachment.ts
CHANGED
|
@@ -1,79 +1,104 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { basename } from "node:path";
|
|
3
|
-
|
|
4
|
-
export interface AttachmentData {
|
|
5
|
-
name: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
file: Uint8Array | ArrayBuffer | Blob | Buffer | string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
1
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface AttachmentData {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
file: Uint8Array | ArrayBuffer | Blob | Buffer | string;
|
|
8
|
+
/**
|
|
9
|
+
* Folder a string `file` must stay inside (symlinks included). Set it
|
|
10
|
+
* whenever the path is not one you wrote yourself, e.g. from a command
|
|
11
|
+
* option: without it, any readable file on the machine can be uploaded.
|
|
12
|
+
*/
|
|
13
|
+
root?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Builder for message attachments and file uploads. */
|
|
17
|
+
export class CreateAttachment {
|
|
18
|
+
public name: string;
|
|
19
|
+
public description?: string;
|
|
20
|
+
public file: Uint8Array | ArrayBuffer | Blob | Buffer | string;
|
|
21
|
+
/** Folder a string `file` must stay inside; see {@link AttachmentData.root}. */
|
|
22
|
+
public root?: string;
|
|
23
|
+
|
|
24
|
+
public constructor(
|
|
25
|
+
file: Uint8Array | ArrayBuffer | Blob | Buffer | string,
|
|
26
|
+
data?: Partial<Omit<AttachmentData, "file">> | string,
|
|
27
|
+
) {
|
|
28
|
+
this.file = file;
|
|
29
|
+
if (typeof data === "string") {
|
|
30
|
+
this.name = data;
|
|
31
|
+
} else {
|
|
32
|
+
this.name =
|
|
33
|
+
data?.name ??
|
|
34
|
+
(typeof file === "string" ? basename(file) : "file.bin");
|
|
35
|
+
this.description = data?.description;
|
|
36
|
+
this.root = data?.root;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Sets the attachment filename. */
|
|
41
|
+
public setName(name: string): this {
|
|
42
|
+
this.name = name;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Sets the attachment description / alt text. */
|
|
47
|
+
public setDescription(description: string): this {
|
|
48
|
+
this.description = description;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Sets the file contents. */
|
|
53
|
+
public setFile(
|
|
54
|
+
file: Uint8Array | ArrayBuffer | Blob | Buffer | string,
|
|
55
|
+
): this {
|
|
56
|
+
this.file = file;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Resolves the attachment payload to a binary Uint8Array. */
|
|
61
|
+
public async toBuffer(): Promise<Uint8Array> {
|
|
62
|
+
if (typeof this.file === "string") {
|
|
63
|
+
const path =
|
|
64
|
+
this.root === undefined
|
|
65
|
+
? this.file
|
|
66
|
+
: await insideRoot(this.root, this.file);
|
|
67
|
+
const buffer = await readFile(path);
|
|
68
|
+
return new Uint8Array(buffer);
|
|
69
|
+
}
|
|
70
|
+
if (this.file instanceof Uint8Array) return this.file;
|
|
71
|
+
if (this.file instanceof ArrayBuffer) return new Uint8Array(this.file);
|
|
72
|
+
if (this.file instanceof Blob) {
|
|
73
|
+
const buf = await this.file.arrayBuffer();
|
|
74
|
+
return new Uint8Array(buf);
|
|
75
|
+
}
|
|
76
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(this.file)) {
|
|
77
|
+
return new Uint8Array(this.file);
|
|
78
|
+
}
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
"Attachment file must be a supported binary value or file path.",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Deprecated names (0.2.2), removed in 0.3.0 ────────────────────────────────
|
|
86
|
+
// Builders are now `CreateX` (`ButtonBuilder` → `CreateButton`); `…Style` became
|
|
87
|
+
// `…Type` and `…Type` became `…Enum`.
|
|
88
|
+
|
|
89
|
+
/** @deprecated Use {@link CreateAttachment}. Removed in 0.3.0. */
|
|
90
|
+
export const AttachmentBuilder = CreateAttachment;
|
|
91
|
+
/** @deprecated Use {@link CreateAttachment}. Removed in 0.3.0. */
|
|
92
|
+
export type AttachmentBuilder = CreateAttachment;
|
|
93
|
+
|
|
94
|
+
/** Resolves `file` against `root`, refusing anything outside it (after following symlinks). */
|
|
95
|
+
async function insideRoot(root: string, file: string): Promise<string> {
|
|
96
|
+
const base = await realpath(root);
|
|
97
|
+
const target = await realpath(resolve(base, file)).catch(() => {
|
|
98
|
+
throw new Error("Attachment file was not found inside its root.");
|
|
99
|
+
});
|
|
100
|
+
const rel = relative(base, target);
|
|
101
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel))
|
|
102
|
+
throw new Error("Attachment file is outside its root folder.");
|
|
103
|
+
return target;
|
|
104
|
+
}
|