mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-18 13:30:48 +00:00
* refactor: move Telegram channel implementation to extensions/telegram/src/ Move all Telegram channel code (123 files + 10 bot/ files + 8 channel plugin files) from src/telegram/ and src/channels/plugins/*/telegram.ts to extensions/telegram/src/. Leave thin re-export shims at original locations so cross-cutting src/ imports continue to resolve. - Fix all relative import paths in moved files (../X/ -> ../../../src/X/) - Fix vi.mock paths in 60 test files - Fix inline typeof import() expressions - Update tsconfig.plugin-sdk.dts.json rootDir to "." for cross-directory DTS - Update write-plugin-sdk-entry-dts.ts for new rootDir structure - Move channel plugin files with correct path remapping * fix: support keyed telegram send deps * fix: sync telegram extension copies with latest main * fix: correct import paths and remove misplaced files in telegram extension * fix: sync outbound-adapter with main (add sendTelegramPayloadMessages) and fix delivery.test import path
202 lines
5.7 KiB
TypeScript
202 lines
5.7 KiB
TypeScript
import type { OpenClawConfig } from "../../../src/config/config.js";
|
|
import { readConfigFileSnapshotForWrite, writeConfigFile } from "../../../src/config/config.js";
|
|
import { loadCronStore, resolveCronStorePath, saveCronStore } from "../../../src/cron/store.js";
|
|
import { createSubsystemLogger } from "../../../src/logging/subsystem.js";
|
|
import {
|
|
normalizeTelegramChatId,
|
|
normalizeTelegramLookupTarget,
|
|
parseTelegramTarget,
|
|
} from "./targets.js";
|
|
|
|
const writebackLogger = createSubsystemLogger("telegram/target-writeback");
|
|
|
|
function asObjectRecord(value: unknown): Record<string, unknown> | null {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function normalizeTelegramLookupTargetForMatch(raw: string): string | undefined {
|
|
const normalized = normalizeTelegramLookupTarget(raw);
|
|
if (!normalized) {
|
|
return undefined;
|
|
}
|
|
return normalized.startsWith("@") ? normalized.toLowerCase() : normalized;
|
|
}
|
|
|
|
function normalizeTelegramTargetForMatch(raw: string): string | undefined {
|
|
const parsed = parseTelegramTarget(raw);
|
|
const normalized = normalizeTelegramLookupTargetForMatch(parsed.chatId);
|
|
if (!normalized) {
|
|
return undefined;
|
|
}
|
|
const threadKey = parsed.messageThreadId == null ? "" : String(parsed.messageThreadId);
|
|
return `${normalized}|${threadKey}`;
|
|
}
|
|
|
|
function buildResolvedTelegramTarget(params: {
|
|
raw: string;
|
|
parsed: ReturnType<typeof parseTelegramTarget>;
|
|
resolvedChatId: string;
|
|
}): string {
|
|
const { raw, parsed, resolvedChatId } = params;
|
|
if (parsed.messageThreadId == null) {
|
|
return resolvedChatId;
|
|
}
|
|
return raw.includes(":topic:")
|
|
? `${resolvedChatId}:topic:${parsed.messageThreadId}`
|
|
: `${resolvedChatId}:${parsed.messageThreadId}`;
|
|
}
|
|
|
|
function resolveLegacyRewrite(params: {
|
|
raw: string;
|
|
resolvedChatId: string;
|
|
}): { matchKey: string; resolvedTarget: string } | null {
|
|
const parsed = parseTelegramTarget(params.raw);
|
|
if (normalizeTelegramChatId(parsed.chatId)) {
|
|
return null;
|
|
}
|
|
const normalized = normalizeTelegramLookupTargetForMatch(parsed.chatId);
|
|
if (!normalized) {
|
|
return null;
|
|
}
|
|
const threadKey = parsed.messageThreadId == null ? "" : String(parsed.messageThreadId);
|
|
return {
|
|
matchKey: `${normalized}|${threadKey}`,
|
|
resolvedTarget: buildResolvedTelegramTarget({
|
|
raw: params.raw,
|
|
parsed,
|
|
resolvedChatId: params.resolvedChatId,
|
|
}),
|
|
};
|
|
}
|
|
|
|
function rewriteTargetIfMatch(params: {
|
|
rawValue: unknown;
|
|
matchKey: string;
|
|
resolvedTarget: string;
|
|
}): string | null {
|
|
if (typeof params.rawValue !== "string" && typeof params.rawValue !== "number") {
|
|
return null;
|
|
}
|
|
const value = String(params.rawValue).trim();
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
if (normalizeTelegramTargetForMatch(value) !== params.matchKey) {
|
|
return null;
|
|
}
|
|
return params.resolvedTarget;
|
|
}
|
|
|
|
function replaceTelegramDefaultToTargets(params: {
|
|
cfg: OpenClawConfig;
|
|
matchKey: string;
|
|
resolvedTarget: string;
|
|
}): boolean {
|
|
let changed = false;
|
|
const telegram = asObjectRecord(params.cfg.channels?.telegram);
|
|
if (!telegram) {
|
|
return changed;
|
|
}
|
|
|
|
const maybeReplace = (holder: Record<string, unknown>, key: string) => {
|
|
const nextTarget = rewriteTargetIfMatch({
|
|
rawValue: holder[key],
|
|
matchKey: params.matchKey,
|
|
resolvedTarget: params.resolvedTarget,
|
|
});
|
|
if (!nextTarget) {
|
|
return;
|
|
}
|
|
holder[key] = nextTarget;
|
|
changed = true;
|
|
};
|
|
|
|
maybeReplace(telegram, "defaultTo");
|
|
const accounts = asObjectRecord(telegram.accounts);
|
|
if (!accounts) {
|
|
return changed;
|
|
}
|
|
for (const accountId of Object.keys(accounts)) {
|
|
const account = asObjectRecord(accounts[accountId]);
|
|
if (!account) {
|
|
continue;
|
|
}
|
|
maybeReplace(account, "defaultTo");
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
export async function maybePersistResolvedTelegramTarget(params: {
|
|
cfg: OpenClawConfig;
|
|
rawTarget: string;
|
|
resolvedChatId: string;
|
|
verbose?: boolean;
|
|
}): Promise<void> {
|
|
const raw = params.rawTarget.trim();
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
const rewrite = resolveLegacyRewrite({
|
|
raw,
|
|
resolvedChatId: params.resolvedChatId,
|
|
});
|
|
if (!rewrite) {
|
|
return;
|
|
}
|
|
const { matchKey, resolvedTarget } = rewrite;
|
|
|
|
try {
|
|
const { snapshot, writeOptions } = await readConfigFileSnapshotForWrite();
|
|
const nextConfig = structuredClone(snapshot.config ?? {});
|
|
const configChanged = replaceTelegramDefaultToTargets({
|
|
cfg: nextConfig,
|
|
matchKey,
|
|
resolvedTarget,
|
|
});
|
|
if (configChanged) {
|
|
await writeConfigFile(nextConfig, writeOptions);
|
|
if (params.verbose) {
|
|
writebackLogger.warn(`resolved Telegram defaultTo target ${raw} -> ${resolvedTarget}`);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (params.verbose) {
|
|
writebackLogger.warn(`failed to persist Telegram defaultTo target ${raw}: ${String(err)}`);
|
|
}
|
|
}
|
|
|
|
try {
|
|
const storePath = resolveCronStorePath(params.cfg.cron?.store);
|
|
const store = await loadCronStore(storePath);
|
|
let cronChanged = false;
|
|
for (const job of store.jobs) {
|
|
if (job.delivery?.channel !== "telegram") {
|
|
continue;
|
|
}
|
|
const nextTarget = rewriteTargetIfMatch({
|
|
rawValue: job.delivery.to,
|
|
matchKey,
|
|
resolvedTarget,
|
|
});
|
|
if (!nextTarget) {
|
|
continue;
|
|
}
|
|
job.delivery.to = nextTarget;
|
|
cronChanged = true;
|
|
}
|
|
if (cronChanged) {
|
|
await saveCronStore(storePath, store);
|
|
if (params.verbose) {
|
|
writebackLogger.warn(`resolved Telegram cron delivery target ${raw} -> ${resolvedTarget}`);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (params.verbose) {
|
|
writebackLogger.warn(`failed to persist Telegram cron target ${raw}: ${String(err)}`);
|
|
}
|
|
}
|
|
}
|