honocord 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The-LukeZ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,495 @@
1
+ # HonoCord
2
+
3
+ A Discord interaction handler library for [Hono](https://hono.dev/), designed to work seamlessly with Cloudflare Workers and other edge runtimes.
4
+
5
+ ## Features
6
+
7
+ - 🔥 Built on top of Hono for maximum performance
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`
18
+
19
+ <small>Yes, AI helped me build this. With the focus on **helped**.</small>
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pnpm install honocord @discordjs/core @discordjs/builders @discordjs/rest discord-api-types
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```typescript
30
+ import { Honocord, SlashCommandHandler } from "honocord";
31
+
32
+ const bot = new Honocord();
33
+
34
+ // Define a slash command
35
+ const pingCommand = new SlashCommandHandler().setName("ping").setDescription("Replies with Pong!");
36
+
37
+ // Add handler to the command
38
+ pingCommand.handler = async (interaction) => {
39
+ await interaction.reply("Pong! 🏓");
40
+ };
41
+
42
+ // Register handlers
43
+ bot.loadHandlers(pingCommand);
44
+
45
+ // Export the app
46
+ export default bot.getApp();
47
+
48
+ // Or use with your own Hono app
49
+ import { Hono } from "hono";
50
+
51
+ const app = new Hono();
52
+ ```
53
+
54
+ ## Usage with and without Cloudflare Workers
55
+
56
+ Hono, well rather CF Workers, have a small issue with responding to interactions.
57
+
58
+ When using HonoCord in a Cloudflare Worker environment, you should ensure that your environment variable `IS_CF_WORKER` is set to `"true"` **or** you pass this information to HonoCord in the constructor:
59
+
60
+ ```typescript
61
+ const bot = new Honocord({ isCFWorker: true }); // true indicates CF Worker environment
62
+ ```
63
+
64
+ <details>
65
+ <summary>Why that is needed?</summary>
66
+
67
+ On Cloudflare Workers, we need to make use of `c.executionContext.waitUntil()` to handle asynchronous tasks properly without blocking the response. By checking for the `IS_CF_WORKER` environment variable, HonoCord can determine if it's running in a Cloudflare Worker environment and adjust its behavior accordingly.
68
+
69
+ This approach allows HonoCord to maintain compatibility with both Cloudflare Workers and other environments, ensuring that interactions are handled correctly regardless of where the code is executed.
70
+
71
+ </details>
72
+
73
+ > [!IMPORTANT]
74
+ > This readme assumes you are using Cloudflare Workers.
75
+ > Most stuff is the same for other environments, but keep in mind the `IS_CF_WORKER` variable and the way you export and use your Hono app.
76
+
77
+ ## Philosophy - How It Works
78
+
79
+ HonoCord leverages Hono's lightweight and fast request handling capabilities to process Discord interactions efficiently. It verifies incoming requests using Discord's public key, ensuring security and authenticity.
80
+
81
+ To handle interactions, you define various handler types (slash commands, context commands, components, modals) and register them with the `Honocord` instance. Each handler processes its respective interaction type.
82
+
83
+ ### Custom IDs
84
+
85
+ Custom IDs for components and modals use a **prefix-based routing system**, allowing you to easily manage multiple interactions with shared logic.
86
+
87
+ Custom IDs follow the pattern: `prefix/component/path?param1/param2`
88
+
89
+ As you can see, it is basically split into two parts: a **path** and **parameters**.
90
+ 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.
91
+
92
+ Use the `parseCustomId` utility to parse custom IDs:
93
+
94
+ ```typescript
95
+ import { parseCustomId } from "honocord";
96
+
97
+ // Example: "approve/user/request?user123/action456"
98
+ const parsed = parseCustomId("approve/user/request?user123/action456");
99
+
100
+ console.log(parsed.prefix); // "approve"
101
+ console.log(parsed.component); // "user"
102
+ console.log(parsed.lastPathItem); // "request"
103
+ console.log(parsed.compPath); // ["approve", "user", "request"]
104
+ console.log(parsed.params); // ["user123", "action456"]
105
+ console.log(parsed.firstParam); // "user123"
106
+ console.log(parsed.lastParam); // "action456"
107
+
108
+ // Get only the prefix
109
+ const prefix = parseCustomId("approve/user/request?user123", true); // "approve"
110
+ ```
111
+
112
+ ## Handlers
113
+
114
+ ### 1. Slash Command Handler
115
+
116
+ Slash commands are the primary way users interact with your bot. They support autocomplete for option values.
117
+
118
+ ```typescript
119
+ import { SlashCommandHandler } from "honocord";
120
+
121
+ const searchCommand = new SlashCommandHandler()
122
+ .setName("search")
123
+ .setDescription("Search for something")
124
+ .addStringOption((option) =>
125
+ option.setName("query").setDescription("What to search for").setRequired(true).setAutocomplete(true)
126
+ )
127
+ .handler(async (interaction) => {
128
+ const query = interaction.options.getString("query", true);
129
+ await interaction.reply(`Searching for: ${query}`);
130
+ })
131
+ .autocomplete(async (interaction) => {
132
+ const focusedValue = interaction.options.getFocused();
133
+ const choices = ["apple", "banana", "cherry", "dragon fruit", "elderberry"]
134
+ .filter((choice) => choice.startsWith(focusedValue.toLowerCase()))
135
+ .slice(0, 25); // Discord limits to 25 choices
136
+
137
+ await interaction.respond(choices.map((choice) => ({ name: choice, value: choice })));
138
+ });
139
+
140
+ bot.loadHandlers(searchCommand);
141
+ ```
142
+
143
+ ### 2. Context Command Handler
144
+
145
+ Context commands appear in the right-click menu for users or messages.
146
+
147
+ ```typescript
148
+ import { ContextCommandHandler } from "honocord";
149
+ import { ApplicationCommandType } from "discord-api-types/v10";
150
+
151
+ // User context command
152
+ const userInfoCommand = new ContextCommandHandler().setName("User Info").setType(ApplicationCommandType.User);
153
+
154
+ userInfoCommand.handler = async (interaction) => {
155
+ const user = interaction.targetUser;
156
+ await interaction.reply(`User: ${user.username} (${user.id})`);
157
+ };
158
+
159
+ // Message context command
160
+ const translateCommand = new ContextCommandHandler().setName("Translate").setType(ApplicationCommandType.Message);
161
+
162
+ translateCommand.handler = async (interaction) => {
163
+ const message = interaction.targetMessage;
164
+ await interaction.reply(`Translating: "${message.content}"`);
165
+ };
166
+
167
+ bot.loadHandlers(userInfoCommand, translateCommand);
168
+ ```
169
+
170
+ ### 3. Component Handler
171
+
172
+ Component handlers handle interactions from buttons, select menus, and other message components. They use a **prefix-based routing system**.
173
+
174
+ ```typescript
175
+ import { ComponentHandler } from "honocord";
176
+ import { ButtonBuilder, ButtonStyle, ActionRowBuilder } from "@discordjs/builders";
177
+
178
+ // Create a button with a custom_id using a prefix
179
+ const button = new ButtonBuilder()
180
+ .setCustomId("approve/user123") // prefix: "approve"
181
+ .setLabel("Approve")
182
+ .setStyle(ButtonStyle.Success);
183
+
184
+ // Handler for all components with the "approve" prefix
185
+ const approveHandler = new ComponentHandler("approve", async (interaction) => {
186
+ // Parse the custom_id to get parameters
187
+ const { params } = parseCustomId(interaction.customId);
188
+ const userId = params[0]; // "user123"
189
+
190
+ await interaction.reply(`Approved user: ${userId}`);
191
+ });
192
+
193
+ bot.loadHandlers(approveHandler);
194
+ ```
195
+
196
+ ### 4. Modal Handler
197
+
198
+ Modal handlers process form submissions. Like components, they use prefix-based routing.
199
+
200
+ ```typescript
201
+ import { ModalHandler } from "honocord";
202
+ import { ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } from "@discordjs/builders";
203
+
204
+ // Create a modal
205
+ const modal = new ModalBuilder()
206
+ .setCustomId("feedback/feature") // prefix: "feedback"
207
+ .setTitle("Feature Feedback");
208
+
209
+ const input = new TextInputBuilder()
210
+ .setCustomId("feedback_text")
211
+ .setLabel("What would you like to see?")
212
+ .setStyle(TextInputStyle.Paragraph)
213
+ .setRequired(true);
214
+
215
+ modal.addComponents(new ActionRowBuilder().addComponents(input));
216
+
217
+ // Handler for all modals with the "feedback" prefix
218
+ const feedbackHandler = new ModalHandler("feedback", async (interaction) => {
219
+ const { component } = parseCustomId(interaction.customId);
220
+ const feedbackText = interaction.fields.getTextInputValue("feedback_text");
221
+
222
+ await interaction.reply({
223
+ content: `Thanks for your ${component} feedback: "${feedbackText}"`,
224
+ ephemeral: true,
225
+ });
226
+ });
227
+
228
+ bot.loadHandlers(feedbackHandler);
229
+ ```
230
+
231
+ ## Complete Example
232
+
233
+ <details>
234
+ <summary>If you want to only use Honocord for your project and have no existing Hono app:</summary>
235
+
236
+ ```typescript
237
+ import { Honocord, SlashCommandHandler, ComponentHandler, ModalHandler } from "honocord";
238
+
239
+ const bot = new Honocord();
240
+
241
+ // Slash command
242
+ const greetCommand = new SlashCommandHandler()
243
+ .setName("greet")
244
+ .setDescription("Sends a greeting")
245
+ .addStringOption((option) => option.setName("name").setDescription("Who to greet").setRequired(true));
246
+
247
+ greetCommand.handler = async (interaction) => {
248
+ const name = interaction.options.getString("name", true);
249
+ await interaction.reply(`Hello, ${name}! 👋`);
250
+ };
251
+
252
+ // Component handler for buttons
253
+ const confirmHandler = new ComponentHandler("confirm", async (interaction) => {
254
+ const { params } = parseCustomId(interaction.customId);
255
+ const action = params[0];
256
+
257
+ await interaction.update({
258
+ content: `You confirmed: ${action}`,
259
+ components: [], // Remove buttons
260
+ });
261
+ });
262
+
263
+ // Modal handler
264
+ const reportHandler = new ModalHandler("report", async (interaction) => {
265
+ const reason = interaction.fields.getTextInputValue("reason");
266
+ const details = interaction.fields.getTextInputValue("details");
267
+
268
+ await interaction.reply({
269
+ content: "Report submitted successfully!",
270
+ ephemeral: true,
271
+ });
272
+
273
+ // Process the report...
274
+ });
275
+
276
+ // Register all handlers
277
+ bot.loadHandlers(greetCommand, confirmHandler, reportHandler);
278
+
279
+ // For Cloudflare Workers
280
+ export default bot.getApp();
281
+ ```
282
+
283
+ </details>
284
+
285
+ or
286
+
287
+ <details>
288
+ <summary>You want to use Honocord with your existing Hono app:</summary>
289
+
290
+ ```typescript
291
+ /**
292
+ * Example: Using HonoCord with custom environment types in a Hono app
293
+ */
294
+
295
+ import { Hono } from "hono";
296
+ import { Honocord, SlashCommandHandler, ComponentHandler } from "honocord";
297
+ import type { BaseHonocordEnv, BaseInteractionContext } from "honocord";
298
+
299
+ // Define your custom bindings
300
+ interface MyBindings {
301
+ DISCORD_TOKEN: string;
302
+ DATABASE: D1Database;
303
+ CACHE: KVNamespace;
304
+ IS_CF_WORKER: "true";
305
+ }
306
+
307
+ // Create custom environment and context types
308
+ type MyEnv = BaseHonocordEnv<MyBindings>;
309
+ type MyContext = BaseInteractionContext<MyBindings>;
310
+
311
+ // Create Hono app
312
+ const app = new Hono<MyEnv>();
313
+
314
+ // Initialize bot
315
+ const bot = new Honocord();
316
+
317
+ // Create command with type-safe environment access
318
+ const pingCommand = new SlashCommandHandler().setName("ping").setDescription("Ping the bot");
319
+
320
+ pingCommand.handler = async (interaction) => {
321
+ const ctx = interaction.ctx as MyContext;
322
+
323
+ // Type-safe access to bindings
324
+ const cache = ctx.env.CACHE;
325
+ await cache.put("last_ping", new Date().toISOString());
326
+
327
+ await interaction.reply("Pong! 🏓");
328
+ };
329
+
330
+ // Create command that queries database
331
+ const statsCommand = new SlashCommandHandler().setName("stats").setDescription("Show bot stats");
332
+
333
+ statsCommand.handler = async (interaction) => {
334
+ const ctx = interaction.ctx as MyContext;
335
+ const db = ctx.env.DATABASE;
336
+
337
+ const result = await db.prepare("SELECT COUNT(*) as count FROM users").first();
338
+ await interaction.reply(`Total users: ${result?.count ?? 0}`);
339
+ };
340
+
341
+ // Component handler
342
+ const approveButton = new ComponentHandler("approve", async (interaction) => {
343
+ const ctx = interaction.ctx as MyContext;
344
+ const db = ctx.env.DATABASE;
345
+
346
+ // Update database
347
+ await db.prepare("UPDATE requests SET approved = 1").run();
348
+
349
+ await interaction.update({ content: "✅ Approved!" });
350
+ });
351
+
352
+ // Load handlers
353
+ bot.loadHandlers(pingCommand, statsCommand, approveButton);
354
+
355
+ // Interaction endpoint
356
+ app.post("/interactions", (c) => bot.handle(c as MyContext));
357
+
358
+ // Regular API routes
359
+ app.get("/", (c) => c.json({ status: "ok" }));
360
+
361
+ export default app;
362
+ ```
363
+
364
+ </details>
365
+
366
+ ## Environment Variables
367
+
368
+ ```env
369
+ DISCORD_TOKEN=your_bot_token_here
370
+ DISCORD_PUBLIC_KEY=your_public_key_here
371
+ ```
372
+
373
+ The `DISCORD_PUBLIC_KEY` is required for request verification and should be available in your environment (e.g., `c.env.DISCORD_PUBLIC_KEY` in Cloudflare Workers).
374
+
375
+ ## TypeScript Support
376
+
377
+ HonoCord is written in TypeScript and provides full type safety.
378
+
379
+ ### Custom Environment Types
380
+
381
+ ```typescript
382
+ import type { BaseHonocordEnv, BaseInteractionContext } from "honocord";
383
+
384
+ // Define your custom environment
385
+ interface MyEnv {
386
+ DISCORD_TOKEN: string;
387
+ DISCORD_PUBLIC_KEY: string;
388
+ DATABASE_URL: string;
389
+ }
390
+
391
+ // Create a custom context type
392
+ type MyContext = BaseInteractionContext<MyEnv>;
393
+
394
+ // Use in your handlers
395
+ const command = new SlashCommandHandler().setName("data").setDescription("Fetch data");
396
+
397
+ command.handler = async (interaction: MyContext) => {
398
+ const dbUrl = interaction.env.DATABASE_URL; // Fully typed!
399
+ // ...
400
+ };
401
+ ```
402
+
403
+ ## Registering Commands with Discord
404
+
405
+ HonoCord handles command execution, but you still need to register your commands with Discord's API. Here's how:
406
+
407
+ ```typescript
408
+ import { REST } from "@discordjs/rest";
409
+ import { Routes } from "discord-api-types/v10";
410
+
411
+ const rest = new REST().setToken(process.env.DISCORD_TOKEN);
412
+
413
+ const commands = [
414
+ pingCommand.toJSON(),
415
+ searchCommand.toJSON(),
416
+ // ... other commands
417
+ ];
418
+
419
+ // Register globally (takes up to 1 hour to propagate)
420
+ await rest.put(Routes.applicationCommands(APPLICATION_ID), { body: commands });
421
+
422
+ // Or register for a specific guild (instant)
423
+ await rest.put(Routes.applicationGuildCommands(APPLICATION_ID, GUILD_ID), {
424
+ body: commands,
425
+ });
426
+ ```
427
+
428
+ ## API Reference
429
+
430
+ ### `Honocord`
431
+
432
+ Main class for handling Discord interactions.
433
+
434
+ **Constructor:**
435
+
436
+ - `new Honocord(options?: HonocordOptions)` - Creates a new instance
437
+
438
+ **Methods:**
439
+
440
+ - `loadHandlers(handlers: Handler[])` - Registers interaction handlers
441
+ - `handle(c: Context)` - Hono handler for processing interactions
442
+ - `getApp()` - Returns a pre-configured Hono app instance
443
+
444
+ ### `SlashCommandHandler`
445
+
446
+ Extends `SlashCommandBuilder` from `@discordjs/builders`.
447
+
448
+ **Properties:**
449
+
450
+ - `handler: (interaction: ChatInputCommandInteraction) => Promise<void> | void` - Command execution handler
451
+ - `autocomplete?: (interaction: AutocompleteInteraction) => Promise<void> | void` - Optional autocomplete handler
452
+
453
+ ### `ContextCommandHandler`
454
+
455
+ Extends `ContextMenuCommandBuilder` from `@discordjs/builders`.
456
+
457
+ **Properties:**
458
+
459
+ - `handler: (interaction: UserCommandInteraction | MessageCommandInteraction) => Promise<void> | void` - Command execution handler
460
+
461
+ ### `ComponentHandler`
462
+
463
+ Handler for message components.
464
+
465
+ **Constructor:**
466
+
467
+ - `new ComponentHandler(prefix: string, handler: Function)` - Creates a handler for components with the given prefix
468
+
469
+ ### `ModalHandler`
470
+
471
+ Handler for modal submissions.
472
+
473
+ **Constructor:**
474
+
475
+ - `new ModalHandler(prefix: string, handler: Function)` - Creates a handler for modals with the given prefix
476
+
477
+ ### `parseCustomId`
478
+
479
+ Utility function for parsing custom IDs.
480
+
481
+ ```typescript
482
+ // Get full parsing details
483
+ parseCustomId(customId: string): ParsedCustomId
484
+
485
+ // Get only the prefix
486
+ parseCustomId(customId: string, onlyPrefix: true): string
487
+ ```
488
+
489
+ ## License
490
+
491
+ MIT
492
+
493
+ ## Contributing
494
+
495
+ Contributions are welcome! Please feel free to submit a Pull Request.