@developer.krd/discord-dashboard 0.1.5 → 0.1.7
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/dist/index.cjs +295 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +295 -47
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/dist/index.cjs
CHANGED
|
@@ -46,7 +46,8 @@ var UserSchema = import_elysia.t.Object({
|
|
|
46
46
|
username: import_elysia.t.String(),
|
|
47
47
|
discriminator: import_elysia.t.String(),
|
|
48
48
|
avatar: import_elysia.t.Nullable(import_elysia.t.String()),
|
|
49
|
-
global_name: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String()))
|
|
49
|
+
global_name: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String())),
|
|
50
|
+
avatarUrl: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String()))
|
|
50
51
|
});
|
|
51
52
|
var GuildSchema = import_elysia.t.Object({
|
|
52
53
|
id: import_elysia.t.String(),
|
|
@@ -55,8 +56,8 @@ var GuildSchema = import_elysia.t.Object({
|
|
|
55
56
|
owner: import_elysia.t.Boolean(),
|
|
56
57
|
permissions: import_elysia.t.String(),
|
|
57
58
|
iconUrl: import_elysia.t.Optional(import_elysia.t.Nullable(import_elysia.t.String())),
|
|
58
|
-
botInGuild: import_elysia.t.Optional(import_elysia.t.
|
|
59
|
-
inviteUrl: import_elysia.t.Optional(import_elysia.t.
|
|
59
|
+
botInGuild: import_elysia.t.Optional(import_elysia.t.Boolean()),
|
|
60
|
+
inviteUrl: import_elysia.t.Optional(import_elysia.t.String())
|
|
60
61
|
});
|
|
61
62
|
var SessionSchema = import_elysia.t.Object({
|
|
62
63
|
oauthState: import_elysia.t.Optional(import_elysia.t.String()),
|
|
@@ -2435,54 +2436,183 @@ function createElysiaAdapter(options) {
|
|
|
2435
2436
|
const basePath = options.basePath ?? "/dashboard";
|
|
2436
2437
|
const sessionName = options.sessionName ?? "discord_dashboard.sid";
|
|
2437
2438
|
const app = options.app ?? new import_elysia2.Elysia({ adapter: (0, import_node.default)() });
|
|
2439
|
+
const getSession = async (sessionSigner, cookie) => {
|
|
2440
|
+
const cookieValue = cookie[sessionName]?.value;
|
|
2441
|
+
if (!cookieValue) return null;
|
|
2442
|
+
const session2 = await sessionSigner.verify(cookieValue);
|
|
2443
|
+
return session2 ?? null;
|
|
2444
|
+
};
|
|
2445
|
+
const setSession = async (sessionSigner, cookie, session2) => {
|
|
2446
|
+
const token = await sessionSigner.sign(session2);
|
|
2447
|
+
cookie[sessionName].set({
|
|
2448
|
+
value: token,
|
|
2449
|
+
httpOnly: true,
|
|
2450
|
+
sameSite: "lax",
|
|
2451
|
+
path: "/"
|
|
2452
|
+
});
|
|
2453
|
+
};
|
|
2454
|
+
const buildContext = (session2, query) => ({
|
|
2455
|
+
user: session2.discordAuth.user,
|
|
2456
|
+
guilds: (session2.discordAuth.guilds || []).map((guild) => ({
|
|
2457
|
+
...guild,
|
|
2458
|
+
botInGuild: guild.botInGuild ?? void 0,
|
|
2459
|
+
inviteUrl: guild.inviteUrl ?? void 0
|
|
2460
|
+
})),
|
|
2461
|
+
accessToken: session2.discordAuth.accessToken || "",
|
|
2462
|
+
selectedGuildId: typeof query?.guildId === "string" ? query.guildId : void 0,
|
|
2463
|
+
helpers: engine.helpers
|
|
2464
|
+
});
|
|
2438
2465
|
const router = new import_elysia2.Elysia({ prefix: basePath }).use(
|
|
2439
2466
|
(0, import_jwt.default)({
|
|
2440
2467
|
name: "sessionSigner",
|
|
2441
2468
|
secret: options.sessionSecret,
|
|
2442
2469
|
schema: SessionSchema
|
|
2443
2470
|
})
|
|
2444
|
-
).
|
|
2445
|
-
|
|
2446
|
-
set.headers["Content-Type"] = "text/html; charset=utf8";
|
|
2447
|
-
return content;
|
|
2448
|
-
}
|
|
2449
|
-
})).get("/", async ({ sessionSigner, cookie, redirect, html }) => {
|
|
2450
|
-
const cookieValue = cookie[sessionName].value;
|
|
2451
|
-
if (!cookieValue) return redirect(`${basePath}/login`);
|
|
2452
|
-
const sessionData = await sessionSigner.verify(cookieValue);
|
|
2471
|
+
).get("/", async ({ sessionSigner, cookie, redirect }) => {
|
|
2472
|
+
const sessionData = await getSession(sessionSigner, cookie);
|
|
2453
2473
|
if (!sessionData || !sessionData.discordAuth) return redirect(`${basePath}/login`);
|
|
2454
|
-
return
|
|
2474
|
+
return new Response(engine.render(basePath), {
|
|
2475
|
+
headers: {
|
|
2476
|
+
"content-type": "text/html; charset=utf-8"
|
|
2477
|
+
}
|
|
2478
|
+
});
|
|
2455
2479
|
}).get("/login", async ({ sessionSigner, cookie, redirect }) => {
|
|
2456
2480
|
const state = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
2457
|
-
|
|
2458
|
-
cookie[sessionName].set({
|
|
2459
|
-
value: token,
|
|
2460
|
-
httpOnly: true,
|
|
2461
|
-
path: "/"
|
|
2462
|
-
});
|
|
2481
|
+
await setSession(sessionSigner, cookie, { oauthState: state });
|
|
2463
2482
|
return redirect(engine.getAuthUrl(state));
|
|
2464
2483
|
}).get("/callback", async ({ query, sessionSigner, cookie, redirect }) => {
|
|
2465
2484
|
const { code, state } = query;
|
|
2466
|
-
const
|
|
2467
|
-
if (!
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2485
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2486
|
+
if (!code || !state || !session2 || state !== session2.oauthState) return redirect(`${basePath}/login`);
|
|
2487
|
+
try {
|
|
2488
|
+
const tokens = await engine.exchangeCode(code);
|
|
2489
|
+
const [user, rawGuilds] = await Promise.all([engine.fetchUser(tokens.access_token), engine.fetchGuilds(tokens.access_token)]);
|
|
2490
|
+
const ADMIN = 8n;
|
|
2491
|
+
const MANAGE_GUILD = 32n;
|
|
2492
|
+
const processedGuilds = rawGuilds.filter((guild) => {
|
|
2493
|
+
const perms = BigInt(guild.permissions || "0");
|
|
2494
|
+
return (perms & ADMIN) === ADMIN || (perms & MANAGE_GUILD) === MANAGE_GUILD;
|
|
2495
|
+
}).map((guild) => ({
|
|
2496
|
+
id: guild.id,
|
|
2497
|
+
name: guild.name,
|
|
2498
|
+
icon: guild.icon,
|
|
2499
|
+
owner: guild.owner,
|
|
2500
|
+
permissions: guild.permissions,
|
|
2501
|
+
iconUrl: engine.helpers.getGuildIconUrl(guild.id, guild.icon),
|
|
2502
|
+
botInGuild: options.client.guilds.cache.has(guild.id)
|
|
2503
|
+
}));
|
|
2504
|
+
await setSession(sessionSigner, cookie, {
|
|
2505
|
+
discordAuth: {
|
|
2506
|
+
accessToken: tokens.access_token,
|
|
2507
|
+
user: {
|
|
2508
|
+
id: user.id,
|
|
2509
|
+
username: user.username,
|
|
2510
|
+
discriminator: user.discriminator,
|
|
2511
|
+
avatar: user.avatar,
|
|
2512
|
+
global_name: user.global_name,
|
|
2513
|
+
avatarUrl: engine.helpers.getUserAvatarUrl(user.id, user.avatar)
|
|
2514
|
+
},
|
|
2515
|
+
guilds: processedGuilds
|
|
2516
|
+
}
|
|
2517
|
+
});
|
|
2518
|
+
return redirect(basePath);
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
console.error("Dashboard Auth Error:", error);
|
|
2521
|
+
return redirect(`${basePath}/login`);
|
|
2522
|
+
}
|
|
2523
|
+
}).get("/api/session", async ({ sessionSigner, cookie }) => {
|
|
2524
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2525
|
+
if (!session2?.discordAuth) return { authenticated: false };
|
|
2526
|
+
return {
|
|
2527
|
+
authenticated: true,
|
|
2528
|
+
user: session2.discordAuth.user,
|
|
2529
|
+
guildCount: session2.discordAuth.guilds.length
|
|
2530
|
+
};
|
|
2531
|
+
}).get("/api/guilds", async ({ sessionSigner, cookie, set }) => {
|
|
2532
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2533
|
+
if (!session2?.discordAuth) {
|
|
2534
|
+
set.status = 401;
|
|
2535
|
+
return { error: "Unauthorized" };
|
|
2536
|
+
}
|
|
2537
|
+
return { guilds: session2.discordAuth.guilds };
|
|
2538
|
+
}).get("/api/overview", async ({ sessionSigner, cookie, query, set }) => {
|
|
2539
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2540
|
+
if (!session2?.discordAuth) {
|
|
2541
|
+
set.status = 401;
|
|
2542
|
+
return { error: "Unauthorized" };
|
|
2543
|
+
}
|
|
2544
|
+
const cards = options.getOverviewCards ? await options.getOverviewCards(buildContext(session2, query)) : [];
|
|
2545
|
+
return { cards };
|
|
2546
|
+
}).get("/api/home/categories", async ({ sessionSigner, cookie, query, set }) => {
|
|
2547
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2548
|
+
if (!session2?.discordAuth) {
|
|
2549
|
+
set.status = 401;
|
|
2550
|
+
return { error: "Unauthorized" };
|
|
2551
|
+
}
|
|
2552
|
+
const categories = options.home?.getCategories ? await options.home.getCategories(buildContext(session2, query)) : [];
|
|
2553
|
+
return { categories };
|
|
2554
|
+
}).get("/api/home", async ({ sessionSigner, cookie, query, set }) => {
|
|
2555
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2556
|
+
if (!session2?.discordAuth) {
|
|
2557
|
+
set.status = 401;
|
|
2558
|
+
return { error: "Unauthorized" };
|
|
2559
|
+
}
|
|
2560
|
+
const context = buildContext(session2, query);
|
|
2561
|
+
const sections = options.home?.getSections ? await options.home.getSections(context) : [];
|
|
2562
|
+
const categoryId = typeof query.categoryId === "string" ? query.categoryId : void 0;
|
|
2563
|
+
const filteredSections = categoryId ? sections.filter((section) => section.categoryId === categoryId) : sections;
|
|
2564
|
+
return { sections: filteredSections };
|
|
2565
|
+
}).post("/api/home/:actionId", async ({ params, body, sessionSigner, cookie, query, set }) => {
|
|
2566
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2567
|
+
if (!session2?.discordAuth) {
|
|
2568
|
+
set.status = 401;
|
|
2569
|
+
return { error: "Unauthorized" };
|
|
2570
|
+
}
|
|
2571
|
+
const actionFn = options.home?.actions?.[params.actionId];
|
|
2572
|
+
if (!actionFn) {
|
|
2573
|
+
set.status = 404;
|
|
2574
|
+
return { error: "Action not found" };
|
|
2575
|
+
}
|
|
2576
|
+
try {
|
|
2577
|
+
return await actionFn(buildContext(session2, query), body);
|
|
2578
|
+
} catch (error) {
|
|
2579
|
+
set.status = 500;
|
|
2580
|
+
return { error: error instanceof Error ? error.message : "Action failed" };
|
|
2581
|
+
}
|
|
2582
|
+
}).get("/api/plugins", async ({ sessionSigner, cookie, query, set }) => {
|
|
2583
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2584
|
+
if (!session2?.discordAuth) {
|
|
2585
|
+
set.status = 401;
|
|
2586
|
+
return { error: "Unauthorized" };
|
|
2587
|
+
}
|
|
2588
|
+
const context = buildContext(session2, query);
|
|
2589
|
+
const resolvedPlugins = await Promise.all(
|
|
2590
|
+
(options.plugins || []).map(async (plugin) => ({
|
|
2591
|
+
id: plugin.id,
|
|
2592
|
+
name: plugin.name,
|
|
2593
|
+
description: plugin.description,
|
|
2594
|
+
panels: plugin.getPanels ? await plugin.getPanels(context) : plugin.panels || []
|
|
2595
|
+
}))
|
|
2596
|
+
);
|
|
2597
|
+
return { plugins: resolvedPlugins };
|
|
2598
|
+
}).post("/api/plugins/:pluginId/:actionId", async ({ params, body, sessionSigner, cookie, query, set }) => {
|
|
2599
|
+
const session2 = await getSession(sessionSigner, cookie);
|
|
2600
|
+
if (!session2?.discordAuth) {
|
|
2601
|
+
set.status = 401;
|
|
2602
|
+
return { error: "Unauthorized" };
|
|
2603
|
+
}
|
|
2604
|
+
const plugin = options.plugins?.find((item) => item.id === params.pluginId);
|
|
2605
|
+
if (!plugin || !plugin.actions?.[params.actionId]) {
|
|
2606
|
+
set.status = 404;
|
|
2607
|
+
return { error: "Plugin or action not found" };
|
|
2608
|
+
}
|
|
2609
|
+
try {
|
|
2610
|
+
return await plugin.actions[params.actionId](buildContext(session2, query), body);
|
|
2611
|
+
} catch (error) {
|
|
2612
|
+
console.error(`Action Error (${params.pluginId}/${params.actionId}):`, error);
|
|
2613
|
+
set.status = 500;
|
|
2614
|
+
return { error: "Internal Server Error" };
|
|
2615
|
+
}
|
|
2486
2616
|
});
|
|
2487
2617
|
app.use(router);
|
|
2488
2618
|
return { app, engine };
|
|
@@ -2493,6 +2623,16 @@ var import_crypto = require("crypto");
|
|
|
2493
2623
|
function createFastifyAdapter(fastify, options) {
|
|
2494
2624
|
const engine = new DashboardEngine(options);
|
|
2495
2625
|
const basePath = options.basePath ?? "/dashboard";
|
|
2626
|
+
const buildContext = (request) => {
|
|
2627
|
+
const query = request.query;
|
|
2628
|
+
return {
|
|
2629
|
+
user: request.session.discordAuth.user,
|
|
2630
|
+
guilds: request.session.discordAuth.guilds || [],
|
|
2631
|
+
accessToken: request.session.discordAuth.accessToken || "",
|
|
2632
|
+
selectedGuildId: typeof query.guildId === "string" ? query.guildId : void 0,
|
|
2633
|
+
helpers: engine.helpers
|
|
2634
|
+
};
|
|
2635
|
+
};
|
|
2496
2636
|
fastify.register(
|
|
2497
2637
|
async (instance) => {
|
|
2498
2638
|
instance.get("/", async (request, reply) => {
|
|
@@ -2507,15 +2647,123 @@ function createFastifyAdapter(fastify, options) {
|
|
|
2507
2647
|
instance.get("/callback", async (request, reply) => {
|
|
2508
2648
|
const { code, state } = request.query;
|
|
2509
2649
|
if (state !== request.session.oauthState) return reply.status(403).send("Invalid OAuth2 state");
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2650
|
+
try {
|
|
2651
|
+
const tokens = await engine.exchangeCode(code);
|
|
2652
|
+
const [user, rawGuilds] = await Promise.all([engine.fetchUser(tokens.access_token), engine.fetchGuilds(tokens.access_token)]);
|
|
2653
|
+
const ADMIN = 8n;
|
|
2654
|
+
const MANAGE_GUILD = 32n;
|
|
2655
|
+
const processedGuilds = rawGuilds.filter((guild) => {
|
|
2656
|
+
const perms = BigInt(guild.permissions || "0");
|
|
2657
|
+
return (perms & ADMIN) === ADMIN || (perms & MANAGE_GUILD) === MANAGE_GUILD;
|
|
2658
|
+
}).map((guild) => ({
|
|
2659
|
+
...guild,
|
|
2660
|
+
iconUrl: engine.helpers.getGuildIconUrl(guild.id, guild.icon),
|
|
2661
|
+
botInGuild: options.client.guilds.cache.has(guild.id)
|
|
2662
|
+
}));
|
|
2663
|
+
request.session.discordAuth = {
|
|
2664
|
+
accessToken: tokens.access_token,
|
|
2665
|
+
user: {
|
|
2666
|
+
...user,
|
|
2667
|
+
avatarUrl: engine.helpers.getUserAvatarUrl(user.id, user.avatar)
|
|
2668
|
+
},
|
|
2669
|
+
guilds: processedGuilds
|
|
2670
|
+
};
|
|
2671
|
+
return reply.redirect(basePath);
|
|
2672
|
+
} catch (error) {
|
|
2673
|
+
console.error("Dashboard Auth Error:", error);
|
|
2674
|
+
return reply.redirect(`${basePath}/login`);
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
instance.get("/api/session", async (request, reply) => {
|
|
2678
|
+
if (!request.session.discordAuth) {
|
|
2679
|
+
return reply.send({ authenticated: false });
|
|
2680
|
+
}
|
|
2681
|
+
return reply.send({
|
|
2682
|
+
authenticated: true,
|
|
2683
|
+
user: request.session.discordAuth.user,
|
|
2684
|
+
guildCount: request.session.discordAuth.guilds.length
|
|
2685
|
+
});
|
|
2686
|
+
});
|
|
2687
|
+
instance.get("/api/guilds", async (request, reply) => {
|
|
2688
|
+
if (!request.session.discordAuth) {
|
|
2689
|
+
return reply.status(401).send({ error: "Unauthorized" });
|
|
2690
|
+
}
|
|
2691
|
+
return reply.send({ guilds: request.session.discordAuth.guilds });
|
|
2692
|
+
});
|
|
2693
|
+
instance.get("/api/overview", async (request, reply) => {
|
|
2694
|
+
if (!request.session.discordAuth) {
|
|
2695
|
+
return reply.status(401).send({ error: "Unauthorized" });
|
|
2696
|
+
}
|
|
2697
|
+
const cards = options.getOverviewCards ? await options.getOverviewCards(buildContext(request)) : [];
|
|
2698
|
+
return reply.send({ cards });
|
|
2699
|
+
});
|
|
2700
|
+
instance.get("/api/home/categories", async (request, reply) => {
|
|
2701
|
+
if (!request.session.discordAuth) {
|
|
2702
|
+
return reply.status(401).send({ error: "Unauthorized" });
|
|
2703
|
+
}
|
|
2704
|
+
const categories = options.home?.getCategories ? await options.home.getCategories(buildContext(request)) : [];
|
|
2705
|
+
return reply.send({ categories });
|
|
2706
|
+
});
|
|
2707
|
+
instance.get("/api/home", async (request, reply) => {
|
|
2708
|
+
if (!request.session.discordAuth) {
|
|
2709
|
+
return reply.status(401).send({ error: "Unauthorized" });
|
|
2710
|
+
}
|
|
2711
|
+
const sections = options.home?.getSections ? await options.home.getSections(buildContext(request)) : [];
|
|
2712
|
+
const query = request.query;
|
|
2713
|
+
const categoryId = typeof query.categoryId === "string" ? query.categoryId : void 0;
|
|
2714
|
+
const filteredSections = categoryId ? sections.filter((section) => section.categoryId === categoryId) : sections;
|
|
2715
|
+
return reply.send({ sections: filteredSections });
|
|
2716
|
+
});
|
|
2717
|
+
instance.post("/api/home/:actionId", async (request, reply) => {
|
|
2718
|
+
if (!request.session.discordAuth) {
|
|
2719
|
+
return reply.status(401).send({ error: "Unauthorized" });
|
|
2720
|
+
}
|
|
2721
|
+
const actionFn = options.home?.actions?.[request.params.actionId];
|
|
2722
|
+
if (!actionFn) {
|
|
2723
|
+
return reply.status(404).send({ error: "Action not found" });
|
|
2724
|
+
}
|
|
2725
|
+
try {
|
|
2726
|
+
const result = await actionFn(buildContext(request), request.body);
|
|
2727
|
+
return reply.send(result);
|
|
2728
|
+
} catch (error) {
|
|
2729
|
+
return reply.status(500).send({ error: error instanceof Error ? error.message : "Action failed" });
|
|
2730
|
+
}
|
|
2731
|
+
});
|
|
2732
|
+
instance.get("/api/plugins", async (request, reply) => {
|
|
2733
|
+
if (!request.session.discordAuth) {
|
|
2734
|
+
return reply.status(401).send({ error: "Unauthorized" });
|
|
2735
|
+
}
|
|
2736
|
+
const context = buildContext(request);
|
|
2737
|
+
const resolvedPlugins = await Promise.all(
|
|
2738
|
+
(options.plugins || []).map(async (plugin) => ({
|
|
2739
|
+
id: plugin.id,
|
|
2740
|
+
name: plugin.name,
|
|
2741
|
+
description: plugin.description,
|
|
2742
|
+
panels: plugin.getPanels ? await plugin.getPanels(context) : plugin.panels || []
|
|
2743
|
+
}))
|
|
2744
|
+
);
|
|
2745
|
+
return reply.send({ plugins: resolvedPlugins });
|
|
2518
2746
|
});
|
|
2747
|
+
instance.post(
|
|
2748
|
+
"/api/plugins/:pluginId/:actionId",
|
|
2749
|
+
async (request, reply) => {
|
|
2750
|
+
if (!request.session.discordAuth) {
|
|
2751
|
+
return reply.status(401).send({ error: "Unauthorized" });
|
|
2752
|
+
}
|
|
2753
|
+
const { pluginId, actionId } = request.params;
|
|
2754
|
+
const plugin = options.plugins?.find((item) => item.id === pluginId);
|
|
2755
|
+
if (!plugin || !plugin.actions?.[actionId]) {
|
|
2756
|
+
return reply.status(404).send({ error: "Plugin or action not found" });
|
|
2757
|
+
}
|
|
2758
|
+
try {
|
|
2759
|
+
const result = await plugin.actions[actionId](buildContext(request), request.body);
|
|
2760
|
+
return reply.send(result);
|
|
2761
|
+
} catch (error) {
|
|
2762
|
+
console.error(`Action Error (${pluginId}/${actionId}):`, error);
|
|
2763
|
+
return reply.status(500).send({ error: "Internal Server Error" });
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
);
|
|
2519
2767
|
},
|
|
2520
2768
|
{ prefix: basePath }
|
|
2521
2769
|
);
|