OpenClawDashboard/bridge/src/routes/notify.ts
Manohar a65902edf9 feat(bridge): Telegram polling, file-based tasks/projects, cron/keys/deploy routes
- routes/notify.ts: Telegram delivery endpoint POST /tiger/notify
- routes/tasks-file.ts: TASKS.md/PROJECTS.md reader (JSON block parser — P0 fix)
  Parser reads only from TASKS_JSON fenced block; returns 502 if block absent
- routes/cron.ts: GET /tiger/cron + POST /tiger/cron/:id/run
- routes/keys.ts: GET /tiger/keys (key presence map, no values exposed)
- routes/agents-activity.ts: GET /tiger/agents/:id/files
- routes/route-task.ts: POST /tiger/route-task (LLM router)
- routes/deploy.ts: POST /tiger/deploy-dashboard (path: OpenClawDashboard)
- db.ts: comment Reserved telegram columns (future task-from-Telegram)
2026-05-02 20:09:43 +00:00

86 lines
2.7 KiB
TypeScript

import { Router, Request, Response } from "express";
import { readFileSync } from "fs";
const router = Router();
const OPENCLAW_CONFIG_PATH =
process.env.OPENCLAW_CONFIG_PATH ||
"/var/lib/docker/volumes/tiger_tiger-config/_data/openclaw.json";
function getBotToken(): string {
if (process.env.TELEGRAM_BOT_TOKEN?.trim()) return process.env.TELEGRAM_BOT_TOKEN.trim();
try {
const cfg = JSON.parse(readFileSync(OPENCLAW_CONFIG_PATH, "utf-8"));
return cfg?.channels?.telegram?.botToken ?? "";
} catch { return ""; }
}
function getChatId(): string {
if (process.env.TELEGRAM_CHAT_ID?.trim()) return process.env.TELEGRAM_CHAT_ID.trim();
return "";
}
/**
* POST /tiger/notify
* Body: { message: string, chatId?: string }
*
* Sends a Telegram message via the bridge's bot token.
* Called by Tiger's cron jobs (via curl from inside the container)
* since OpenClaw's native Telegram channel is disabled (bridge owns polling).
*
* curl example from inside container:
* curl -s -X POST http://172.17.0.1:3456/tiger/notify \
* -H "Content-Type: application/json" \
* -H "Authorization: Bearer $BRIDGE_TOKEN" \
* -d '{"message":"HEARTBEAT_OK"}'
*/
router.post("/", async (req: Request, res: Response) => {
const { message, chatId: overrideChatId } = req.body as {
message?: string;
chatId?: string;
};
if (!message?.trim()) {
return res.status(400).json({ ok: false, error: "message is required" });
}
const token = getBotToken();
const chatId = overrideChatId || getChatId();
if (!token) {
return res.status(503).json({ ok: false, error: "No bot token configured" });
}
if (!chatId) {
return res.status(503).json({
ok: false,
error: "No TELEGRAM_CHAT_ID in bridge .env — set it so Tiger knows where to send notifications",
});
}
try {
const tgRes = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: chatId,
text: message.slice(0, 4096),
parse_mode: "Markdown",
}),
signal: AbortSignal.timeout(10_000),
});
const data = await tgRes.json() as any;
if (!data.ok) {
console.error("[notify] Telegram error:", data.description);
return res.status(502).json({ ok: false, error: data.description });
}
console.log(`[notify] Sent to chat ${chatId}: ${message.slice(0, 60)}`);
res.json({ ok: true, messageId: data.result?.message_id });
} catch (err: any) {
console.error("[notify] fetch failed:", err.message);
res.status(502).json({ ok: false, error: err.message });
}
});
export default router;