@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
402 lines (393 loc) • 14.3 kB
JavaScript
import { n as isTruthyEnvValue, tt as normalizeE164 } from "./entry.js";
import { c as normalizeAccountId, t as DEFAULT_ACCOUNT_ID } from "./session-key-BGiG_JcT.js";
import { h as createAccountListHelpers } from "./accounts-BpzwDfBB.js";
import { t as normalizeChatType } from "./chat-type-CeFzWU-6.js";
import { i as resolveDefaultAgentBoundAccountId, r as listBoundAccountIds } from "./bindings-KqaGKS1E.js";
import fs from "node:fs";
//#region src/channels/plugins/account-action-gate.ts
function createAccountActionGate(params) {
return (key, defaultValue = true) => {
const accountValue = params.accountActions?.[key];
if (accountValue !== void 0) return accountValue;
const baseValue = params.baseActions?.[key];
if (baseValue !== void 0) return baseValue;
return defaultValue;
};
}
//#endregion
//#region src/discord/token.ts
function normalizeDiscordToken(raw) {
if (!raw) return;
const trimmed = raw.trim();
if (!trimmed) return;
return trimmed.replace(/^Bot\s+/i, "");
}
function resolveDiscordToken(cfg, opts = {}) {
const accountId = normalizeAccountId(opts.accountId);
const discordCfg = cfg?.channels?.discord;
const accountToken = normalizeDiscordToken((accountId !== DEFAULT_ACCOUNT_ID ? discordCfg?.accounts?.[accountId] : discordCfg?.accounts?.[DEFAULT_ACCOUNT_ID])?.token ?? void 0);
if (accountToken) return {
token: accountToken,
source: "config"
};
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const configToken = allowEnv ? normalizeDiscordToken(discordCfg?.token ?? void 0) : void 0;
if (configToken) return {
token: configToken,
source: "config"
};
const envToken = allowEnv ? normalizeDiscordToken(opts.envToken ?? process.env.DISCORD_BOT_TOKEN) : void 0;
if (envToken) return {
token: envToken,
source: "env"
};
return {
token: "",
source: "none"
};
}
//#endregion
//#region src/discord/accounts.ts
const { listAccountIds: listAccountIds$1, resolveDefaultAccountId: resolveDefaultAccountId$1 } = createAccountListHelpers("discord");
const listDiscordAccountIds = listAccountIds$1;
function resolveAccountConfig$2(cfg, accountId) {
const accounts = cfg.channels?.discord?.accounts;
if (!accounts || typeof accounts !== "object") return;
return accounts[accountId];
}
function mergeDiscordAccountConfig(cfg, accountId) {
const { accounts: _ignored, ...base } = cfg.channels?.discord ?? {};
const account = resolveAccountConfig$2(cfg, accountId) ?? {};
return {
...base,
...account
};
}
function createDiscordActionGate(params) {
const accountId = normalizeAccountId(params.accountId);
return createAccountActionGate({
baseActions: params.cfg.channels?.discord?.actions,
accountActions: resolveAccountConfig$2(params.cfg, accountId)?.actions
});
}
function resolveDiscordAccount(params) {
const accountId = normalizeAccountId(params.accountId);
const baseEnabled = params.cfg.channels?.discord?.enabled !== false;
const merged = mergeDiscordAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const tokenResolution = resolveDiscordToken(params.cfg, { accountId });
return {
accountId,
enabled,
name: merged.name?.trim() || void 0,
token: tokenResolution.token,
tokenSource: tokenResolution.source,
config: merged
};
}
function listEnabledDiscordAccounts(cfg) {
return listDiscordAccountIds(cfg).map((accountId) => resolveDiscordAccount({
cfg,
accountId
})).filter((account) => account.enabled);
}
//#endregion
//#region src/slack/token.ts
function normalizeSlackToken(raw) {
const trimmed = raw?.trim();
return trimmed ? trimmed : void 0;
}
function resolveSlackBotToken(raw) {
return normalizeSlackToken(raw);
}
function resolveSlackAppToken(raw) {
return normalizeSlackToken(raw);
}
//#endregion
//#region src/slack/accounts.ts
const { listAccountIds, resolveDefaultAccountId } = createAccountListHelpers("slack");
function resolveAccountConfig$1(cfg, accountId) {
const accounts = cfg.channels?.slack?.accounts;
if (!accounts || typeof accounts !== "object") return;
return accounts[accountId];
}
function mergeSlackAccountConfig(cfg, accountId) {
const { accounts: _ignored, ...base } = cfg.channels?.slack ?? {};
const account = resolveAccountConfig$1(cfg, accountId) ?? {};
return {
...base,
...account
};
}
function resolveSlackAccount(params) {
const accountId = normalizeAccountId(params.accountId);
const baseEnabled = params.cfg.channels?.slack?.enabled !== false;
const merged = mergeSlackAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const envBot = allowEnv ? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN) : void 0;
const envApp = allowEnv ? resolveSlackAppToken(process.env.SLACK_APP_TOKEN) : void 0;
const configBot = resolveSlackBotToken(merged.botToken);
const configApp = resolveSlackAppToken(merged.appToken);
const botToken = configBot ?? envBot;
const appToken = configApp ?? envApp;
const botTokenSource = configBot ? "config" : envBot ? "env" : "none";
const appTokenSource = configApp ? "config" : envApp ? "env" : "none";
return {
accountId,
enabled,
name: merged.name?.trim() || void 0,
botToken,
appToken,
botTokenSource,
appTokenSource,
config: merged,
groupPolicy: merged.groupPolicy,
textChunkLimit: merged.textChunkLimit,
mediaMaxMb: merged.mediaMaxMb,
reactionNotifications: merged.reactionNotifications,
reactionAllowlist: merged.reactionAllowlist,
replyToMode: merged.replyToMode,
replyToModeByChatType: merged.replyToModeByChatType,
actions: merged.actions,
slashCommand: merged.slashCommand,
dm: merged.dm,
channels: merged.channels
};
}
function resolveSlackReplyToMode(account, chatType) {
const normalized = normalizeChatType(chatType ?? void 0);
if (normalized && account.replyToModeByChatType?.[normalized] !== void 0) return account.replyToModeByChatType[normalized] ?? "off";
if (normalized === "direct" && account.dm?.replyToMode !== void 0) return account.dm.replyToMode;
return account.replyToMode ?? "off";
}
//#endregion
//#region src/telegram/token.ts
function resolveTelegramToken(cfg, opts = {}) {
const accountId = normalizeAccountId(opts.accountId);
const telegramCfg = cfg?.channels?.telegram;
const resolveAccountCfg = (id) => {
const accounts = telegramCfg?.accounts;
if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) return;
const direct = accounts[id];
if (direct) return direct;
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === id);
return matchKey ? accounts[matchKey] : void 0;
};
const accountCfg = resolveAccountCfg(accountId !== DEFAULT_ACCOUNT_ID ? accountId : DEFAULT_ACCOUNT_ID);
const accountTokenFile = accountCfg?.tokenFile?.trim();
if (accountTokenFile) {
if (!fs.existsSync(accountTokenFile)) {
opts.logMissingFile?.(`channels.telegram.accounts.${accountId}.tokenFile not found: ${accountTokenFile}`);
return {
token: "",
source: "none"
};
}
try {
const token = fs.readFileSync(accountTokenFile, "utf-8").trim();
if (token) return {
token,
source: "tokenFile"
};
} catch (err) {
opts.logMissingFile?.(`channels.telegram.accounts.${accountId}.tokenFile read failed: ${String(err)}`);
return {
token: "",
source: "none"
};
}
return {
token: "",
source: "none"
};
}
const accountToken = accountCfg?.botToken?.trim();
if (accountToken) return {
token: accountToken,
source: "config"
};
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const tokenFile = telegramCfg?.tokenFile?.trim();
if (tokenFile && allowEnv) {
if (!fs.existsSync(tokenFile)) {
opts.logMissingFile?.(`channels.telegram.tokenFile not found: ${tokenFile}`);
return {
token: "",
source: "none"
};
}
try {
const token = fs.readFileSync(tokenFile, "utf-8").trim();
if (token) return {
token,
source: "tokenFile"
};
} catch (err) {
opts.logMissingFile?.(`channels.telegram.tokenFile read failed: ${String(err)}`);
return {
token: "",
source: "none"
};
}
}
const configToken = telegramCfg?.botToken?.trim();
if (configToken && allowEnv) return {
token: configToken,
source: "config"
};
const envToken = allowEnv ? (opts.envToken ?? process.env.TELEGRAM_BOT_TOKEN)?.trim() : "";
if (envToken) return {
token: envToken,
source: "env"
};
return {
token: "",
source: "none"
};
}
//#endregion
//#region src/telegram/accounts.ts
const debugAccounts = (...args) => {
if (isTruthyEnvValue(process.env.OPENCLAW_DEBUG_TELEGRAM_ACCOUNTS)) console.warn("[telegram:accounts]", ...args);
};
function listConfiguredAccountIds(cfg) {
const accounts = cfg.channels?.telegram?.accounts;
if (!accounts || typeof accounts !== "object") return [];
const ids = /* @__PURE__ */ new Set();
for (const key of Object.keys(accounts)) {
if (!key) continue;
ids.add(normalizeAccountId(key));
}
return [...ids];
}
function listTelegramAccountIds(cfg) {
const ids = Array.from(new Set([...listConfiguredAccountIds(cfg), ...listBoundAccountIds(cfg, "telegram")]));
debugAccounts("listTelegramAccountIds", ids);
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
return ids.toSorted((a, b) => a.localeCompare(b));
}
function resolveDefaultTelegramAccountId(cfg) {
const boundDefault = resolveDefaultAgentBoundAccountId(cfg, "telegram");
if (boundDefault) return boundDefault;
const ids = listTelegramAccountIds(cfg);
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
return ids[0] ?? DEFAULT_ACCOUNT_ID;
}
function resolveAccountConfig(cfg, accountId) {
const accounts = cfg.channels?.telegram?.accounts;
if (!accounts || typeof accounts !== "object") return;
const direct = accounts[accountId];
if (direct) return direct;
const normalized = normalizeAccountId(accountId);
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
return matchKey ? accounts[matchKey] : void 0;
}
function mergeTelegramAccountConfig(cfg, accountId) {
const { accounts: _ignored, ...base } = cfg.channels?.telegram ?? {};
const account = resolveAccountConfig(cfg, accountId) ?? {};
return {
...base,
...account
};
}
function createTelegramActionGate(params) {
const accountId = normalizeAccountId(params.accountId);
return createAccountActionGate({
baseActions: params.cfg.channels?.telegram?.actions,
accountActions: resolveAccountConfig(params.cfg, accountId)?.actions
});
}
function resolveTelegramAccount(params) {
const hasExplicitAccountId = Boolean(params.accountId?.trim());
const baseEnabled = params.cfg.channels?.telegram?.enabled !== false;
const resolve = (accountId) => {
const merged = mergeTelegramAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const tokenResolution = resolveTelegramToken(params.cfg, { accountId });
debugAccounts("resolve", {
accountId,
enabled,
tokenSource: tokenResolution.source
});
return {
accountId,
enabled,
name: merged.name?.trim() || void 0,
token: tokenResolution.token,
tokenSource: tokenResolution.source,
config: merged
};
};
const primary = resolve(normalizeAccountId(params.accountId));
if (hasExplicitAccountId) return primary;
if (primary.tokenSource !== "none") return primary;
const fallbackId = resolveDefaultTelegramAccountId(params.cfg);
if (fallbackId === primary.accountId) return primary;
const fallback = resolve(fallbackId);
if (fallback.tokenSource === "none") return primary;
return fallback;
}
function listEnabledTelegramAccounts(cfg) {
return listTelegramAccountIds(cfg).map((accountId) => resolveTelegramAccount({
cfg,
accountId
})).filter((account) => account.enabled);
}
//#endregion
//#region src/whatsapp/normalize.ts
const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?\.whatsapp\.net$/i;
const WHATSAPP_LID_RE = /^(\d+)$/i;
function stripWhatsAppTargetPrefixes(value) {
let candidate = value.trim();
for (;;) {
const before = candidate;
candidate = candidate.replace(/^whatsapp:/i, "").trim();
if (candidate === before) return candidate;
}
}
function isWhatsAppGroupJid(value) {
const candidate = stripWhatsAppTargetPrefixes(value);
if (!candidate.toLowerCase().endsWith("@g.us")) return false;
const localPart = candidate.slice(0, candidate.length - 5);
if (!localPart || localPart.includes("@")) return false;
return /^[0-9]+(-[0-9]+)*$/.test(localPart);
}
/**
* Check if value looks like a WhatsApp user target (e.g. "41796666864:0@s.whatsapp.net" or "123@lid").
*/
function isWhatsAppUserTarget(value) {
const candidate = stripWhatsAppTargetPrefixes(value);
return WHATSAPP_USER_JID_RE.test(candidate) || WHATSAPP_LID_RE.test(candidate);
}
/**
* Extract the phone number from a WhatsApp user JID.
* "41796666864:0@s.whatsapp.net" -> "41796666864"
* "123456@lid" -> "123456"
*/
function extractUserJidPhone(jid) {
const userMatch = jid.match(WHATSAPP_USER_JID_RE);
if (userMatch) return userMatch[1];
const lidMatch = jid.match(WHATSAPP_LID_RE);
if (lidMatch) return lidMatch[1];
return null;
}
function normalizeWhatsAppTarget(value) {
const candidate = stripWhatsAppTargetPrefixes(value);
if (!candidate) return null;
if (isWhatsAppGroupJid(candidate)) return `${candidate.slice(0, candidate.length - 5)}.us`;
if (isWhatsAppUserTarget(candidate)) {
const phone = extractUserJidPhone(candidate);
if (!phone) return null;
const normalized = normalizeE164(phone);
return normalized.length > 1 ? normalized : null;
}
if (candidate.includes("@")) return null;
const normalized = normalizeE164(candidate);
return normalized.length > 1 ? normalized : null;
}
//#endregion
export { listTelegramAccountIds as a, resolveSlackAccount as c, resolveSlackBotToken as d, createDiscordActionGate as f, normalizeDiscordToken as h, listEnabledTelegramAccounts as i, resolveSlackReplyToMode as l, resolveDiscordAccount as m, normalizeWhatsAppTarget as n, resolveTelegramAccount as o, listEnabledDiscordAccounts as p, createTelegramActionGate as r, resolveTelegramToken as s, isWhatsAppGroupJid as t, resolveSlackAppToken as u };