From 24bafca6ebe7c75e943ba91af056d16581f6dd41 Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Tue, 26 May 2026 20:04:33 +0530 Subject: [PATCH] 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; +}