refactor(telegram): centralize target parsing
This commit is contained in:
@@ -20,10 +20,7 @@ vi.mock("../agents/model-catalog.js", () => ({
|
||||
|
||||
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import {
|
||||
parseTelegramTarget,
|
||||
runCronIsolatedAgentTurn,
|
||||
} from "./isolated-agent.js";
|
||||
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
|
||||
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-cron-"));
|
||||
@@ -673,63 +670,3 @@ describe("runCronIsolatedAgentTurn", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseTelegramTarget", () => {
|
||||
it("parses plain chatId", () => {
|
||||
expect(parseTelegramTarget("-1001234567890")).toEqual({
|
||||
chatId: "-1001234567890",
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("parses @username", () => {
|
||||
expect(parseTelegramTarget("@mychannel")).toEqual({
|
||||
chatId: "@mychannel",
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("parses chatId:topicId format", () => {
|
||||
expect(parseTelegramTarget("-1001234567890:123")).toEqual({
|
||||
chatId: "-1001234567890",
|
||||
topicId: 123,
|
||||
});
|
||||
});
|
||||
|
||||
it("parses chatId:topic:topicId format", () => {
|
||||
expect(parseTelegramTarget("-1001234567890:topic:456")).toEqual({
|
||||
chatId: "-1001234567890",
|
||||
topicId: 456,
|
||||
});
|
||||
});
|
||||
|
||||
it("trims whitespace", () => {
|
||||
expect(parseTelegramTarget(" -1001234567890:99 ")).toEqual({
|
||||
chatId: "-1001234567890",
|
||||
topicId: 99,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not treat non-numeric suffix as topicId", () => {
|
||||
expect(parseTelegramTarget("-1001234567890:abc")).toEqual({
|
||||
chatId: "-1001234567890:abc",
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("strips internal telegram prefix", () => {
|
||||
expect(parseTelegramTarget("telegram:123")).toEqual({
|
||||
chatId: "123",
|
||||
topicId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("strips internal telegram + group prefixes before parsing topic", () => {
|
||||
expect(
|
||||
parseTelegramTarget("telegram:group:-1001234567890:topic:456"),
|
||||
).toEqual({
|
||||
chatId: "-1001234567890",
|
||||
topicId: 456,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,45 +50,6 @@ import { resolveTelegramToken } from "../telegram/token.js";
|
||||
import { normalizeE164 } from "../utils.js";
|
||||
import type { CronJob } from "./types.js";
|
||||
|
||||
/**
|
||||
* Parse a Telegram delivery target into chatId and optional topicId.
|
||||
* Supports formats:
|
||||
* - `chatId` (plain chat ID or @username)
|
||||
* - `chatId:topicId` (chat ID with topic/thread ID)
|
||||
* - `chatId:topic:topicId` (alternative format with explicit "topic" marker)
|
||||
*/
|
||||
export function parseTelegramTarget(to: string): {
|
||||
chatId: string;
|
||||
topicId: number | undefined;
|
||||
} {
|
||||
let trimmed = to.trim();
|
||||
|
||||
// Cron "lastTo" values can include internal prefixes like `telegram:...` or
|
||||
// `telegram:group:...` (see normalizeChatId in telegram/send.ts).
|
||||
// Strip these before parsing `:topic:` / `:<topicId>` suffixes.
|
||||
while (true) {
|
||||
const next = trimmed.replace(/^(telegram|tg|group):/i, "").trim();
|
||||
if (next === trimmed) break;
|
||||
trimmed = next;
|
||||
}
|
||||
|
||||
// Try format: chatId:topic:topicId
|
||||
const topicMatch = /^(.+?):topic:(\d+)$/.exec(trimmed);
|
||||
if (topicMatch) {
|
||||
return { chatId: topicMatch[1], topicId: parseInt(topicMatch[2], 10) };
|
||||
}
|
||||
|
||||
// Try format: chatId:topicId (where topicId is numeric)
|
||||
// Be careful not to match @username or other non-numeric suffixes
|
||||
const colonMatch = /^(.+):(\d+)$/.exec(trimmed);
|
||||
if (colonMatch) {
|
||||
return { chatId: colonMatch[1], topicId: parseInt(colonMatch[2], 10) };
|
||||
}
|
||||
|
||||
// Plain chatId, no topic
|
||||
return { chatId: trimmed, topicId: undefined };
|
||||
}
|
||||
|
||||
export type RunCronAgentTurnResult = {
|
||||
status: "ok" | "error" | "skipped";
|
||||
summary?: string;
|
||||
@@ -526,7 +487,6 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
summary: "Delivery skipped (no Telegram chatId).",
|
||||
};
|
||||
}
|
||||
const { chatId, topicId } = parseTelegramTarget(resolvedDelivery.to);
|
||||
const textLimit = resolveTextChunkLimit(params.cfg, "telegram");
|
||||
try {
|
||||
for (const payload of payloads) {
|
||||
@@ -537,23 +497,29 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
payload.text ?? "",
|
||||
textLimit,
|
||||
)) {
|
||||
await params.deps.sendMessageTelegram(chatId, chunk, {
|
||||
verbose: false,
|
||||
token: telegramToken || undefined,
|
||||
messageThreadId: topicId,
|
||||
});
|
||||
await params.deps.sendMessageTelegram(
|
||||
resolvedDelivery.to,
|
||||
chunk,
|
||||
{
|
||||
verbose: false,
|
||||
token: telegramToken || undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let first = true;
|
||||
for (const url of mediaList) {
|
||||
const caption = first ? (payload.text ?? "") : "";
|
||||
first = false;
|
||||
await params.deps.sendMessageTelegram(chatId, caption, {
|
||||
verbose: false,
|
||||
mediaUrl: url,
|
||||
token: telegramToken || undefined,
|
||||
messageThreadId: topicId,
|
||||
});
|
||||
await params.deps.sendMessageTelegram(
|
||||
resolvedDelivery.to,
|
||||
caption,
|
||||
{
|
||||
verbose: false,
|
||||
mediaUrl: url,
|
||||
token: telegramToken || undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user