@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 +178 -557
- package/dist/index.cjs +2270 -2232
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +374 -136
- package/dist/index.d.ts +374 -136
- package/dist/index.js +2266 -2228
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -1,673 +1,294 @@
|
|
|
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
|
-
```
|
|
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
|
-
##
|
|
7
|
+
## ✨ Features
|
|
27
8
|
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
- Fastest way for simple dashboards.
|
|
32
|
-
- Use `home`, `plugins`, and `getOverviewCards` directly.
|
|
17
|
+
---
|
|
33
18
|
|
|
34
|
-
|
|
35
|
-
- Best for larger bots and modular structure.
|
|
36
|
-
- Use `setupCategory`, `userCategory`, `guildCategory`, and `onHomeAction`.
|
|
19
|
+
## 🎨 Templates
|
|
37
20
|
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
- Choose built-in template (`default`, `compact`) with `uiTemplate`.
|
|
44
|
-
- Register custom full HTML templates with `uiTemplates` or Designer `addTemplate`.
|
|
23
|
+
---
|
|
45
24
|
|
|
46
|
-
##
|
|
25
|
+
## 📦 Installation
|
|
47
26
|
|
|
48
|
-
|
|
27
|
+
```bash
|
|
28
|
+
npm install @developer.krd/discord-dashboard discord.js
|
|
29
|
+
```
|
|
49
30
|
|
|
50
|
-
|
|
31
|
+
Install only the server stack you use:
|
|
51
32
|
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
33
|
+
```bash
|
|
34
|
+
# Express
|
|
35
|
+
npm install express express-session
|
|
55
36
|
|
|
56
|
-
|
|
37
|
+
# Elysia
|
|
38
|
+
npm install elysia
|
|
57
39
|
|
|
58
|
-
|
|
59
|
-
|
|
40
|
+
# Fastify
|
|
41
|
+
npm install fastify @fastify/cookie @fastify/session
|
|
60
42
|
```
|
|
61
43
|
|
|
62
|
-
|
|
44
|
+
Node.js `>=18` is required.
|
|
63
45
|
|
|
64
|
-
|
|
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 {
|
|
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
|
-
|
|
58
|
+
createExpressAdapter({
|
|
77
59
|
app,
|
|
60
|
+
client,
|
|
78
61
|
basePath: "/dashboard",
|
|
79
|
-
dashboardName: "
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
##
|
|
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 {
|
|
121
|
+
import { Elysia } from "elysia";
|
|
122
|
+
import { Client, GatewayIntentBits } from "discord.js";
|
|
123
|
+
import { createElysiaAdapter } from "@developer.krd/discord-dashboard";
|
|
169
124
|
|
|
170
|
-
const
|
|
171
|
-
|
|
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
|
-
|
|
128
|
+
createElysiaAdapter({
|
|
129
|
+
app,
|
|
130
|
+
client,
|
|
131
|
+
basePath: "/dashboard",
|
|
132
|
+
dashboardName: "My Bot Control",
|
|
208
133
|
|
|
209
|
-
|
|
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
|
-
|
|
212
|
-
|
|
140
|
+
uiTemplate: "shadcn-magic",
|
|
141
|
+
uiTheme: "shadcn-magic",
|
|
142
|
+
});
|
|
213
143
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
149
|
+
## 🚀 Quick Start (Fastify)
|
|
245
150
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
167
|
+
createFastifyAdapter(fastify, {
|
|
168
|
+
client,
|
|
169
|
+
basePath: "/dashboard",
|
|
170
|
+
dashboardName: "My Bot Control",
|
|
251
171
|
|
|
252
|
-
|
|
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
|
-
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
+
---
|
|
269
188
|
|
|
270
|
-
|
|
189
|
+
## 🔌 Adapter Notes
|
|
271
190
|
|
|
272
|
-
- `
|
|
273
|
-
- `
|
|
191
|
+
- **Express:** `createExpressAdapter(options)`
|
|
192
|
+
- **Elysia:** `createElysiaAdapter(options)`
|
|
193
|
+
- **Fastify:** `createFastifyAdapter(fastify, options)`
|
|
274
194
|
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
createDashboard({
|
|
279
|
-
app,
|
|
280
|
-
botToken,
|
|
281
|
-
clientId,
|
|
282
|
-
clientSecret,
|
|
283
|
-
redirectUri,
|
|
284
|
-
sessionSecret,
|
|
285
|
-
uiTemplate: "compact"
|
|
286
|
-
});
|
|
287
|
-
```
|
|
197
|
+
---
|
|
288
198
|
|
|
289
|
-
|
|
199
|
+
## 🛠️ Extensible Plugins
|
|
290
200
|
|
|
291
201
|
```ts
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
228
|
+
---
|
|
318
229
|
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
Plugins can now be separated by target dashboard scope:
|
|
245
|
+
---
|
|
354
246
|
|
|
355
|
-
|
|
356
|
-
- `scope: "guild"` → shown only on guild dashboard
|
|
357
|
-
- `scope: "both"` (default) → shown on both
|
|
247
|
+
## 🔍 Lookup Fields
|
|
358
248
|
|
|
359
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
263
|
+
---
|
|
487
264
|
|
|
488
|
-
|
|
489
|
-
import type { BotEvent } from "../types";
|
|
265
|
+
## 📚 API Reference
|
|
490
266
|
|
|
491
|
-
|
|
492
|
-
name: "interactionCreate",
|
|
493
|
-
async execute(context, interaction) {
|
|
494
|
-
if (!interaction.isChatInputCommand()) return;
|
|
267
|
+
`DashboardOptions` includes:
|
|
495
268
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
503
|
-
}
|
|
504
|
-
};
|
|
274
|
+
Also exported:
|
|
505
275
|
|
|
506
|
-
|
|
507
|
-
|
|
276
|
+
- `DashboardEngine`
|
|
277
|
+
- `DashboardDesigner`
|
|
508
278
|
|
|
509
|
-
|
|
279
|
+
---
|
|
510
280
|
|
|
511
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
288
|
+
Use a strong `sessionSecret` in production and serve behind HTTPS.
|
|
574
289
|
|
|
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
|
-
```
|
|
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
|