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