@openclaw/bluebubbles 2026.1.29 → 2026.2.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/index.ts +0 -1
- package/openclaw.plugin.json +1 -3
- package/package.json +5 -2
- package/src/accounts.ts +13 -5
- package/src/actions.test.ts +1 -2
- package/src/actions.ts +70 -35
- package/src/attachments.test.ts +1 -2
- package/src/attachments.ts +38 -20
- package/src/channel.ts +50 -35
- package/src/chat.test.ts +0 -1
- package/src/chat.ts +48 -24
- package/src/media-send.ts +12 -6
- package/src/monitor.test.ts +253 -57
- package/src/monitor.ts +377 -163
- package/src/onboarding.ts +19 -7
- package/src/probe.ts +19 -11
- package/src/reactions.test.ts +0 -1
- package/src/reactions.ts +20 -15
- package/src/send.test.ts +1 -2
- package/src/send.ts +75 -26
- package/src/targets.test.ts +0 -1
- package/src/targets.ts +151 -52
package/src/targets.ts
CHANGED
|
@@ -20,20 +20,27 @@ const SERVICE_PREFIXES: Array<{ prefix: string; service: BlueBubblesService }> =
|
|
|
20
20
|
{ prefix: "sms:", service: "sms" },
|
|
21
21
|
{ prefix: "auto:", service: "auto" },
|
|
22
22
|
];
|
|
23
|
-
const CHAT_IDENTIFIER_UUID_RE =
|
|
24
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
23
|
+
const CHAT_IDENTIFIER_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
25
24
|
const CHAT_IDENTIFIER_HEX_RE = /^[0-9a-f]{24,64}$/i;
|
|
26
25
|
|
|
27
26
|
function parseRawChatGuid(value: string): string | null {
|
|
28
27
|
const trimmed = value.trim();
|
|
29
|
-
if (!trimmed)
|
|
28
|
+
if (!trimmed) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
30
31
|
const parts = trimmed.split(";");
|
|
31
|
-
if (parts.length !== 3)
|
|
32
|
+
if (parts.length !== 3) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
32
35
|
const service = parts[0]?.trim();
|
|
33
36
|
const separator = parts[1]?.trim();
|
|
34
37
|
const identifier = parts[2]?.trim();
|
|
35
|
-
if (!service || !identifier)
|
|
36
|
-
|
|
38
|
+
if (!service || !identifier) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (separator !== "+" && separator !== "-") {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
37
44
|
return `${service};${separator};${identifier}`;
|
|
38
45
|
}
|
|
39
46
|
|
|
@@ -43,26 +50,44 @@ function stripPrefix(value: string, prefix: string): string {
|
|
|
43
50
|
|
|
44
51
|
function stripBlueBubblesPrefix(value: string): string {
|
|
45
52
|
const trimmed = value.trim();
|
|
46
|
-
if (!trimmed)
|
|
47
|
-
|
|
53
|
+
if (!trimmed) {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
if (!trimmed.toLowerCase().startsWith("bluebubbles:")) {
|
|
57
|
+
return trimmed;
|
|
58
|
+
}
|
|
48
59
|
return trimmed.slice("bluebubbles:".length).trim();
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
function looksLikeRawChatIdentifier(value: string): boolean {
|
|
52
63
|
const trimmed = value.trim();
|
|
53
|
-
if (!trimmed)
|
|
54
|
-
|
|
64
|
+
if (!trimmed) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (/^chat\d+$/i.test(trimmed)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
55
70
|
return CHAT_IDENTIFIER_UUID_RE.test(trimmed) || CHAT_IDENTIFIER_HEX_RE.test(trimmed);
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
export function normalizeBlueBubblesHandle(raw: string): string {
|
|
59
74
|
const trimmed = raw.trim();
|
|
60
|
-
if (!trimmed)
|
|
75
|
+
if (!trimmed) {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
61
78
|
const lowered = trimmed.toLowerCase();
|
|
62
|
-
if (lowered.startsWith("imessage:"))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
79
|
+
if (lowered.startsWith("imessage:")) {
|
|
80
|
+
return normalizeBlueBubblesHandle(trimmed.slice(9));
|
|
81
|
+
}
|
|
82
|
+
if (lowered.startsWith("sms:")) {
|
|
83
|
+
return normalizeBlueBubblesHandle(trimmed.slice(4));
|
|
84
|
+
}
|
|
85
|
+
if (lowered.startsWith("auto:")) {
|
|
86
|
+
return normalizeBlueBubblesHandle(trimmed.slice(5));
|
|
87
|
+
}
|
|
88
|
+
if (trimmed.includes("@")) {
|
|
89
|
+
return trimmed.toLowerCase();
|
|
90
|
+
}
|
|
66
91
|
return trimmed.replace(/\s+/g, "");
|
|
67
92
|
}
|
|
68
93
|
|
|
@@ -76,30 +101,44 @@ export function extractHandleFromChatGuid(chatGuid: string): string | null {
|
|
|
76
101
|
// DM format: service;-;handle (3 parts, middle is "-")
|
|
77
102
|
if (parts.length === 3 && parts[1] === "-") {
|
|
78
103
|
const handle = parts[2]?.trim();
|
|
79
|
-
if (handle)
|
|
104
|
+
if (handle) {
|
|
105
|
+
return normalizeBlueBubblesHandle(handle);
|
|
106
|
+
}
|
|
80
107
|
}
|
|
81
108
|
return null;
|
|
82
109
|
}
|
|
83
110
|
|
|
84
111
|
export function normalizeBlueBubblesMessagingTarget(raw: string): string | undefined {
|
|
85
112
|
let trimmed = raw.trim();
|
|
86
|
-
if (!trimmed)
|
|
113
|
+
if (!trimmed) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
87
116
|
trimmed = stripBlueBubblesPrefix(trimmed);
|
|
88
|
-
if (!trimmed)
|
|
117
|
+
if (!trimmed) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
89
120
|
try {
|
|
90
121
|
const parsed = parseBlueBubblesTarget(trimmed);
|
|
91
|
-
if (parsed.kind === "chat_id")
|
|
122
|
+
if (parsed.kind === "chat_id") {
|
|
123
|
+
return `chat_id:${parsed.chatId}`;
|
|
124
|
+
}
|
|
92
125
|
if (parsed.kind === "chat_guid") {
|
|
93
126
|
// For DM chat_guids, normalize to just the handle for easier comparison.
|
|
94
127
|
// This allows "chat_guid:iMessage;-;+1234567890" to match "+1234567890".
|
|
95
128
|
const handle = extractHandleFromChatGuid(parsed.chatGuid);
|
|
96
|
-
if (handle)
|
|
129
|
+
if (handle) {
|
|
130
|
+
return handle;
|
|
131
|
+
}
|
|
97
132
|
// For group chats or unrecognized formats, keep the full chat_guid
|
|
98
133
|
return `chat_guid:${parsed.chatGuid}`;
|
|
99
134
|
}
|
|
100
|
-
if (parsed.kind === "chat_identifier")
|
|
135
|
+
if (parsed.kind === "chat_identifier") {
|
|
136
|
+
return `chat_identifier:${parsed.chatIdentifier}`;
|
|
137
|
+
}
|
|
101
138
|
const handle = normalizeBlueBubblesHandle(parsed.to);
|
|
102
|
-
if (!handle)
|
|
139
|
+
if (!handle) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
103
142
|
return parsed.service === "auto" ? handle : `${parsed.service}:${handle}`;
|
|
104
143
|
} catch {
|
|
105
144
|
return trimmed;
|
|
@@ -108,12 +147,20 @@ export function normalizeBlueBubblesMessagingTarget(raw: string): string | undef
|
|
|
108
147
|
|
|
109
148
|
export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string): boolean {
|
|
110
149
|
const trimmed = raw.trim();
|
|
111
|
-
if (!trimmed)
|
|
150
|
+
if (!trimmed) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
112
153
|
const candidate = stripBlueBubblesPrefix(trimmed);
|
|
113
|
-
if (!candidate)
|
|
114
|
-
|
|
154
|
+
if (!candidate) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (parseRawChatGuid(candidate)) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
115
160
|
const lowered = candidate.toLowerCase();
|
|
116
|
-
if (/^(imessage|sms|auto):/.test(lowered))
|
|
161
|
+
if (/^(imessage|sms|auto):/.test(lowered)) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
117
164
|
if (
|
|
118
165
|
/^(chat_id|chatid|chat|chat_guid|chatguid|guid|chat_identifier|chatidentifier|chatident|group):/.test(
|
|
119
166
|
lowered,
|
|
@@ -122,14 +169,24 @@ export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string):
|
|
|
122
169
|
return true;
|
|
123
170
|
}
|
|
124
171
|
// Recognize chat<digits> patterns (e.g., "chat660250192681427962") as chat IDs
|
|
125
|
-
if (/^chat\d+$/i.test(candidate))
|
|
126
|
-
|
|
127
|
-
|
|
172
|
+
if (/^chat\d+$/i.test(candidate)) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
if (looksLikeRawChatIdentifier(candidate)) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
if (candidate.includes("@")) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
128
181
|
const digitsOnly = candidate.replace(/[\s().-]/g, "");
|
|
129
|
-
if (/^\+?\d{3,}$/.test(digitsOnly))
|
|
182
|
+
if (/^\+?\d{3,}$/.test(digitsOnly)) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
130
185
|
if (normalized) {
|
|
131
186
|
const normalizedTrimmed = normalized.trim();
|
|
132
|
-
if (!normalizedTrimmed)
|
|
187
|
+
if (!normalizedTrimmed) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
133
190
|
const normalizedLower = normalizedTrimmed.toLowerCase();
|
|
134
191
|
if (
|
|
135
192
|
/^(imessage|sms|auto):/.test(normalizedLower) ||
|
|
@@ -143,13 +200,17 @@ export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string):
|
|
|
143
200
|
|
|
144
201
|
export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
|
|
145
202
|
const trimmed = stripBlueBubblesPrefix(raw);
|
|
146
|
-
if (!trimmed)
|
|
203
|
+
if (!trimmed) {
|
|
204
|
+
throw new Error("BlueBubbles target is required");
|
|
205
|
+
}
|
|
147
206
|
const lower = trimmed.toLowerCase();
|
|
148
207
|
|
|
149
208
|
for (const { prefix, service } of SERVICE_PREFIXES) {
|
|
150
209
|
if (lower.startsWith(prefix)) {
|
|
151
210
|
const remainder = stripPrefix(trimmed, prefix);
|
|
152
|
-
if (!remainder)
|
|
211
|
+
if (!remainder) {
|
|
212
|
+
throw new Error(`${prefix} target is required`);
|
|
213
|
+
}
|
|
153
214
|
const remainderLower = remainder.toLowerCase();
|
|
154
215
|
const isChatTarget =
|
|
155
216
|
CHAT_ID_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
|
|
@@ -177,7 +238,9 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
|
|
|
177
238
|
for (const prefix of CHAT_GUID_PREFIXES) {
|
|
178
239
|
if (lower.startsWith(prefix)) {
|
|
179
240
|
const value = stripPrefix(trimmed, prefix);
|
|
180
|
-
if (!value)
|
|
241
|
+
if (!value) {
|
|
242
|
+
throw new Error("chat_guid is required");
|
|
243
|
+
}
|
|
181
244
|
return { kind: "chat_guid", chatGuid: value };
|
|
182
245
|
}
|
|
183
246
|
}
|
|
@@ -185,7 +248,9 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
|
|
|
185
248
|
for (const prefix of CHAT_IDENTIFIER_PREFIXES) {
|
|
186
249
|
if (lower.startsWith(prefix)) {
|
|
187
250
|
const value = stripPrefix(trimmed, prefix);
|
|
188
|
-
if (!value)
|
|
251
|
+
if (!value) {
|
|
252
|
+
throw new Error("chat_identifier is required");
|
|
253
|
+
}
|
|
189
254
|
return { kind: "chat_identifier", chatIdentifier: value };
|
|
190
255
|
}
|
|
191
256
|
}
|
|
@@ -196,7 +261,9 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
|
|
|
196
261
|
if (Number.isFinite(chatId)) {
|
|
197
262
|
return { kind: "chat_id", chatId };
|
|
198
263
|
}
|
|
199
|
-
if (!value)
|
|
264
|
+
if (!value) {
|
|
265
|
+
throw new Error("group target is required");
|
|
266
|
+
}
|
|
200
267
|
return { kind: "chat_guid", chatGuid: value };
|
|
201
268
|
}
|
|
202
269
|
|
|
@@ -221,13 +288,17 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
|
|
|
221
288
|
|
|
222
289
|
export function parseBlueBubblesAllowTarget(raw: string): BlueBubblesAllowTarget {
|
|
223
290
|
const trimmed = raw.trim();
|
|
224
|
-
if (!trimmed)
|
|
291
|
+
if (!trimmed) {
|
|
292
|
+
return { kind: "handle", handle: "" };
|
|
293
|
+
}
|
|
225
294
|
const lower = trimmed.toLowerCase();
|
|
226
295
|
|
|
227
296
|
for (const { prefix } of SERVICE_PREFIXES) {
|
|
228
297
|
if (lower.startsWith(prefix)) {
|
|
229
298
|
const remainder = stripPrefix(trimmed, prefix);
|
|
230
|
-
if (!remainder)
|
|
299
|
+
if (!remainder) {
|
|
300
|
+
return { kind: "handle", handle: "" };
|
|
301
|
+
}
|
|
231
302
|
return parseBlueBubblesAllowTarget(remainder);
|
|
232
303
|
}
|
|
233
304
|
}
|
|
@@ -236,29 +307,39 @@ export function parseBlueBubblesAllowTarget(raw: string): BlueBubblesAllowTarget
|
|
|
236
307
|
if (lower.startsWith(prefix)) {
|
|
237
308
|
const value = stripPrefix(trimmed, prefix);
|
|
238
309
|
const chatId = Number.parseInt(value, 10);
|
|
239
|
-
if (Number.isFinite(chatId))
|
|
310
|
+
if (Number.isFinite(chatId)) {
|
|
311
|
+
return { kind: "chat_id", chatId };
|
|
312
|
+
}
|
|
240
313
|
}
|
|
241
314
|
}
|
|
242
315
|
|
|
243
316
|
for (const prefix of CHAT_GUID_PREFIXES) {
|
|
244
317
|
if (lower.startsWith(prefix)) {
|
|
245
318
|
const value = stripPrefix(trimmed, prefix);
|
|
246
|
-
if (value)
|
|
319
|
+
if (value) {
|
|
320
|
+
return { kind: "chat_guid", chatGuid: value };
|
|
321
|
+
}
|
|
247
322
|
}
|
|
248
323
|
}
|
|
249
324
|
|
|
250
325
|
for (const prefix of CHAT_IDENTIFIER_PREFIXES) {
|
|
251
326
|
if (lower.startsWith(prefix)) {
|
|
252
327
|
const value = stripPrefix(trimmed, prefix);
|
|
253
|
-
if (value)
|
|
328
|
+
if (value) {
|
|
329
|
+
return { kind: "chat_identifier", chatIdentifier: value };
|
|
330
|
+
}
|
|
254
331
|
}
|
|
255
332
|
}
|
|
256
333
|
|
|
257
334
|
if (lower.startsWith("group:")) {
|
|
258
335
|
const value = stripPrefix(trimmed, "group:");
|
|
259
336
|
const chatId = Number.parseInt(value, 10);
|
|
260
|
-
if (Number.isFinite(chatId))
|
|
261
|
-
|
|
337
|
+
if (Number.isFinite(chatId)) {
|
|
338
|
+
return { kind: "chat_id", chatId };
|
|
339
|
+
}
|
|
340
|
+
if (value) {
|
|
341
|
+
return { kind: "chat_guid", chatGuid: value };
|
|
342
|
+
}
|
|
262
343
|
}
|
|
263
344
|
|
|
264
345
|
// Handle chat<digits> pattern (e.g., "chat660250192681427962") as chat_identifier
|
|
@@ -283,8 +364,12 @@ export function isAllowedBlueBubblesSender(params: {
|
|
|
283
364
|
chatIdentifier?: string | null;
|
|
284
365
|
}): boolean {
|
|
285
366
|
const allowFrom = params.allowFrom.map((entry) => String(entry).trim());
|
|
286
|
-
if (allowFrom.length === 0)
|
|
287
|
-
|
|
367
|
+
if (allowFrom.length === 0) {
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
if (allowFrom.includes("*")) {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
288
373
|
|
|
289
374
|
const senderNormalized = normalizeBlueBubblesHandle(params.sender);
|
|
290
375
|
const chatId = params.chatId ?? undefined;
|
|
@@ -292,16 +377,26 @@ export function isAllowedBlueBubblesSender(params: {
|
|
|
292
377
|
const chatIdentifier = params.chatIdentifier?.trim();
|
|
293
378
|
|
|
294
379
|
for (const entry of allowFrom) {
|
|
295
|
-
if (!entry)
|
|
380
|
+
if (!entry) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
296
383
|
const parsed = parseBlueBubblesAllowTarget(entry);
|
|
297
384
|
if (parsed.kind === "chat_id" && chatId !== undefined) {
|
|
298
|
-
if (parsed.chatId === chatId)
|
|
385
|
+
if (parsed.chatId === chatId) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
299
388
|
} else if (parsed.kind === "chat_guid" && chatGuid) {
|
|
300
|
-
if (parsed.chatGuid === chatGuid)
|
|
389
|
+
if (parsed.chatGuid === chatGuid) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
301
392
|
} else if (parsed.kind === "chat_identifier" && chatIdentifier) {
|
|
302
|
-
if (parsed.chatIdentifier === chatIdentifier)
|
|
393
|
+
if (parsed.chatIdentifier === chatIdentifier) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
303
396
|
} else if (parsed.kind === "handle" && senderNormalized) {
|
|
304
|
-
if (parsed.handle === senderNormalized)
|
|
397
|
+
if (parsed.handle === senderNormalized) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
305
400
|
}
|
|
306
401
|
}
|
|
307
402
|
return false;
|
|
@@ -316,8 +411,12 @@ export function formatBlueBubblesChatTarget(params: {
|
|
|
316
411
|
return `chat_id:${params.chatId}`;
|
|
317
412
|
}
|
|
318
413
|
const guid = params.chatGuid?.trim();
|
|
319
|
-
if (guid)
|
|
414
|
+
if (guid) {
|
|
415
|
+
return `chat_guid:${guid}`;
|
|
416
|
+
}
|
|
320
417
|
const identifier = params.chatIdentifier?.trim();
|
|
321
|
-
if (identifier)
|
|
418
|
+
if (identifier) {
|
|
419
|
+
return `chat_identifier:${identifier}`;
|
|
420
|
+
}
|
|
322
421
|
return "";
|
|
323
422
|
}
|