@openclaw/whatsapp 2026.5.2-beta.1 → 2026.5.2
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/package.json +4 -4
- package/src/channel.ts +7 -2
- package/src/inbound/send-api.test.ts +12 -0
- package/src/inbound/send-api.ts +4 -0
- package/src/normalize-target.ts +11 -0
- package/src/normalize.ts +1 -0
- package/src/resolve-outbound-target.test.ts +27 -4
- package/src/resolve-outbound-target.ts +29 -25
- package/src/resolve-target.test.ts +23 -0
- package/src/send.test.ts +19 -0
- package/src/send.ts +7 -2
- package/src/session-route.test.ts +41 -0
- package/src/session-route.ts +9 -3
- package/src/shared.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/whatsapp",
|
|
3
|
-
"version": "2026.5.2
|
|
3
|
+
"version": "2026.5.2",
|
|
4
4
|
"description": "OpenClaw WhatsApp channel plugin",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"openclaw": "workspace:*"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"openclaw": ">=2026.5.2
|
|
22
|
+
"openclaw": ">=2026.5.2"
|
|
23
23
|
},
|
|
24
24
|
"peerDependenciesMeta": {
|
|
25
25
|
"openclaw": {
|
|
@@ -61,10 +61,10 @@
|
|
|
61
61
|
"minHostVersion": ">=2026.4.25"
|
|
62
62
|
},
|
|
63
63
|
"compat": {
|
|
64
|
-
"pluginApi": ">=2026.5.2
|
|
64
|
+
"pluginApi": ">=2026.5.2"
|
|
65
65
|
},
|
|
66
66
|
"build": {
|
|
67
|
-
"openclawVersion": "2026.5.2
|
|
67
|
+
"openclawVersion": "2026.5.2"
|
|
68
68
|
},
|
|
69
69
|
"release": {
|
|
70
70
|
"publishToClawHub": true,
|
package/src/channel.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import { checkWhatsAppHeartbeatReady } from "./heartbeat.js";
|
|
29
29
|
import {
|
|
30
30
|
isWhatsAppGroupJid,
|
|
31
|
+
isWhatsAppNewsletterJid,
|
|
31
32
|
looksLikeWhatsAppTargetId,
|
|
32
33
|
normalizeWhatsAppMessagingTarget,
|
|
33
34
|
normalizeWhatsAppTarget,
|
|
@@ -56,7 +57,11 @@ function parseWhatsAppExplicitTarget(raw: string) {
|
|
|
56
57
|
}
|
|
57
58
|
return {
|
|
58
59
|
to: normalized,
|
|
59
|
-
chatType: isWhatsAppGroupJid(normalized)
|
|
60
|
+
chatType: isWhatsAppGroupJid(normalized)
|
|
61
|
+
? ("group" as const)
|
|
62
|
+
: isWhatsAppNewsletterJid(normalized)
|
|
63
|
+
? ("channel" as const)
|
|
64
|
+
: ("direct" as const),
|
|
60
65
|
};
|
|
61
66
|
}
|
|
62
67
|
|
|
@@ -117,7 +122,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> =
|
|
|
117
122
|
inferTargetChatType: ({ to }) => parseWhatsAppExplicitTarget(to)?.chatType,
|
|
118
123
|
targetResolver: {
|
|
119
124
|
looksLikeId: looksLikeWhatsAppTargetId,
|
|
120
|
-
hint: "<E.164|group JID>",
|
|
125
|
+
hint: "<E.164|group JID|newsletter JID>",
|
|
121
126
|
},
|
|
122
127
|
},
|
|
123
128
|
directory: {
|
|
@@ -260,6 +260,18 @@ describe("createWebSendApi", () => {
|
|
|
260
260
|
expect(sendPresenceUpdate).toHaveBeenCalledWith("composing", "1555@s.whatsapp.net");
|
|
261
261
|
});
|
|
262
262
|
|
|
263
|
+
it("does not send composing presence to newsletter JIDs", async () => {
|
|
264
|
+
await api.sendComposingTo("120363401234567890@newsletter");
|
|
265
|
+
expect(sendPresenceUpdate).not.toHaveBeenCalled();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("preserves newsletter JIDs for outbound sends", async () => {
|
|
269
|
+
await api.sendMessage("120363401234567890@newsletter", "hello");
|
|
270
|
+
expect(sendMessage).toHaveBeenCalledWith("120363401234567890@newsletter", {
|
|
271
|
+
text: "hello",
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
263
275
|
it("sends media as document when mediaType is undefined", async () => {
|
|
264
276
|
const mediaBuffer = Buffer.from("test");
|
|
265
277
|
|
package/src/inbound/send-api.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
WAPresence,
|
|
6
6
|
} from "@whiskeysockets/baileys";
|
|
7
7
|
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
8
|
+
import { isWhatsAppNewsletterJid } from "../normalize.js";
|
|
8
9
|
import { buildQuotedMessageOptions } from "../quoted-message.js";
|
|
9
10
|
import { toWhatsappJid } from "../text-runtime.js";
|
|
10
11
|
import {
|
|
@@ -135,6 +136,9 @@ export function createWebSendApi(params: {
|
|
|
135
136
|
},
|
|
136
137
|
sendComposingTo: async (to: string): Promise<void> => {
|
|
137
138
|
const jid = toWhatsappJid(to);
|
|
139
|
+
if (isWhatsAppNewsletterJid(jid)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
138
142
|
await params.sock.sendPresenceUpdate("composing", jid);
|
|
139
143
|
},
|
|
140
144
|
} as const;
|
package/src/normalize-target.ts
CHANGED
|
@@ -5,6 +5,7 @@ const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
|
|
|
5
5
|
const WHATSAPP_LEGACY_USER_JID_RE = /^(\d+)@c\.us$/i;
|
|
6
6
|
const WHATSAPP_LID_RE = /^(\d+)@lid$/i;
|
|
7
7
|
const NON_WHATSAPP_PROVIDER_PREFIX_RE = /^[a-z][a-z0-9-]*:/i;
|
|
8
|
+
const WHATSAPP_NEWSLETTER_JID_RE = /^([0-9]+)@newsletter$/i;
|
|
8
9
|
|
|
9
10
|
function stripWhatsAppTargetPrefixes(value: string): string {
|
|
10
11
|
let candidate = value.trim();
|
|
@@ -30,6 +31,11 @@ export function isWhatsAppGroupJid(value: string): boolean {
|
|
|
30
31
|
return /^[0-9]+(-[0-9]+)*$/.test(localPart);
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
export function isWhatsAppNewsletterJid(value: string): boolean {
|
|
35
|
+
const candidate = stripWhatsAppTargetPrefixes(value);
|
|
36
|
+
return WHATSAPP_NEWSLETTER_JID_RE.test(candidate);
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
export function isWhatsAppUserTarget(value: string): boolean {
|
|
34
40
|
const candidate = stripWhatsAppTargetPrefixes(value);
|
|
35
41
|
return (
|
|
@@ -64,6 +70,10 @@ export function normalizeWhatsAppTarget(value: string): string | null {
|
|
|
64
70
|
const localPart = candidate.slice(0, candidate.length - "@g.us".length);
|
|
65
71
|
return `${localPart}@g.us`;
|
|
66
72
|
}
|
|
73
|
+
if (isWhatsAppNewsletterJid(candidate)) {
|
|
74
|
+
const match = candidate.match(WHATSAPP_NEWSLETTER_JID_RE);
|
|
75
|
+
return match ? `${match[1]}@newsletter` : null;
|
|
76
|
+
}
|
|
67
77
|
if (isWhatsAppUserTarget(candidate)) {
|
|
68
78
|
const phone = extractUserJidPhone(candidate);
|
|
69
79
|
if (!phone) {
|
|
@@ -106,6 +116,7 @@ export function looksLikeWhatsAppTargetId(raw: string): boolean {
|
|
|
106
116
|
return (
|
|
107
117
|
/^whatsapp:/i.test(trimmed) ||
|
|
108
118
|
isWhatsAppGroupJid(trimmed) ||
|
|
119
|
+
isWhatsAppNewsletterJid(trimmed) ||
|
|
109
120
|
isWhatsAppUserTarget(trimmed) ||
|
|
110
121
|
normalizeWhatsAppTarget(trimmed) !== null
|
|
111
122
|
);
|
package/src/normalize.ts
CHANGED
|
@@ -130,6 +130,29 @@ describe("resolveWhatsAppOutboundTarget", () => {
|
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
describe("newsletter JID handling", () => {
|
|
134
|
+
it("returns success for valid newsletter JID without applying DM allowFrom", () => {
|
|
135
|
+
vi.mocked(normalize.normalizeWhatsAppTarget).mockReturnValueOnce(
|
|
136
|
+
"120363123456789@newsletter",
|
|
137
|
+
);
|
|
138
|
+
vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false);
|
|
139
|
+
vi.mocked(normalize.isWhatsAppNewsletterJid).mockReturnValueOnce(true);
|
|
140
|
+
|
|
141
|
+
expectResolutionOk(
|
|
142
|
+
{
|
|
143
|
+
to: "120363123456789@newsletter",
|
|
144
|
+
allowFrom: [SECONDARY_TARGET],
|
|
145
|
+
mode: "implicit",
|
|
146
|
+
},
|
|
147
|
+
"120363123456789@newsletter",
|
|
148
|
+
);
|
|
149
|
+
expect(vi.mocked(normalize.normalizeWhatsAppTarget)).toHaveBeenCalledOnce();
|
|
150
|
+
expect(vi.mocked(normalize.normalizeWhatsAppTarget)).toHaveBeenCalledWith(
|
|
151
|
+
"120363123456789@newsletter",
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
133
156
|
describe("implicit/heartbeat mode with allowList", () => {
|
|
134
157
|
it("allows message when wildcard is present", () => {
|
|
135
158
|
mockNormalizedDirectMessage(PRIMARY_TARGET, PRIMARY_TARGET);
|
|
@@ -154,14 +177,14 @@ describe("resolveWhatsAppOutboundTarget", () => {
|
|
|
154
177
|
allowFrom: [SECONDARY_TARGET],
|
|
155
178
|
mode: "implicit",
|
|
156
179
|
},
|
|
157
|
-
`Target "${
|
|
180
|
+
`Target "${PRIMARY_TARGET}" is not listed in the configured WhatsApp allowFrom policy.`,
|
|
158
181
|
);
|
|
159
182
|
});
|
|
160
183
|
|
|
161
184
|
it("uses the normalized target in the allowFrom error message", () => {
|
|
162
185
|
vi.mocked(normalize.normalizeWhatsAppTarget)
|
|
163
|
-
.mockReturnValueOnce(
|
|
164
|
-
.mockReturnValueOnce(
|
|
186
|
+
.mockReturnValueOnce(PRIMARY_TARGET)
|
|
187
|
+
.mockReturnValueOnce(SECONDARY_TARGET);
|
|
165
188
|
vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false);
|
|
166
189
|
|
|
167
190
|
expectResolutionErrorMessage(
|
|
@@ -189,8 +212,8 @@ describe("resolveWhatsAppOutboundTarget", () => {
|
|
|
189
212
|
|
|
190
213
|
it("filters out invalid normalized entries from allowList", () => {
|
|
191
214
|
vi.mocked(normalize.normalizeWhatsAppTarget)
|
|
192
|
-
.mockReturnValueOnce(null)
|
|
193
215
|
.mockReturnValueOnce("+11234567890")
|
|
216
|
+
.mockReturnValueOnce(null)
|
|
194
217
|
.mockReturnValueOnce("+11234567890");
|
|
195
218
|
vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false);
|
|
196
219
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { missingTargetError } from "openclaw/plugin-sdk/channel-feedback";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isWhatsAppGroupJid,
|
|
4
|
+
isWhatsAppNewsletterJid,
|
|
5
|
+
normalizeWhatsAppTarget,
|
|
6
|
+
} from "./normalize-target.js";
|
|
3
7
|
|
|
4
8
|
export type WhatsAppOutboundTargetResolution =
|
|
5
9
|
| { ok: true; to: string }
|
|
@@ -15,6 +19,24 @@ export function resolveWhatsAppOutboundTarget(params: {
|
|
|
15
19
|
mode: string | null | undefined;
|
|
16
20
|
}): WhatsAppOutboundTargetResolution {
|
|
17
21
|
const trimmed = params.to?.trim() ?? "";
|
|
22
|
+
if (!trimmed) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
error: missingTargetError("WhatsApp", "<E.164|group JID|newsletter JID>"),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const normalizedTo = normalizeWhatsAppTarget(trimmed);
|
|
30
|
+
if (!normalizedTo) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
error: missingTargetError("WhatsApp", "<E.164|group JID|newsletter JID>"),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (isWhatsAppGroupJid(normalizedTo) || isWhatsAppNewsletterJid(normalizedTo)) {
|
|
37
|
+
return { ok: true, to: normalizedTo };
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
const allowListRaw = (params.allowFrom ?? [])
|
|
19
41
|
.map((entry) => String(entry).trim())
|
|
20
42
|
.filter(Boolean);
|
|
@@ -23,32 +45,14 @@ export function resolveWhatsAppOutboundTarget(params: {
|
|
|
23
45
|
.filter((entry) => entry !== "*")
|
|
24
46
|
.map((entry) => normalizeWhatsAppTarget(entry))
|
|
25
47
|
.filter((entry): entry is string => Boolean(entry));
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
ok: false,
|
|
32
|
-
error: missingTargetError("WhatsApp", "<E.164|group JID>"),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
if (isWhatsAppGroupJid(normalizedTo)) {
|
|
36
|
-
return { ok: true, to: normalizedTo };
|
|
37
|
-
}
|
|
38
|
-
if (hasWildcard || allowList.length === 0) {
|
|
39
|
-
return { ok: true, to: normalizedTo };
|
|
40
|
-
}
|
|
41
|
-
if (allowList.includes(normalizedTo)) {
|
|
42
|
-
return { ok: true, to: normalizedTo };
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
ok: false,
|
|
46
|
-
error: whatsappAllowFromPolicyError(normalizedTo),
|
|
47
|
-
};
|
|
48
|
+
if (hasWildcard || allowList.length === 0) {
|
|
49
|
+
return { ok: true, to: normalizedTo };
|
|
50
|
+
}
|
|
51
|
+
if (allowList.includes(normalizedTo)) {
|
|
52
|
+
return { ok: true, to: normalizedTo };
|
|
48
53
|
}
|
|
49
|
-
|
|
50
54
|
return {
|
|
51
55
|
ok: false,
|
|
52
|
-
error:
|
|
56
|
+
error: whatsappAllowFromPolicyError(normalizedTo),
|
|
53
57
|
};
|
|
54
58
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
isWhatsAppGroupJid,
|
|
4
|
+
isWhatsAppNewsletterJid,
|
|
4
5
|
looksLikeWhatsAppTargetId,
|
|
5
6
|
isWhatsAppUserTarget,
|
|
6
7
|
normalizeWhatsAppMessagingTarget,
|
|
@@ -16,6 +17,15 @@ describe("normalizeWhatsAppTarget", () => {
|
|
|
16
17
|
);
|
|
17
18
|
});
|
|
18
19
|
|
|
20
|
+
it("preserves newsletter JIDs", () => {
|
|
21
|
+
expect(normalizeWhatsAppTarget("120363401234567890@newsletter")).toBe(
|
|
22
|
+
"120363401234567890@newsletter",
|
|
23
|
+
);
|
|
24
|
+
expect(normalizeWhatsAppTarget("WhatsApp:120363401234567890@NEWSLETTER")).toBe(
|
|
25
|
+
"120363401234567890@newsletter",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
19
29
|
it("normalizes direct JIDs to E.164", () => {
|
|
20
30
|
expect(normalizeWhatsAppTarget("1555123@s.whatsapp.net")).toBe("+1555123");
|
|
21
31
|
});
|
|
@@ -40,6 +50,7 @@ describe("normalizeWhatsAppTarget", () => {
|
|
|
40
50
|
expect(normalizeWhatsAppTarget("group:123456789-987654321@g.us")).toBeNull();
|
|
41
51
|
expect(normalizeWhatsAppTarget(" WhatsApp:Group:123456789-987654321@G.US ")).toBeNull();
|
|
42
52
|
expect(normalizeWhatsAppTarget("abc@s.whatsapp.net")).toBeNull();
|
|
53
|
+
expect(normalizeWhatsAppTarget("abc@newsletter")).toBeNull();
|
|
43
54
|
});
|
|
44
55
|
|
|
45
56
|
it("rejects non-WhatsApp provider-prefixed phone-like targets", () => {
|
|
@@ -68,6 +79,17 @@ describe("isWhatsAppUserTarget", () => {
|
|
|
68
79
|
});
|
|
69
80
|
});
|
|
70
81
|
|
|
82
|
+
describe("isWhatsAppNewsletterJid", () => {
|
|
83
|
+
it("detects newsletter JIDs with or without prefixes", () => {
|
|
84
|
+
expect(isWhatsAppNewsletterJid("120363401234567890@newsletter")).toBe(true);
|
|
85
|
+
expect(isWhatsAppNewsletterJid("whatsapp:120363401234567890@newsletter")).toBe(true);
|
|
86
|
+
expect(isWhatsAppNewsletterJid("120363401234567890@NEWSLETTER")).toBe(true);
|
|
87
|
+
expect(isWhatsAppNewsletterJid("abc@newsletter")).toBe(false);
|
|
88
|
+
expect(isWhatsAppNewsletterJid("120363401234567890@g.us")).toBe(false);
|
|
89
|
+
expect(isWhatsAppNewsletterJid("+1555123")).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
71
93
|
describe("isWhatsAppGroupJid", () => {
|
|
72
94
|
it("detects group JIDs with or without prefixes", () => {
|
|
73
95
|
expect(isWhatsAppGroupJid("120363401234567890@g.us")).toBe(true);
|
|
@@ -91,6 +113,7 @@ describe("looksLikeWhatsAppTargetId", () => {
|
|
|
91
113
|
it("detects common WhatsApp target forms", () => {
|
|
92
114
|
expect(looksLikeWhatsAppTargetId("whatsapp:+15555550123")).toBe(true);
|
|
93
115
|
expect(looksLikeWhatsAppTargetId("15555550123@c.us")).toBe(true);
|
|
116
|
+
expect(looksLikeWhatsAppTargetId("120363401234567890@newsletter")).toBe(true);
|
|
94
117
|
expect(looksLikeWhatsAppTargetId("+15555550123")).toBe(true);
|
|
95
118
|
expect(looksLikeWhatsAppTargetId("")).toBe(false);
|
|
96
119
|
});
|
package/src/send.test.ts
CHANGED
|
@@ -144,6 +144,25 @@ describe("web outbound", () => {
|
|
|
144
144
|
expect(sendMessage).toHaveBeenCalledWith("+1555", "hi", undefined, undefined);
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
+
it("sends newsletter messages via the active listener without composing presence", async () => {
|
|
148
|
+
const result = await sendMessageWhatsApp("120363401234567890@newsletter", "hi", {
|
|
149
|
+
verbose: false,
|
|
150
|
+
cfg: WHATSAPP_TEST_CFG,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(result).toEqual({
|
|
154
|
+
messageId: "msg123",
|
|
155
|
+
toJid: "120363401234567890@newsletter",
|
|
156
|
+
});
|
|
157
|
+
expect(sendComposingTo).not.toHaveBeenCalled();
|
|
158
|
+
expect(sendMessage).toHaveBeenCalledWith(
|
|
159
|
+
"120363401234567890@newsletter",
|
|
160
|
+
"hi",
|
|
161
|
+
undefined,
|
|
162
|
+
undefined,
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
147
166
|
it("uses configured defaultAccount when outbound accountId is omitted", async () => {
|
|
148
167
|
hoisted.controllerListeners.clear();
|
|
149
168
|
hoisted.controllerListeners.set("work", {
|
package/src/send.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "./accounts.js";
|
|
17
17
|
import { getRegisteredWhatsAppConnectionController } from "./connection-controller-registry.js";
|
|
18
18
|
import type { ActiveWebListener, ActiveWebSendOptions } from "./inbound/types.js";
|
|
19
|
+
import { isWhatsAppNewsletterJid } from "./normalize.js";
|
|
19
20
|
import {
|
|
20
21
|
normalizeWhatsAppPayloadText,
|
|
21
22
|
prepareWhatsAppOutboundMedia,
|
|
@@ -142,7 +143,9 @@ export async function sendMessageWhatsApp(
|
|
|
142
143
|
}
|
|
143
144
|
outboundLog.info(`Sending message -> ${redactedJid}${primaryMediaUrl ? " (media)" : ""}`);
|
|
144
145
|
logger.info({ jid: redactedJid, hasMedia: Boolean(primaryMediaUrl) }, "sending message");
|
|
145
|
-
|
|
146
|
+
if (!isWhatsAppNewsletterJid(jid)) {
|
|
147
|
+
await active.sendComposingTo(to);
|
|
148
|
+
}
|
|
146
149
|
const hasExplicitAccountId = Boolean(options.accountId?.trim());
|
|
147
150
|
const accountId = hasExplicitAccountId ? resolvedAccountId : undefined;
|
|
148
151
|
const sendOptions: ActiveWebSendOptions | undefined =
|
|
@@ -192,7 +195,9 @@ export async function sendTypingWhatsApp(
|
|
|
192
195
|
cfg,
|
|
193
196
|
accountId: options.accountId,
|
|
194
197
|
});
|
|
195
|
-
|
|
198
|
+
if (!isWhatsAppNewsletterJid(toWhatsappJid(to))) {
|
|
199
|
+
await active.sendComposingTo(to);
|
|
200
|
+
}
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
export async function sendReactionWhatsApp(
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { resolveWhatsAppOutboundSessionRoute } from "./session-route.js";
|
|
3
|
+
|
|
4
|
+
describe("resolveWhatsAppOutboundSessionRoute", () => {
|
|
5
|
+
it("routes newsletter JIDs as channel sessions", () => {
|
|
6
|
+
const route = resolveWhatsAppOutboundSessionRoute({
|
|
7
|
+
cfg: {},
|
|
8
|
+
agentId: "main",
|
|
9
|
+
target: "120363401234567890@newsletter",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(route).toMatchObject({
|
|
13
|
+
sessionKey: "agent:main:whatsapp:channel:120363401234567890@newsletter",
|
|
14
|
+
baseSessionKey: "agent:main:whatsapp:channel:120363401234567890@newsletter",
|
|
15
|
+
peer: {
|
|
16
|
+
kind: "channel",
|
|
17
|
+
id: "120363401234567890@newsletter",
|
|
18
|
+
},
|
|
19
|
+
chatType: "channel",
|
|
20
|
+
from: "120363401234567890@newsletter",
|
|
21
|
+
to: "120363401234567890@newsletter",
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("keeps direct user targets on direct session semantics", () => {
|
|
26
|
+
const route = resolveWhatsAppOutboundSessionRoute({
|
|
27
|
+
cfg: { session: { dmScope: "per-channel-peer" } },
|
|
28
|
+
agentId: "main",
|
|
29
|
+
target: "+15551234567",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(route).toMatchObject({
|
|
33
|
+
sessionKey: "agent:main:whatsapp:direct:+15551234567",
|
|
34
|
+
peer: {
|
|
35
|
+
kind: "direct",
|
|
36
|
+
id: "+15551234567",
|
|
37
|
+
},
|
|
38
|
+
chatType: "direct",
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
package/src/session-route.ts
CHANGED
|
@@ -2,7 +2,11 @@ import {
|
|
|
2
2
|
buildChannelOutboundSessionRoute,
|
|
3
3
|
type ChannelOutboundSessionRouteParams,
|
|
4
4
|
} from "openclaw/plugin-sdk/core";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
isWhatsAppGroupJid,
|
|
7
|
+
isWhatsAppNewsletterJid,
|
|
8
|
+
normalizeWhatsAppTarget,
|
|
9
|
+
} from "./normalize.js";
|
|
6
10
|
|
|
7
11
|
export function resolveWhatsAppOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
|
|
8
12
|
const normalized = normalizeWhatsAppTarget(params.target);
|
|
@@ -10,16 +14,18 @@ export function resolveWhatsAppOutboundSessionRoute(params: ChannelOutboundSessi
|
|
|
10
14
|
return null;
|
|
11
15
|
}
|
|
12
16
|
const isGroup = isWhatsAppGroupJid(normalized);
|
|
17
|
+
const isNewsletter = isWhatsAppNewsletterJid(normalized);
|
|
18
|
+
const chatType = isGroup ? "group" : isNewsletter ? "channel" : "direct";
|
|
13
19
|
return buildChannelOutboundSessionRoute({
|
|
14
20
|
cfg: params.cfg,
|
|
15
21
|
agentId: params.agentId,
|
|
16
22
|
channel: "whatsapp",
|
|
17
23
|
accountId: params.accountId,
|
|
18
24
|
peer: {
|
|
19
|
-
kind:
|
|
25
|
+
kind: chatType,
|
|
20
26
|
id: normalized,
|
|
21
27
|
},
|
|
22
|
-
chatType
|
|
28
|
+
chatType,
|
|
23
29
|
from: normalized,
|
|
24
30
|
to: normalized,
|
|
25
31
|
});
|
package/src/shared.ts
CHANGED
|
@@ -208,7 +208,7 @@ export function createWhatsAppPluginBase(params: {
|
|
|
208
208
|
},
|
|
209
209
|
setupWizard: params.setupWizard,
|
|
210
210
|
capabilities: {
|
|
211
|
-
chatTypes: ["direct", "group"],
|
|
211
|
+
chatTypes: ["direct", "group", "channel"],
|
|
212
212
|
polls: true,
|
|
213
213
|
reactions: true,
|
|
214
214
|
media: true,
|