capture-telegram-adapter.mjs
137 lines 4.4 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 2 days ago
1 #!/usr/bin/env node
2 /**
3 * Telegram webhook adapter. Receives POSTs with Telegram Bot API update payloads
4 * (or a simplified JSON) and forwards message text to the Knowtation capture endpoint.
5 *
6 * Usage:
7 * node scripts/capture-telegram-adapter.mjs [--port 3134]
8 * CAPTURE_URL=http://localhost:3333/api/v1/capture node scripts/capture-telegram-adapter.mjs
9 *
10 * POST body (JSON):
11 * Telegram Bot API shape: { "message": { "text": "...", "message_id", "chat": { "id" } } }
12 * Or simplified: { "body": "message text", "source_id?", "project?", "tags?" } or { "text": "..." }
13 *
14 * Env: CAPTURE_URL (default http://localhost:3333/api/v1/capture), CAPTURE_WEBHOOK_SECRET
15 *
16 * Use with Telegram Bot API webhook, Zapier, n8n, or any service that can POST this shape.
17 */
18
19 import http from 'http';
20 import path from 'path';
21 import { fileURLToPath } from 'url';
22
23 const __dirname = path.dirname(fileURLToPath(import.meta.url));
24 const DEFAULT_CAPTURE_URL = 'http://localhost:3333/api/v1/capture';
25 const CAPTURE_URL = process.env.CAPTURE_URL || DEFAULT_CAPTURE_URL;
26
27 function parseArgs() {
28 const args = process.argv.slice(2);
29 let port = parseInt(process.env.PORT || '3134', 10);
30 for (let i = 0; i < args.length; i++) {
31 if (args[i] === '--port' && args[i + 1]) port = parseInt(args[++i], 10);
32 }
33 return port;
34 }
35
36 function parseJsonBody(req) {
37 return new Promise((resolve, reject) => {
38 let data = '';
39 req.on('data', (chunk) => { data += chunk; });
40 req.on('end', () => {
41 try {
42 resolve(data ? JSON.parse(data) : {});
43 } catch (e) {
44 reject(new Error('Invalid JSON'));
45 }
46 });
47 req.on('error', reject);
48 });
49 }
50
51 async function postToCapture(payload) {
52 const headers = { 'Content-Type': 'application/json' };
53 if (process.env.CAPTURE_WEBHOOK_SECRET) {
54 headers['X-Webhook-Secret'] = process.env.CAPTURE_WEBHOOK_SECRET;
55 }
56 const res = await fetch(CAPTURE_URL, {
57 method: 'POST',
58 headers,
59 body: JSON.stringify(payload),
60 });
61 const text = await res.text();
62 if (!res.ok) throw new Error(`Capture returned ${res.status}: ${text}`);
63 return text;
64 }
65
66 function extractFromTelegramUpdate(payload) {
67 const msg = payload.message;
68 if (msg && typeof msg.text === 'string') {
69 const chatId = msg.chat && msg.chat.id;
70 const mid = msg.message_id;
71 const sourceId = chatId != null && mid != null ? `${chatId}_${mid}` : undefined;
72 return { body: msg.text, source_id: sourceId };
73 }
74 return null;
75 }
76
77 function main() {
78 const port = parseArgs();
79 const server = http.createServer(async (req, res) => {
80 res.setHeader('Content-Type', 'application/json');
81 if (req.method !== 'POST' || (req.url !== '/' && req.url !== '/capture' && req.url !== '')) {
82 res.writeHead(404);
83 res.end(JSON.stringify({ error: 'Not found. POST / or /capture with JSON (Telegram update or { body, source_id?, project?, tags? })' }));
84 return;
85 }
86
87 let payload;
88 try {
89 payload = await parseJsonBody(req);
90 } catch (e) {
91 res.writeHead(400);
92 res.end(JSON.stringify({ error: e.message }));
93 return;
94 }
95
96 let body = payload.body ?? payload.text;
97 let sourceId = payload.source_id;
98 if (!body && payload.message) {
99 const extracted = extractFromTelegramUpdate(payload);
100 if (extracted) {
101 body = extracted.body;
102 sourceId = sourceId ?? extracted.source_id;
103 }
104 }
105 if (!body || typeof body !== 'string') {
106 res.writeHead(400);
107 res.end(JSON.stringify({ error: 'body (or text, or message.text) string is required' }));
108 return;
109 }
110
111 const capturePayload = {
112 body,
113 source: 'telegram',
114 source_id: sourceId || undefined,
115 project: payload.project || undefined,
116 tags: payload.tags != null ? (Array.isArray(payload.tags) ? payload.tags.join(',') : String(payload.tags)) : undefined,
117 };
118
119 try {
120 await postToCapture(capturePayload);
121 res.writeHead(200);
122 res.end(JSON.stringify({ ok: true }));
123 } catch (e) {
124 console.error('capture-telegram-adapter: capture error', e.message);
125 res.writeHead(502);
126 res.end(JSON.stringify({ error: e.message }));
127 }
128 });
129
130 server.listen(port, () => {
131 console.log(`capture-telegram-adapter listening on http://localhost:${port}`);
132 console.log(' CAPTURE_URL:', CAPTURE_URL);
133 console.log(' POST / or /capture with JSON: Telegram update or { body, source_id?, project?, tags? }');
134 });
135 }
136
137 main();
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago