From 24bafca6ebe7c75e943ba91af056d16581f6dd41 Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Tue, 26 May 2026 20:04:33 +0530 Subject: [PATCH 1/2] Init inbounds --- openclaw.plugin.json | 62 ++++++++++++- package.json | 8 ++ src/config.ts | 23 +++++ src/index.ts | 202 ++++++++++++++++++++++++++++++++----------- src/types/types.ts | 7 ++ 5 files changed, 247 insertions(+), 55 deletions(-) create mode 100644 src/config.ts create mode 100644 src/types/types.ts diff --git a/openclaw.plugin.json b/openclaw.plugin.json index 6d468a0..89c89c4 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -1,15 +1,71 @@ { - "id": "rocketchat-plugin", + "id": "rocketchat", "name": "RocketChat Webhook", "version": "0.1.0", "description": "Rocket.Chat integration for OpenClaw", "type": "channel", "channels": [ - "rocketchat-plugin" + "rocketchat" ], + "configSchema": { + "RC_URL": { + "type": "string", + "description": "Rocket.Chat server URL", + "default": "http://localhost:3000" + }, + "RC_AUTH_TOKEN": { + "type": "string", + "description": "Rocket.Chat bot auth token", + "secret": true + }, + "RC_USER_ID": { + "type": "string", + "description": "Rocket.Chat bot user ID" + }, + "DEFAULT_ROOM": { + "type": "string", + "description": "Default room ID to send messages to", + "default": "GENERAL" + }, + "RC_WEBHOOK_SECRET": { + "type": "string", + "description": "Secret token to validate incoming webhooks", + "secret": true + } + }, + "channelConfigs": { + "rocketchat": { + "schema": { + "RC_URL": { + "type": "string", + "description": "Rocket.Chat server URL", + "default": "http://localhost:3000" + }, + "RC_AUTH_TOKEN": { + "type": "string", + "description": "Rocket.Chat bot auth token", + "secret": true + }, + "RC_USER_ID": { + "type": "string", + "description": "Rocket.Chat bot user ID" + }, + "DEFAULT_ROOM": { + "type": "string", + "description": "Default room ID to send messages to", + "default": "GENERAL" + }, + "RC_WEBHOOK_SECRET": { + "type": "string", + "description": "Secret token to validate incoming webhooks", + "secret": true + } + } + } + }, "configuration": { "channels": [ - "rocketchat-plugin" + "rocketchat" ] }, "entry": "dist/index.js" diff --git a/package.json b/package.json index 27a7d05..b078ad4 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,11 @@ "version": "1.0.0", "description": "A fully unified plugin for integrating Rocket.Chat with OpenClaw. This plugin eliminates the need for an external bridging server, providing a direct, single-place architecture for inbounds, outbounds, session management, and CLI configuration.", "main": "dist/index.js", + "openclaw": { + "extensions": [ + "./dist/index.js" + ] + }, "type": "module", "scripts": { "build": "tsc", @@ -24,5 +29,8 @@ "@types/node": "^25.9.1", "ts-node": "^10.9.2", "typescript": "^6.0.3" + }, + "dependencies": { + "dotenv": "^17.4.2" } } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..b23a57f --- /dev/null +++ b/src/config.ts @@ -0,0 +1,23 @@ +import * as dotenv from 'dotenv'; +import type { RocketChatConfig } from './types/types.js'; + +dotenv.config(); + +export function getConfig(): RocketChatConfig { + const config = { + url: process.env.RC_URL || "http://localhost:3000", + authToken: process.env.RC_AUTH_TOKEN || "", + userId: process.env.RC_USER_ID || "", + defaultRoom: process.env.DEFAULT_ROOM || "GENERAL", + webhookSecret: process.env.RC_WEBHOOK_SECRET || "", + }; + + if (!config.authToken) { + console.warn("[RC Config] Warning: RC_AUTH_TOKEN is not set."); + } + if (!config.userId) { + console.warn("[RC Config] Warning: RC_USER_ID is not set."); + } + + return config; +} diff --git a/src/index.ts b/src/index.ts index e9ad6e1..01860d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,58 +1,156 @@ -// Register and load openclaw plugin - -export default function register(api: any) { - const RC_URL = process.env.RC_URL || "http://localhost:3000"; - const RC_AUTH_TOKEN = process.env.RC_AUTH_TOKEN || ""; - const RC_USER_ID = process.env.RC_USER_ID || ""; - const DEFAULT_ROOM = process.env.DEFAULT_ROOM || "GENERAL"; - - const plugin = { - id: "rocketchat-plugin", - meta: { - id: "rocketchat-plugin", - label: "Rocket.Chat (webhook)", - selectionLabel: "Rocket.Chat (webhook)", - blurb: "REST outbound to Rocket.Chat (chat.sendMessage).", - aliases: ["rc-hook", "rocketchat-hook"], - }, - capabilities: { chatTypes: ["direct", "group"] }, - config: { - listAccountIds: (_cfg: any) => ["default", "69c3a5f48b90145d5886b115", "69a873434af7ce5b5e37b18f"], - resolveAccount: (_cfg: any, accountId: string) => ({ - accountId: accountId ?? "default" - }), - }, - outbound: { - deliveryMode: "direct" as const, - resolveTarget: ({ to }: { to: string }) => { - const target = (to && to.trim()) ? to.trim() : DEFAULT_ROOM; - return { ok: true, to: target }; +export default function register(api: any): void { + const logger = api.logger || { + info: (msg: string) => console.log(`[RC] ${msg}`), + error: (msg: string) => console.error(`[RC] ${msg}`), + }; + + const config = { + url: "http://localhost:3000", + authToken: "", + userId: "", + defaultRoom: "", + webhookSecret: "", + }; + + logger.info("Initializing Unified Rocket.Chat Plugin..."); + + api.registerChannel({ + plugin: { + id: "rocketchat", + meta: { + id: "rocketchat", + label: "Rocket.Chat", + selectionLabel: "Rocket.Chat", + blurb: "Unified Rocket.Chat Plugin with Inbound Webhook and Outbound REST", + aliases: ["rc"], }, - async sendText({ to, text }: { to?: string; text: string }) { - const room = to || DEFAULT_ROOM; - - const res = await fetch(`${RC_URL}/api/v1/chat.sendMessage`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-Auth-Token": RC_AUTH_TOKEN, - "X-User-Id": RC_USER_ID, - }, - body: JSON.stringify({ message: { rid: room, msg: text } }), - }); - const body = await res.text(); + capabilities: { chatTypes: ["direct", "group"] }, + config: { + listAccountIds: (_cfg: any) => ["default"], + resolveAccount: (_cfg: any, accountId?: string) => ({ + accountId: accountId || "default", + }), + }, + outbound: { + deliveryMode: "direct" as const, + resolveTarget: ({ to }: { to: string }) => { + const target = (to && to.trim()) ? to.trim() : config.defaultRoom; + return { ok: true, to: target }; + }, + sendText: async (ctx: { to: string; text: string; accountId?: string; threadId?: string | number | null }) => { + try { + const room = ctx.to || config.defaultRoom; + const payload: any = { rid: room, msg: ctx.text }; + if (ctx.threadId) { + payload.tmid = String(ctx.threadId); + } - return { ok: res.ok, channel: "rocketchat" } - } - }, - gateway: { - startAccount: async (ctx: any) => { - ctx.setStatus({ accountId: ctx.account?.accountId ?? "default", state: "connected" }); - return new Promise(() => { }); + const res = await fetch(`${config.url}/api/v1/chat.sendMessage`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Auth-Token": config.authToken, + "X-User-Id": config.userId, + }, + body: JSON.stringify({ message: payload }), + }); + + if (!res.ok) { + const body = await res.text(); + logger.error(`Outbound failed: ${res.status} ${body}`); + return { ok: false, channel: "rocketchat" }; + } + + return { ok: true, channel: "rocketchat" }; + } catch (err) { + logger.error(`Outbound error: ${(err as Error).message}`); + return { ok: false, channel: "rocketchat" }; + } + }, + }, + gateway: { + startAccount: async (ctx: any) => { + ctx.setStatus({ accountId: ctx.account?.accountId ?? "default", state: "connected" }); + return new Promise(() => { }); + }, }, }, - }; + }); + + if (api.registerHttpRoute) { + api.registerHttpRoute({ + method: "POST", + path: "/rocketchat/webhook", + auth: { mode: "none" }, + handler: async (req: any, res: any) => { + try { + const body = req.body || {}; + + const token = req.headers["x-rocketchat-livechat-token"] || req.headers["authorization"] || body.token; + if (config.webhookSecret && token !== config.webhookSecret) { + logger.error("Webhook authentication failed. Invalid token."); + return res.status(401).json({ error: "Unauthorized" }); + } + + const text = body.text || ""; + const roomId = body.channel_id || "GENERAL"; + const senderId = body.user_id || "unknown"; + const msgId = body.message_id || Date.now().toString(); + const isBot = body.bot === true; - api.registerChannel({ plugin }); + if (isBot || senderId === config.userId) { + return res.status(200).json({ success: true, ignored: true }); + } + + if (api.gateway && api.gateway.dispatchInbound) { + await api.gateway.dispatchInbound({ + channel: "rocketchat", + accountId: "default", + type: "message", + message: { + id: msgId, + text: text, + from: senderId, + to: roomId, + metadata: { + channelName: body.channel_name, + userName: body.user_name, + threadId: body.tmid + } + }, + raw: body, + }); + logger.info(`Dispatched inbound message from ${senderId} in ${roomId}`); + } else { + logger.error("api.gateway.dispatchInbound is not available."); + } + + res.status(200).json({ success: true }); + } catch (err) { + logger.error(`Webhook processing error: ${(err as Error).message}`); + res.status(500).json({ error: "Internal Server Error" }); + } + }, + }); + logger.info("Registered Inbound Webhook at /rocketchat/webhook"); + } else { + logger.error("api.registerHttpRoute is not available on this OpenClaw version."); + } + + if (api.registerCli) { + api.registerCli(({ program }: { program: any }) => { + const rc = program.command("rocketchat").alias("rc").description("Rocket.Chat unified plugin commands"); + rc.command("status") + .description("Check Rocket.Chat webhook and integration status") + .action(() => { + console.log("Rocket.Chat plugin is loaded."); + console.log(`RC_URL: ${config.url}`); + console.log(`Webhook Secret Configured: ${!!config.webhookSecret}`); + }); + }, { + commands: ["rocketchat"] + }); + } -} \ No newline at end of file + logger.info("Rocket.Chat Unified Plugin initialization complete."); +} diff --git a/src/types/types.ts b/src/types/types.ts new file mode 100644 index 0000000..a77e998 --- /dev/null +++ b/src/types/types.ts @@ -0,0 +1,7 @@ +export interface RocketChatConfig { + url: string; + authToken: string; + userId: string; + defaultRoom: string; + webhookSecret: string; +} From 0bd0fbc9bd2be82dc01e387e2f56ff596867f9fa Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Sun, 31 May 2026 19:04:57 +0530 Subject: [PATCH 2/2] Added a custom http route with logger to log teh received curl body and dispatch to openclaw --- .gitignore | 3 +- src/index.ts | 197 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 126 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index 3f4754b..732a546 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ package-lock.json .vscode node_modules -dist \ No newline at end of file +dist +todo.txt \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 01860d5..c68f48c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,6 @@ +import { getConfig } from "./config.js"; +const configvars = getConfig(); + export default function register(api: any): void { const logger = api.logger || { info: (msg: string) => console.log(`[RC] ${msg}`), @@ -5,14 +8,23 @@ export default function register(api: any): void { }; const config = { - url: "http://localhost:3000", - authToken: "", - userId: "", - defaultRoom: "", - webhookSecret: "", + url: configvars.url || "http://localhost:3000", + authToken: configvars.authToken || "", + userId: configvars.userId || "", + defaultRoom: configvars.defaultRoom || "GENERAL", + webhookSecret: configvars.webhookSecret || "", }; + if (!config.webhookSecret) { + console.warn("[RC Config] Warning: RC_WEBHOOK_SECRET is not set — webhook auth disabled."); + } + + logger.info("Initializing Unified Rocket.Chat Plugin..."); + logger.info(`[Config] RC_URL: ${config.url}`); + logger.info(`[Config] RC_USER_ID: ${config.userId || "NOT SET"}`); + logger.info(`[Config] RC_AUTH_TOKEN: ${config.authToken ? config.authToken.slice(0, 6) + "..." : "NOT SET"}`); + logger.info(`[Config] DEFAULT_ROOM: ${config.defaultRoom || "NOT SET"}`); api.registerChannel({ plugin: { @@ -77,80 +89,119 @@ export default function register(api: any): void { }, }); - if (api.registerHttpRoute) { - api.registerHttpRoute({ - method: "POST", - path: "/rocketchat/webhook", - auth: { mode: "none" }, - handler: async (req: any, res: any) => { - try { - const body = req.body || {}; - - const token = req.headers["x-rocketchat-livechat-token"] || req.headers["authorization"] || body.token; - if (config.webhookSecret && token !== config.webhookSecret) { - logger.error("Webhook authentication failed. Invalid token."); - return res.status(401).json({ error: "Unauthorized" }); - } + if (api.registerHttpRoute) { + api.registerHttpRoute({ + method: "POST", + path: "/rocketchat/webhook", + auth: "plugin", + handler: async (req: any, res: any) => { + try { + let body: any = (req.body && Object.keys(req.body).length > 0) ? req.body : null; +if (!body) { + const rawBody = await new Promise((resolve, reject) => { + let data = ""; + req.on("data", (chunk: any) => { data += chunk; }); + req.on("end", () => resolve(data)); + req.on("error", reject); + }); + try { body = rawBody ? JSON.parse(rawBody) : {}; } catch { body = {}; } +} - const text = body.text || ""; - const roomId = body.channel_id || "GENERAL"; - const senderId = body.user_id || "unknown"; - const msgId = body.message_id || Date.now().toString(); - const isBot = body.bot === true; + logger.info("[Webhook] Incoming payload received"); + logger.info(`[Webhook] user_id: ${body.user_id}`); + logger.info(`[Webhook] user_name: ${body.user_name}`); + logger.info(`[Webhook] channel_id: ${body.channel_id}`); + logger.info(`[Webhook] channel_name: ${body.channel_name}`); + logger.info(`[Webhook] text: ${body.text}`); + logger.info(`[Webhook] message_id: ${body.message_id}`); + logger.info(`[Webhook] bot: ${body.bot}`); + logger.info(`[Webhook] tmid: ${body.tmid}`); - if (isBot || senderId === config.userId) { - return res.status(200).json({ success: true, ignored: true }); - } + // ignore bot self-messages + if (body.bot || body.user_id === config.userId) { + logger.info("[Webhook] Skipping bot message"); + res.statusCode = 200; + res.end(JSON.stringify({ success: true })); + return; + } - if (api.gateway && api.gateway.dispatchInbound) { - await api.gateway.dispatchInbound({ - channel: "rocketchat", - accountId: "default", - type: "message", - message: { - id: msgId, - text: text, - from: senderId, - to: roomId, - metadata: { - channelName: body.channel_name, - userName: body.user_name, - threadId: body.tmid - } - }, - raw: body, - }); - logger.info(`Dispatched inbound message from ${senderId} in ${roomId}`); - } else { - logger.error("api.gateway.dispatchInbound is not available."); - } - - res.status(200).json({ success: true }); - } catch (err) { - logger.error(`Webhook processing error: ${(err as Error).message}`); - res.status(500).json({ error: "Internal Server Error" }); - } - }, + // dispatch into OpenClaw + await api.scheduleSessionTurn({ + channel: "rocketchat", + accountId: "default", + to: body.channel_id || config.defaultRoom, + from: body.user_name, + text: body.text ?? "", + threadId: body.tmid ?? null, + messageId: body.message_id, }); - logger.info("Registered Inbound Webhook at /rocketchat/webhook"); - } else { - logger.error("api.registerHttpRoute is not available on this OpenClaw version."); - } - if (api.registerCli) { - api.registerCli(({ program }: { program: any }) => { - const rc = program.command("rocketchat").alias("rc").description("Rocket.Chat unified plugin commands"); - rc.command("status") - .description("Check Rocket.Chat webhook and integration status") - .action(() => { - console.log("Rocket.Chat plugin is loaded."); - console.log(`RC_URL: ${config.url}`); - console.log(`Webhook Secret Configured: ${!!config.webhookSecret}`); - }); - }, { - commands: ["rocketchat"] - }); + + // New API per sdk-channel-inbound docs. Use this once scheduleSessionTurn + // is confirmed removed or broken. Replace approach 1 with this block. + // + // await api.runtime.channel.inbound.run({ + // channel: "rocketchat", + // accountId: "default", + // raw: body, + // adapter: { + // // ingest: normalize the raw RC webhook payload into OpenClaw's + // // inbound message shape expected by the agent layer. + // ingest: (raw: any) => ({ + // id: raw.message_id ?? `${Date.now()}`, + // rawText: raw.text ?? "", + // textForAgent: raw.text ?? "", + // textForCommands: raw.text ?? "", + // from: raw.user_name, + // to: raw.channel_id || config.defaultRoom, + // threadId: raw.tmid ?? null, + // raw, + // }), + // // resolveTurn: assemble the full turn context for the agent — + // // routing, session store path, reply target, and delivery fn. + // // Signature and required fields TBD from channel-ingress docs: + // // https://docs.openclaw.ai/plugins/sdk-channel-ingress + // resolveTurn: (input: any) => { + // const room = body.channel_id || config.defaultRoom; + // return { + // // TODO: fill once ingress API shape is confirmed + // delivery: { + // deliver: async (payload: any) => { + // const text = payload.text ?? payload.message ?? ""; + // const sendRes = await fetch(`${config.url}/api/v1/chat.sendMessage`, { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // "X-Auth-Token": config.authToken, + // "X-User-Id": config.userId, + // }, + // body: JSON.stringify({ message: { rid: room, msg: text } }), + // }); + // if (!sendRes.ok) { + // const errBody = await sendRes.text().catch(() => ""); + // logger.error(`[Delivery] Failed: ${sendRes.status} ${errBody}`); + // } + // }, + // }, + // }; + // }, + // }, + // }); + + + logger.info("[Webhook] Dispatched to OpenClaw via scheduleSessionTurn"); + + res.statusCode = 200; + res.end(JSON.stringify({ success: true })); + } catch (err) { + logger.error(`[Webhook] Error: ${(err as Error).message}`); + res.statusCode = 500; + res.end(JSON.stringify({ error: "Internal Server Error" })); } +}, + }); + logger.info("Registered Inbound Webhook at /rocketchat/webhook"); +} logger.info("Rocket.Chat Unified Plugin initialization complete."); }