From fb7f187ede14d837a8cd8df7e3d51bc888c393cf Mon Sep 17 00:00:00 2001 From: saopig1 <4x7sw862st@gmail.com> Date: Thu, 25 Jun 2026 11:11:10 +0800 Subject: [PATCH] feat(config): add default-off guestMode block with deep-merge --- src/data/config.test.ts | 25 +++++++++++++++++++++++++ src/data/config.ts | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/data/config.test.ts b/src/data/config.test.ts index 5e81537..17c1c89 100644 --- a/src/data/config.test.ts +++ b/src/data/config.test.ts @@ -106,3 +106,28 @@ describe("config", () => { expect(migrated).toBe(false); }); }); + +describe("guestMode config", () => { + it("defaults to disabled, all-bots, append-only", () => { + const c = getDefaultConfig(); + expect(c.guestMode.enabled).toBe(false); + expect(c.guestMode.bots).toBe("all"); + expect(c.guestMode.permissions).toEqual({ + addToQueue: true, playNext: false, playNow: false, + skip: false, transport: false, removeClear: false, playMode: false, + }); + }); + + it("deep-merges a partial guestMode so missing sub-keys are back-filled", () => { + const dir = mkdtempSync(join(tmpdir(), "tsmb-cfg-")); + const p = join(dir, "config.json"); + writeFileSync(p, JSON.stringify({ guestMode: { enabled: true, permissions: { playNext: true } } })); + const c = loadConfig(p); + expect(c.guestMode.enabled).toBe(true); + expect(c.guestMode.bots).toBe("all"); // back-filled + expect(c.guestMode.permissions.playNext).toBe(true); + expect(c.guestMode.permissions.addToQueue).toBe(true); // back-filled default + expect(c.guestMode.permissions.skip).toBe(false); // back-filled default + rmSync(dir, { recursive: true, force: true }); + }); +}); diff --git a/src/data/config.ts b/src/data/config.ts index 80aed56..cb8a4a7 100755 --- a/src/data/config.ts +++ b/src/data/config.ts @@ -1,5 +1,12 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync, rmSync } from "node:fs"; import { dirname } from "node:path"; +import type { BotAccess, GuestPermissions } from "./permissions.js"; + +export interface GuestModeConfig { + enabled: boolean; + bots: BotAccess; // "all" | string[] + permissions: GuestPermissions; +} export interface BotConfig { webPort: number; @@ -22,6 +29,7 @@ export interface BotConfig { // (nginx/Caddy/Cloudflare). Required for correct protocol/host detection // behind HTTPS-terminating proxies. trustProxy: boolean; + guestMode: GuestModeConfig; } export function getDefaultConfig(): BotConfig { @@ -43,6 +51,19 @@ export function getDefaultConfig(): BotConfig { idleTimeoutMinutes: 0, publicUrl: "", trustProxy: false, + guestMode: { + enabled: false, + bots: "all", + permissions: { + addToQueue: true, + playNext: false, + playNow: false, + skip: false, + transport: false, + removeClear: false, + playMode: false, + }, + }, }; } @@ -51,7 +72,18 @@ export function loadConfig(path: string): BotConfig { try { const raw = readFileSync(path, "utf-8"); const partial = JSON.parse(raw) as Partial; - return { ...defaults, ...partial }; + return { + ...defaults, + ...partial, + guestMode: { + ...defaults.guestMode, + ...(partial.guestMode ?? {}), + permissions: { + ...defaults.guestMode.permissions, + ...(partial.guestMode?.permissions ?? {}), + }, + }, + }; } catch { return defaults; }