@developer.krd/discord-dashboard 0.1.2 → 0.1.5

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 CHANGED
@@ -1,673 +1,294 @@
1
1
  # @developer.krd/discord-dashboard
2
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
3
+ An advanced, plug-and-play Discord dashboard package for bot developers.
21
4
 
22
- ```bash
23
- npm install @developer.krd/discord-dashboard
24
- ```
5
+ Build a full web dashboard for your bot without writing frontend code. The package provides built-in rendering, Discord OAuth2 flow, and adapters for Express, Elysia, and Fastify.
25
6
 
26
- ## Ways to Create a Dashboard
7
+ ## Features
27
8
 
28
- You can build your dashboard in multiple styles depending on your project:
9
+ - **No frontend app required:** Server-rendered dashboard UI with built-in client script.
10
+ - **Multiple adapters:** `createExpressAdapter`, `createElysiaAdapter`, `createFastifyAdapter`.
11
+ - **Discord OAuth2 flow:** Login, callback exchange, and session persistence.
12
+ - **Theming and layouts:** Built-in layouts/themes (`default`, `compact`, `shadcn-magic`) plus custom renderers.
13
+ - **Home & plugin builders:** Dynamic sections/panels with typed action handlers.
14
+ - **Discord helper API:** Role/channel/member fetch & search helpers inside action context.
15
+ - **TypeScript-first:** Strict typed options, context, fields, and plugin contracts.
29
16
 
30
- 1. **Direct Config (`createDashboard`)**
31
- - Fastest way for simple dashboards.
32
- - Use `home`, `plugins`, and `getOverviewCards` directly.
17
+ ---
33
18
 
34
- 2. **Fluent Designer (`createDashboardDesigner`)**
35
- - Best for larger bots and modular structure.
36
- - Use `setupCategory`, `userCategory`, `guildCategory`, and `onHomeAction`.
19
+ ## 🎨 Templates
37
20
 
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.
21
+ Browse built-in templates and screenshot placeholders in [src/templates/templates.md](src/templates/templates.md).
41
22
 
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`.
23
+ ---
45
24
 
46
- ## Language Support
25
+ ## 📦 Installation
47
26
 
48
- This package supports TypeScript, JavaScript (`.js`), and ESM JavaScript (`.mjs`).
27
+ ```bash
28
+ npm install @developer.krd/discord-dashboard discord.js
29
+ ```
49
30
 
50
- TypeScript / ESM:
31
+ Install only the server stack you use:
51
32
 
52
- ```ts
53
- import { createDashboard } from "@developer.krd/discord-dashboard";
54
- ```
33
+ ```bash
34
+ # Express
35
+ npm install express express-session
55
36
 
56
- JavaScript ESM (`.mjs`):
37
+ # Elysia
38
+ npm install elysia
57
39
 
58
- ```js
59
- import { createDashboard } from "@developer.krd/discord-dashboard";
40
+ # Fastify
41
+ npm install fastify @fastify/cookie @fastify/session
60
42
  ```
61
43
 
62
- JavaScript CommonJS (`.js` with `require`):
44
+ Node.js `>=18` is required.
63
45
 
64
- ```js
65
- const { createDashboard } = require("@developer.krd/discord-dashboard");
66
- ```
46
+ ---
67
47
 
68
- ## Quick Start
48
+ ## 🚀 Quick Start (Express)
69
49
 
70
50
  ```ts
71
51
  import express from "express";
72
- import { createDashboard } from "@developer.krd/discord-dashboard";
52
+ import { Client, GatewayIntentBits } from "discord.js";
53
+ import { createExpressAdapter } from "@developer.krd/discord-dashboard";
73
54
 
74
55
  const app = express();
56
+ const client = new Client({ intents: [GatewayIntentBits.Guilds] });
75
57
 
76
- const dashboard = createDashboard({
58
+ createExpressAdapter({
77
59
  app,
60
+ client,
78
61
  basePath: "/dashboard",
79
- dashboardName: "KRD Bot Control",
62
+ dashboardName: "My Bot Control",
63
+
80
64
  botToken: process.env.DISCORD_BOT_TOKEN!,
81
65
  clientId: process.env.DISCORD_CLIENT_ID!,
82
66
  clientSecret: process.env.DISCORD_CLIENT_SECRET!,
83
67
  redirectUri: "http://localhost:3000/dashboard/callback",
84
68
  sessionSecret: process.env.DASHBOARD_SESSION_SECRET!,
85
- ownerIds: ["123456789012345678"],
69
+
70
+ uiTemplate: "shadcn-magic",
71
+ uiTheme: "shadcn-magic",
72
+
86
73
  getOverviewCards: async (context) => [
87
74
  {
88
75
  id: "uptime",
89
76
  title: "Bot Uptime",
90
77
  value: `${Math.floor(process.uptime() / 60)} min`,
91
- subtitle: `Logged in as ${context.user.username}`
78
+ subtitle: `Logged in as ${context.user.username}`,
92
79
  },
93
80
  {
94
81
  id: "guilds",
95
- title: "Guilds",
96
- value: context.guilds.length
97
- }
82
+ title: "Manageable Guilds",
83
+ value: context.guilds.length,
84
+ },
98
85
  ],
86
+
99
87
  home: {
100
88
  async getSections(context) {
101
89
  return [
102
90
  {
103
91
  id: "welcome",
104
92
  title: "Welcome Settings",
93
+ categoryId: "general",
105
94
  description: context.selectedGuildId ? "Guild-specific setup" : "User-level setup",
106
95
  fields: [
107
96
  { 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!" }
97
+ { id: "message", label: "Message", type: "textarea", value: "Welcome to the server!" },
110
98
  ],
111
- actions: [{ id: "saveWelcome", label: "Save", variant: "primary" }]
112
- }
99
+ actions: [{ id: "saveWelcome", label: "Save", variant: "primary" }],
100
+ },
113
101
  ];
114
102
  },
115
103
  actions: {
116
104
  async saveWelcome(context, payload) {
117
- console.log("Saving", context.selectedGuildId, payload.values);
105
+ console.log("Saving data for", context.selectedGuildId, payload.values);
118
106
  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
107
  },
142
- actions: {
143
- async sync() {
144
- return { ok: true, message: "Moderation rules synced.", refresh: true };
145
- }
146
- }
147
- }
148
- ]
108
+ },
109
+ },
149
110
  });
150
111
 
112
+ await client.login(process.env.DISCORD_BOT_TOKEN!);
151
113
  app.listen(3000, () => {
152
- console.log("Dashboard: http://localhost:3000/dashboard");
114
+ console.log("Dashboard live at: http://localhost:3000/dashboard");
153
115
  });
154
116
  ```
155
117
 
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
118
+ ## 🚀 Quick Start (Elysia)
166
119
 
167
120
  ```ts
168
- import { createDashboard, createDashboardDesigner } from "@developer.krd/discord-dashboard";
121
+ import { Elysia } from "elysia";
122
+ import { Client, GatewayIntentBits } from "discord.js";
123
+ import { createElysiaAdapter } from "@developer.krd/discord-dashboard";
169
124
 
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
- ```
125
+ const client = new Client({ intents: [GatewayIntentBits.Guilds] });
126
+ const app = new Elysia();
206
127
 
207
- ## Discord-Style Flexible Flow (`onLoad` / `onSave`)
128
+ createElysiaAdapter({
129
+ app,
130
+ client,
131
+ basePath: "/dashboard",
132
+ dashboardName: "My Bot Control",
208
133
 
209
- You can also build pages in a Discord-client-like style:
134
+ botToken: process.env.DISCORD_BOT_TOKEN!,
135
+ clientId: process.env.DISCORD_CLIENT_ID!,
136
+ clientSecret: process.env.DISCORD_CLIENT_SECRET!,
137
+ redirectUri: "http://localhost:3000/dashboard/callback",
138
+ sessionSecret: process.env.DASHBOARD_SESSION_SECRET!,
210
139
 
211
- ```ts
212
- import { createDashboard, createDashboardDesigner } from "@developer.krd/discord-dashboard";
140
+ uiTemplate: "shadcn-magic",
141
+ uiTheme: "shadcn-magic",
142
+ });
213
143
 
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() });
144
+ await client.login(process.env.DISCORD_BOT_TOKEN!);
145
+ app.listen({ port: 3000 });
146
+ console.log("Dashboard live at: http://localhost:3000/dashboard");
242
147
  ```
243
148
 
244
- Notes:
149
+ ## 🚀 Quick Start (Fastify)
245
150
 
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.
151
+ ```ts
152
+ import Fastify from "fastify";
153
+ import fastifyCookie from "@fastify/cookie";
154
+ import fastifySession from "@fastify/session";
155
+ import { Client, GatewayIntentBits } from "discord.js";
156
+ import { createFastifyAdapter } from "@developer.krd/discord-dashboard";
157
+
158
+ const fastify = Fastify({ logger: true });
159
+ const client = new Client({ intents: [GatewayIntentBits.Guilds] });
160
+
161
+ await fastify.register(fastifyCookie);
162
+ await fastify.register(fastifySession, {
163
+ secret: process.env.DASHBOARD_SESSION_SECRET!,
164
+ cookie: { secure: false },
165
+ });
249
166
 
250
- ## Designer API Reference
167
+ createFastifyAdapter(fastify, {
168
+ client,
169
+ basePath: "/dashboard",
170
+ dashboardName: "My Bot Control",
251
171
 
252
- `createDashboardDesigner(baseOptions)` supports:
172
+ botToken: process.env.DISCORD_BOT_TOKEN!,
173
+ clientId: process.env.DISCORD_CLIENT_ID!,
174
+ clientSecret: process.env.DISCORD_CLIENT_SECRET!,
175
+ redirectUri: "http://localhost:3000/dashboard/callback",
176
+ sessionSecret: process.env.DASHBOARD_SESSION_SECRET!,
253
177
 
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(...)`.
178
+ uiTemplate: "shadcn-magic",
179
+ uiTheme: "shadcn-magic",
180
+ });
265
181
 
266
- ## UI Template System (for package developers)
182
+ await client.login(process.env.DISCORD_BOT_TOKEN!);
183
+ await fastify.listen({ port: 3000, host: "0.0.0.0" });
184
+ console.log("Dashboard live at: http://localhost:3000/dashboard");
185
+ ```
267
186
 
268
- You can ship multiple UI/UX templates and choose one by id.
187
+ ---
269
188
 
270
- Built-in templates:
189
+ ## 🔌 Adapter Notes
271
190
 
272
- - `default`
273
- - `compact`
191
+ - **Express:** `createExpressAdapter(options)`
192
+ - **Elysia:** `createElysiaAdapter(options)`
193
+ - **Fastify:** `createFastifyAdapter(fastify, options)`
274
194
 
275
- Use built-in compact mode:
195
+ For Fastify, you must register `@fastify/cookie` and `@fastify/session` before wiring the adapter, since dashboard routes read/write `request.session`.
276
196
 
277
- ```ts
278
- createDashboard({
279
- app,
280
- botToken,
281
- clientId,
282
- clientSecret,
283
- redirectUri,
284
- sessionSecret,
285
- uiTemplate: "compact"
286
- });
287
- ```
197
+ ---
288
198
 
289
- Direct `createDashboard(...)` usage:
199
+ ## 🛠️ Extensible Plugins
290
200
 
291
201
  ```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
- }
202
+ createExpressAdapter({
203
+ // ... core config ...
204
+ plugins: [
205
+ {
206
+ id: "runtime",
207
+ name: "System Runtime",
208
+ description: "Live bot diagnostics",
209
+ getPanels: async () => [
210
+ {
211
+ id: "runtime-status",
212
+ title: "Diagnostics",
213
+ fields: [
214
+ { label: "Logged in as", value: client.user?.tag ?? "Unknown" },
215
+ { label: "Uptime", value: `${Math.floor(process.uptime())}s` },
216
+ ],
217
+ actions: [{ id: "refreshRuntime", label: "Refresh", variant: "primary", collectFields: false }],
218
+ },
219
+ ],
220
+ actions: {
221
+ refreshRuntime: async () => ({ ok: true, message: "Data refreshed", refresh: true }),
222
+ },
223
+ },
224
+ ],
314
225
  });
315
226
  ```
316
227
 
317
- Designer usage:
228
+ ---
318
229
 
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:
230
+ ## 🧩 Built-in Helper Functions
335
231
 
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:
232
+ Inside `home.actions` and `plugins.actions`, use `context.helpers`:
344
233
 
234
+ - `getGuildIconUrl(guildId, iconHash)`
235
+ - `getUserAvatarUrl(userId, avatarHash)`
345
236
  - `getChannel(channelId)`
346
237
  - `getGuildChannels(guildId)`
238
+ - `searchGuildChannels(guildId, query, options?)`
347
239
  - `getRole(guildId, roleId)`
348
240
  - `getGuildRoles(guildId)`
241
+ - `searchGuildRoles(guildId, query, options?)`
242
+ - `searchGuildMembers(guildId, query, options?)`
349
243
  - `getGuildMember(guildId, userId)`
350
244
 
351
- ## Plugin Scopes and Form Actions
352
-
353
- Plugins can now be separated by target dashboard scope:
245
+ ---
354
246
 
355
- - `scope: "user"` → shown only on user dashboard
356
- - `scope: "guild"` → shown only on guild dashboard
357
- - `scope: "both"` (default) → shown on both
247
+ ## 🔍 Lookup Fields
358
248
 
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`)
249
+ Field types include `role-search`, `channel-search`, and `member-search`, and you can provide lookup options in field config (for limits and filters).
467
250
 
468
251
  ```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;
252
+ {
253
+ id: "logChannel",
254
+ label: "Logs",
255
+ type: "channel-search",
256
+ lookup: {
257
+ limit: 5,
258
+ channelTypes: [0, 5],
259
+ },
260
+ }
484
261
  ```
485
262
 
486
- ### Event example (`interactionCreate`)
263
+ ---
487
264
 
488
- ```ts
489
- import type { BotEvent } from "../types";
265
+ ## 📚 API Reference
490
266
 
491
- const event: BotEvent<"interactionCreate"> = {
492
- name: "interactionCreate",
493
- async execute(context, interaction) {
494
- if (!interaction.isChatInputCommand()) return;
267
+ `DashboardOptions` includes:
495
268
 
496
- const command = context.commands.get(interaction.commandName);
497
- if (!command) {
498
- await interaction.reply({ content: "Command not found.", ephemeral: true });
499
- return;
500
- }
269
+ - OAuth/session config (`clientId`, `clientSecret`, `redirectUri`, `sessionSecret`, ...)
270
+ - Dashboard presentation (`dashboardName`, `basePath`, `uiTemplate`, `uiTheme`, `setupDesign`)
271
+ - Dynamic content (`getOverviewCards`, `home`, `plugins`)
272
+ - Runtime dependencies (`client`, optional framework app where supported)
501
273
 
502
- await command.execute(context, interaction);
503
- }
504
- };
274
+ Also exported:
505
275
 
506
- export default event;
507
- ```
276
+ - `DashboardEngine`
277
+ - `DashboardDesigner`
508
278
 
509
- ### Dashboard creation example (classic + flexible pages)
279
+ ---
510
280
 
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
- });
281
+ ## 🔒 Required Discord OAuth2 Setup
531
282
 
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
- ```
283
+ 1. Go to the [Discord Developer Portal](https://discord.com/developers/applications).
284
+ 2. Open your Application → **OAuth2**.
285
+ 3. Add your redirect URI (example: `http://localhost:3000/dashboard/callback`).
286
+ 4. Use your `CLIENT_ID` and `CLIENT_SECRET` in dashboard config.
572
287
 
573
- ### Moderation Toggles + Limits
288
+ Use a strong `sessionSecret` in production and serve behind HTTPS.
574
289
 
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
- ```
290
+ ---
670
291
 
671
- ## License
292
+ ## 📄 License
672
293
 
673
294
  MIT