@cloudrise/openclaw-channel-rocketchat 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -145,6 +145,10 @@ Then restart the gateway.
145
145
 
146
146
  ## Features
147
147
 
148
+ - **Inbound attachments**: receives images, PDFs/documents, and audio; forwards them to OpenClaw for vision/document understanding and transcription.
149
+ - **Outbound attachments**: can send local file paths as real Rocket.Chat uploads (inline previews when supported).
150
+ - **Reactions**: can react to messages with emoji (via `chat.react`).
151
+
148
152
  - **File attachments**: receives images, PDFs, documents, audio uploaded to Rocket.Chat and passes them to the vision model.
149
153
  - **Model prefix**: honors `messages.responsePrefix` (e.g. `({model}) `) so replies can include the model name.
150
154
 
@@ -335,4 +339,4 @@ This repository is intended to be publishable (no secrets committed).
335
339
 
336
340
  ## License
337
341
 
338
- MIT
342
+ MIT
package/index.ts CHANGED
@@ -11,6 +11,9 @@ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
11
11
  import { rocketChatPlugin } from "./src/channel.js";
12
12
  import { setRocketChatRuntime } from "./src/runtime.js";
13
13
 
14
+ // Re-export send/react functions for OpenClaw message tool
15
+ export { reactMessageRocketChat, sendMessageRocketChat } from "./src/rocketchat/send.js";
16
+
14
17
  const plugin = {
15
18
  id: "rocketchat",
16
19
  name: "Rocket.Chat",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudrise/openclaw-channel-rocketchat",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Rocket.Chat channel plugin for OpenClaw (Cloudrise)",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/channel.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  } from "./rocketchat/accounts.js";
16
16
  import { normalizeRocketChatBaseUrl } from "./rocketchat/client.js";
17
17
  import { monitorRocketChatProvider } from "./rocketchat/monitor.js";
18
- import { sendMessageRocketChat } from "./rocketchat/send.js";
18
+ import { reactMessageRocketChat, sendMessageRocketChat } from "./rocketchat/send.js";
19
19
  import { getRocketChatRuntime } from "./runtime.js";
20
20
 
21
21
  const meta = {
@@ -245,7 +245,7 @@ export type RocketChatUploadResult = {
245
245
  * Upload a file to a Rocket.Chat room.
246
246
  * RC 8.x uses a two-step process:
247
247
  * 1. POST /api/v1/rooms.media/{roomId} → returns file._id
248
- * 2. POST /api/v1/chat.sendMessage with file reference
248
+ * 2. POST /api/v1/rooms.mediaConfirm/{roomId}/{fileId} creates displayable message
249
249
  */
250
250
  export async function uploadRocketChatFile(
251
251
  client: RocketChatClient,
@@ -300,39 +300,68 @@ export async function uploadRocketChatFile(
300
300
  throw new Error("Rocket.Chat media upload failed: no file ID returned");
301
301
  }
302
302
 
303
- // Step 2: Send message with file reference
304
- const messagePayload = {
305
- message: {
306
- rid: opts.roomId,
307
- msg: opts.description ?? "",
308
- file: { _id: uploadData.file._id },
309
- ...(opts.tmid && { tmid: opts.tmid }),
310
- },
311
- };
303
+ // Step 2: Confirm media upload (generates thumbnail, dimensions, etc.)
304
+ const confirmUrl = `${client.baseUrl}/api/v1/rooms.mediaConfirm/${opts.roomId}/${uploadData.file._id}`;
305
+
306
+ const confirmPayload: Record<string, string> = {};
307
+ if (opts.description) confirmPayload.msg = opts.description;
308
+ if (opts.tmid) confirmPayload.tmid = opts.tmid;
312
309
 
313
- const sendRes = await client.fetch(`${client.baseUrl}/api/v1/chat.sendMessage`, {
310
+ const confirmRes = await client.fetch(confirmUrl, {
314
311
  method: "POST",
315
312
  headers: {
316
313
  "X-Auth-Token": client.authToken,
317
314
  "X-User-Id": client.userId,
318
315
  "Content-Type": "application/json",
319
316
  },
320
- body: JSON.stringify(messagePayload),
317
+ body: JSON.stringify(confirmPayload),
321
318
  });
322
319
 
323
- if (!sendRes.ok) {
324
- const text = await sendRes.text().catch(() => "");
325
- throw new Error(`Rocket.Chat send message error ${sendRes.status}: ${text}`);
320
+ if (!confirmRes.ok) {
321
+ const text = await confirmRes.text().catch(() => "");
322
+ throw new Error(`Rocket.Chat media confirm error ${confirmRes.status}: ${text}`);
326
323
  }
327
324
 
328
- const sendData = await sendRes.json() as {
325
+ const confirmData = await confirmRes.json() as {
329
326
  message: { _id: string; rid: string; ts: string };
330
327
  success: boolean
331
328
  };
332
329
 
333
330
  return {
334
- _id: sendData.message._id,
335
- rid: sendData.message.rid,
336
- ts: sendData.message.ts,
331
+ _id: confirmData.message._id,
332
+ rid: confirmData.message.rid,
333
+ ts: confirmData.message.ts,
337
334
  };
338
335
  }
336
+
337
+ /**
338
+ * React to a message with an emoji.
339
+ * @param emoji - Emoji name (e.g., "thumbsup", ":rocket:", or unicode "🚀")
340
+ * @param shouldReact - true to add reaction, false to remove (default: true)
341
+ */
342
+ export async function reactRocketChatMessage(
343
+ client: RocketChatClient,
344
+ messageId: string,
345
+ emoji: string,
346
+ shouldReact = true
347
+ ): Promise<void> {
348
+ // Normalize emoji format - RC accepts "thumbsup", ":thumbsup:", or unicode
349
+ const normalizedEmoji = emoji.startsWith(":") ? emoji : `:${emoji.replace(/:/g, "")}:`;
350
+
351
+ const res = await rcFetch<{ success: boolean }>(
352
+ client,
353
+ "/api/v1/chat.react",
354
+ {
355
+ method: "POST",
356
+ body: JSON.stringify({
357
+ messageId,
358
+ emoji: normalizedEmoji,
359
+ shouldReact,
360
+ }),
361
+ }
362
+ );
363
+
364
+ if (!res.success) {
365
+ throw new Error("Rocket.Chat reaction failed");
366
+ }
367
+ }
@@ -13,6 +13,7 @@ import {
13
13
  fetchRocketChatUserByUsername,
14
14
  normalizeRocketChatBaseUrl,
15
15
  postRocketChatMessage,
16
+ reactRocketChatMessage,
16
17
  uploadRocketChatFile,
17
18
  type RocketChatUser,
18
19
  } from "./client.js";
@@ -261,3 +262,58 @@ export async function sendMessageRocketChat(
261
262
  roomId: post.rid ?? roomId,
262
263
  };
263
264
  }
265
+
266
+ export type RocketChatReactOpts = {
267
+ accountId?: string;
268
+ shouldReact?: boolean;
269
+ };
270
+
271
+ /**
272
+ * React to a Rocket.Chat message with an emoji.
273
+ * @param messageId - The message ID to react to
274
+ * @param emoji - Emoji name (e.g., "thumbsup", ":rocket:", "🚀")
275
+ */
276
+ export async function reactMessageRocketChat(
277
+ messageId: string,
278
+ emoji: string,
279
+ opts: RocketChatReactOpts = {}
280
+ ): Promise<void> {
281
+ const core = getRocketChatRuntime();
282
+ const logger = core?.logging?.getChildLogger?.({ module: "rocketchat" });
283
+ const cfg = core?.config?.loadConfig?.() ?? {};
284
+
285
+ const account = resolveRocketChatAccount({ cfg, accountId: opts.accountId });
286
+
287
+ const authToken = account.authToken?.trim();
288
+ if (!authToken) {
289
+ throw new Error(
290
+ `Rocket.Chat authToken missing for account "${account.accountId}"`
291
+ );
292
+ }
293
+
294
+ const userId = account.userId?.trim();
295
+ if (!userId) {
296
+ throw new Error(
297
+ `Rocket.Chat userId missing for account "${account.accountId}"`
298
+ );
299
+ }
300
+
301
+ const baseUrl = normalizeRocketChatBaseUrl(account.baseUrl);
302
+ if (!baseUrl) {
303
+ throw new Error(
304
+ `Rocket.Chat baseUrl missing for account "${account.accountId}"`
305
+ );
306
+ }
307
+
308
+ const client = createRocketChatClient({ baseUrl, userId, authToken });
309
+
310
+ logger?.debug?.(`Reacting to message ${messageId} with ${emoji}`);
311
+
312
+ await reactRocketChatMessage(client, messageId, emoji, opts.shouldReact ?? true);
313
+
314
+ core?.channel?.activity?.record?.({
315
+ channel: "rocketchat",
316
+ accountId: account.accountId,
317
+ direction: "outbound",
318
+ });
319
+ }