@emotion-machine/claw-messenger 0.1.14 → 0.1.17
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 +1 -1
- package/dist/channel.d.ts +1 -1
- package/dist/channel.js +14 -27
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/outbound/send.js +2 -2
- package/dist/reactions.d.ts +21 -0
- package/dist/reactions.js +68 -0
- package/dist/runtime.d.ts +1 -1
- package/package.json +2 -2
- package/src/types/openclaw.d.ts +18 -60
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ After installing, add to your config under `channels`:
|
|
|
30
30
|
## Features
|
|
31
31
|
|
|
32
32
|
- **Send & receive** text messages and media (images, video, audio, documents)
|
|
33
|
-
- **iMessage reactions** —
|
|
33
|
+
- **iMessage reactions** — six standard tapbacks plus any single Unicode emoji
|
|
34
34
|
- **Group chats** — send to existing groups or create new ones
|
|
35
35
|
- **Typing indicators** — sent and received
|
|
36
36
|
- **DM security policies** — open, pairing-based approval, or allowlist
|
package/dist/channel.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChannelPlugin } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
2
2
|
import { type ClawMessengerConfig } from "./config.js";
|
|
3
3
|
import { WsClient } from "./ws/client.js";
|
|
4
4
|
import { type SendResult } from "./outbound/send.js";
|
package/dist/channel.js
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID as OPENCLAW_DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, buildChannelConfigSchema, formatPairingApproveHint, normalizeAccountId, } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
2
2
|
import { ClawMessengerConfigSchema } from "./config.js";
|
|
3
3
|
import { isInboundMessageEvent, normalizeInboundMessage, validateInboundRoute, } from "./inbound.js";
|
|
4
4
|
import { getRuntime } from "./runtime.js";
|
|
5
|
+
import { sendReaction } from "./reactions.js";
|
|
5
6
|
import { WsClient } from "./ws/client.js";
|
|
6
7
|
import { normalizeDirectTarget, sendText, sendMedia, sendToGroup, sendGroupMedia, sendToNewGroup, } from "./outbound/send.js";
|
|
7
|
-
const EMOJI_TO_REACTION = {
|
|
8
|
-
"❤️": "love", "♥️": "love", "🩷": "love", "💕": "love", "😍": "love",
|
|
9
|
-
"👍": "like", "👍🏻": "like", "👍🏼": "like", "👍🏽": "like", "👍🏾": "like", "👍🏿": "like",
|
|
10
|
-
"👎": "dislike", "👎🏻": "dislike", "👎🏼": "dislike", "👎🏽": "dislike", "👎🏾": "dislike", "👎🏿": "dislike",
|
|
11
|
-
"😂": "laugh", "🤣": "laugh", "😆": "laugh",
|
|
12
|
-
"‼️": "emphasize", "❗": "emphasize", "❕": "emphasize", "⚡": "emphasize",
|
|
13
|
-
"❓": "question", "❔": "question", "🤔": "question",
|
|
14
|
-
};
|
|
15
|
-
function emojiToReaction(emoji) {
|
|
16
|
-
return EMOJI_TO_REACTION[emoji];
|
|
17
|
-
}
|
|
18
8
|
// -- Account resolution --
|
|
19
9
|
const FALLBACK_ACCOUNT_ID = "default";
|
|
20
10
|
function hasAccountId(value) {
|
|
@@ -25,7 +15,7 @@ function hasAccountId(value) {
|
|
|
25
15
|
}
|
|
26
16
|
function normalizeAccountIdCompat(accountId) {
|
|
27
17
|
const fallback = hasAccountId(accountId) ? accountId.trim() : FALLBACK_ACCOUNT_ID;
|
|
28
|
-
const normalize =
|
|
18
|
+
const normalize = normalizeAccountId;
|
|
29
19
|
if (typeof normalize === "function") {
|
|
30
20
|
try {
|
|
31
21
|
const normalized = normalize(fallback);
|
|
@@ -38,7 +28,7 @@ function normalizeAccountIdCompat(accountId) {
|
|
|
38
28
|
}
|
|
39
29
|
return fallback;
|
|
40
30
|
}
|
|
41
|
-
const DEFAULT_ACCOUNT_ID = normalizeAccountIdCompat(
|
|
31
|
+
const DEFAULT_ACCOUNT_ID = normalizeAccountIdCompat(OPENCLAW_DEFAULT_ACCOUNT_ID);
|
|
42
32
|
const MESSAGE_TOOL_ACTIONS = ["send", "react"];
|
|
43
33
|
function resolveAccount(cfg, accountId) {
|
|
44
34
|
const cloudConfig = (cfg.channels?.["claw-messenger"] ?? {});
|
|
@@ -121,7 +111,7 @@ export const clawMessengerPlugin = {
|
|
|
121
111
|
const ws = wsClients.get(account.accountId);
|
|
122
112
|
if (!ws)
|
|
123
113
|
throw new Error("Not connected to claw-messenger");
|
|
124
|
-
await sendText(ws, id,
|
|
114
|
+
await sendText(ws, id, PAIRING_APPROVED_MESSAGE, account.preferredService);
|
|
125
115
|
},
|
|
126
116
|
},
|
|
127
117
|
capabilities: {
|
|
@@ -133,7 +123,7 @@ export const clawMessengerPlugin = {
|
|
|
133
123
|
blockStreaming: true,
|
|
134
124
|
},
|
|
135
125
|
reload: { configPrefixes: ["channels.claw-messenger"] },
|
|
136
|
-
configSchema:
|
|
126
|
+
configSchema: buildChannelConfigSchema(ClawMessengerConfigSchema),
|
|
137
127
|
config: {
|
|
138
128
|
listAccountIds: () => [DEFAULT_ACCOUNT_ID],
|
|
139
129
|
resolveAccount: (cfg, accountId) => resolveAccount(cfg, accountId),
|
|
@@ -167,7 +157,7 @@ export const clawMessengerPlugin = {
|
|
|
167
157
|
allowFrom: account.config.allowFrom ?? [],
|
|
168
158
|
policyPath: "channels.claw-messenger.dmPolicy",
|
|
169
159
|
allowFromPath: "channels.claw-messenger.",
|
|
170
|
-
approveHint:
|
|
160
|
+
approveHint: formatPairingApproveHint("claw-messenger"),
|
|
171
161
|
}),
|
|
172
162
|
collectWarnings: ({ cfg }) => {
|
|
173
163
|
const account = resolveAccount(cfg);
|
|
@@ -192,7 +182,7 @@ export const clawMessengerPlugin = {
|
|
|
192
182
|
},
|
|
193
183
|
agentPrompt: {
|
|
194
184
|
messageToolHints: () => [
|
|
195
|
-
"You can react to messages
|
|
185
|
+
"You can react to messages via the react action. The six iMessage tapbacks are love (❤️), like (👍), dislike (👎), laugh (😂), emphasize (‼️), and question (❓). Any other single Unicode emoji is sent as a custom reaction.",
|
|
196
186
|
"Use reactions naturally — the way a real person would in iMessage.",
|
|
197
187
|
"If you encounter connection issues, the server is WebSocket-only — do NOT test with HTTP. Full API docs and troubleshooting: https://clawmessenger.com/docs",
|
|
198
188
|
"If a user is stuck connecting, the fix is almost always: make sure the Claw Messenger plugin is enabled and the API key is set. Verified setup guides to hand them: OpenClaw setup https://www.clawmessenger.com/blog/openclaw-imessage-setup-guide, Hermes setup https://www.clawmessenger.com/blog/hermes-imessage-setup, troubleshooting https://www.clawmessenger.com/blog/openclaw-imessage-troubleshooting.",
|
|
@@ -211,17 +201,14 @@ export const clawMessengerPlugin = {
|
|
|
211
201
|
const messageId = params.messageId;
|
|
212
202
|
if (!messageId)
|
|
213
203
|
throw new Error("messageId is required for react action");
|
|
214
|
-
const emoji = params.emoji?.trim()
|
|
204
|
+
const emoji = params.emoji?.trim() || "❤️";
|
|
215
205
|
const remove = params.remove === true;
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
reactionType,
|
|
221
|
-
remove,
|
|
222
|
-
});
|
|
206
|
+
const result = await sendReaction(ws, messageId, emoji, remove);
|
|
207
|
+
const reaction = result.reactionType === "custom"
|
|
208
|
+
? result.emoji
|
|
209
|
+
: result.reactionType;
|
|
223
210
|
return {
|
|
224
|
-
content: [{ type: "text", text: JSON.stringify({ ok: true, action: remove ? "removed" : "added", reaction
|
|
211
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, action: remove ? "removed" : "added", reaction }) }],
|
|
225
212
|
};
|
|
226
213
|
}
|
|
227
214
|
if (action === "send") {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
1
|
+
import { emptyPluginConfigSchema, } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import { clawMessengerPlugin, getConnectionStatus, getWsClient, createGroup } from "./channel.js";
|
|
4
4
|
import { setRuntime, getRuntime } from "./runtime.js";
|
|
@@ -99,7 +99,7 @@ const plugin = {
|
|
|
99
99
|
const ws = getWsClient(status.accountId);
|
|
100
100
|
const diagnostics = ws?.getDiagnostics() ?? { errors: [], connectionLog: [] };
|
|
101
101
|
const report = {
|
|
102
|
-
plugin_version: "0.1.
|
|
102
|
+
plugin_version: "0.1.15",
|
|
103
103
|
node_version: process.version,
|
|
104
104
|
connected: status.connected,
|
|
105
105
|
server_url: status.serverUrl,
|
package/dist/outbound/send.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
|
|
2
2
|
function stripLegacyNamespacePrefix(target) {
|
|
3
3
|
let normalized = target.trim();
|
|
4
4
|
while (normalized.startsWith("claw-messenger:") || normalized.startsWith("linq:")) {
|
|
@@ -21,7 +21,7 @@ function fallbackNormalizeE164(input) {
|
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
23
|
function normalizeE164Compat(input) {
|
|
24
|
-
const sdkNormalize = typeof
|
|
24
|
+
const sdkNormalize = typeof normalizeE164 === "function" ? normalizeE164 : null;
|
|
25
25
|
if (sdkNormalize) {
|
|
26
26
|
try {
|
|
27
27
|
return sdkNormalize(input);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type StandardReactionType = "love" | "like" | "dislike" | "laugh" | "emphasize" | "question";
|
|
2
|
+
export type ReactionType = StandardReactionType | "custom";
|
|
3
|
+
export interface ReactionResult {
|
|
4
|
+
type: "reaction.result";
|
|
5
|
+
id?: string;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
messageId?: string;
|
|
8
|
+
reactionType?: ReactionType;
|
|
9
|
+
emoji?: string;
|
|
10
|
+
remove?: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
errorCode?: string;
|
|
13
|
+
retryable?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface ReactionWsClient {
|
|
16
|
+
request(message: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
17
|
+
}
|
|
18
|
+
export declare function isSingleEmoji(value: string): boolean;
|
|
19
|
+
export declare function reactionFrame(messageId: string, rawEmoji: string, remove?: boolean): Record<string, unknown>;
|
|
20
|
+
export declare function sendReaction(ws: ReactionWsClient, messageId: string, emoji: string, remove?: boolean): Promise<ReactionResult>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const STANDARD_EMOJI_TO_REACTION = {
|
|
2
|
+
"❤": "love",
|
|
3
|
+
"❤️": "love",
|
|
4
|
+
"♥️": "love",
|
|
5
|
+
"🩷": "love",
|
|
6
|
+
"💕": "love",
|
|
7
|
+
"😍": "love",
|
|
8
|
+
"👍": "like",
|
|
9
|
+
"👍🏻": "like",
|
|
10
|
+
"👍🏼": "like",
|
|
11
|
+
"👍🏽": "like",
|
|
12
|
+
"👍🏾": "like",
|
|
13
|
+
"👍🏿": "like",
|
|
14
|
+
"👎": "dislike",
|
|
15
|
+
"👎🏻": "dislike",
|
|
16
|
+
"👎🏼": "dislike",
|
|
17
|
+
"👎🏽": "dislike",
|
|
18
|
+
"👎🏾": "dislike",
|
|
19
|
+
"👎🏿": "dislike",
|
|
20
|
+
"😂": "laugh",
|
|
21
|
+
"🤣": "laugh",
|
|
22
|
+
"😆": "laugh",
|
|
23
|
+
"‼": "emphasize",
|
|
24
|
+
"‼️": "emphasize",
|
|
25
|
+
"❗": "emphasize",
|
|
26
|
+
"❕": "emphasize",
|
|
27
|
+
"⚡": "emphasize",
|
|
28
|
+
"❓": "question",
|
|
29
|
+
"❔": "question",
|
|
30
|
+
"🤔": "question",
|
|
31
|
+
};
|
|
32
|
+
export function isSingleEmoji(value) {
|
|
33
|
+
if (!value || value !== value.trim() || value.length > 64)
|
|
34
|
+
return false;
|
|
35
|
+
const segments = Array.from(new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(value));
|
|
36
|
+
if (segments.length !== 1)
|
|
37
|
+
return false;
|
|
38
|
+
return (/\p{Extended_Pictographic}/u.test(value)
|
|
39
|
+
|| /^\p{Regional_Indicator}{2}$/u.test(value)
|
|
40
|
+
|| /^[#*0-9]\uFE0F?\u20E3$/u.test(value));
|
|
41
|
+
}
|
|
42
|
+
export function reactionFrame(messageId, rawEmoji, remove = false) {
|
|
43
|
+
const normalizedMessageId = messageId?.trim();
|
|
44
|
+
if (!normalizedMessageId)
|
|
45
|
+
throw new Error("messageId is required for react action");
|
|
46
|
+
const emoji = rawEmoji?.trim();
|
|
47
|
+
if (!isSingleEmoji(emoji)) {
|
|
48
|
+
throw new Error("emoji must contain exactly one Unicode emoji");
|
|
49
|
+
}
|
|
50
|
+
const standardReaction = STANDARD_EMOJI_TO_REACTION[emoji];
|
|
51
|
+
return {
|
|
52
|
+
type: "reaction",
|
|
53
|
+
messageId: normalizedMessageId,
|
|
54
|
+
reactionType: standardReaction ?? "custom",
|
|
55
|
+
...(standardReaction ? {} : { emoji }),
|
|
56
|
+
remove,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export async function sendReaction(ws, messageId, emoji, remove = false) {
|
|
60
|
+
const response = await ws.request(reactionFrame(messageId, emoji, remove));
|
|
61
|
+
if (response.type !== "reaction.result") {
|
|
62
|
+
throw new Error("Claw Messenger returned an invalid reaction response");
|
|
63
|
+
}
|
|
64
|
+
if (response.ok !== true) {
|
|
65
|
+
throw new Error(typeof response.error === "string" ? response.error : "Reaction request failed");
|
|
66
|
+
}
|
|
67
|
+
return response;
|
|
68
|
+
}
|
package/dist/runtime.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emotion-machine/claw-messenger",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.17",
|
|
4
|
+
"description": "OpenClaw channel plugin for Claw Messenger, a managed, Mac-free iMessage, RCS, and SMS API for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
package/src/types/openclaw.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Type declarations for
|
|
2
|
+
* Type declarations for the focused OpenClaw plugin SDK subpaths used here.
|
|
3
3
|
*
|
|
4
4
|
* These are derived from the runtime shapes in openclaw's compiled JS.
|
|
5
|
-
*
|
|
5
|
+
* They mirror the subset needed by the plugin without making OpenClaw a runtime dependency.
|
|
6
6
|
*/
|
|
7
|
-
declare module "openclaw/plugin-sdk" {
|
|
7
|
+
declare module "openclaw/plugin-sdk/channel-core" {
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
// ── Config ──
|
|
@@ -220,7 +220,14 @@ declare module "openclaw/plugin-sdk" {
|
|
|
220
220
|
[key: string]: any;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
declare module "openclaw/plugin-sdk/plugin-entry" {
|
|
226
|
+
import type {
|
|
227
|
+
ChannelPlugin,
|
|
228
|
+
OpenclawConfig,
|
|
229
|
+
PluginRuntime,
|
|
230
|
+
} from "openclaw/plugin-sdk/channel-core";
|
|
224
231
|
|
|
225
232
|
export interface OpenclawPluginApi {
|
|
226
233
|
id: string;
|
|
@@ -246,66 +253,17 @@ declare module "openclaw/plugin-sdk" {
|
|
|
246
253
|
on(hookName: string, handler: any, opts?: any): void;
|
|
247
254
|
}
|
|
248
255
|
|
|
249
|
-
|
|
256
|
+
export function emptyPluginConfigSchema(): any;
|
|
257
|
+
}
|
|
250
258
|
|
|
259
|
+
declare module "openclaw/plugin-sdk/channel-plugin-common" {
|
|
251
260
|
export const DEFAULT_ACCOUNT_ID: string;
|
|
252
261
|
export const PAIRING_APPROVED_MESSAGE: string;
|
|
253
|
-
|
|
254
|
-
export function normalizeAccountId(accountId?: string | null): string;
|
|
255
|
-
export function normalizeE164(input: string): string | null;
|
|
256
262
|
export function buildChannelConfigSchema(schema: any): any;
|
|
257
|
-
export function emptyPluginConfigSchema(): any;
|
|
258
|
-
export function getChatChannelMeta(channel: string): any;
|
|
259
263
|
export function formatPairingApproveHint(channel: string): string;
|
|
264
|
+
export function normalizeAccountId(accountId?: string | null): string;
|
|
265
|
+
}
|
|
260
266
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
sectionKey: string;
|
|
264
|
-
accountId: string;
|
|
265
|
-
enabled: boolean;
|
|
266
|
-
allowTopLevel?: boolean;
|
|
267
|
-
}): OpenclawConfig;
|
|
268
|
-
|
|
269
|
-
export function deleteAccountFromConfigSection(ctx: {
|
|
270
|
-
cfg: OpenclawConfig;
|
|
271
|
-
sectionKey: string;
|
|
272
|
-
accountId: string;
|
|
273
|
-
clearBaseFields?: string[];
|
|
274
|
-
}): OpenclawConfig;
|
|
275
|
-
|
|
276
|
-
export function registerPluginHttpRoute(params: {
|
|
277
|
-
path: string;
|
|
278
|
-
pluginId?: string;
|
|
279
|
-
accountId?: string;
|
|
280
|
-
source?: string;
|
|
281
|
-
log?: (msg: string) => void;
|
|
282
|
-
handler: (req: any, res: any) => Promise<void> | void;
|
|
283
|
-
registry?: any;
|
|
284
|
-
fallbackPath?: string;
|
|
285
|
-
}): () => void;
|
|
286
|
-
|
|
287
|
-
export function normalizePluginHttpPath(path?: string, fallback?: string): string | null;
|
|
288
|
-
|
|
289
|
-
// ── Ack Reactions ──
|
|
290
|
-
|
|
291
|
-
export function resolveAckReaction(cfg: OpenclawConfig, agentId?: string): string | undefined;
|
|
292
|
-
|
|
293
|
-
export function shouldAckReaction(params: {
|
|
294
|
-
scope: string;
|
|
295
|
-
isDirect: boolean;
|
|
296
|
-
isGroup: boolean;
|
|
297
|
-
isMentionableGroup?: boolean;
|
|
298
|
-
requireMention?: boolean;
|
|
299
|
-
canDetectMention?: boolean;
|
|
300
|
-
effectiveWasMentioned?: boolean;
|
|
301
|
-
shouldBypassMention?: boolean;
|
|
302
|
-
}): boolean;
|
|
303
|
-
|
|
304
|
-
// Config schemas
|
|
305
|
-
export const IMessageConfigSchema: any;
|
|
306
|
-
export const TelegramConfigSchema: any;
|
|
307
|
-
export const DiscordConfigSchema: any;
|
|
308
|
-
export const SlackConfigSchema: any;
|
|
309
|
-
export const SignalConfigSchema: any;
|
|
310
|
-
export const WhatsAppConfigSchema: any;
|
|
267
|
+
declare module "openclaw/plugin-sdk/account-resolution" {
|
|
268
|
+
export function normalizeE164(input: string): string | null;
|
|
311
269
|
}
|