honocord 1.2.0 → 1.2.1
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 +300 -268
- package/dist/index.d.mts +7 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +15 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,401 +1,433 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Features
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Honocord brings together the power of Discord's Interaction API and Hono's lightweight framework with a focus on developer experience and edge computing.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
_Yes, AI helped me build this - with the focus on **helped**._
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- ⚡ Perfect for Cloudflare Workers and edge runtimes
|
|
9
|
-
- 🎯 Type-safe interaction handlers
|
|
10
|
-
- 🔐 Built-in request verification
|
|
11
|
-
- 🎨 Support for all Discord interaction types:
|
|
12
|
-
- Slash commands with autocomplete
|
|
13
|
-
- User and Message context menu commands
|
|
14
|
-
- Message components (buttons, select menus)
|
|
15
|
-
- Modal submissions
|
|
16
|
-
- 🗂️ Prefix-based routing for components and modals
|
|
17
|
-
- 📦 Minimal dependencies using `@discordjs/core` and `@discordjs/builders`
|
|
7
|
+
## 🚀 Core Features
|
|
18
8
|
|
|
19
|
-
|
|
9
|
+
---
|
|
20
10
|
|
|
21
|
-
|
|
11
|
+
### Type-Safe Development
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
Full TypeScript support throughout the entire stack:
|
|
14
|
+
|
|
15
|
+
- **End-to-End Types** - From handlers to Discord API responses
|
|
16
|
+
- **Autocomplete & IntelliSense** - IDE support for all methods and properties
|
|
17
|
+
- **Type Guards** - Runtime type checking with TypeScript inference
|
|
18
|
+
- **Custom Environment Types** - Extend with your own bindings and variables
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// TypeScript knows the exact type
|
|
22
|
+
const name = interaction.options.getString("name", true); // string
|
|
23
|
+
const count = interaction.options.getInteger("count"); // number | null
|
|
24
|
+
|
|
25
|
+
if (interaction.inGuild()) {
|
|
26
|
+
const guildId = interaction.guildId; // TypeScript knows this exists
|
|
27
|
+
}
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
### Handler-Based Architecture
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
Clean, modular code organization:
|
|
30
35
|
|
|
31
36
|
```typescript
|
|
32
|
-
|
|
37
|
+
// Define handlers separately
|
|
38
|
+
const pingCommand = new SlashCommandHandler()
|
|
39
|
+
.setName("ping")
|
|
40
|
+
.setDescription("Pong!")
|
|
41
|
+
.addHandler(async (interaction) => {
|
|
42
|
+
await interaction.reply("🏓 Pong!");
|
|
43
|
+
});
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
// Load them together
|
|
46
|
+
bot.loadHandlers(pingCommand, greetCommand, buttonHandler);
|
|
47
|
+
```
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
const pingCommand = new SlashCommandHandler().setName("ping").setDescription("Replies with Pong!");
|
|
49
|
+
**Benefits:**
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
- **Separation of Concerns** - Each handler is self-contained
|
|
52
|
+
- **Easy Testing** - Test handlers in isolation
|
|
53
|
+
- **Reusability** - Share handlers across projects
|
|
54
|
+
- **Hot Reloading** - Change handlers without restarting (in dev mode)
|
|
43
55
|
|
|
44
|
-
|
|
45
|
-
bot.loadHandlers(pingCommand);
|
|
56
|
+
---
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
export default bot.getApp();
|
|
58
|
+
## 💬 Interaction Support
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
import { Hono } from "hono";
|
|
60
|
+
### Slash Commands
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
Full-featured slash command support:
|
|
63
|
+
|
|
64
|
+
- **Options & Validation** - String, integer, boolean, user, role, channel, and more
|
|
65
|
+
- **Subcommands & Groups** - Organize complex command structures
|
|
66
|
+
- **Required & Optional** - Fine-grained control over user input
|
|
67
|
+
- **Autocomplete** - Real-time suggestions as users type
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const searchCommand = new SlashCommandHandler()
|
|
71
|
+
.setName("search")
|
|
72
|
+
.setDescription("Search for items")
|
|
73
|
+
.addStringOption((option) =>
|
|
74
|
+
option.setName("query").setDescription("What to search for").setAutocomplete(true).setRequired(true)
|
|
75
|
+
)
|
|
76
|
+
.addHandler(handleSearch)
|
|
77
|
+
.addAutocompleteHandler(handleAutocomplete);
|
|
54
78
|
```
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
---
|
|
57
81
|
|
|
58
|
-
|
|
82
|
+
### Context Menus
|
|
59
83
|
|
|
60
|
-
|
|
84
|
+
Right-click context menu commands:
|
|
85
|
+
|
|
86
|
+
- **User Commands** - Actions on users (ban, view profile, etc.)
|
|
87
|
+
- **Message Commands** - Actions on messages (report, translate, etc.)
|
|
61
88
|
|
|
62
89
|
```typescript
|
|
63
|
-
const
|
|
90
|
+
const reportUser = new ContextCommandHandler()
|
|
91
|
+
.setName("Report User")
|
|
92
|
+
.setType(ApplicationCommandType.User)
|
|
93
|
+
.addHandler(async (interaction) => {
|
|
94
|
+
const user = interaction.targetUser;
|
|
95
|
+
// Handle report...
|
|
96
|
+
});
|
|
64
97
|
```
|
|
65
98
|
|
|
66
|
-
|
|
67
|
-
<summary>Why that is needed?</summary>
|
|
99
|
+
---
|
|
68
100
|
|
|
69
|
-
|
|
101
|
+
### Message Components
|
|
70
102
|
|
|
71
|
-
|
|
103
|
+
Interactive buttons and select menus:
|
|
72
104
|
|
|
73
|
-
|
|
105
|
+
- **Buttons** - Primary, secondary, success, danger, link styles
|
|
106
|
+
- **Select Menus** - String, user, role, channel, mentionable options
|
|
107
|
+
- **Prefix Matching** - One handler for multiple related components
|
|
108
|
+
- **Parameter Passing** - Pass data through custom IDs
|
|
74
109
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
```typescript
|
|
111
|
+
// Create a button
|
|
112
|
+
new ButtonBuilder().setCustomId("confirm/action?1234567890").setLabel("Confirm").setStyle(ButtonStyle.Success);
|
|
113
|
+
|
|
114
|
+
// Handle with prefix matching
|
|
115
|
+
const confirmHandler = new ComponentHandler("confirm").addHandler(async (interaction) => {
|
|
116
|
+
const { firstParam } = parseCustomId(interaction.custom_id);
|
|
117
|
+
// Use param...
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### Modals (Forms)
|
|
78
124
|
|
|
79
|
-
|
|
125
|
+
Full modal/form support:
|
|
80
126
|
|
|
81
|
-
|
|
127
|
+
- **Text Inputs** - Short and paragraph styles
|
|
128
|
+
- **Validation** - Min/max length, required fields
|
|
129
|
+
- **Field Access** - Type-safe field resolution
|
|
130
|
+
- **Flexible Triggers** - Show from commands or components
|
|
82
131
|
|
|
83
|
-
|
|
132
|
+
```typescript
|
|
133
|
+
await interaction.showModal(
|
|
134
|
+
new ModalBuilder({
|
|
135
|
+
custom_id: "feedback",
|
|
136
|
+
title: "Send Feedback",
|
|
137
|
+
}).addLabelComponents(
|
|
138
|
+
new LabelBuilder()
|
|
139
|
+
.setLabel("Your Feedback")
|
|
140
|
+
.setTextInputComponent((input) => input.setCustomId("message").setStyle(TextInputStyle.Paragraph).setRequired(true))
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
```
|
|
84
144
|
|
|
85
|
-
|
|
145
|
+
---
|
|
86
146
|
|
|
87
|
-
|
|
147
|
+
## 🔧 Developer Experience
|
|
88
148
|
|
|
89
|
-
|
|
149
|
+
### Minimal Boilerplate
|
|
90
150
|
|
|
91
|
-
|
|
92
|
-
Both parts are separated by a `?`, and each part is further divided by `/` (every path-segment and param). However, the `prefix` defines which handler will process the interaction.
|
|
151
|
+
_Well, I tried at least..._
|
|
93
152
|
|
|
94
|
-
|
|
153
|
+
Get started in seconds:
|
|
95
154
|
|
|
96
155
|
```typescript
|
|
97
|
-
import {
|
|
156
|
+
import { Honocord, SlashCommandHandler } from "honocord";
|
|
98
157
|
|
|
99
|
-
|
|
100
|
-
const parsed = parseCustomId("approve/user/request?user123/action456");
|
|
158
|
+
const bot = new Honocord();
|
|
101
159
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
console.log(parsed.lastParam); // "action456"
|
|
160
|
+
bot.loadHandlers(
|
|
161
|
+
new SlashCommandHandler()
|
|
162
|
+
.setName("ping")
|
|
163
|
+
.setDescription("Pong!")
|
|
164
|
+
.addHandler(async (i) => await i.reply("Pong!"))
|
|
165
|
+
);
|
|
109
166
|
|
|
110
|
-
|
|
111
|
-
const prefix = parseCustomId("approve/user/request?user123", true); // "approve"
|
|
167
|
+
export default bot.getApp();
|
|
112
168
|
```
|
|
113
169
|
|
|
114
|
-
|
|
170
|
+
---
|
|
115
171
|
|
|
116
|
-
###
|
|
172
|
+
### Built-in Utilities
|
|
117
173
|
|
|
118
|
-
|
|
174
|
+
Helpful utilities included:
|
|
175
|
+
|
|
176
|
+
**Custom ID Parser:**
|
|
119
177
|
|
|
120
178
|
```typescript
|
|
121
|
-
|
|
179
|
+
const { prefix, component, params } = parseCustomId("action/button?user123");
|
|
180
|
+
```
|
|
122
181
|
|
|
123
|
-
|
|
124
|
-
.setName("search")
|
|
125
|
-
.setDescription("Search for something")
|
|
126
|
-
.addStringOption((option) =>
|
|
127
|
-
option.setName("query").setDescription("What to search for").setRequired(true).setAutocomplete(true)
|
|
128
|
-
)
|
|
129
|
-
.addHandler(async (interaction) => {
|
|
130
|
-
const query = interaction.options.getString("query", true);
|
|
131
|
-
await interaction.reply(`Searching for: ${query}`);
|
|
132
|
-
})
|
|
133
|
-
.addAutocompleteHandler(async (interaction) => {
|
|
134
|
-
const focusedValue = interaction.options.getFocused();
|
|
135
|
-
const choices = ["apple", "banana", "cherry", "dragon fruit", "elderberry"]
|
|
136
|
-
.filter((choice) => choice.startsWith(focusedValue.toLowerCase()))
|
|
137
|
-
.slice(0, 25); // Discord limits to 25 choices
|
|
138
|
-
|
|
139
|
-
await interaction.respond(choices.map((choice) => ({ name: choice, value: choice })));
|
|
140
|
-
});
|
|
182
|
+
**Autocomplete Helper:**
|
|
141
183
|
|
|
142
|
-
|
|
184
|
+
```typescript
|
|
185
|
+
const autocompleteResponse = new AutocompleteHelper("query", interaction.user)
|
|
186
|
+
.addChoices({ name: "Option 1", value: "opt1" })
|
|
187
|
+
.response(); // Automatically filters
|
|
188
|
+
await interaction.respond(autocompleteResponse); // Sends filtered choices
|
|
143
189
|
```
|
|
144
190
|
|
|
145
|
-
|
|
191
|
+
**Color Constants:**
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { Colors } from "honocord";
|
|
195
|
+
|
|
196
|
+
const embed = new EmbedBuilder().setColor(Colors.Blue);
|
|
197
|
+
```
|
|
146
198
|
|
|
147
|
-
|
|
199
|
+
**Command Registration:**
|
|
148
200
|
|
|
149
201
|
```typescript
|
|
150
|
-
|
|
151
|
-
|
|
202
|
+
await registerCommands(token, appId, ...handlers);
|
|
203
|
+
// You can also pass a single array
|
|
204
|
+
await registerCommands(token, appId, handlers);
|
|
205
|
+
```
|
|
152
206
|
|
|
153
|
-
|
|
154
|
-
const userInfoCommand = new ContextCommandHandler().setName("User Info").setType(ApplicationCommandType.User);
|
|
207
|
+
---
|
|
155
208
|
|
|
156
|
-
|
|
157
|
-
const user = interaction.targetUser;
|
|
158
|
-
await interaction.reply(`User: ${user.username} (${user.id})`);
|
|
159
|
-
});
|
|
209
|
+
### Flexible Integration
|
|
160
210
|
|
|
161
|
-
|
|
162
|
-
const translateCommand = new ContextCommandHandler().setName("Translate").setType(ApplicationCommandType.Message);
|
|
211
|
+
Use Honocord your way:
|
|
163
212
|
|
|
164
|
-
|
|
165
|
-
const message = interaction.targetMessage;
|
|
166
|
-
await interaction.reply(`Translating: "${message.content}"`);
|
|
167
|
-
});
|
|
213
|
+
**Standalone App:**
|
|
168
214
|
|
|
169
|
-
|
|
215
|
+
```typescript
|
|
216
|
+
const bot = new Honocord();
|
|
217
|
+
export default bot.getApp();
|
|
170
218
|
```
|
|
171
219
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
Component handlers handle interactions from buttons, select menus, and other message components. They use a **prefix-based routing system**.
|
|
220
|
+
**Custom Hono Integration:**
|
|
175
221
|
|
|
176
222
|
```typescript
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
.setLabel("Approve")
|
|
184
|
-
.setStyle(ButtonStyle.Success);
|
|
185
|
-
|
|
186
|
-
// Handler for all components with the "approve" prefix
|
|
187
|
-
const approveHandler = new ComponentHandler("approve", async (interaction) => {
|
|
188
|
-
// Parse the custom_id to get parameters
|
|
189
|
-
const { params } = parseCustomId(interaction.customId);
|
|
190
|
-
const userId = params[0]; // "user123"
|
|
191
|
-
|
|
192
|
-
await interaction.reply(`Approved user: ${userId}`);
|
|
193
|
-
});
|
|
223
|
+
const app = new Hono();
|
|
224
|
+
app.get("/", (c) => c.text("Hello"));
|
|
225
|
+
app.post("/interactions", bot.handle);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**With Middleware:**
|
|
194
229
|
|
|
195
|
-
|
|
230
|
+
```typescript
|
|
231
|
+
app.use("*", logger());
|
|
232
|
+
app.use("*", cors());
|
|
233
|
+
app.post("/interactions", bot.handle);
|
|
196
234
|
```
|
|
197
235
|
|
|
198
|
-
|
|
236
|
+
---
|
|
199
237
|
|
|
200
|
-
|
|
238
|
+
## 🔐 Security
|
|
201
239
|
|
|
202
|
-
|
|
203
|
-
import { ModalHandler } from "honocord";
|
|
204
|
-
import { ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } from "@discordjs/builders";
|
|
205
|
-
|
|
206
|
-
// Create a modal
|
|
207
|
-
const modal = new ModalBuilder()
|
|
208
|
-
.setCustomId("feedback/feature") // prefix: "feedback"
|
|
209
|
-
.setTitle("Feature Feedback");
|
|
210
|
-
|
|
211
|
-
// ActionRows in modals are deprecated and should not be used because Honocord doesn't process them!
|
|
212
|
-
const input = new LabelBuilder()
|
|
213
|
-
.setLabel("What would you like to see?")
|
|
214
|
-
.setTextInputComponent(
|
|
215
|
-
new TextInputBuilder()
|
|
216
|
-
.setCustomId("feedback_text")
|
|
217
|
-
.setStyle(TextInputStyle.Paragraph)
|
|
218
|
-
.setRequired(true)
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
modal.addLabelComponents(input);
|
|
222
|
-
|
|
223
|
-
// Handler for all modals with the "feedback" prefix
|
|
224
|
-
const feedbackHandler = new ModalHandler("feedback", async (interaction) => {
|
|
225
|
-
const { component } = parseCustomId(interaction.customId);
|
|
226
|
-
const feedbackText = interaction.fields.getTextInputValue("feedback_text");
|
|
227
|
-
|
|
228
|
-
await interaction.reply({
|
|
229
|
-
content: `Thanks for your ${component} feedback: "${feedbackText}"`,
|
|
230
|
-
ephemeral: true,
|
|
231
|
-
});
|
|
232
|
-
});
|
|
240
|
+
### Automatic Signature Verification
|
|
233
241
|
|
|
234
|
-
|
|
235
|
-
|
|
242
|
+
Every request is automatically verified by the same logic [discord-interactions](https://github.com/discord/discord-interactions-js) has (without the extra dependency).
|
|
243
|
+
|
|
244
|
+
---
|
|
236
245
|
|
|
237
|
-
|
|
246
|
+
### Environment-Based Configuration
|
|
238
247
|
|
|
239
|
-
|
|
248
|
+
Keep secrets secure:
|
|
240
249
|
|
|
241
|
-
|
|
250
|
+
```typescript
|
|
251
|
+
// Access through context
|
|
252
|
+
const token = interaction.context.env.DISCORD_TOKEN;
|
|
242
253
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
DISCORD_TOKEN=your_bot_token_here # always required
|
|
246
|
-
DISCORD_PUBLIC_KEY=your_public_key_here # always required
|
|
247
|
-
IS_CF_WORKER=true # only required if using Cloudflare Workers
|
|
254
|
+
// Never hardcoded
|
|
255
|
+
const bot = new Honocord(); // Uses env vars automatically
|
|
248
256
|
```
|
|
249
257
|
|
|
250
|
-
|
|
258
|
+
---
|
|
251
259
|
|
|
252
|
-
##
|
|
260
|
+
## ⚡ Performance
|
|
253
261
|
|
|
254
|
-
|
|
262
|
+
### Efficient Routing
|
|
255
263
|
|
|
256
|
-
|
|
264
|
+
Optimized handler lookup:
|
|
257
265
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
import type { BaseHonocordEnv, BaseInteractionContext } from "honocord";
|
|
261
|
-
|
|
262
|
-
type MyEnv = {
|
|
263
|
-
MY_VARIABLE: string;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
export type HonoEnv = BaseHonocordEnv<MyEnv>;
|
|
267
|
-
export type MyContext = BaseInteractionContext<MyEnv>;
|
|
268
|
-
|
|
269
|
-
// src/index.ts
|
|
270
|
-
import type { BaseHonocordEnv, BaseInteractionContext } from "honocord";
|
|
271
|
-
import type { HonoEnv, MyContext } from "./types";
|
|
272
|
-
|
|
273
|
-
// Use in your handlers
|
|
274
|
-
const command = new SlashCommandHandler()
|
|
275
|
-
.setName("data")
|
|
276
|
-
.setDescription("Fetch data")
|
|
277
|
-
.addHandler(async (interaction: MyContext) => {
|
|
278
|
-
const myVariable = interaction.env.MY_VARIABLE; // Fully typed!
|
|
279
|
-
// ...
|
|
280
|
-
});
|
|
281
|
-
```
|
|
266
|
+
- **Commands, Components and Modals**: O(1) hash map lookup by name/custom_id-prefix
|
|
267
|
+
- **Minimal Overhead**: Direct API access, no unnecessary layers (except the discordjs API wrapper and REST client)
|
|
282
268
|
|
|
283
|
-
|
|
269
|
+
---
|
|
284
270
|
|
|
285
|
-
|
|
271
|
+
### Deferred Responses
|
|
272
|
+
|
|
273
|
+
Handle long-running operations:
|
|
286
274
|
|
|
287
275
|
```typescript
|
|
288
|
-
|
|
289
|
-
export * from "./pingCommand";
|
|
290
|
-
export * from "./approveHandler";
|
|
291
|
-
// ...export other handlers
|
|
276
|
+
await interaction.deferReply();
|
|
292
277
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
import * as handlers from "./handlers/index";
|
|
278
|
+
// Do expensive work
|
|
279
|
+
await processLargeDataset();
|
|
296
280
|
|
|
297
|
-
|
|
281
|
+
// Update when ready
|
|
282
|
+
await interaction.editReply("Complete!");
|
|
298
283
|
```
|
|
299
284
|
|
|
300
|
-
|
|
285
|
+
---
|
|
301
286
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
287
|
+
### Ephemeral Messages
|
|
288
|
+
|
|
289
|
+
Reduce server clutter:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// Only visible to user
|
|
293
|
+
await interaction.reply("Secret info", true);
|
|
309
294
|
```
|
|
310
295
|
|
|
311
|
-
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 🎨 Rich Content
|
|
312
299
|
|
|
313
|
-
###
|
|
300
|
+
### Discord.js Builders
|
|
314
301
|
|
|
315
|
-
|
|
302
|
+
Re-exports most important builders like EmbedBuilder and Components V2 Builders.
|
|
316
303
|
|
|
317
|
-
|
|
304
|
+
```typescript
|
|
305
|
+
import { EmbedBuilder, ActionRowBuilder, ButtonBuilder } from "honocord";
|
|
306
|
+
|
|
307
|
+
const embed = new EmbedBuilder().setTitle("Hello!").setColor(Colors.Blue).setDescription("Welcome to the server");
|
|
318
308
|
|
|
319
|
-
|
|
309
|
+
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
310
|
+
new ButtonBuilder().setCustomId("welcome").setLabel("Get Started").setStyle(1) // Primary Style
|
|
311
|
+
);
|
|
320
312
|
|
|
321
|
-
|
|
313
|
+
await interaction.reply({
|
|
314
|
+
embeds: [embed],
|
|
315
|
+
components: [row],
|
|
316
|
+
});
|
|
322
317
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
- `getApp()` - Returns a pre-configured Hono app instance
|
|
326
|
-
- Route: `POST /interactions` - Handles Discord interaction requests
|
|
327
|
-
- Route: `GET *` - Displays a generic "Is Running" text.
|
|
318
|
+
// Or with Components V2
|
|
319
|
+
import { ContainerBuilder, TextDisplayBuilder, Colors } from "honocord";
|
|
328
320
|
|
|
329
|
-
|
|
321
|
+
const container = new ContainerBuilder().setAccentColor(Colors.Green).addTextDisplayComponents(
|
|
322
|
+
new TextDisplayBuilder().setContent("# Hello, world!");
|
|
323
|
+
)
|
|
330
324
|
|
|
331
|
-
|
|
325
|
+
await interaction.reply({
|
|
326
|
+
components: [container],
|
|
327
|
+
});
|
|
328
|
+
```
|
|
332
329
|
|
|
333
|
-
|
|
330
|
+
You don't need to call `.toJSON()` manually - Honocord does it for you. Discord.js inspired.
|
|
334
331
|
|
|
335
|
-
|
|
336
|
-
- `addAutocompleteHandler(handler: (interaction: AutocompleteInteraction) => Promise<void> | void)` - Adds an optional autocomplete handler
|
|
332
|
+
---
|
|
337
333
|
|
|
338
|
-
|
|
334
|
+
## 🌍 Deployment Options
|
|
339
335
|
|
|
340
|
-
|
|
336
|
+
### Cloudflare Workers
|
|
341
337
|
|
|
342
|
-
|
|
338
|
+
Deploy globally in seconds:
|
|
343
339
|
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
T extends ContextCommandType = ContextCommandType,
|
|
347
|
-
InteractionData = T extends ContextCommandType.User ? UserContextInteraction : MessageContextInteraction,
|
|
348
|
-
> ()
|
|
340
|
+
```bash
|
|
341
|
+
wrangler deploy
|
|
349
342
|
```
|
|
350
343
|
|
|
351
|
-
|
|
344
|
+
Cloudflare Workers has some unique configurations that Honocord needs to function. See the [Cloudflare Workers Deployment Guide](/wiki/Deployment-Guide#cloudflare-workers) for details.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### Traditional Servers
|
|
349
|
+
|
|
350
|
+
Works anywhere Node.js runs:
|
|
352
351
|
|
|
353
|
-
|
|
352
|
+
```bash
|
|
353
|
+
node index.js
|
|
354
|
+
# or
|
|
355
|
+
bun run index.ts
|
|
356
|
+
```
|
|
354
357
|
|
|
355
|
-
|
|
358
|
+
---
|
|
356
359
|
|
|
357
|
-
###
|
|
360
|
+
### Docker
|
|
358
361
|
|
|
359
|
-
|
|
362
|
+
Containerize your bot:
|
|
360
363
|
|
|
361
|
-
|
|
364
|
+
```dockerfile
|
|
365
|
+
FROM oven/bun:1
|
|
366
|
+
COPY . .
|
|
367
|
+
RUN bun install
|
|
368
|
+
CMD ["bun", "run", "index.ts"]
|
|
369
|
+
```
|
|
362
370
|
|
|
363
|
-
|
|
371
|
+
---
|
|
364
372
|
|
|
365
|
-
|
|
373
|
+
## 📦 Lightweight
|
|
366
374
|
|
|
367
|
-
|
|
375
|
+
### Minimal Dependencies
|
|
368
376
|
|
|
369
|
-
|
|
377
|
+
Honocord only requires:
|
|
370
378
|
|
|
371
|
-
|
|
379
|
+
- `hono` - Web framework
|
|
380
|
+
- `discord-api-types` - Type definitions
|
|
381
|
+
- `@discordjs/core` - API wrapper
|
|
382
|
+
- `@discordjs/rest` - HTTP REST client
|
|
383
|
+
- `@discordjs/builders` - Builders
|
|
372
384
|
|
|
373
|
-
|
|
385
|
+
---
|
|
374
386
|
|
|
375
|
-
|
|
387
|
+
### TypeScript First
|
|
376
388
|
|
|
377
|
-
|
|
389
|
+
Written in TypeScript, designed for TypeScript:
|
|
378
390
|
|
|
379
|
-
|
|
391
|
+
- Source maps included
|
|
392
|
+
- Full type inference
|
|
393
|
+
- JSDoc comments
|
|
394
|
+
- Declaration files
|
|
380
395
|
|
|
381
|
-
|
|
396
|
+
---
|
|
382
397
|
|
|
383
|
-
###
|
|
398
|
+
### Extensible
|
|
384
399
|
|
|
385
|
-
|
|
400
|
+
Extend with custom types:
|
|
386
401
|
|
|
387
402
|
```typescript
|
|
388
|
-
|
|
389
|
-
|
|
403
|
+
interface MyEnv {
|
|
404
|
+
DATABASE: D1Database;
|
|
405
|
+
CACHE: KVNamespace;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
type MyContext = BaseInteractionContext<MyEnv>;
|
|
390
409
|
|
|
391
|
-
//
|
|
392
|
-
|
|
410
|
+
// Now fully typed in handlers
|
|
411
|
+
new SlashCommandHandler<MyContext>().addHandler(async (interaction) => {
|
|
412
|
+
const db = interaction.context.env.DATABASE;
|
|
413
|
+
const cache = interaction.context.env.CACHE;
|
|
414
|
+
});
|
|
393
415
|
```
|
|
394
416
|
|
|
395
|
-
|
|
417
|
+
---
|
|
396
418
|
|
|
397
|
-
|
|
419
|
+
### Rate Limit Handling
|
|
398
420
|
|
|
399
|
-
|
|
421
|
+
Respects Discord rate limits automatically via `@discordjs/rest`.
|
|
400
422
|
|
|
401
|
-
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
### Monitoring
|
|
426
|
+
|
|
427
|
+
Built-in debug logging:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const bot = new Honocord({
|
|
431
|
+
debugRest: true, // Log all REST requests
|
|
432
|
+
});
|
|
433
|
+
```
|