From 0c06aeb36cde286e1907878f9c70ad5b98e57144 Mon Sep 17 00:00:00 2001 From: orion Date: Sun, 8 Mar 2026 05:31:08 +0000 Subject: [PATCH] refactor(plugin): extract common utils and moderator discord helpers --- plugin/core/moderator-discord.ts | 49 +++++++++++++++++ plugin/core/utils.ts | 45 ++++++++++++++++ plugin/index.ts | 92 +------------------------------- 3 files changed, 96 insertions(+), 90 deletions(-) create mode 100644 plugin/core/moderator-discord.ts create mode 100644 plugin/core/utils.ts diff --git a/plugin/core/moderator-discord.ts b/plugin/core/moderator-discord.ts new file mode 100644 index 0000000..4bca80d --- /dev/null +++ b/plugin/core/moderator-discord.ts @@ -0,0 +1,49 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; + +function userIdFromToken(token: string): string | undefined { + try { + const segment = token.split(".")[0]; + const padded = segment + "=".repeat((4 - (segment.length % 4)) % 4); + return Buffer.from(padded, "base64").toString("utf8"); + } catch { + return undefined; + } +} + +export function resolveDiscordUserId(api: OpenClawPluginApi, accountId: string): string | undefined { + const root = (api.config as Record) || {}; + const channels = (root.channels as Record) || {}; + const discord = (channels.discord as Record) || {}; + const accounts = (discord.accounts as Record>) || {}; + const acct = accounts[accountId]; + if (!acct?.token || typeof acct.token !== "string") return undefined; + return userIdFromToken(acct.token); +} + +export async function sendModeratorMessage( + token: string, + channelId: string, + content: string, + logger: { info: (msg: string) => void; warn: (msg: string) => void }, +): Promise { + try { + const r = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, { + method: "POST", + headers: { + Authorization: `Bot ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ content }), + }); + if (!r.ok) { + const text = await r.text(); + logger.warn(`dirigent: moderator send failed (${r.status}): ${text}`); + return false; + } + logger.info(`dirigent: moderator message sent to channel=${channelId}`); + return true; + } catch (err) { + logger.warn(`dirigent: moderator send error: ${String(err)}`); + return false; + } +} diff --git a/plugin/core/utils.ts b/plugin/core/utils.ts new file mode 100644 index 0000000..a38760a --- /dev/null +++ b/plugin/core/utils.ts @@ -0,0 +1,45 @@ +type DebugConfig = { + enableDebugLogs?: boolean; + debugLogChannelIds?: string[]; +}; + +export function pickDefined(input: Record): Record { + const out: Record = {}; + for (const [k, v] of Object.entries(input)) { + if (v !== undefined) out[k] = v; + } + return out; +} + +export function shouldDebugLog(cfg: DebugConfig, channelId?: string): boolean { + if (!cfg.enableDebugLogs) return false; + const allow = Array.isArray(cfg.debugLogChannelIds) ? cfg.debugLogChannelIds : []; + if (allow.length === 0) return true; + if (!channelId) return true; + return allow.includes(channelId); +} + +export function debugCtxSummary(ctx: Record, event: Record) { + const meta = ((ctx.metadata || event.metadata || {}) as Record) || {}; + return { + sessionKey: typeof ctx.sessionKey === "string" ? ctx.sessionKey : undefined, + commandSource: typeof ctx.commandSource === "string" ? ctx.commandSource : undefined, + messageProvider: typeof ctx.messageProvider === "string" ? ctx.messageProvider : undefined, + channel: typeof ctx.channel === "string" ? ctx.channel : undefined, + channelId: typeof ctx.channelId === "string" ? ctx.channelId : undefined, + senderId: typeof ctx.senderId === "string" ? ctx.senderId : undefined, + from: typeof ctx.from === "string" ? ctx.from : undefined, + metaSenderId: + typeof meta.senderId === "string" + ? meta.senderId + : typeof meta.sender_id === "string" + ? meta.sender_id + : undefined, + metaUserId: + typeof meta.userId === "string" + ? meta.userId + : typeof meta.user_id === "string" + ? meta.user_id + : undefined, + }; +} diff --git a/plugin/index.ts b/plugin/index.ts index 3e7a679..75a6d1e 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -16,6 +16,8 @@ import { ensurePolicyStateLoaded, persistPolicies, policyState } from "./policy/ import { buildAgentIdentity, buildUserIdToAccountIdMap, resolveAccountId } from "./core/identity.js"; import { extractMentionedUserIds, getModeratorUserId } from "./core/mentions.js"; import { ensureTurnOrder, recordChannelAccount } from "./core/turn-bootstrap.js"; +import { debugCtxSummary, pickDefined, shouldDebugLog } from "./core/utils.js"; +import { resolveDiscordUserId, sendModeratorMessage } from "./core/moderator-discord.js"; // ── No-Reply API child process lifecycle ────────────────────────────── let noReplyProcess: ChildProcess | null = null; @@ -110,96 +112,6 @@ function pruneDecisionMap(now = Date.now()) { } } -// --- Moderator bot helpers --- - -/** Extract Discord user ID from a bot token (base64-encoded in first segment) */ -function userIdFromToken(token: string): string | undefined { - try { - const segment = token.split(".")[0]; - // Add padding - const padded = segment + "=".repeat((4 - (segment.length % 4)) % 4); - return Buffer.from(padded, "base64").toString("utf8"); - } catch { - return undefined; - } -} - -/** Resolve accountId → Discord user ID by reading the account's bot token from config */ -function resolveDiscordUserId(api: OpenClawPluginApi, accountId: string): string | undefined { - const root = (api.config as Record) || {}; - const channels = (root.channels as Record) || {}; - const discord = (channels.discord as Record) || {}; - const accounts = (discord.accounts as Record>) || {}; - const acct = accounts[accountId]; - if (!acct?.token || typeof acct.token !== "string") return undefined; - return userIdFromToken(acct.token); -} - -/** Send a message as the moderator bot via Discord REST API */ -async function sendModeratorMessage(token: string, channelId: string, content: string, logger: { info: (msg: string) => void; warn: (msg: string) => void }): Promise { - try { - const r = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, { - method: "POST", - headers: { - "Authorization": `Bot ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ content }), - }); - if (!r.ok) { - const text = await r.text(); - logger.warn(`dirigent: moderator send failed (${r.status}): ${text}`); - return false; - } - logger.info(`dirigent: moderator message sent to channel=${channelId}`); - return true; - } catch (err) { - logger.warn(`dirigent: moderator send error: ${String(err)}`); - return false; - } -} - -function pickDefined(input: Record) { - const out: Record = {}; - for (const [k, v] of Object.entries(input)) { - if (v !== undefined) out[k] = v; - } - return out; -} - -function shouldDebugLog(cfg: DebugConfig, channelId?: string): boolean { - if (!cfg.enableDebugLogs) return false; - const allow = Array.isArray(cfg.debugLogChannelIds) ? cfg.debugLogChannelIds : []; - if (allow.length === 0) return true; - if (!channelId) return true; - return allow.includes(channelId); -} - -function debugCtxSummary(ctx: Record, event: Record) { - const meta = ((ctx.metadata || event.metadata || {}) as Record) || {}; - return { - sessionKey: typeof ctx.sessionKey === "string" ? ctx.sessionKey : undefined, - commandSource: typeof ctx.commandSource === "string" ? ctx.commandSource : undefined, - messageProvider: typeof ctx.messageProvider === "string" ? ctx.messageProvider : undefined, - channel: typeof ctx.channel === "string" ? ctx.channel : undefined, - channelId: typeof ctx.channelId === "string" ? ctx.channelId : undefined, - senderId: typeof ctx.senderId === "string" ? ctx.senderId : undefined, - from: typeof ctx.from === "string" ? ctx.from : undefined, - metaSenderId: - typeof meta.senderId === "string" - ? meta.senderId - : typeof meta.sender_id === "string" - ? meta.sender_id - : undefined, - metaUserId: - typeof meta.userId === "string" - ? meta.userId - : typeof meta.user_id === "string" - ? meta.user_id - : undefined, - }; -} - export default { id: "dirigent", name: "Dirigent",