274 lines
9.8 KiB
TypeScript
274 lines
9.8 KiB
TypeScript
import type { ClawdbotConfig } from "../../config/config.js";
|
|
import {
|
|
loadConfig,
|
|
readConfigFileSnapshot,
|
|
writeConfigFile,
|
|
} from "../../config/config.js";
|
|
import { type DiscordProbe, probeDiscord } from "../../discord/probe.js";
|
|
import { type IMessageProbe, probeIMessage } from "../../imessage/probe.js";
|
|
import { webAuthExists } from "../../providers/web/index.js";
|
|
import { probeSignal, type SignalProbe } from "../../signal/probe.js";
|
|
import { probeSlack, type SlackProbe } from "../../slack/probe.js";
|
|
import {
|
|
resolveSlackAppToken,
|
|
resolveSlackBotToken,
|
|
} from "../../slack/token.js";
|
|
import { probeTelegram, type TelegramProbe } from "../../telegram/probe.js";
|
|
import { resolveTelegramToken } from "../../telegram/token.js";
|
|
import { getWebAuthAgeMs, readWebSelfId } from "../../web/session.js";
|
|
import {
|
|
ErrorCodes,
|
|
errorShape,
|
|
formatValidationErrors,
|
|
validateProvidersStatusParams,
|
|
} from "../protocol/index.js";
|
|
import { formatForLog } from "../ws-log.js";
|
|
import type { GatewayRequestHandlers } from "./types.js";
|
|
|
|
export const providersHandlers: GatewayRequestHandlers = {
|
|
"providers.status": async ({ params, respond, context }) => {
|
|
if (!validateProvidersStatusParams(params)) {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(
|
|
ErrorCodes.INVALID_REQUEST,
|
|
`invalid providers.status params: ${formatValidationErrors(validateProvidersStatusParams.errors)}`,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
const probe = (params as { probe?: boolean }).probe === true;
|
|
const timeoutMsRaw = (params as { timeoutMs?: unknown }).timeoutMs;
|
|
const timeoutMs =
|
|
typeof timeoutMsRaw === "number" ? Math.max(1000, timeoutMsRaw) : 10_000;
|
|
const cfg = loadConfig();
|
|
const telegramCfg = cfg.telegram;
|
|
const telegramEnabled =
|
|
Boolean(telegramCfg) && telegramCfg?.enabled !== false;
|
|
const { token: telegramToken, source: tokenSource } = telegramEnabled
|
|
? resolveTelegramToken(cfg)
|
|
: { token: "", source: "none" as const };
|
|
let telegramProbe: TelegramProbe | undefined;
|
|
let lastProbeAt: number | null = null;
|
|
if (probe && telegramToken && telegramEnabled) {
|
|
telegramProbe = await probeTelegram(
|
|
telegramToken,
|
|
timeoutMs,
|
|
telegramCfg?.proxy,
|
|
);
|
|
lastProbeAt = Date.now();
|
|
}
|
|
|
|
const discordCfg = cfg.discord;
|
|
const discordEnabled = Boolean(discordCfg) && discordCfg?.enabled !== false;
|
|
const discordEnvToken = discordEnabled
|
|
? process.env.DISCORD_BOT_TOKEN?.trim()
|
|
: "";
|
|
const discordConfigToken = discordEnabled ? discordCfg?.token?.trim() : "";
|
|
const discordToken = discordEnvToken || discordConfigToken || "";
|
|
const discordTokenSource = discordEnvToken
|
|
? "env"
|
|
: discordConfigToken
|
|
? "config"
|
|
: "none";
|
|
let discordProbe: DiscordProbe | undefined;
|
|
let discordLastProbeAt: number | null = null;
|
|
if (probe && discordToken && discordEnabled) {
|
|
discordProbe = await probeDiscord(discordToken, timeoutMs);
|
|
discordLastProbeAt = Date.now();
|
|
}
|
|
|
|
const slackCfg = cfg.slack;
|
|
const slackEnabled = slackCfg?.enabled !== false;
|
|
const slackBotEnvToken = slackEnabled
|
|
? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN)
|
|
: undefined;
|
|
const slackBotConfigToken = slackEnabled
|
|
? resolveSlackBotToken(slackCfg?.botToken)
|
|
: undefined;
|
|
const slackBotToken = slackBotEnvToken ?? slackBotConfigToken ?? "";
|
|
const slackBotTokenSource = slackBotEnvToken
|
|
? "env"
|
|
: slackBotConfigToken
|
|
? "config"
|
|
: "none";
|
|
const slackAppEnvToken = slackEnabled
|
|
? resolveSlackAppToken(process.env.SLACK_APP_TOKEN)
|
|
: undefined;
|
|
const slackAppConfigToken = slackEnabled
|
|
? resolveSlackAppToken(slackCfg?.appToken)
|
|
: undefined;
|
|
const slackAppToken = slackAppEnvToken ?? slackAppConfigToken ?? "";
|
|
const slackAppTokenSource = slackAppEnvToken
|
|
? "env"
|
|
: slackAppConfigToken
|
|
? "config"
|
|
: "none";
|
|
const slackConfigured =
|
|
slackEnabled && Boolean(slackBotToken) && Boolean(slackAppToken);
|
|
let slackProbe: SlackProbe | undefined;
|
|
let slackLastProbeAt: number | null = null;
|
|
if (probe && slackConfigured) {
|
|
slackProbe = await probeSlack(slackBotToken, timeoutMs);
|
|
slackLastProbeAt = Date.now();
|
|
}
|
|
|
|
const signalCfg = cfg.signal;
|
|
const signalEnabled = signalCfg?.enabled !== false;
|
|
const signalHost = signalCfg?.httpHost?.trim() || "127.0.0.1";
|
|
const signalPort = signalCfg?.httpPort ?? 8080;
|
|
const signalBaseUrl =
|
|
signalCfg?.httpUrl?.trim() || `http://${signalHost}:${signalPort}`;
|
|
const signalConfigured =
|
|
Boolean(signalCfg) &&
|
|
signalEnabled &&
|
|
Boolean(
|
|
signalCfg?.account?.trim() ||
|
|
signalCfg?.httpUrl?.trim() ||
|
|
signalCfg?.cliPath?.trim() ||
|
|
signalCfg?.httpHost?.trim() ||
|
|
typeof signalCfg?.httpPort === "number" ||
|
|
typeof signalCfg?.autoStart === "boolean",
|
|
);
|
|
let signalProbe: SignalProbe | undefined;
|
|
let signalLastProbeAt: number | null = null;
|
|
if (probe && signalConfigured) {
|
|
signalProbe = await probeSignal(signalBaseUrl, timeoutMs);
|
|
signalLastProbeAt = Date.now();
|
|
}
|
|
|
|
const imessageCfg = cfg.imessage;
|
|
const imessageEnabled = imessageCfg?.enabled !== false;
|
|
const imessageConfigured = Boolean(imessageCfg) && imessageEnabled;
|
|
let imessageProbe: IMessageProbe | undefined;
|
|
let imessageLastProbeAt: number | null = null;
|
|
if (probe && imessageConfigured) {
|
|
imessageProbe = await probeIMessage(timeoutMs);
|
|
imessageLastProbeAt = Date.now();
|
|
}
|
|
|
|
const linked = await webAuthExists();
|
|
const authAgeMs = getWebAuthAgeMs();
|
|
const self = readWebSelfId();
|
|
const runtime = context.getRuntimeSnapshot();
|
|
|
|
respond(
|
|
true,
|
|
{
|
|
ts: Date.now(),
|
|
whatsapp: {
|
|
configured: linked,
|
|
linked,
|
|
authAgeMs,
|
|
self,
|
|
running: runtime.whatsapp.running,
|
|
connected: runtime.whatsapp.connected,
|
|
lastConnectedAt: runtime.whatsapp.lastConnectedAt ?? null,
|
|
lastDisconnect: runtime.whatsapp.lastDisconnect ?? null,
|
|
reconnectAttempts: runtime.whatsapp.reconnectAttempts,
|
|
lastMessageAt: runtime.whatsapp.lastMessageAt ?? null,
|
|
lastEventAt: runtime.whatsapp.lastEventAt ?? null,
|
|
lastError: runtime.whatsapp.lastError ?? null,
|
|
},
|
|
telegram: {
|
|
configured: telegramEnabled && Boolean(telegramToken),
|
|
tokenSource,
|
|
running: runtime.telegram.running,
|
|
mode: runtime.telegram.mode ?? null,
|
|
lastStartAt: runtime.telegram.lastStartAt ?? null,
|
|
lastStopAt: runtime.telegram.lastStopAt ?? null,
|
|
lastError: runtime.telegram.lastError ?? null,
|
|
probe: telegramProbe,
|
|
lastProbeAt,
|
|
},
|
|
discord: {
|
|
configured: discordEnabled && Boolean(discordToken),
|
|
tokenSource: discordTokenSource,
|
|
running: runtime.discord.running,
|
|
lastStartAt: runtime.discord.lastStartAt ?? null,
|
|
lastStopAt: runtime.discord.lastStopAt ?? null,
|
|
lastError: runtime.discord.lastError ?? null,
|
|
probe: discordProbe,
|
|
lastProbeAt: discordLastProbeAt,
|
|
},
|
|
slack: {
|
|
configured: slackConfigured,
|
|
botTokenSource: slackBotTokenSource,
|
|
appTokenSource: slackAppTokenSource,
|
|
running: runtime.slack.running,
|
|
lastStartAt: runtime.slack.lastStartAt ?? null,
|
|
lastStopAt: runtime.slack.lastStopAt ?? null,
|
|
lastError: runtime.slack.lastError ?? null,
|
|
probe: slackProbe,
|
|
lastProbeAt: slackLastProbeAt,
|
|
},
|
|
signal: {
|
|
configured: signalConfigured,
|
|
baseUrl: signalBaseUrl,
|
|
running: runtime.signal.running,
|
|
lastStartAt: runtime.signal.lastStartAt ?? null,
|
|
lastStopAt: runtime.signal.lastStopAt ?? null,
|
|
lastError: runtime.signal.lastError ?? null,
|
|
probe: signalProbe,
|
|
lastProbeAt: signalLastProbeAt,
|
|
},
|
|
imessage: {
|
|
configured: imessageConfigured,
|
|
running: runtime.imessage.running,
|
|
lastStartAt: runtime.imessage.lastStartAt ?? null,
|
|
lastStopAt: runtime.imessage.lastStopAt ?? null,
|
|
lastError: runtime.imessage.lastError ?? null,
|
|
cliPath: runtime.imessage.cliPath ?? null,
|
|
dbPath: runtime.imessage.dbPath ?? null,
|
|
probe: imessageProbe,
|
|
lastProbeAt: imessageLastProbeAt,
|
|
},
|
|
},
|
|
undefined,
|
|
);
|
|
},
|
|
"telegram.logout": async ({ respond, context }) => {
|
|
try {
|
|
await context.stopTelegramProvider();
|
|
const snapshot = await readConfigFileSnapshot();
|
|
if (!snapshot.valid) {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(
|
|
ErrorCodes.INVALID_REQUEST,
|
|
"config invalid; fix it before logging out",
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
const cfg = snapshot.config ?? {};
|
|
const envToken = process.env.TELEGRAM_BOT_TOKEN?.trim() ?? "";
|
|
const hadToken = Boolean(cfg.telegram?.botToken);
|
|
const nextTelegram = cfg.telegram ? { ...cfg.telegram } : undefined;
|
|
if (nextTelegram) {
|
|
delete nextTelegram.botToken;
|
|
}
|
|
const nextCfg = { ...cfg } as ClawdbotConfig;
|
|
if (nextTelegram && Object.keys(nextTelegram).length > 0) {
|
|
nextCfg.telegram = nextTelegram;
|
|
} else {
|
|
delete nextCfg.telegram;
|
|
}
|
|
await writeConfigFile(nextCfg);
|
|
respond(
|
|
true,
|
|
{ cleared: hadToken, envToken: Boolean(envToken) },
|
|
undefined,
|
|
);
|
|
} catch (err) {
|
|
respond(
|
|
false,
|
|
undefined,
|
|
errorShape(ErrorCodes.UNAVAILABLE, formatForLog(err)),
|
|
);
|
|
}
|
|
},
|
|
};
|