@gakr-gakr/whatsapp 0.1.0

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.
Files changed (159) hide show
  1. package/action-runtime-api.ts +1 -0
  2. package/action-runtime.runtime.ts +1 -0
  3. package/api.ts +67 -0
  4. package/auth-presence.ts +80 -0
  5. package/autobot.plugin.json +23 -0
  6. package/channel-config-api.ts +1 -0
  7. package/channel-plugin-api.ts +3 -0
  8. package/config-api.ts +4 -0
  9. package/constants.ts +1 -0
  10. package/contract-api.ts +29 -0
  11. package/directory-contract-api.ts +4 -0
  12. package/doctor-contract-api.ts +8 -0
  13. package/index.ts +16 -0
  14. package/legacy-session-surface-api.ts +6 -0
  15. package/legacy-state-migrations-api.ts +1 -0
  16. package/light-runtime-api.ts +12 -0
  17. package/login-qr-api.ts +1 -0
  18. package/login-qr-runtime.ts +23 -0
  19. package/outbound-payload-test-api.ts +1 -0
  20. package/package.json +76 -0
  21. package/runtime-api.ts +84 -0
  22. package/secret-contract-api.ts +4 -0
  23. package/security-contract-api.ts +4 -0
  24. package/setup-entry.ts +21 -0
  25. package/setup-plugin-api.ts +3 -0
  26. package/src/account-config.ts +77 -0
  27. package/src/account-ids.ts +17 -0
  28. package/src/account-types.ts +5 -0
  29. package/src/accounts.ts +176 -0
  30. package/src/action-runtime-target-auth.ts +27 -0
  31. package/src/action-runtime.ts +76 -0
  32. package/src/active-listener.ts +17 -0
  33. package/src/agent-tools-login.ts +113 -0
  34. package/src/approval-auth.ts +27 -0
  35. package/src/auth-store.runtime.ts +1 -0
  36. package/src/auth-store.ts +494 -0
  37. package/src/auto-reply/config.runtime.ts +16 -0
  38. package/src/auto-reply/constants.ts +1 -0
  39. package/src/auto-reply/deliver-reply.ts +332 -0
  40. package/src/auto-reply/loggers.ts +6 -0
  41. package/src/auto-reply/mentions.ts +131 -0
  42. package/src/auto-reply/monitor/ack-reaction.ts +99 -0
  43. package/src/auto-reply/monitor/audio-preflight.runtime.ts +9 -0
  44. package/src/auto-reply/monitor/broadcast.ts +153 -0
  45. package/src/auto-reply/monitor/commands.ts +19 -0
  46. package/src/auto-reply/monitor/echo.ts +64 -0
  47. package/src/auto-reply/monitor/group-activation.runtime.ts +1 -0
  48. package/src/auto-reply/monitor/group-activation.ts +73 -0
  49. package/src/auto-reply/monitor/group-gating.runtime.ts +8 -0
  50. package/src/auto-reply/monitor/group-gating.ts +218 -0
  51. package/src/auto-reply/monitor/group-members.ts +65 -0
  52. package/src/auto-reply/monitor/inbound-context.ts +92 -0
  53. package/src/auto-reply/monitor/inbound-dispatch.runtime.ts +22 -0
  54. package/src/auto-reply/monitor/inbound-dispatch.ts +749 -0
  55. package/src/auto-reply/monitor/last-route.ts +61 -0
  56. package/src/auto-reply/monitor/listener-log.ts +28 -0
  57. package/src/auto-reply/monitor/message-line.runtime.ts +38 -0
  58. package/src/auto-reply/monitor/message-line.ts +54 -0
  59. package/src/auto-reply/monitor/on-message.ts +333 -0
  60. package/src/auto-reply/monitor/peer.ts +17 -0
  61. package/src/auto-reply/monitor/process-message.ts +584 -0
  62. package/src/auto-reply/monitor/runtime-api.ts +36 -0
  63. package/src/auto-reply/monitor/status-reaction.ts +108 -0
  64. package/src/auto-reply/monitor-state.ts +114 -0
  65. package/src/auto-reply/monitor.ts +720 -0
  66. package/src/auto-reply/reply-resolver.runtime.ts +1 -0
  67. package/src/auto-reply/types.ts +48 -0
  68. package/src/auto-reply/util.ts +62 -0
  69. package/src/auto-reply.impl.ts +6 -0
  70. package/src/auto-reply.ts +1 -0
  71. package/src/channel-actions.runtime.ts +7 -0
  72. package/src/channel-actions.ts +85 -0
  73. package/src/channel-outbound.ts +87 -0
  74. package/src/channel-react-action.runtime.ts +10 -0
  75. package/src/channel-react-action.ts +247 -0
  76. package/src/channel.runtime.ts +117 -0
  77. package/src/channel.setup.ts +32 -0
  78. package/src/channel.ts +356 -0
  79. package/src/command-policy.ts +7 -0
  80. package/src/config-accessors.ts +22 -0
  81. package/src/config-schema.ts +6 -0
  82. package/src/config-ui-hints.ts +24 -0
  83. package/src/connection-controller-registry.ts +49 -0
  84. package/src/connection-controller.ts +680 -0
  85. package/src/creds-files.ts +19 -0
  86. package/src/creds-persistence.ts +71 -0
  87. package/src/directory-config.ts +40 -0
  88. package/src/doctor-contract.ts +11 -0
  89. package/src/doctor.ts +56 -0
  90. package/src/document-filename.ts +17 -0
  91. package/src/group-intro.ts +15 -0
  92. package/src/group-policy.ts +40 -0
  93. package/src/group-session-contract.ts +20 -0
  94. package/src/group-session-key.ts +42 -0
  95. package/src/heartbeat.ts +34 -0
  96. package/src/identity.ts +164 -0
  97. package/src/inbound/access-control.ts +187 -0
  98. package/src/inbound/dedupe.ts +132 -0
  99. package/src/inbound/extract.ts +484 -0
  100. package/src/inbound/lifecycle.ts +39 -0
  101. package/src/inbound/media.ts +128 -0
  102. package/src/inbound/monitor.ts +1042 -0
  103. package/src/inbound/outbound-mentions.ts +260 -0
  104. package/src/inbound/runtime-api.ts +7 -0
  105. package/src/inbound/save-media.runtime.ts +1 -0
  106. package/src/inbound/send-api.ts +203 -0
  107. package/src/inbound/send-result.ts +109 -0
  108. package/src/inbound/types.ts +107 -0
  109. package/src/inbound-policy.ts +215 -0
  110. package/src/inbound.ts +9 -0
  111. package/src/login-qr.ts +542 -0
  112. package/src/login.ts +83 -0
  113. package/src/media.ts +10 -0
  114. package/src/monitor-inbox.allows-messages-from-senders-allowfrom-list.test-support.ts +417 -0
  115. package/src/monitor-inbox.append-upsert.test-support.ts +133 -0
  116. package/src/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test-support.ts +418 -0
  117. package/src/monitor-inbox.captures-media-path-image-messages.test-support.ts +308 -0
  118. package/src/monitor-inbox.streams-inbound-messages.test-support.ts +824 -0
  119. package/src/normalize-target.ts +148 -0
  120. package/src/normalize.ts +8 -0
  121. package/src/outbound-adapter.ts +36 -0
  122. package/src/outbound-base.ts +256 -0
  123. package/src/outbound-media-contract.ts +307 -0
  124. package/src/outbound-media.runtime.ts +41 -0
  125. package/src/outbound-send-deps.ts +1 -0
  126. package/src/outbound-test-support.ts +16 -0
  127. package/src/qa-driver.runtime.ts +189 -0
  128. package/src/qr-image.ts +1 -0
  129. package/src/qr-terminal.ts +1 -0
  130. package/src/quoted-message.ts +184 -0
  131. package/src/reaction-level.ts +24 -0
  132. package/src/reconnect.ts +55 -0
  133. package/src/resolve-outbound-target.ts +58 -0
  134. package/src/runtime-api.ts +59 -0
  135. package/src/runtime-group-policy.ts +16 -0
  136. package/src/runtime.ts +9 -0
  137. package/src/security-contract.ts +47 -0
  138. package/src/security-fix.ts +71 -0
  139. package/src/send.ts +342 -0
  140. package/src/session-contract.ts +43 -0
  141. package/src/session-errors.ts +125 -0
  142. package/src/session-route.ts +32 -0
  143. package/src/session.runtime.ts +8 -0
  144. package/src/session.ts +327 -0
  145. package/src/setup-core.ts +52 -0
  146. package/src/setup-finalize.ts +450 -0
  147. package/src/setup-surface.ts +71 -0
  148. package/src/setup-test-helpers.ts +217 -0
  149. package/src/shared.ts +291 -0
  150. package/src/socket-timing.ts +38 -0
  151. package/src/state-migrations.ts +55 -0
  152. package/src/status-issues.ts +185 -0
  153. package/src/system-prompt.ts +31 -0
  154. package/src/targets-runtime.ts +221 -0
  155. package/src/text-runtime.ts +18 -0
  156. package/src/vcard.ts +84 -0
  157. package/targets.ts +5 -0
  158. package/test-api.ts +2 -0
  159. package/tsconfig.json +16 -0
@@ -0,0 +1,308 @@
1
+ import "./monitor-inbox.test-harness.js";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import {
4
+ DEFAULT_ACCOUNT_ID,
5
+ getAuthDir,
6
+ getMonitorWebInbox,
7
+ getSock,
8
+ installWebMonitorInboxUnitTestHooks,
9
+ mockLoadConfig,
10
+ } from "./monitor-inbox.test-harness.js";
11
+ let monitorWebInbox: typeof import("./inbound.js").monitorWebInbox;
12
+ const inboundLoggerInfoMock = vi.hoisted(() => vi.fn());
13
+
14
+ vi.mock("autobot/plugin-sdk/logging-core", async () => {
15
+ const actual = await vi.importActual<typeof import("autobot/plugin-sdk/logging-core")>(
16
+ "autobot/plugin-sdk/logging-core",
17
+ );
18
+ return {
19
+ ...actual,
20
+ getChildLogger: () => ({
21
+ info: inboundLoggerInfoMock,
22
+ warn: vi.fn(),
23
+ error: vi.fn(),
24
+ debug: vi.fn(),
25
+ }),
26
+ };
27
+ });
28
+
29
+ describe("web monitor inbox", () => {
30
+ installWebMonitorInboxUnitTestHooks();
31
+
32
+ beforeEach(() => {
33
+ inboundLoggerInfoMock.mockReset();
34
+ monitorWebInbox = getMonitorWebInbox();
35
+ });
36
+
37
+ async function openMonitor(onMessage = vi.fn()) {
38
+ return await monitorWebInbox({
39
+ cfg: mockLoadConfig() as never,
40
+ verbose: false,
41
+ accountId: DEFAULT_ACCOUNT_ID,
42
+ authDir: getAuthDir(),
43
+ onMessage,
44
+ });
45
+ }
46
+
47
+ async function runSingleUpsertAndCapture(upsert: unknown) {
48
+ const onMessage = vi.fn();
49
+ const listener = await openMonitor(onMessage);
50
+ const sock = getSock();
51
+ sock.ev.emit("messages.upsert", upsert);
52
+ await new Promise((resolve) => setTimeout(resolve, 25));
53
+ return { onMessage, listener, sock };
54
+ }
55
+
56
+ function expectSingleGroupMessage(
57
+ onMessage: ReturnType<typeof vi.fn>,
58
+ expected: Record<string, unknown>,
59
+ ) {
60
+ expect(onMessage).toHaveBeenCalledTimes(1);
61
+ const message = onMessage.mock.calls[0]?.[0] as Record<string, unknown> | undefined;
62
+ if (!message) {
63
+ throw new Error("expected inbound group message");
64
+ }
65
+ for (const [key, value] of Object.entries(expected)) {
66
+ expect(message[key]).toEqual(value);
67
+ }
68
+ }
69
+
70
+ it("captures media path for image messages", async () => {
71
+ const { onMessage, listener, sock } = await runSingleUpsertAndCapture({
72
+ type: "notify",
73
+ messages: [
74
+ {
75
+ key: { id: "med1", fromMe: false, remoteJid: "888@s.whatsapp.net" },
76
+ message: { imageMessage: { mimetype: "image/jpeg" } },
77
+ messageTimestamp: 1_700_000_100,
78
+ },
79
+ ],
80
+ });
81
+
82
+ expect(onMessage).toHaveBeenCalledTimes(1);
83
+ expect(onMessage.mock.calls[0]?.[0]?.body).toBe("<media:image>");
84
+ expect(sock.readMessages).toHaveBeenCalledWith([
85
+ {
86
+ remoteJid: "888@s.whatsapp.net",
87
+ id: "med1",
88
+ participant: undefined,
89
+ fromMe: false,
90
+ },
91
+ ]);
92
+ expect(sock.sendPresenceUpdate).toHaveBeenNthCalledWith(1, "available");
93
+ await listener.close();
94
+ });
95
+
96
+ it("sets gifPlayback on outbound video payloads when requested", async () => {
97
+ const onMessage = vi.fn();
98
+ const listener = await openMonitor(onMessage);
99
+ const sock = getSock();
100
+ const buf = Buffer.from("gifvid");
101
+
102
+ await listener.sendMessage("+1555", "gif", buf, "video/mp4", {
103
+ gifPlayback: true,
104
+ });
105
+
106
+ expect(sock.sendMessage).toHaveBeenCalledWith("1555@s.whatsapp.net", {
107
+ video: buf,
108
+ caption: "gif",
109
+ mimetype: "video/mp4",
110
+ gifPlayback: true,
111
+ });
112
+
113
+ await listener.close();
114
+ });
115
+
116
+ it("resolves onClose when the socket closes", async () => {
117
+ const listener = await openMonitor(vi.fn());
118
+ const sock = getSock();
119
+ const reasonPromise = listener.onClose;
120
+ sock.ev.emit("connection.update", {
121
+ connection: "close",
122
+ lastDisconnect: { error: { output: { statusCode: 500 } } },
123
+ });
124
+ await expect(reasonPromise).resolves.toEqual({
125
+ status: 500,
126
+ isLoggedOut: false,
127
+ error: { output: { statusCode: 500 } },
128
+ });
129
+ await listener.close();
130
+ });
131
+
132
+ it("detaches inbound listeners and ends the socket on close()", async () => {
133
+ const listener = await openMonitor(vi.fn());
134
+ const sock = getSock();
135
+
136
+ expect(sock.ev.listenerCount("messages.upsert")).toBeGreaterThan(0);
137
+ expect(sock.ev.listenerCount("connection.update")).toBeGreaterThan(0);
138
+
139
+ await listener.close();
140
+
141
+ expect(sock.ev.listenerCount("messages.upsert")).toBe(0);
142
+ expect(sock.ev.listenerCount("connection.update")).toBe(0);
143
+ expect(sock.end).toHaveBeenCalledTimes(1);
144
+ const closeError = sock.end.mock.calls[0]?.[0];
145
+ expect(closeError).toBeInstanceOf(Error);
146
+ expect(closeError?.message).toBe("AutoBot WhatsApp listener close");
147
+ expect(sock.ws.close).not.toHaveBeenCalled();
148
+ });
149
+
150
+ it("logs inbound bodies through the inbound child logger", async () => {
151
+ const { listener } = await runSingleUpsertAndCapture({
152
+ type: "notify",
153
+ messages: [
154
+ {
155
+ key: { id: "abc", fromMe: false, remoteJid: "999@s.whatsapp.net" },
156
+ message: { conversation: "ping" },
157
+ messageTimestamp: 1_700_000_000,
158
+ pushName: "Tester",
159
+ },
160
+ ],
161
+ });
162
+
163
+ expect(inboundLoggerInfoMock).toHaveBeenCalledTimes(1);
164
+ expect(inboundLoggerInfoMock.mock.calls[0]?.[0]?.body).toBe("ping");
165
+ expect(inboundLoggerInfoMock.mock.calls[0]?.[0]?.from).toBe("+999");
166
+ expect(inboundLoggerInfoMock.mock.calls[0]?.[1]).toBe("inbound message");
167
+ await listener.close();
168
+ });
169
+
170
+ it("includes participant when marking group messages read", async () => {
171
+ const { listener, sock } = await runSingleUpsertAndCapture({
172
+ type: "notify",
173
+ messages: [
174
+ {
175
+ key: {
176
+ id: "grp1",
177
+ fromMe: false,
178
+ remoteJid: "12345-67890@g.us",
179
+ participant: "111@s.whatsapp.net",
180
+ },
181
+ message: { conversation: "group ping" },
182
+ },
183
+ ],
184
+ });
185
+
186
+ expect(sock.readMessages).toHaveBeenCalledWith([
187
+ {
188
+ remoteJid: "12345-67890@g.us",
189
+ id: "grp1",
190
+ participant: "111@s.whatsapp.net",
191
+ fromMe: false,
192
+ },
193
+ ]);
194
+ await listener.close();
195
+ });
196
+
197
+ it("passes through group messages with participant metadata", async () => {
198
+ const { onMessage, listener } = await runSingleUpsertAndCapture({
199
+ type: "notify",
200
+ messages: [
201
+ {
202
+ key: {
203
+ id: "grp2",
204
+ fromMe: false,
205
+ remoteJid: "99999@g.us",
206
+ participant: "777@s.whatsapp.net",
207
+ },
208
+ pushName: "Alice",
209
+ message: {
210
+ extendedTextMessage: {
211
+ text: "@bot ping",
212
+ contextInfo: { mentionedJid: ["123@s.whatsapp.net"] },
213
+ },
214
+ },
215
+ messageTimestamp: 1_700_000_000,
216
+ },
217
+ ],
218
+ });
219
+
220
+ expectSingleGroupMessage(onMessage, {
221
+ chatType: "group",
222
+ conversationId: "99999@g.us",
223
+ senderE164: "+777",
224
+ mentionedJids: ["123@s.whatsapp.net"],
225
+ });
226
+ await listener.close();
227
+ });
228
+
229
+ it("unwraps ephemeral messages, preserves mentions, and still delivers group pings", async () => {
230
+ const { onMessage, listener } = await runSingleUpsertAndCapture({
231
+ type: "notify",
232
+ messages: [
233
+ {
234
+ key: {
235
+ id: "grp-ephem",
236
+ fromMe: false,
237
+ remoteJid: "424242@g.us",
238
+ participant: "888@s.whatsapp.net",
239
+ },
240
+ message: {
241
+ ephemeralMessage: {
242
+ message: {
243
+ extendedTextMessage: {
244
+ text: "oh hey @Clawd UK !",
245
+ contextInfo: { mentionedJid: ["123@s.whatsapp.net"] },
246
+ },
247
+ },
248
+ },
249
+ },
250
+ },
251
+ ],
252
+ });
253
+ expectSingleGroupMessage(onMessage, {
254
+ chatType: "group",
255
+ conversationId: "424242@g.us",
256
+ body: "oh hey @Clawd UK !",
257
+ mentionedJids: ["123@s.whatsapp.net"],
258
+ senderE164: "+888",
259
+ });
260
+ await listener.close();
261
+ });
262
+
263
+ it("still forwards group messages (with sender info) even when allowFrom is restrictive", async () => {
264
+ mockLoadConfig.mockReturnValue({
265
+ channels: {
266
+ whatsapp: {
267
+ // does not include +777
268
+ allowFrom: ["+111"],
269
+ groupPolicy: "open",
270
+ },
271
+ },
272
+ messages: {
273
+ messagePrefix: undefined,
274
+ responsePrefix: undefined,
275
+ },
276
+ });
277
+
278
+ const { onMessage, listener } = await runSingleUpsertAndCapture({
279
+ type: "notify",
280
+ messages: [
281
+ {
282
+ key: {
283
+ id: "grp-allow",
284
+ fromMe: false,
285
+ remoteJid: "55555@g.us",
286
+ participant: "777@s.whatsapp.net",
287
+ },
288
+ message: {
289
+ extendedTextMessage: {
290
+ text: "@bot hi",
291
+ contextInfo: { mentionedJid: ["123@s.whatsapp.net"] },
292
+ },
293
+ },
294
+ },
295
+ ],
296
+ });
297
+ expectSingleGroupMessage(onMessage, {
298
+ chatType: "group",
299
+ from: "55555@g.us",
300
+ senderE164: "+777",
301
+ senderJid: "777@s.whatsapp.net",
302
+ mentionedJids: ["123@s.whatsapp.net"],
303
+ selfE164: "+123",
304
+ selfJid: "123@s.whatsapp.net",
305
+ });
306
+ await listener.close();
307
+ });
308
+ });