#!/usr/bin/env node 'use strict'; // outgoing_validator.js // Usage: // 1) echo '{"channel":"discord","message":"hello"}' | ./outgoing_validator.js // 2) ./outgoing_validator.js --channel discord --message "hi" // Options: // --send : actually run `openclaw message send --channel ... --message ...` (needs openclaw CLI in PATH) const { spawnSync } = require('child_process'); function usageAndExit() { console.error('Usage: outgoing_validator.js [--channel ] --message [--send]'); process.exit(2); } function readStdinSync() { try { const fs = require('fs'); const stat = fs.fstatSync(0); if (stat.size > 0) { return fs.readFileSync(0, 'utf8'); } } catch (e) { // ignore } return null; } function cleanObject(o) { if (Array.isArray(o)) return o.map(cleanObject); if (o && typeof o === 'object') { const rv = {}; for (const k of Object.keys(o)) { const v = o[k]; if (v === null || v === undefined) continue; const cv = cleanObject(v); // skip empty strings? keep but trim if (typeof cv === 'string' && cv.trim() === '') continue; rv[k] = cv; } return rv; } if (typeof o === 'string') { // Trim, collapse weird control chars, limit length let s = o.trim(); // remove null bytes and vertical tabs s = s.replace(/[\x00\x0B\x0C]/g, ''); // collapse repeated whitespace s = s.replace(/\s{2,}/g, ' '); return s; } return o; } function limitString(s, max) { if (s.length <= max) return s; return s.slice(0, max); } // Parse args const argv = process.argv.slice(2); let channel = null; let message = null; let doSend = false; for (let i = 0; i < argv.length; i++) { const a = argv[i]; if (a === '--channel' && argv[i+1]) { channel = argv[++i]; continue; } if (a === '--message' && argv[i+1]) { message = argv[++i]; continue; } if (a === '--send') { doSend = true; continue; } if (a === '--help' || a === '-h') usageAndExit(); } // If no message provided via args, try stdin as JSON or plain text if (!message) { const stdin = readStdinSync(); if (stdin) { // Try parse JSON try { const parsed = JSON.parse(stdin); if (parsed) { channel = channel || parsed.channel || parsed.to || parsed.channelName || 'discord'; message = parsed.message || parsed.text || parsed.body || JSON.stringify(parsed); } } catch (e) { // treat stdin as plain text message = stdin.trim(); } } } if (!message) { console.error('Aborted: empty message after reading args/stdin'); process.exit(2); } channel = channel || 'discord'; // Build payload let payload = { channel, message }; // Clean payload = cleanObject(payload); if (!payload || !payload.message) { console.error('Aborted: message cleaned to empty'); process.exit(2); } // Truncate long messages (OpenClaw/Discord limits) const MAX_LEN = 1900; if (typeof payload.message === 'string') { if (payload.message.length > MAX_LEN) { payload.message = limitString(payload.message, MAX_LEN); payload.message += '\n\n[...trimmed]'; } } // Validate final JSON let jsonOut; try { jsonOut = JSON.stringify(payload); } catch (e) { console.error('Failed to serialize payload JSON:', e.message); process.exit(3); } // Log repaired payload locally try { const fs = require('fs'); const logDir = '/home/alex/clawd/logs'; try { fs.mkdirSync(logDir, { recursive: true }); } catch (e) {} const ts = new Date().toISOString(); fs.appendFileSync(logDir + '/outgoing_validator.log', ts + ' ' + jsonOut + '\n'); } catch (e) { // ignore logging errors } // Optional: increment Redis counter if redis-cli available and REDIS_KEY env set if (process.env.REDIS_KEY) { try { const r = spawnSync('redis-cli', ['INCR', process.env.REDIS_KEY]); if (r.error) { // redis-cli not available or failed - ignore } } catch (e) {} } // Output repaired JSON console.log(jsonOut); if (doSend) { // Use openclaw CLI to send (if available). Don't fail loudly if not installed. try { const ocArgs = ['message', 'send', '--channel', payload.channel, '--message']; // Use JSON string for message argument safely const msgArg = payload.message; const cmd = ['openclaw', ...ocArgs, msgArg]; const r = spawnSync('openclaw', ['message', 'send', '--channel', payload.channel, '--message', payload.message], { encoding: 'utf8' }); if (r.error) { console.error('OpenClaw send failed to start:', r.error.message); process.exit(4); } console.log('send result:', r.stdout || r.stderr); } catch (e) { console.error('Send attempt failed:', e.message); process.exit(4); } } process.exit(0);