@developer.krd/discord-dashboard 0.1.2 → 0.1.3
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 +166 -554
- package/dist/index.cjs +569 -711
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +141 -69
- package/dist/index.d.ts +141 -69
- package/dist/index.js +568 -707
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,101 +1,69 @@
|
|
|
1
1
|
# @developer.krd/discord-dashboard
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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:
|
|
5
|
+
Build a fully functional, beautiful web dashboard for your bot without writing a single line of frontend code. Powered by Express, this package handles the OAuth2 login flow, session management, UI rendering, and API routing out of the box.
|
|
29
6
|
|
|
30
|
-
|
|
31
|
-
- Fastest way for simple dashboards.
|
|
32
|
-
- Use `home`, `plugins`, and `getOverviewCards` directly.
|
|
7
|
+
## ✨ Features
|
|
33
8
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
9
|
+
- **No Frontend Coding:** Generates a beautiful React/Vue-like UI using pure server-side rendering.
|
|
10
|
+
- **Built-in Auth:** Complete Discord OAuth2 login flow with secure session management.
|
|
11
|
+
- **Guild Access Control:** Automatically filters guilds based on admin/manage server permissions.
|
|
12
|
+
- **Fluent Designer API:** Build your dashboard cleanly using a chainable builder pattern.
|
|
13
|
+
- **Custom CSS Injection:** Fully theme the dashboard to match your bot's branding.
|
|
14
|
+
- **Extensible Plugins:** Create separate plugin panels with actionable buttons and forms.
|
|
15
|
+
- **Rich Form Fields:** Support for text, selects, booleans, and drag-and-drop string lists.
|
|
16
|
+
- **Smart Discord Lookups:** Website autocomplete fields for finding Roles, Channels, and Members.
|
|
17
|
+
- **Discord-like UI:** Familiar server rail with avatars and invite-on-click for missing guilds.
|
|
37
18
|
|
|
38
|
-
|
|
39
|
-
- Use `setupPage + onLoad/onSave` (also `onload/onsave`).
|
|
40
|
-
- Great when pages need dynamic loading and per-page save handlers.
|
|
19
|
+
---
|
|
41
20
|
|
|
42
|
-
|
|
43
|
-
- Choose built-in template (`default`, `compact`) with `uiTemplate`.
|
|
44
|
-
- Register custom full HTML templates with `uiTemplates` or Designer `addTemplate`.
|
|
21
|
+
## 📦 Installation
|
|
45
22
|
|
|
46
|
-
|
|
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";
|
|
23
|
+
```bash
|
|
24
|
+
npm install @developer.krd/discord-dashboard
|
|
54
25
|
```
|
|
55
26
|
|
|
56
|
-
JavaScript ESM
|
|
27
|
+
_(This package supports TypeScript, JavaScript, and ESM out of the box. Node.js >= 18 is required)._
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
import { createDashboard } from "@developer.krd/discord-dashboard";
|
|
60
|
-
```
|
|
29
|
+
---
|
|
61
30
|
|
|
62
|
-
|
|
31
|
+
## 🚀 Quick Start (Direct Configuration)
|
|
63
32
|
|
|
64
|
-
|
|
65
|
-
const { createDashboard } = require("@developer.krd/discord-dashboard");
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Quick Start
|
|
33
|
+
The fastest way to get your dashboard running is by instantiating the `DiscordDashboard` class directly.
|
|
69
34
|
|
|
70
35
|
```ts
|
|
71
36
|
import express from "express";
|
|
72
|
-
import {
|
|
37
|
+
import { DiscordDashboard } from "@developer.krd/discord-dashboard";
|
|
73
38
|
|
|
74
39
|
const app = express();
|
|
75
40
|
|
|
76
|
-
const dashboard =
|
|
77
|
-
app,
|
|
41
|
+
const dashboard = new DiscordDashboard({
|
|
42
|
+
app, // Attach to your existing Express app
|
|
78
43
|
basePath: "/dashboard",
|
|
79
|
-
dashboardName: "
|
|
44
|
+
dashboardName: "My Bot Control",
|
|
80
45
|
botToken: process.env.DISCORD_BOT_TOKEN!,
|
|
81
46
|
clientId: process.env.DISCORD_CLIENT_ID!,
|
|
82
47
|
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
|
83
48
|
redirectUri: "http://localhost:3000/dashboard/callback",
|
|
84
49
|
sessionSecret: process.env.DASHBOARD_SESSION_SECRET!,
|
|
85
|
-
ownerIds: ["
|
|
50
|
+
ownerIds: ["YOUR_DISCORD_USER_ID"],
|
|
51
|
+
|
|
86
52
|
getOverviewCards: async (context) => [
|
|
87
53
|
{
|
|
88
54
|
id: "uptime",
|
|
89
55
|
title: "Bot Uptime",
|
|
90
56
|
value: `${Math.floor(process.uptime() / 60)} min`,
|
|
91
|
-
subtitle: `Logged in as ${context.user.username}
|
|
57
|
+
subtitle: `Logged in as ${context.user.username}`,
|
|
58
|
+
intent: "success",
|
|
92
59
|
},
|
|
93
60
|
{
|
|
94
61
|
id: "guilds",
|
|
95
|
-
title: "Guilds",
|
|
96
|
-
value: context.guilds.length
|
|
97
|
-
}
|
|
62
|
+
title: "Manageable Guilds",
|
|
63
|
+
value: context.guilds.length,
|
|
64
|
+
},
|
|
98
65
|
],
|
|
66
|
+
|
|
99
67
|
home: {
|
|
100
68
|
async getSections(context) {
|
|
101
69
|
return [
|
|
@@ -105,569 +73,213 @@ const dashboard = createDashboard({
|
|
|
105
73
|
description: context.selectedGuildId ? "Guild-specific setup" : "User-level setup",
|
|
106
74
|
fields: [
|
|
107
75
|
{ id: "enabled", label: "Enable Welcome", type: "boolean", value: true },
|
|
108
|
-
{ id: "channel", label: "Channel
|
|
109
|
-
{ id: "message", label: "Message", type: "textarea", value: "Welcome to the server!" }
|
|
76
|
+
{ id: "channel", label: "Channel", type: "channel-search" },
|
|
77
|
+
{ id: "message", label: "Message", type: "textarea", value: "Welcome to the server!" },
|
|
110
78
|
],
|
|
111
|
-
actions: [{ id: "saveWelcome", label: "Save", variant: "primary" }]
|
|
112
|
-
}
|
|
79
|
+
actions: [{ id: "saveWelcome", label: "Save", variant: "primary" }],
|
|
80
|
+
},
|
|
113
81
|
];
|
|
114
82
|
},
|
|
115
83
|
actions: {
|
|
116
84
|
async saveWelcome(context, payload) {
|
|
117
|
-
console.log("Saving", context.selectedGuildId, payload.values);
|
|
85
|
+
console.log("Saving data for", context.selectedGuildId, payload.values);
|
|
118
86
|
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
87
|
},
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return { ok: true, message: "Moderation rules synced.", refresh: true };
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
]
|
|
88
|
+
},
|
|
89
|
+
},
|
|
149
90
|
});
|
|
150
91
|
|
|
92
|
+
// Start your Express server normally
|
|
151
93
|
app.listen(3000, () => {
|
|
152
|
-
console.log("Dashboard: http://localhost:3000/dashboard");
|
|
94
|
+
console.log("Dashboard live at: http://localhost:3000/dashboard");
|
|
153
95
|
});
|
|
154
96
|
```
|
|
155
97
|
|
|
156
|
-
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 🎨 Fluent Dashboard Designer (Recommended)
|
|
157
101
|
|
|
158
|
-
|
|
102
|
+
For larger bots, putting all your configuration in one object gets messy. Use the `DashboardDesigner` class to build your dashboard modularly.
|
|
159
103
|
|
|
160
|
-
|
|
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
|
|
104
|
+
This approach also allows you to easily inject **Custom CSS** and define dashboard colors.
|
|
166
105
|
|
|
167
106
|
```ts
|
|
168
|
-
import
|
|
107
|
+
import express from "express";
|
|
108
|
+
import { DashboardDesigner } from "@developer.krd/discord-dashboard";
|
|
109
|
+
|
|
110
|
+
const app = express();
|
|
169
111
|
|
|
170
|
-
const
|
|
112
|
+
const dashboard = new DashboardDesigner({
|
|
171
113
|
app,
|
|
172
|
-
botToken
|
|
173
|
-
clientId
|
|
174
|
-
clientSecret
|
|
175
|
-
redirectUri,
|
|
176
|
-
sessionSecret
|
|
114
|
+
botToken: process.env.DISCORD_BOT_TOKEN!,
|
|
115
|
+
clientId: process.env.DISCORD_CLIENT_ID!,
|
|
116
|
+
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
|
117
|
+
redirectUri: "http://localhost:3000/dashboard/callback",
|
|
118
|
+
sessionSecret: process.env.DASHBOARD_SESSION_SECRET!,
|
|
177
119
|
})
|
|
178
|
-
.setup({
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
});
|
|
120
|
+
.setup({
|
|
121
|
+
ownerIds: ["1234567890"],
|
|
122
|
+
botInvitePermissions: "8",
|
|
188
123
|
})
|
|
124
|
+
// Customize the default color palette
|
|
125
|
+
.setupDesign({
|
|
126
|
+
primary: "#4f46e5",
|
|
127
|
+
rail: "#181a20",
|
|
128
|
+
panel: "#2f3136",
|
|
129
|
+
})
|
|
130
|
+
// Inject your own CSS rules
|
|
131
|
+
.customCss(
|
|
132
|
+
`
|
|
133
|
+
.brand { font-size: 1.2rem; text-transform: uppercase; }
|
|
134
|
+
button.primary { box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4); }
|
|
135
|
+
`,
|
|
136
|
+
)
|
|
137
|
+
// Build a Guild-specific settings category
|
|
189
138
|
.guildCategory("pets", "Pets", (category) => {
|
|
190
139
|
category.section({
|
|
191
140
|
id: "pets-guild",
|
|
192
141
|
title: "Guild Pets",
|
|
193
|
-
fields: [{ id: "petsChannelId", label: "Pets Channel", type: "
|
|
194
|
-
actions: [{ id: "saveGuildPets", label: "Save", variant: "primary" }]
|
|
142
|
+
fields: [{ id: "petsChannelId", label: "Pets Channel", type: "channel-search" }],
|
|
143
|
+
actions: [{ id: "saveGuildPets", label: "Save", variant: "primary" }],
|
|
195
144
|
});
|
|
196
145
|
})
|
|
146
|
+
// Handle the save action
|
|
197
147
|
.onHomeAction("saveGuildPets", async (context, payload) => {
|
|
198
148
|
const channelId = String(payload.values.petsChannelId ?? "");
|
|
199
149
|
const channel = await context.helpers.getChannel(channelId);
|
|
200
150
|
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
|
-
]
|
|
151
|
+
return { ok: true, message: "Saved successfully!", refresh: true };
|
|
231
152
|
})
|
|
232
|
-
|
|
233
|
-
|
|
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.
|
|
153
|
+
// Automatically instantiates the DiscordDashboard class
|
|
154
|
+
.createDashboard();
|
|
249
155
|
|
|
250
|
-
|
|
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
|
-
});
|
|
156
|
+
app.listen(3000, () => console.log("Dashboard ready!"));
|
|
315
157
|
```
|
|
316
158
|
|
|
317
|
-
|
|
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).
|
|
159
|
+
---
|
|
340
160
|
|
|
341
|
-
## Built-in Helper Functions
|
|
161
|
+
## 🧩 Built-in Helper Functions
|
|
342
162
|
|
|
343
|
-
|
|
163
|
+
Whenever you handle an action (`onHomeAction` or inside a Plugin), you get access to the `context.helpers` object. These automatically use the bot token to fetch data from the Discord API.
|
|
344
164
|
|
|
345
165
|
- `getChannel(channelId)`
|
|
346
166
|
- `getGuildChannels(guildId)`
|
|
347
167
|
- `getRole(guildId, roleId)`
|
|
348
168
|
- `getGuildRoles(guildId)`
|
|
349
169
|
- `getGuildMember(guildId, userId)`
|
|
170
|
+
- `searchGuildRoles(guildId, query, options)`
|
|
171
|
+
- `searchGuildChannels(guildId, query, options)`
|
|
350
172
|
|
|
351
|
-
|
|
173
|
+
---
|
|
352
174
|
|
|
353
|
-
|
|
175
|
+
## 🛠️ Plugin Scopes & Forms
|
|
354
176
|
|
|
355
|
-
|
|
356
|
-
- `scope: "guild"` → shown only on guild dashboard
|
|
357
|
-
- `scope: "both"` (default) → shown on both
|
|
177
|
+
Plugins are modular features you can attach to the dashboard. They can be restricted to specific scopes.
|
|
358
178
|
|
|
359
|
-
|
|
179
|
+
- `scope: "user"` → Shows only on the User Dashboard (when no server is selected).
|
|
180
|
+
- `scope: "guild"` → Shows only on the Guild Dashboard.
|
|
181
|
+
- `scope: "both"` → (Default) Shows everywhere.
|
|
360
182
|
|
|
361
|
-
|
|
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`)
|
|
183
|
+
### Example: Ticket Panel Builder Plugin
|
|
467
184
|
|
|
468
185
|
```ts
|
|
469
|
-
import {
|
|
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
|
-
};
|
|
186
|
+
import { DiscordDashboard } from "@developer.krd/discord-dashboard";
|
|
482
187
|
|
|
483
|
-
|
|
188
|
+
const dashboard = new DiscordDashboard({
|
|
189
|
+
// ... core credentials ...
|
|
190
|
+
plugins: [
|
|
191
|
+
{
|
|
192
|
+
id: "tickets",
|
|
193
|
+
name: "Tickets",
|
|
194
|
+
scope: "guild",
|
|
195
|
+
getPanels: async () => [
|
|
196
|
+
{
|
|
197
|
+
id: "ticket-panel",
|
|
198
|
+
title: "Ticket Panel Generator",
|
|
199
|
+
fields: [
|
|
200
|
+
{ id: "targetChannel", label: "Target Channel", type: "channel-search", editable: true },
|
|
201
|
+
{ id: "title", label: "Embed Title", type: "text", editable: true, value: "Need help?" },
|
|
202
|
+
{ id: "description", label: "Embed Description", type: "textarea", editable: true },
|
|
203
|
+
{ id: "buttonLabel", label: "Button Label", type: "text", editable: true, value: "Open Ticket" },
|
|
204
|
+
],
|
|
205
|
+
actions: [{ id: "publishTicket", label: "Publish to Channel", variant: "primary", collectFields: true }],
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
actions: {
|
|
209
|
+
publishTicket: async (context, body) => {
|
|
210
|
+
// body.values contains the form data because collectFields was true
|
|
211
|
+
const data = body as { values?: Record<string, unknown> };
|
|
212
|
+
const values = data.values ?? {};
|
|
213
|
+
|
|
214
|
+
console.log(`Creating ticket panel in ${values.targetChannel}`);
|
|
215
|
+
return { ok: true, message: `Ticket panel published!`, data: values };
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
});
|
|
484
221
|
```
|
|
485
222
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
```ts
|
|
489
|
-
import type { BotEvent } from "../types";
|
|
223
|
+
---
|
|
490
224
|
|
|
491
|
-
|
|
492
|
-
name: "interactionCreate",
|
|
493
|
-
async execute(context, interaction) {
|
|
494
|
-
if (!interaction.isChatInputCommand()) return;
|
|
225
|
+
## 🔍 Lookup Fields (Discord Entities)
|
|
495
226
|
|
|
496
|
-
|
|
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
|
-
};
|
|
227
|
+
Instead of forcing users to copy/paste IDs, use lookup fields to provide a rich website autocomplete experience.
|
|
505
228
|
|
|
506
|
-
|
|
507
|
-
|
|
229
|
+
- `type: "role-search"`: Type a role name, select it, and the action receives the full Role object.
|
|
230
|
+
- `type: "channel-search"`: Type a channel name.
|
|
231
|
+
- `type: "member-search"`: Type a username or nickname.
|
|
508
232
|
|
|
509
|
-
|
|
233
|
+
You can configure lookups with filters:
|
|
510
234
|
|
|
511
235
|
```ts
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
}
|
|
236
|
+
{
|
|
237
|
+
id: "logChannel",
|
|
238
|
+
label: "Logs",
|
|
239
|
+
type: "channel-search",
|
|
240
|
+
lookup: {
|
|
241
|
+
limit: 5,
|
|
242
|
+
channelTypes: [0, 5] // 0 = GUILD_TEXT, 5 = GUILD_ANNOUNCEMENT
|
|
529
243
|
}
|
|
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
|
-
});
|
|
244
|
+
}
|
|
540
245
|
```
|
|
541
246
|
|
|
542
|
-
|
|
247
|
+
---
|
|
543
248
|
|
|
544
|
-
##
|
|
249
|
+
## 📚 API Reference
|
|
545
250
|
|
|
546
|
-
###
|
|
251
|
+
### `DashboardDesigner` Methods
|
|
547
252
|
|
|
548
|
-
|
|
549
|
-
|
|
253
|
+
- **`setup(options)`**: Set core info (`ownerIds`, `dashboardName`, `basePath`, `uiTemplate`).
|
|
254
|
+
- **`setupDesign(config)`**: Override theme colors (e.g., `primary`, `bg`, `panel`).
|
|
255
|
+
- **`customCss(cssString)`**: Inject raw CSS to completely customize the dashboard.
|
|
256
|
+
- **`setupCategory(id, label, build)`**: Add tabs to the one-time setup view.
|
|
257
|
+
- **`userCategory(id, label, build)`**: Add tabs to the user dashboard.
|
|
258
|
+
- **`guildCategory(id, label, build)`**: Add tabs to the server dashboard.
|
|
259
|
+
- **`onHomeAction(actionId, handler)`**: Define what happens when a button is clicked.
|
|
260
|
+
- **`createDashboard()`**: Terminal method. Builds the config and returns a `DiscordDashboard` instance.
|
|
550
261
|
|
|
551
|
-
|
|
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
|
-
});
|
|
262
|
+
### `DiscordDashboard` Methods
|
|
569
263
|
|
|
570
|
-
|
|
571
|
-
|
|
264
|
+
- **`app`**: The Express instance (either the one you provided, or a newly created one).
|
|
265
|
+
- **`start()`**: Starts the internal HTTP server (only if you didn't pass your own Express app).
|
|
266
|
+
- **`stop()`**: Gracefully shuts down the internal server.
|
|
572
267
|
|
|
573
|
-
|
|
268
|
+
---
|
|
574
269
|
|
|
575
|
-
|
|
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
|
-
```
|
|
270
|
+
## 🔒 Required Discord OAuth2 Setup
|
|
599
271
|
|
|
600
|
-
|
|
272
|
+
To make login work:
|
|
601
273
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
```
|
|
274
|
+
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications).
|
|
275
|
+
2. Open your Application -> **OAuth2**.
|
|
276
|
+
3. Add your Redirect URI (e.g., `http://localhost:3000/dashboard/callback`).
|
|
277
|
+
4. Grab your `CLIENT_ID` and `CLIENT_SECRET` to use in your dashboard config.
|
|
631
278
|
|
|
632
|
-
|
|
279
|
+
_Note: Ensure your `sessionSecret` is a long, random string in production, and run your bot behind HTTPS._
|
|
633
280
|
|
|
634
|
-
|
|
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
|
-
```
|
|
281
|
+
---
|
|
670
282
|
|
|
671
|
-
## License
|
|
283
|
+
## 📄 License
|
|
672
284
|
|
|
673
285
|
MIT
|