@developer.krd/discord-dashboard 0.1.0
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 +673 -0
- package/dist/index.cjs +2495 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +317 -0
- package/dist/index.d.ts +317 -0
- package/dist/index.js +2453 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
# @developer.krd/discord-dashboard
|
|
2
|
+
|
|
3
|
+
Advanced plug-and-play Discord dashboard package for bot developers.
|
|
4
|
+
|
|
5
|
+
- No frontend coding required.
|
|
6
|
+
- Built-in Discord OAuth2 login flow.
|
|
7
|
+
- Ready-made dashboard UI.
|
|
8
|
+
- Guild access control.
|
|
9
|
+
- Discord-like server rail with user/guild avatars.
|
|
10
|
+
- Invite-on-click flow for guilds where bot is not installed.
|
|
11
|
+
- Extensible plugin panels + server actions.
|
|
12
|
+
- Flexible Home Builder with editable sections and save actions.
|
|
13
|
+
- Fluent designer API for setup/user/guild dashboard separation.
|
|
14
|
+
- Fluent `.setupDesign(...)` API for dashboard main colors.
|
|
15
|
+
- Home section width presets: `100` (default), `50`, `33`, `20`.
|
|
16
|
+
- Built-in Discord utility helpers in actions (`getChannel`, `getRole`, `getGuildRoles`, etc.).
|
|
17
|
+
- Overview-first tabs with separate module categories (like pets, permissions, etc.).
|
|
18
|
+
- Website autocomplete field types for role/channel selection with filtering.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @developer.krd/discord-dashboard
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Ways to Create a Dashboard
|
|
27
|
+
|
|
28
|
+
You can build your dashboard in multiple styles depending on your project:
|
|
29
|
+
|
|
30
|
+
1. **Direct Config (`createDashboard`)**
|
|
31
|
+
- Fastest way for simple dashboards.
|
|
32
|
+
- Use `home`, `plugins`, and `getOverviewCards` directly.
|
|
33
|
+
|
|
34
|
+
2. **Fluent Designer (`createDashboardDesigner`)**
|
|
35
|
+
- Best for larger bots and modular structure.
|
|
36
|
+
- Use `setupCategory`, `userCategory`, `guildCategory`, and `onHomeAction`.
|
|
37
|
+
|
|
38
|
+
3. **Discord-like Lifecycle Pages**
|
|
39
|
+
- Use `setupPage + onLoad/onSave` (also `onload/onsave`).
|
|
40
|
+
- Great when pages need dynamic loading and per-page save handlers.
|
|
41
|
+
|
|
42
|
+
4. **Template-based UI/UX**
|
|
43
|
+
- Choose built-in template (`default`, `compact`) with `uiTemplate`.
|
|
44
|
+
- Register custom full HTML templates with `uiTemplates` or Designer `addTemplate`.
|
|
45
|
+
|
|
46
|
+
## Language Support
|
|
47
|
+
|
|
48
|
+
This package supports TypeScript, JavaScript (`.js`), and ESM JavaScript (`.mjs`).
|
|
49
|
+
|
|
50
|
+
TypeScript / ESM:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { createDashboard } from "@developer.krd/discord-dashboard";
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
JavaScript ESM (`.mjs`):
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import { createDashboard } from "@developer.krd/discord-dashboard";
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
JavaScript CommonJS (`.js` with `require`):
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const { createDashboard } = require("@developer.krd/discord-dashboard");
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import express from "express";
|
|
72
|
+
import { createDashboard } from "@developer.krd/discord-dashboard";
|
|
73
|
+
|
|
74
|
+
const app = express();
|
|
75
|
+
|
|
76
|
+
const dashboard = createDashboard({
|
|
77
|
+
app,
|
|
78
|
+
basePath: "/dashboard",
|
|
79
|
+
dashboardName: "KRD Bot Control",
|
|
80
|
+
botToken: process.env.DISCORD_BOT_TOKEN!,
|
|
81
|
+
clientId: process.env.DISCORD_CLIENT_ID!,
|
|
82
|
+
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
|
83
|
+
redirectUri: "http://localhost:3000/dashboard/callback",
|
|
84
|
+
sessionSecret: process.env.DASHBOARD_SESSION_SECRET!,
|
|
85
|
+
ownerIds: ["123456789012345678"],
|
|
86
|
+
getOverviewCards: async (context) => [
|
|
87
|
+
{
|
|
88
|
+
id: "uptime",
|
|
89
|
+
title: "Bot Uptime",
|
|
90
|
+
value: `${Math.floor(process.uptime() / 60)} min`,
|
|
91
|
+
subtitle: `Logged in as ${context.user.username}`
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "guilds",
|
|
95
|
+
title: "Guilds",
|
|
96
|
+
value: context.guilds.length
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
home: {
|
|
100
|
+
async getSections(context) {
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
id: "welcome",
|
|
104
|
+
title: "Welcome Settings",
|
|
105
|
+
description: context.selectedGuildId ? "Guild-specific setup" : "User-level setup",
|
|
106
|
+
fields: [
|
|
107
|
+
{ id: "enabled", label: "Enable Welcome", type: "boolean", value: true },
|
|
108
|
+
{ id: "channel", label: "Channel ID", type: "text", value: "" },
|
|
109
|
+
{ id: "message", label: "Message", type: "textarea", value: "Welcome to the server!" }
|
|
110
|
+
],
|
|
111
|
+
actions: [{ id: "saveWelcome", label: "Save", variant: "primary" }]
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
},
|
|
115
|
+
actions: {
|
|
116
|
+
async saveWelcome(context, payload) {
|
|
117
|
+
console.log("Saving", context.selectedGuildId, payload.values);
|
|
118
|
+
return { ok: true, message: "Welcome settings saved", refresh: false };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
plugins: [
|
|
123
|
+
{
|
|
124
|
+
id: "moderation",
|
|
125
|
+
name: "Moderation",
|
|
126
|
+
description: "Live moderation controls",
|
|
127
|
+
async getPanels(context) {
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
id: "status",
|
|
131
|
+
title: "Status",
|
|
132
|
+
fields: [
|
|
133
|
+
{ label: "Selected Guild", value: context.selectedGuildId ?? "None" },
|
|
134
|
+
{ label: "Access", value: "Ready" }
|
|
135
|
+
],
|
|
136
|
+
actions: [
|
|
137
|
+
{ id: "sync", label: "Sync Rules", variant: "primary" }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
},
|
|
142
|
+
actions: {
|
|
143
|
+
async sync() {
|
|
144
|
+
return { ok: true, message: "Moderation rules synced.", refresh: true };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
app.listen(3000, () => {
|
|
152
|
+
console.log("Dashboard: http://localhost:3000/dashboard");
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Fluent Dashboard Designer (Recommended)
|
|
157
|
+
|
|
158
|
+
Use a clean modular structure instead of putting all configuration in one place:
|
|
159
|
+
|
|
160
|
+
- `setup(...)` for important defaults
|
|
161
|
+
- `setupDesign(...)` for dashboard colors (`primary`, `bg`, `rail`, `panel`, etc.)
|
|
162
|
+
- `setupCategory(...)` for one-time setup UI
|
|
163
|
+
- `userCategory(...)` for user dashboard sections
|
|
164
|
+
- `guildCategory(...)` for server dashboard sections
|
|
165
|
+
- `onHomeAction(...)` for save handlers
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { createDashboard, createDashboardDesigner } from "@developer.krd/discord-dashboard";
|
|
169
|
+
|
|
170
|
+
const designer = createDashboardDesigner({
|
|
171
|
+
app,
|
|
172
|
+
botToken,
|
|
173
|
+
clientId,
|
|
174
|
+
clientSecret,
|
|
175
|
+
redirectUri,
|
|
176
|
+
sessionSecret
|
|
177
|
+
})
|
|
178
|
+
.setup({ ownerIds: ["123"], botInvitePermissions: "8" })
|
|
179
|
+
.setupDesign({ primary: "#4f46e5", rail: "#181a20", panel: "#2f3136" })
|
|
180
|
+
.userCategory("pets", "Pets", (category) => {
|
|
181
|
+
category.section({
|
|
182
|
+
id: "pets-user",
|
|
183
|
+
title: "User Pets",
|
|
184
|
+
width: 50,
|
|
185
|
+
fields: [{ id: "petName", label: "Pet Name", type: "text", value: "Luna" }],
|
|
186
|
+
actions: [{ id: "saveUserPets", label: "Save", variant: "primary" }]
|
|
187
|
+
});
|
|
188
|
+
})
|
|
189
|
+
.guildCategory("pets", "Pets", (category) => {
|
|
190
|
+
category.section({
|
|
191
|
+
id: "pets-guild",
|
|
192
|
+
title: "Guild Pets",
|
|
193
|
+
fields: [{ id: "petsChannelId", label: "Pets Channel", type: "text", value: "" }],
|
|
194
|
+
actions: [{ id: "saveGuildPets", label: "Save", variant: "primary" }]
|
|
195
|
+
});
|
|
196
|
+
})
|
|
197
|
+
.onHomeAction("saveGuildPets", async (context, payload) => {
|
|
198
|
+
const channelId = String(payload.values.petsChannelId ?? "");
|
|
199
|
+
const channel = await context.helpers.getChannel(channelId);
|
|
200
|
+
if (!channel) return { ok: false, message: "Channel not found" };
|
|
201
|
+
return { ok: true, message: "Saved", refresh: true };
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
createDashboard({ ...designer.build() });
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Discord-Style Flexible Flow (`onLoad` / `onSave`)
|
|
208
|
+
|
|
209
|
+
You can also build pages in a Discord-client-like style:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { createDashboard, createDashboardDesigner } from "@developer.krd/discord-dashboard";
|
|
213
|
+
|
|
214
|
+
const dashboard = createDashboardDesigner({
|
|
215
|
+
app,
|
|
216
|
+
botToken,
|
|
217
|
+
clientId,
|
|
218
|
+
clientSecret,
|
|
219
|
+
redirectUri,
|
|
220
|
+
sessionSecret
|
|
221
|
+
})
|
|
222
|
+
.setupPage({
|
|
223
|
+
id: "profile",
|
|
224
|
+
title: "Profile",
|
|
225
|
+
scope: "user",
|
|
226
|
+
width: 50,
|
|
227
|
+
fields: [
|
|
228
|
+
{ id: "bio", label: "Bio", type: "textarea", value: "" },
|
|
229
|
+
{ id: "notifications", label: "Notifications", type: "boolean", value: true }
|
|
230
|
+
]
|
|
231
|
+
})
|
|
232
|
+
.onLoad("profile", async (ctx, section) => ({
|
|
233
|
+
...section,
|
|
234
|
+
description: `Loaded for ${ctx.user.username}`
|
|
235
|
+
}))
|
|
236
|
+
.onSave("profile", async (ctx, payload) => {
|
|
237
|
+
console.log("save profile", ctx.user.id, payload.values);
|
|
238
|
+
return { ok: true, message: "Profile saved", refresh: true };
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
createDashboard({ ...dashboard.build() });
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Notes:
|
|
245
|
+
|
|
246
|
+
- `setupPage(...)` creates a page/section with optional fields/actions.
|
|
247
|
+
- `onLoad(pageId, handler)` (or lowercase `onload`) runs when that page is resolved.
|
|
248
|
+
- `onSave(pageId, handler)` (or lowercase `onsave`) auto-adds a default **Save** action if missing.
|
|
249
|
+
|
|
250
|
+
## Designer API Reference
|
|
251
|
+
|
|
252
|
+
`createDashboardDesigner(baseOptions)` supports:
|
|
253
|
+
|
|
254
|
+
- `setup({...})`: core setup (`ownerIds`, invite permissions/scopes, name/path, `uiTemplate`).
|
|
255
|
+
- `setupDesign({...})`: color/theme tokens for built-in templates.
|
|
256
|
+
- `setupCategory(id, label, build)`
|
|
257
|
+
- `userCategory(id, label, build)`
|
|
258
|
+
- `guildCategory(id, label, build)`
|
|
259
|
+
- `onHomeAction(actionId, handler)`
|
|
260
|
+
- `setupPage({...})`: page-style section definition.
|
|
261
|
+
- `onLoad(pageId, handler)` + alias `onload(...)`.
|
|
262
|
+
- `onSave(pageId, handler)` + alias `onsave(...)`.
|
|
263
|
+
- `addTemplate(templateId, renderer)` + `useTemplate(templateId)`.
|
|
264
|
+
- `build()`: returns full `DashboardOptions` for `createDashboard(...)`.
|
|
265
|
+
|
|
266
|
+
## UI Template System (for package developers)
|
|
267
|
+
|
|
268
|
+
You can ship multiple UI/UX templates and choose one by id.
|
|
269
|
+
|
|
270
|
+
Built-in templates:
|
|
271
|
+
|
|
272
|
+
- `default`
|
|
273
|
+
- `compact`
|
|
274
|
+
|
|
275
|
+
Use built-in compact mode:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
createDashboard({
|
|
279
|
+
app,
|
|
280
|
+
botToken,
|
|
281
|
+
clientId,
|
|
282
|
+
clientSecret,
|
|
283
|
+
redirectUri,
|
|
284
|
+
sessionSecret,
|
|
285
|
+
uiTemplate: "compact"
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Direct `createDashboard(...)` usage:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { createDashboard } from "@developer.krd/discord-dashboard";
|
|
293
|
+
|
|
294
|
+
createDashboard({
|
|
295
|
+
app,
|
|
296
|
+
botToken,
|
|
297
|
+
clientId,
|
|
298
|
+
clientSecret,
|
|
299
|
+
redirectUri,
|
|
300
|
+
sessionSecret,
|
|
301
|
+
uiTemplate: "glass",
|
|
302
|
+
uiTemplates: {
|
|
303
|
+
glass: ({ dashboardName, basePath }) => `
|
|
304
|
+
<!doctype html>
|
|
305
|
+
<html>
|
|
306
|
+
<head><title>${dashboardName}</title></head>
|
|
307
|
+
<body>
|
|
308
|
+
<h1>${dashboardName}</h1>
|
|
309
|
+
<p>Custom template active. Base path: ${basePath}</p>
|
|
310
|
+
</body>
|
|
311
|
+
</html>
|
|
312
|
+
`
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Designer usage:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const designer = createDashboardDesigner({
|
|
321
|
+
app,
|
|
322
|
+
botToken,
|
|
323
|
+
clientId,
|
|
324
|
+
clientSecret,
|
|
325
|
+
redirectUri,
|
|
326
|
+
sessionSecret
|
|
327
|
+
})
|
|
328
|
+
.addTemplate("glass", ({ dashboardName }) => `<html><body><h1>${dashboardName}</h1></body></html>`)
|
|
329
|
+
.useTemplate("glass");
|
|
330
|
+
|
|
331
|
+
createDashboard({ ...designer.build() });
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Template renderer signature:
|
|
335
|
+
|
|
336
|
+
- Input: `{ dashboardName, basePath, setupDesign }`
|
|
337
|
+
- Output: full HTML string (complete document/template, not only CSS overrides)
|
|
338
|
+
|
|
339
|
+
Template files in this package are organized under [src/templates](src/templates).
|
|
340
|
+
|
|
341
|
+
## Built-in Helper Functions
|
|
342
|
+
|
|
343
|
+
Available in `context.helpers` inside home/plugin actions:
|
|
344
|
+
|
|
345
|
+
- `getChannel(channelId)`
|
|
346
|
+
- `getGuildChannels(guildId)`
|
|
347
|
+
- `getRole(guildId, roleId)`
|
|
348
|
+
- `getGuildRoles(guildId)`
|
|
349
|
+
- `getGuildMember(guildId, userId)`
|
|
350
|
+
|
|
351
|
+
## Plugin Scopes and Form Actions
|
|
352
|
+
|
|
353
|
+
Plugins can now be separated by target dashboard scope:
|
|
354
|
+
|
|
355
|
+
- `scope: "user"` → shown only on user dashboard
|
|
356
|
+
- `scope: "guild"` → shown only on guild dashboard
|
|
357
|
+
- `scope: "both"` (default) → shown on both
|
|
358
|
+
|
|
359
|
+
Plugin action forms:
|
|
360
|
+
|
|
361
|
+
- Mark a panel field with `editable: true` and set `type` (text, textarea, select, boolean, role-search, channel-search, member-search, url).
|
|
362
|
+
- Use `type: "string-list"` for drag-and-drop ordered labels (great for poll buttons).
|
|
363
|
+
- Set action `collectFields: true` to send panel values in the action payload.
|
|
364
|
+
- Action body format:
|
|
365
|
+
- `panelId`
|
|
366
|
+
- `values` (contains selected lookup objects and typed field values)
|
|
367
|
+
|
|
368
|
+
## Lookup Field Types (Website UI)
|
|
369
|
+
|
|
370
|
+
Use these in home sections to let users type and select Discord objects:
|
|
371
|
+
|
|
372
|
+
- `role-search`: type role name, get matching roles, select one, submit role object data.
|
|
373
|
+
- `channel-search`: type channel name, get matching channels with filters, select one, submit channel object data.
|
|
374
|
+
|
|
375
|
+
Lookup filters:
|
|
376
|
+
|
|
377
|
+
- `limit`
|
|
378
|
+
- `minQueryLength`
|
|
379
|
+
- `includeManaged` (roles)
|
|
380
|
+
- `nsfw` (channels)
|
|
381
|
+
- `channelTypes` (channels)
|
|
382
|
+
|
|
383
|
+
## Required Discord OAuth2 Setup
|
|
384
|
+
|
|
385
|
+
1. Open the Discord Developer Portal for your application.
|
|
386
|
+
2. In OAuth2 settings, add your redirect URI (example: `http://localhost:3000/dashboard/callback`).
|
|
387
|
+
3. Make sure scopes include at least `identify guilds`.
|
|
388
|
+
4. Use your app `CLIENT_ID` and `CLIENT_SECRET` in `createDashboard()`.
|
|
389
|
+
|
|
390
|
+
## API Reference
|
|
391
|
+
|
|
392
|
+
### `createDashboard(options)`
|
|
393
|
+
|
|
394
|
+
Creates and mounts a complete dashboard with OAuth2 + UI.
|
|
395
|
+
|
|
396
|
+
Key `options`:
|
|
397
|
+
|
|
398
|
+
- `app?`: existing Express app instance.
|
|
399
|
+
- `basePath?`: dashboard route prefix. Default: `/dashboard`.
|
|
400
|
+
- `dashboardName?`: topbar/dashboard title.
|
|
401
|
+
- `setupDesign?`: override CSS theme variables (`primary`, `bg`, `rail`, `panel`, etc.).
|
|
402
|
+
- `uiTemplate?`: template id to render (`default`, `compact`, or your custom id).
|
|
403
|
+
- `uiTemplates?`: custom full-HTML template renderers.
|
|
404
|
+
- `botToken`: bot token (for your own plugin logic).
|
|
405
|
+
- `clientId`, `clientSecret`, `redirectUri`: Discord OAuth2 credentials.
|
|
406
|
+
- `sessionSecret`: secret for encrypted session cookies.
|
|
407
|
+
- `sessionName?`, `sessionMaxAgeMs?`: cookie/session configuration.
|
|
408
|
+
- `scopes?`: OAuth scopes for login (default includes `identify guilds`).
|
|
409
|
+
- `botInvitePermissions?`, `botInviteScopes?`: controls invite link generation.
|
|
410
|
+
- `ownerIds?`: optional allow-list of Discord user IDs.
|
|
411
|
+
- `guildFilter?`: async filter for guild visibility.
|
|
412
|
+
- `getOverviewCards?`: dynamic card resolver.
|
|
413
|
+
- `home?`: configurable homepage sections + save handlers.
|
|
414
|
+
- `home.getOverviewSections?`: add overview-scoped sections.
|
|
415
|
+
- `home.getCategories?`: define category tabs (setup/user/guild).
|
|
416
|
+
- `home.getSections?`: define sections for active scope.
|
|
417
|
+
- `home.actions?`: action handlers for section actions.
|
|
418
|
+
- `plugins?`: plugin definitions with panels/actions.
|
|
419
|
+
- `trustProxy?`: proxy configuration when running behind reverse proxies.
|
|
420
|
+
- `host?`, `port?`: only used when you call `dashboard.start()` without passing `app`.
|
|
421
|
+
|
|
422
|
+
Return value:
|
|
423
|
+
|
|
424
|
+
- `app`: Express app instance.
|
|
425
|
+
- `start()`: starts internal server only if you didn’t pass `app`.
|
|
426
|
+
- `stop()`: stops internal server.
|
|
427
|
+
|
|
428
|
+
## Security Notes
|
|
429
|
+
|
|
430
|
+
- Uses secure HTTP-only session cookies.
|
|
431
|
+
- Uses OAuth2 `state` validation for callback integrity.
|
|
432
|
+
- Use HTTPS in production and strong `sessionSecret`.
|
|
433
|
+
- Optionally set trusted proxy with `trustProxy`.
|
|
434
|
+
|
|
435
|
+
## Local Development
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
npm install
|
|
439
|
+
npm run typecheck
|
|
440
|
+
npm run build
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Run the example app:
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
cp .env.example .env
|
|
447
|
+
# Fill .env with your Discord app credentials first
|
|
448
|
+
npm run example
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Run the real-bot entry directly:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
npm run real-bot
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Real Bot Code Examples
|
|
458
|
+
|
|
459
|
+
The real-bot sample is in:
|
|
460
|
+
|
|
461
|
+
- [examples/real-bot/main.ts](examples/real-bot/main.ts)
|
|
462
|
+
- [examples/real-bot/dashboard.ts](examples/real-bot/dashboard.ts)
|
|
463
|
+
- [examples/real-bot/commands](examples/real-bot/commands)
|
|
464
|
+
- [examples/real-bot/events](examples/real-bot/events)
|
|
465
|
+
|
|
466
|
+
### Slash command example (`/ping`)
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
import { SlashCommandBuilder } from "discord.js";
|
|
470
|
+
import type { BotCommand } from "../types";
|
|
471
|
+
|
|
472
|
+
const command: BotCommand = {
|
|
473
|
+
data: new SlashCommandBuilder().setName("ping").setDescription("Check latency"),
|
|
474
|
+
async execute(context, interaction) {
|
|
475
|
+
const wsPing = context.client.ws.ping;
|
|
476
|
+
const start = interaction.createdTimestamp;
|
|
477
|
+
const response = await interaction.reply({ content: "Pinging...", fetchReply: true });
|
|
478
|
+
const apiPing = response.createdTimestamp - start;
|
|
479
|
+
await interaction.editReply(`🏓 Pong! WS: ${wsPing}ms | API: ${apiPing}ms`);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
export default command;
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Event example (`interactionCreate`)
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
import type { BotEvent } from "../types";
|
|
490
|
+
|
|
491
|
+
const event: BotEvent<"interactionCreate"> = {
|
|
492
|
+
name: "interactionCreate",
|
|
493
|
+
async execute(context, interaction) {
|
|
494
|
+
if (!interaction.isChatInputCommand()) return;
|
|
495
|
+
|
|
496
|
+
const command = context.commands.get(interaction.commandName);
|
|
497
|
+
if (!command) {
|
|
498
|
+
await interaction.reply({ content: "Command not found.", ephemeral: true });
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
await command.execute(context, interaction);
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
export default event;
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Dashboard creation example (classic + flexible pages)
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
createDashboard({
|
|
513
|
+
app,
|
|
514
|
+
botToken,
|
|
515
|
+
clientId,
|
|
516
|
+
clientSecret,
|
|
517
|
+
redirectUri,
|
|
518
|
+
sessionSecret,
|
|
519
|
+
uiTemplate: "compact",
|
|
520
|
+
home: {
|
|
521
|
+
async getSections(ctx) {
|
|
522
|
+
return [{
|
|
523
|
+
id: "classic",
|
|
524
|
+
title: "Classic Home",
|
|
525
|
+
scope: ctx.selectedGuildId ? "guild" : "user",
|
|
526
|
+
fields: [{ id: "mode", label: "Mode", type: "text", value: ctx.selectedGuildId ? "Guild" : "User", readOnly: true }]
|
|
527
|
+
}];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// Flexible style:
|
|
533
|
+
createDashboard({
|
|
534
|
+
...createDashboardDesigner({ app, botToken, clientId, clientSecret, redirectUri, sessionSecret })
|
|
535
|
+
.setupPage({ id: "profile", title: "Profile", scope: "user", fields: [{ id: "bio", label: "Bio", type: "textarea" }] })
|
|
536
|
+
.onLoad("profile", async (ctx, section) => ({ ...section, description: `Loaded for ${ctx.user.username}` }))
|
|
537
|
+
.onSave("profile", async () => ({ ok: true, message: "Saved", refresh: true }))
|
|
538
|
+
.build()
|
|
539
|
+
});
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
While using the real-bot example, live edits and bot activity are persisted to [examples/dashboard-demo-state.json](examples/dashboard-demo-state.json).
|
|
543
|
+
|
|
544
|
+
## Common Recipes
|
|
545
|
+
|
|
546
|
+
### Welcome System (Guild Home Section)
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import { createDashboard, createDashboardDesigner } from "@developer.krd/discord-dashboard";
|
|
550
|
+
|
|
551
|
+
const designer = createDashboardDesigner({ app, botToken, clientId, clientSecret, redirectUri, sessionSecret })
|
|
552
|
+
.guildCategory("welcome", "Welcome", (category) => {
|
|
553
|
+
category.section({
|
|
554
|
+
id: "welcome-settings",
|
|
555
|
+
title: "Welcome Settings",
|
|
556
|
+
fields: [
|
|
557
|
+
{ id: "enabled", label: "Enabled", type: "boolean", value: true },
|
|
558
|
+
{ id: "channel", label: "Welcome Channel", type: "channel-search", placeholder: "Search channel..." },
|
|
559
|
+
{ id: "message", label: "Message", type: "textarea", value: "Welcome to the server, {user}!" }
|
|
560
|
+
],
|
|
561
|
+
actions: [{ id: "saveWelcome", label: "Save Welcome", variant: "primary" }]
|
|
562
|
+
});
|
|
563
|
+
})
|
|
564
|
+
.onHomeAction("saveWelcome", async (context, payload) => {
|
|
565
|
+
if (!context.selectedGuildId) return { ok: false, message: "Select a guild first" };
|
|
566
|
+
const channel = payload.values.channel as { id?: string; name?: string } | null;
|
|
567
|
+
return { ok: true, message: `Welcome config saved for ${context.selectedGuildId} in #${channel?.name ?? "unknown"}` };
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
createDashboard({ ...designer.build() });
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Moderation Toggles + Limits
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
home: {
|
|
577
|
+
getSections: async (context) => [
|
|
578
|
+
{
|
|
579
|
+
id: "moderation-core",
|
|
580
|
+
title: "Moderation",
|
|
581
|
+
scope: "guild",
|
|
582
|
+
fields: [
|
|
583
|
+
{ id: "antiSpam", label: "Anti-Spam", type: "boolean", value: true },
|
|
584
|
+
{ id: "antiLinks", label: "Block Suspicious Links", type: "boolean", value: true },
|
|
585
|
+
{ id: "maxMentions", label: "Max Mentions", type: "number", value: 5 }
|
|
586
|
+
],
|
|
587
|
+
actions: [{ id: "saveModeration", label: "Save Moderation", variant: "primary" }]
|
|
588
|
+
}
|
|
589
|
+
],
|
|
590
|
+
actions: {
|
|
591
|
+
saveModeration: async (_context, payload) => ({
|
|
592
|
+
ok: true,
|
|
593
|
+
message: `Saved moderation: antiSpam=${Boolean(payload.values.antiSpam)}, maxMentions=${Number(payload.values.maxMentions ?? 0)}`,
|
|
594
|
+
refresh: true
|
|
595
|
+
})
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Ticket Panel Builder (Plugin)
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
plugins: [
|
|
604
|
+
{
|
|
605
|
+
id: "tickets",
|
|
606
|
+
name: "Tickets",
|
|
607
|
+
scope: "guild",
|
|
608
|
+
getPanels: async () => [
|
|
609
|
+
{
|
|
610
|
+
id: "ticket-panel",
|
|
611
|
+
title: "Ticket Panel",
|
|
612
|
+
fields: [
|
|
613
|
+
{ id: "targetChannel", label: "Target Channel", type: "channel-search", editable: true, value: "" },
|
|
614
|
+
{ id: "title", label: "Embed Title", type: "text", editable: true, value: "Need help?" },
|
|
615
|
+
{ id: "description", label: "Embed Description", type: "textarea", editable: true, value: "Click below to open a ticket." },
|
|
616
|
+
{ id: "buttonLabel", label: "Button Label", type: "text", editable: true, value: "Open Ticket" }
|
|
617
|
+
],
|
|
618
|
+
actions: [{ id: "publishTicketPanel", label: "Publish", variant: "primary", collectFields: true }]
|
|
619
|
+
}
|
|
620
|
+
],
|
|
621
|
+
actions: {
|
|
622
|
+
publishTicketPanel: async (_context, body) => {
|
|
623
|
+
const data = body as { values?: Record<string, unknown> };
|
|
624
|
+
const values = data.values ?? {};
|
|
625
|
+
return { ok: true, message: `Ticket panel published as '${String(values.title ?? "Need help?")}'`, data: values };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
]
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Announcement Composer (with Role Mention)
|
|
633
|
+
|
|
634
|
+
```ts
|
|
635
|
+
plugins: [
|
|
636
|
+
{
|
|
637
|
+
id: "announcements",
|
|
638
|
+
name: "Announcements",
|
|
639
|
+
scope: "guild",
|
|
640
|
+
getPanels: async () => [
|
|
641
|
+
{
|
|
642
|
+
id: "announce",
|
|
643
|
+
title: "Announcement Composer",
|
|
644
|
+
fields: [
|
|
645
|
+
{ id: "channel", label: "Channel", type: "channel-search", editable: true, value: "" },
|
|
646
|
+
{ id: "role", label: "Mention Role", type: "role-search", editable: true, value: "" },
|
|
647
|
+
{ id: "content", label: "Content", type: "textarea", editable: true, value: "Server update goes here..." }
|
|
648
|
+
],
|
|
649
|
+
actions: [{ id: "sendAnnouncement", label: "Send", variant: "primary", collectFields: true }]
|
|
650
|
+
}
|
|
651
|
+
],
|
|
652
|
+
actions: {
|
|
653
|
+
sendAnnouncement: async (_context, body) => {
|
|
654
|
+
const payload = body as { values?: Record<string, unknown> };
|
|
655
|
+
const channel = payload.values?.channel as { id?: string; name?: string } | null;
|
|
656
|
+
const role = payload.values?.role as { id?: string; name?: string } | null;
|
|
657
|
+
const mention = role?.id ? `<@&${role.id}> ` : "";
|
|
658
|
+
const content = String(payload.values?.content ?? "");
|
|
659
|
+
|
|
660
|
+
return {
|
|
661
|
+
ok: true,
|
|
662
|
+
message: `Announcement queued for #${channel?.name ?? "unknown"}`,
|
|
663
|
+
data: { message: mention + content, channel }
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
]
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## License
|
|
672
|
+
|
|
673
|
+
MIT
|