loudmouth-ai 0.1.0 → 0.2.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.
- package/README.md +148 -77
- package/dist/build-info.json +3 -3
- package/extensions/package.json +6 -0
- package/package.json +1 -1
- package/skills/autopilot/SKILL.md +179 -0
- package/skills/goals/SKILL.md +189 -0
- package/skills/wordpress/SKILL.md +232 -0
- package/extensions/bluebubbles/clawdbot.plugin.json +0 -11
- package/extensions/bluebubbles/index.ts +0 -20
- package/extensions/bluebubbles/package.json +0 -33
- package/extensions/bluebubbles/src/accounts.ts +0 -80
- package/extensions/bluebubbles/src/actions.test.ts +0 -651
- package/extensions/bluebubbles/src/actions.ts +0 -403
- package/extensions/bluebubbles/src/attachments.test.ts +0 -346
- package/extensions/bluebubbles/src/attachments.ts +0 -282
- package/extensions/bluebubbles/src/channel.ts +0 -399
- package/extensions/bluebubbles/src/chat.test.ts +0 -462
- package/extensions/bluebubbles/src/chat.ts +0 -354
- package/extensions/bluebubbles/src/config-schema.ts +0 -51
- package/extensions/bluebubbles/src/media-send.ts +0 -168
- package/extensions/bluebubbles/src/monitor.test.ts +0 -2140
- package/extensions/bluebubbles/src/monitor.ts +0 -2101
- package/extensions/bluebubbles/src/onboarding.ts +0 -340
- package/extensions/bluebubbles/src/probe.ts +0 -127
- package/extensions/bluebubbles/src/reactions.test.ts +0 -393
- package/extensions/bluebubbles/src/reactions.ts +0 -183
- package/extensions/bluebubbles/src/runtime.ts +0 -14
- package/extensions/bluebubbles/src/send.test.ts +0 -809
- package/extensions/bluebubbles/src/send.ts +0 -418
- package/extensions/bluebubbles/src/targets.test.ts +0 -184
- package/extensions/bluebubbles/src/targets.ts +0 -323
- package/extensions/bluebubbles/src/types.ts +0 -127
- package/extensions/copilot-proxy/README.md +0 -24
- package/extensions/copilot-proxy/clawdbot.plugin.json +0 -11
- package/extensions/copilot-proxy/index.ts +0 -142
- package/extensions/copilot-proxy/package.json +0 -11
- package/extensions/google-antigravity-auth/README.md +0 -24
- package/extensions/google-antigravity-auth/clawdbot.plugin.json +0 -11
- package/extensions/google-antigravity-auth/index.ts +0 -437
- package/extensions/google-antigravity-auth/package.json +0 -11
- package/extensions/google-gemini-cli-auth/README.md +0 -35
- package/extensions/google-gemini-cli-auth/clawdbot.plugin.json +0 -11
- package/extensions/google-gemini-cli-auth/index.ts +0 -91
- package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -228
- package/extensions/google-gemini-cli-auth/oauth.ts +0 -580
- package/extensions/google-gemini-cli-auth/package.json +0 -11
- package/extensions/googlechat/clawdbot.plugin.json +0 -11
- package/extensions/googlechat/index.ts +0 -20
- package/extensions/googlechat/package.json +0 -39
- package/extensions/googlechat/src/accounts.ts +0 -133
- package/extensions/googlechat/src/actions.ts +0 -162
- package/extensions/googlechat/src/api.test.ts +0 -62
- package/extensions/googlechat/src/api.ts +0 -259
- package/extensions/googlechat/src/auth.ts +0 -113
- package/extensions/googlechat/src/channel.ts +0 -580
- package/extensions/googlechat/src/monitor.test.ts +0 -27
- package/extensions/googlechat/src/monitor.ts +0 -900
- package/extensions/googlechat/src/onboarding.ts +0 -278
- package/extensions/googlechat/src/runtime.ts +0 -14
- package/extensions/googlechat/src/targets.test.ts +0 -35
- package/extensions/googlechat/src/targets.ts +0 -55
- package/extensions/googlechat/src/types.config.ts +0 -3
- package/extensions/googlechat/src/types.ts +0 -73
- package/extensions/imessage/clawdbot.plugin.json +0 -11
- package/extensions/imessage/index.ts +0 -18
- package/extensions/imessage/package.json +0 -11
- package/extensions/imessage/src/channel.ts +0 -294
- package/extensions/imessage/src/runtime.ts +0 -14
- package/extensions/line/clawdbot.plugin.json +0 -11
- package/extensions/line/index.ts +0 -20
- package/extensions/line/package.json +0 -29
- package/extensions/line/src/card-command.ts +0 -338
- package/extensions/line/src/channel.logout.test.ts +0 -96
- package/extensions/line/src/channel.sendPayload.test.ts +0 -308
- package/extensions/line/src/channel.ts +0 -773
- package/extensions/line/src/runtime.ts +0 -14
- package/extensions/matrix/CHANGELOG.md +0 -54
- package/extensions/matrix/clawdbot.plugin.json +0 -11
- package/extensions/matrix/index.ts +0 -18
- package/extensions/matrix/package.json +0 -36
- package/extensions/matrix/src/actions.ts +0 -185
- package/extensions/matrix/src/channel.directory.test.ts +0 -56
- package/extensions/matrix/src/channel.ts +0 -417
- package/extensions/matrix/src/config-schema.ts +0 -62
- package/extensions/matrix/src/directory-live.ts +0 -175
- package/extensions/matrix/src/group-mentions.ts +0 -61
- package/extensions/matrix/src/matrix/accounts.test.ts +0 -83
- package/extensions/matrix/src/matrix/accounts.ts +0 -63
- package/extensions/matrix/src/matrix/actions/client.ts +0 -53
- package/extensions/matrix/src/matrix/actions/messages.ts +0 -120
- package/extensions/matrix/src/matrix/actions/pins.ts +0 -70
- package/extensions/matrix/src/matrix/actions/reactions.ts +0 -84
- package/extensions/matrix/src/matrix/actions/room.ts +0 -88
- package/extensions/matrix/src/matrix/actions/summary.ts +0 -77
- package/extensions/matrix/src/matrix/actions/types.ts +0 -84
- package/extensions/matrix/src/matrix/actions.ts +0 -15
- package/extensions/matrix/src/matrix/active-client.ts +0 -11
- package/extensions/matrix/src/matrix/client/config.ts +0 -165
- package/extensions/matrix/src/matrix/client/create-client.ts +0 -127
- package/extensions/matrix/src/matrix/client/logging.ts +0 -35
- package/extensions/matrix/src/matrix/client/runtime.ts +0 -4
- package/extensions/matrix/src/matrix/client/shared.ts +0 -169
- package/extensions/matrix/src/matrix/client/storage.ts +0 -131
- package/extensions/matrix/src/matrix/client/types.ts +0 -34
- package/extensions/matrix/src/matrix/client.test.ts +0 -57
- package/extensions/matrix/src/matrix/client.ts +0 -9
- package/extensions/matrix/src/matrix/credentials.ts +0 -103
- package/extensions/matrix/src/matrix/deps.ts +0 -57
- package/extensions/matrix/src/matrix/format.test.ts +0 -34
- package/extensions/matrix/src/matrix/format.ts +0 -22
- package/extensions/matrix/src/matrix/index.ts +0 -11
- package/extensions/matrix/src/matrix/monitor/allowlist.ts +0 -58
- package/extensions/matrix/src/matrix/monitor/auto-join.ts +0 -68
- package/extensions/matrix/src/matrix/monitor/direct.ts +0 -105
- package/extensions/matrix/src/matrix/monitor/events.ts +0 -103
- package/extensions/matrix/src/matrix/monitor/handler.ts +0 -645
- package/extensions/matrix/src/matrix/monitor/index.ts +0 -279
- package/extensions/matrix/src/matrix/monitor/location.ts +0 -83
- package/extensions/matrix/src/matrix/monitor/media.test.ts +0 -103
- package/extensions/matrix/src/matrix/monitor/media.ts +0 -113
- package/extensions/matrix/src/matrix/monitor/mentions.ts +0 -31
- package/extensions/matrix/src/matrix/monitor/replies.ts +0 -96
- package/extensions/matrix/src/matrix/monitor/room-info.ts +0 -58
- package/extensions/matrix/src/matrix/monitor/rooms.ts +0 -43
- package/extensions/matrix/src/matrix/monitor/threads.ts +0 -64
- package/extensions/matrix/src/matrix/monitor/types.ts +0 -39
- package/extensions/matrix/src/matrix/poll-types.test.ts +0 -22
- package/extensions/matrix/src/matrix/poll-types.ts +0 -157
- package/extensions/matrix/src/matrix/probe.ts +0 -70
- package/extensions/matrix/src/matrix/send/client.ts +0 -63
- package/extensions/matrix/src/matrix/send/formatting.ts +0 -92
- package/extensions/matrix/src/matrix/send/media.ts +0 -220
- package/extensions/matrix/src/matrix/send/targets.test.ts +0 -102
- package/extensions/matrix/src/matrix/send/targets.ts +0 -144
- package/extensions/matrix/src/matrix/send/types.ts +0 -109
- package/extensions/matrix/src/matrix/send.test.ts +0 -172
- package/extensions/matrix/src/matrix/send.ts +0 -255
- package/extensions/matrix/src/onboarding.ts +0 -432
- package/extensions/matrix/src/outbound.ts +0 -53
- package/extensions/matrix/src/resolve-targets.ts +0 -89
- package/extensions/matrix/src/runtime.ts +0 -14
- package/extensions/matrix/src/tool-actions.ts +0 -160
- package/extensions/matrix/src/types.ts +0 -95
- package/extensions/mattermost/clawdbot.plugin.json +0 -11
- package/extensions/mattermost/index.ts +0 -18
- package/extensions/mattermost/package.json +0 -25
- package/extensions/mattermost/src/channel.test.ts +0 -43
- package/extensions/mattermost/src/channel.ts +0 -339
- package/extensions/mattermost/src/config-schema.ts +0 -56
- package/extensions/mattermost/src/group-mentions.ts +0 -14
- package/extensions/mattermost/src/mattermost/accounts.ts +0 -115
- package/extensions/mattermost/src/mattermost/client.ts +0 -208
- package/extensions/mattermost/src/mattermost/index.ts +0 -9
- package/extensions/mattermost/src/mattermost/monitor-helpers.ts +0 -150
- package/extensions/mattermost/src/mattermost/monitor.ts +0 -921
- package/extensions/mattermost/src/mattermost/probe.ts +0 -70
- package/extensions/mattermost/src/mattermost/send.ts +0 -217
- package/extensions/mattermost/src/normalize.ts +0 -38
- package/extensions/mattermost/src/onboarding-helpers.ts +0 -42
- package/extensions/mattermost/src/onboarding.ts +0 -187
- package/extensions/mattermost/src/runtime.ts +0 -14
- package/extensions/mattermost/src/types.ts +0 -50
- package/extensions/msteams/CHANGELOG.md +0 -51
- package/extensions/msteams/clawdbot.plugin.json +0 -11
- package/extensions/msteams/index.ts +0 -18
- package/extensions/msteams/package.json +0 -36
- package/extensions/msteams/src/attachments/download.ts +0 -206
- package/extensions/msteams/src/attachments/graph.ts +0 -319
- package/extensions/msteams/src/attachments/html.ts +0 -76
- package/extensions/msteams/src/attachments/payload.ts +0 -22
- package/extensions/msteams/src/attachments/shared.ts +0 -235
- package/extensions/msteams/src/attachments/types.ts +0 -37
- package/extensions/msteams/src/attachments.test.ts +0 -424
- package/extensions/msteams/src/attachments.ts +0 -18
- package/extensions/msteams/src/channel.directory.test.ts +0 -46
- package/extensions/msteams/src/channel.ts +0 -436
- package/extensions/msteams/src/conversation-store-fs.test.ts +0 -88
- package/extensions/msteams/src/conversation-store-fs.ts +0 -155
- package/extensions/msteams/src/conversation-store-memory.ts +0 -45
- package/extensions/msteams/src/conversation-store.ts +0 -41
- package/extensions/msteams/src/directory-live.ts +0 -179
- package/extensions/msteams/src/errors.test.ts +0 -46
- package/extensions/msteams/src/errors.ts +0 -158
- package/extensions/msteams/src/file-consent-helpers.test.ts +0 -234
- package/extensions/msteams/src/file-consent-helpers.ts +0 -73
- package/extensions/msteams/src/file-consent.ts +0 -122
- package/extensions/msteams/src/graph-chat.ts +0 -52
- package/extensions/msteams/src/graph-upload.ts +0 -445
- package/extensions/msteams/src/inbound.test.ts +0 -67
- package/extensions/msteams/src/inbound.ts +0 -38
- package/extensions/msteams/src/index.ts +0 -4
- package/extensions/msteams/src/media-helpers.test.ts +0 -186
- package/extensions/msteams/src/media-helpers.ts +0 -77
- package/extensions/msteams/src/messenger.test.ts +0 -245
- package/extensions/msteams/src/messenger.ts +0 -460
- package/extensions/msteams/src/monitor-handler/inbound-media.ts +0 -123
- package/extensions/msteams/src/monitor-handler/message-handler.ts +0 -629
- package/extensions/msteams/src/monitor-handler.ts +0 -166
- package/extensions/msteams/src/monitor-types.ts +0 -5
- package/extensions/msteams/src/monitor.ts +0 -290
- package/extensions/msteams/src/onboarding.ts +0 -432
- package/extensions/msteams/src/outbound.ts +0 -47
- package/extensions/msteams/src/pending-uploads.ts +0 -87
- package/extensions/msteams/src/policy.test.ts +0 -210
- package/extensions/msteams/src/policy.ts +0 -202
- package/extensions/msteams/src/polls-store-memory.ts +0 -30
- package/extensions/msteams/src/polls-store.test.ts +0 -40
- package/extensions/msteams/src/polls.test.ts +0 -72
- package/extensions/msteams/src/polls.ts +0 -299
- package/extensions/msteams/src/probe.test.ts +0 -57
- package/extensions/msteams/src/probe.ts +0 -99
- package/extensions/msteams/src/reply-dispatcher.ts +0 -128
- package/extensions/msteams/src/resolve-allowlist.ts +0 -277
- package/extensions/msteams/src/runtime.ts +0 -14
- package/extensions/msteams/src/sdk-types.ts +0 -19
- package/extensions/msteams/src/sdk.ts +0 -33
- package/extensions/msteams/src/send-context.ts +0 -156
- package/extensions/msteams/src/send.ts +0 -489
- package/extensions/msteams/src/sent-message-cache.test.ts +0 -16
- package/extensions/msteams/src/sent-message-cache.ts +0 -41
- package/extensions/msteams/src/storage.ts +0 -22
- package/extensions/msteams/src/store-fs.ts +0 -80
- package/extensions/msteams/src/token.ts +0 -19
- package/extensions/nextcloud-talk/clawdbot.plugin.json +0 -11
- package/extensions/nextcloud-talk/index.ts +0 -18
- package/extensions/nextcloud-talk/package.json +0 -30
- package/extensions/nextcloud-talk/src/accounts.ts +0 -154
- package/extensions/nextcloud-talk/src/channel.ts +0 -404
- package/extensions/nextcloud-talk/src/config-schema.ts +0 -78
- package/extensions/nextcloud-talk/src/format.ts +0 -79
- package/extensions/nextcloud-talk/src/inbound.ts +0 -336
- package/extensions/nextcloud-talk/src/monitor.ts +0 -246
- package/extensions/nextcloud-talk/src/normalize.ts +0 -31
- package/extensions/nextcloud-talk/src/onboarding.ts +0 -341
- package/extensions/nextcloud-talk/src/policy.ts +0 -175
- package/extensions/nextcloud-talk/src/room-info.ts +0 -111
- package/extensions/nextcloud-talk/src/runtime.ts +0 -14
- package/extensions/nextcloud-talk/src/send.ts +0 -206
- package/extensions/nextcloud-talk/src/signature.ts +0 -67
- package/extensions/nextcloud-talk/src/types.ts +0 -179
- package/extensions/nostr/CHANGELOG.md +0 -46
- package/extensions/nostr/README.md +0 -136
- package/extensions/nostr/clawdbot.plugin.json +0 -11
- package/extensions/nostr/index.ts +0 -69
- package/extensions/nostr/package.json +0 -31
- package/extensions/nostr/src/channel.test.ts +0 -141
- package/extensions/nostr/src/channel.ts +0 -342
- package/extensions/nostr/src/config-schema.ts +0 -90
- package/extensions/nostr/src/metrics.ts +0 -464
- package/extensions/nostr/src/nostr-bus.fuzz.test.ts +0 -544
- package/extensions/nostr/src/nostr-bus.integration.test.ts +0 -452
- package/extensions/nostr/src/nostr-bus.test.ts +0 -199
- package/extensions/nostr/src/nostr-bus.ts +0 -741
- package/extensions/nostr/src/nostr-profile-http.test.ts +0 -378
- package/extensions/nostr/src/nostr-profile-http.ts +0 -500
- package/extensions/nostr/src/nostr-profile-import.test.ts +0 -120
- package/extensions/nostr/src/nostr-profile-import.ts +0 -259
- package/extensions/nostr/src/nostr-profile.fuzz.test.ts +0 -479
- package/extensions/nostr/src/nostr-profile.test.ts +0 -410
- package/extensions/nostr/src/nostr-profile.ts +0 -242
- package/extensions/nostr/src/nostr-state-store.test.ts +0 -128
- package/extensions/nostr/src/nostr-state-store.ts +0 -226
- package/extensions/nostr/src/runtime.ts +0 -14
- package/extensions/nostr/src/seen-tracker.ts +0 -271
- package/extensions/nostr/src/types.test.ts +0 -161
- package/extensions/nostr/src/types.ts +0 -99
- package/extensions/nostr/test/setup.ts +0 -5
- package/extensions/open-prose/README.md +0 -25
- package/extensions/open-prose/clawdbot.plugin.json +0 -11
- package/extensions/open-prose/index.ts +0 -5
- package/extensions/open-prose/package.json +0 -11
- package/extensions/open-prose/skills/prose/LICENSE +0 -21
- package/extensions/open-prose/skills/prose/SKILL.md +0 -318
- package/extensions/open-prose/skills/prose/alt-borges.md +0 -141
- package/extensions/open-prose/skills/prose/alts/arabian-nights.md +0 -358
- package/extensions/open-prose/skills/prose/alts/borges.md +0 -360
- package/extensions/open-prose/skills/prose/alts/folk.md +0 -322
- package/extensions/open-prose/skills/prose/alts/homer.md +0 -346
- package/extensions/open-prose/skills/prose/alts/kafka.md +0 -373
- package/extensions/open-prose/skills/prose/compiler.md +0 -2967
- package/extensions/open-prose/skills/prose/examples/01-hello-world.prose +0 -4
- package/extensions/open-prose/skills/prose/examples/02-research-and-summarize.prose +0 -6
- package/extensions/open-prose/skills/prose/examples/03-code-review.prose +0 -17
- package/extensions/open-prose/skills/prose/examples/04-write-and-refine.prose +0 -14
- package/extensions/open-prose/skills/prose/examples/05-debug-issue.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/06-explain-codebase.prose +0 -17
- package/extensions/open-prose/skills/prose/examples/07-refactor.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/08-blog-post.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/09-research-with-agents.prose +0 -25
- package/extensions/open-prose/skills/prose/examples/10-code-review-agents.prose +0 -32
- package/extensions/open-prose/skills/prose/examples/11-skills-and-imports.prose +0 -27
- package/extensions/open-prose/skills/prose/examples/12-secure-agent-permissions.prose +0 -43
- package/extensions/open-prose/skills/prose/examples/13-variables-and-context.prose +0 -51
- package/extensions/open-prose/skills/prose/examples/14-composition-blocks.prose +0 -48
- package/extensions/open-prose/skills/prose/examples/15-inline-sequences.prose +0 -23
- package/extensions/open-prose/skills/prose/examples/16-parallel-reviews.prose +0 -19
- package/extensions/open-prose/skills/prose/examples/17-parallel-research.prose +0 -19
- package/extensions/open-prose/skills/prose/examples/18-mixed-parallel-sequential.prose +0 -36
- package/extensions/open-prose/skills/prose/examples/19-advanced-parallel.prose +0 -71
- package/extensions/open-prose/skills/prose/examples/20-fixed-loops.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/21-pipeline-operations.prose +0 -35
- package/extensions/open-prose/skills/prose/examples/22-error-handling.prose +0 -51
- package/extensions/open-prose/skills/prose/examples/23-retry-with-backoff.prose +0 -63
- package/extensions/open-prose/skills/prose/examples/24-choice-blocks.prose +0 -86
- package/extensions/open-prose/skills/prose/examples/25-conditionals.prose +0 -114
- package/extensions/open-prose/skills/prose/examples/26-parameterized-blocks.prose +0 -100
- package/extensions/open-prose/skills/prose/examples/27-string-interpolation.prose +0 -105
- package/extensions/open-prose/skills/prose/examples/28-automated-pr-review.prose +0 -37
- package/extensions/open-prose/skills/prose/examples/28-gas-town.prose +0 -1572
- package/extensions/open-prose/skills/prose/examples/29-captains-chair.prose +0 -218
- package/extensions/open-prose/skills/prose/examples/30-captains-chair-simple.prose +0 -42
- package/extensions/open-prose/skills/prose/examples/31-captains-chair-with-memory.prose +0 -145
- package/extensions/open-prose/skills/prose/examples/33-pr-review-autofix.prose +0 -168
- package/extensions/open-prose/skills/prose/examples/34-content-pipeline.prose +0 -204
- package/extensions/open-prose/skills/prose/examples/35-feature-factory.prose +0 -296
- package/extensions/open-prose/skills/prose/examples/36-bug-hunter.prose +0 -237
- package/extensions/open-prose/skills/prose/examples/37-the-forge.prose +0 -1474
- package/extensions/open-prose/skills/prose/examples/38-skill-scan.prose +0 -455
- package/extensions/open-prose/skills/prose/examples/39-architect-by-simulation.prose +0 -277
- package/extensions/open-prose/skills/prose/examples/40-rlm-self-refine.prose +0 -32
- package/extensions/open-prose/skills/prose/examples/41-rlm-divide-conquer.prose +0 -38
- package/extensions/open-prose/skills/prose/examples/42-rlm-filter-recurse.prose +0 -46
- package/extensions/open-prose/skills/prose/examples/43-rlm-pairwise.prose +0 -50
- package/extensions/open-prose/skills/prose/examples/44-run-endpoint-ux-test.prose +0 -261
- package/extensions/open-prose/skills/prose/examples/45-plugin-release.prose +0 -159
- package/extensions/open-prose/skills/prose/examples/45-run-endpoint-ux-test-with-remediation.prose +0 -637
- package/extensions/open-prose/skills/prose/examples/46-run-endpoint-ux-test-fast.prose +0 -148
- package/extensions/open-prose/skills/prose/examples/46-workflow-crystallizer.prose +0 -225
- package/extensions/open-prose/skills/prose/examples/47-language-self-improvement.prose +0 -356
- package/extensions/open-prose/skills/prose/examples/48-habit-miner.prose +0 -445
- package/extensions/open-prose/skills/prose/examples/49-prose-run-retrospective.prose +0 -210
- package/extensions/open-prose/skills/prose/examples/README.md +0 -391
- package/extensions/open-prose/skills/prose/examples/roadmap/README.md +0 -22
- package/extensions/open-prose/skills/prose/examples/roadmap/iterative-refinement.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/roadmap/parallel-review.prose +0 -18
- package/extensions/open-prose/skills/prose/examples/roadmap/simple-pipeline.prose +0 -17
- package/extensions/open-prose/skills/prose/examples/roadmap/syntax/open-prose-syntax.prose +0 -223
- package/extensions/open-prose/skills/prose/guidance/antipatterns.md +0 -951
- package/extensions/open-prose/skills/prose/guidance/patterns.md +0 -700
- package/extensions/open-prose/skills/prose/guidance/system-prompt.md +0 -180
- package/extensions/open-prose/skills/prose/help.md +0 -143
- package/extensions/open-prose/skills/prose/lib/README.md +0 -105
- package/extensions/open-prose/skills/prose/lib/calibrator.prose +0 -215
- package/extensions/open-prose/skills/prose/lib/cost-analyzer.prose +0 -174
- package/extensions/open-prose/skills/prose/lib/error-forensics.prose +0 -250
- package/extensions/open-prose/skills/prose/lib/inspector.prose +0 -196
- package/extensions/open-prose/skills/prose/lib/profiler.prose +0 -460
- package/extensions/open-prose/skills/prose/lib/program-improver.prose +0 -275
- package/extensions/open-prose/skills/prose/lib/project-memory.prose +0 -118
- package/extensions/open-prose/skills/prose/lib/user-memory.prose +0 -93
- package/extensions/open-prose/skills/prose/lib/vm-improver.prose +0 -243
- package/extensions/open-prose/skills/prose/primitives/session.md +0 -587
- package/extensions/open-prose/skills/prose/prose.md +0 -1235
- package/extensions/open-prose/skills/prose/state/filesystem.md +0 -478
- package/extensions/open-prose/skills/prose/state/in-context.md +0 -380
- package/extensions/open-prose/skills/prose/state/postgres.md +0 -875
- package/extensions/open-prose/skills/prose/state/sqlite.md +0 -572
- package/extensions/qwen-portal-auth/README.md +0 -24
- package/extensions/qwen-portal-auth/clawdbot.plugin.json +0 -11
- package/extensions/qwen-portal-auth/index.ts +0 -127
- package/extensions/qwen-portal-auth/oauth.ts +0 -190
- package/extensions/signal/clawdbot.plugin.json +0 -11
- package/extensions/signal/index.ts +0 -18
- package/extensions/signal/package.json +0 -11
- package/extensions/signal/src/channel.ts +0 -312
- package/extensions/signal/src/runtime.ts +0 -14
- package/extensions/telegram/clawdbot.plugin.json +0 -11
- package/extensions/telegram/index.ts +0 -18
- package/extensions/telegram/package.json +0 -11
- package/extensions/telegram/src/channel.ts +0 -478
- package/extensions/telegram/src/runtime.ts +0 -14
- package/extensions/tlon/README.md +0 -5
- package/extensions/tlon/clawdbot.plugin.json +0 -11
- package/extensions/tlon/index.ts +0 -18
- package/extensions/tlon/package.json +0 -30
- package/extensions/tlon/src/channel.ts +0 -379
- package/extensions/tlon/src/config-schema.test.ts +0 -32
- package/extensions/tlon/src/config-schema.ts +0 -43
- package/extensions/tlon/src/monitor/discovery.ts +0 -71
- package/extensions/tlon/src/monitor/history.ts +0 -87
- package/extensions/tlon/src/monitor/index.ts +0 -501
- package/extensions/tlon/src/monitor/processed-messages.test.ts +0 -24
- package/extensions/tlon/src/monitor/processed-messages.ts +0 -38
- package/extensions/tlon/src/monitor/utils.ts +0 -83
- package/extensions/tlon/src/onboarding.ts +0 -213
- package/extensions/tlon/src/runtime.ts +0 -14
- package/extensions/tlon/src/targets.ts +0 -79
- package/extensions/tlon/src/types.ts +0 -85
- package/extensions/tlon/src/urbit/auth.ts +0 -18
- package/extensions/tlon/src/urbit/http-api.ts +0 -36
- package/extensions/tlon/src/urbit/send.test.ts +0 -38
- package/extensions/tlon/src/urbit/send.ts +0 -127
- package/extensions/tlon/src/urbit/sse-client.test.ts +0 -41
- package/extensions/tlon/src/urbit/sse-client.ts +0 -367
- package/extensions/twitch/CHANGELOG.md +0 -21
- package/extensions/twitch/README.md +0 -89
- package/extensions/twitch/clawdbot.plugin.json +0 -9
- package/extensions/twitch/index.ts +0 -20
- package/extensions/twitch/package.json +0 -20
- package/extensions/twitch/src/access-control.test.ts +0 -489
- package/extensions/twitch/src/access-control.ts +0 -154
- package/extensions/twitch/src/actions.ts +0 -173
- package/extensions/twitch/src/client-manager-registry.ts +0 -115
- package/extensions/twitch/src/config-schema.ts +0 -82
- package/extensions/twitch/src/config.test.ts +0 -88
- package/extensions/twitch/src/config.ts +0 -116
- package/extensions/twitch/src/monitor.ts +0 -257
- package/extensions/twitch/src/onboarding.test.ts +0 -311
- package/extensions/twitch/src/onboarding.ts +0 -411
- package/extensions/twitch/src/outbound.test.ts +0 -373
- package/extensions/twitch/src/outbound.ts +0 -186
- package/extensions/twitch/src/plugin.test.ts +0 -39
- package/extensions/twitch/src/plugin.ts +0 -274
- package/extensions/twitch/src/probe.test.ts +0 -198
- package/extensions/twitch/src/probe.ts +0 -118
- package/extensions/twitch/src/resolver.ts +0 -137
- package/extensions/twitch/src/runtime.ts +0 -14
- package/extensions/twitch/src/send.test.ts +0 -289
- package/extensions/twitch/src/send.ts +0 -136
- package/extensions/twitch/src/status.test.ts +0 -270
- package/extensions/twitch/src/status.ts +0 -176
- package/extensions/twitch/src/token.test.ts +0 -171
- package/extensions/twitch/src/token.ts +0 -87
- package/extensions/twitch/src/twitch-client.test.ts +0 -574
- package/extensions/twitch/src/twitch-client.ts +0 -277
- package/extensions/twitch/src/types.ts +0 -141
- package/extensions/twitch/src/utils/markdown.ts +0 -92
- package/extensions/twitch/src/utils/twitch.ts +0 -78
- package/extensions/twitch/test/setup.ts +0 -7
- package/extensions/voice-call/CHANGELOG.md +0 -72
- package/extensions/voice-call/README.md +0 -134
- package/extensions/voice-call/clawdbot.plugin.json +0 -601
- package/extensions/voice-call/index.ts +0 -497
- package/extensions/voice-call/package.json +0 -16
- package/extensions/voice-call/src/cli.ts +0 -300
- package/extensions/voice-call/src/config.test.ts +0 -204
- package/extensions/voice-call/src/config.ts +0 -493
- package/extensions/voice-call/src/core-bridge.ts +0 -196
- package/extensions/voice-call/src/manager/context.ts +0 -21
- package/extensions/voice-call/src/manager/events.ts +0 -177
- package/extensions/voice-call/src/manager/lookup.ts +0 -33
- package/extensions/voice-call/src/manager/outbound.ts +0 -248
- package/extensions/voice-call/src/manager/state.ts +0 -50
- package/extensions/voice-call/src/manager/store.ts +0 -88
- package/extensions/voice-call/src/manager/timers.ts +0 -86
- package/extensions/voice-call/src/manager/twiml.ts +0 -9
- package/extensions/voice-call/src/manager.test.ts +0 -108
- package/extensions/voice-call/src/manager.ts +0 -876
- package/extensions/voice-call/src/media-stream.test.ts +0 -97
- package/extensions/voice-call/src/media-stream.ts +0 -393
- package/extensions/voice-call/src/providers/base.ts +0 -67
- package/extensions/voice-call/src/providers/index.ts +0 -10
- package/extensions/voice-call/src/providers/mock.ts +0 -168
- package/extensions/voice-call/src/providers/plivo.test.ts +0 -28
- package/extensions/voice-call/src/providers/plivo.ts +0 -504
- package/extensions/voice-call/src/providers/stt-openai-realtime.ts +0 -311
- package/extensions/voice-call/src/providers/telnyx.ts +0 -364
- package/extensions/voice-call/src/providers/tts-openai.ts +0 -264
- package/extensions/voice-call/src/providers/twilio/api.ts +0 -45
- package/extensions/voice-call/src/providers/twilio/webhook.ts +0 -29
- package/extensions/voice-call/src/providers/twilio.test.ts +0 -64
- package/extensions/voice-call/src/providers/twilio.ts +0 -595
- package/extensions/voice-call/src/response-generator.ts +0 -171
- package/extensions/voice-call/src/runtime.ts +0 -205
- package/extensions/voice-call/src/telephony-audio.ts +0 -88
- package/extensions/voice-call/src/telephony-tts.ts +0 -95
- package/extensions/voice-call/src/tunnel.ts +0 -331
- package/extensions/voice-call/src/types.ts +0 -272
- package/extensions/voice-call/src/utils.ts +0 -12
- package/extensions/voice-call/src/voice-mapping.ts +0 -65
- package/extensions/voice-call/src/webhook-security.test.ts +0 -233
- package/extensions/voice-call/src/webhook-security.ts +0 -446
- package/extensions/voice-call/src/webhook.ts +0 -490
- package/extensions/whatsapp/clawdbot.plugin.json +0 -11
- package/extensions/whatsapp/index.ts +0 -18
- package/extensions/whatsapp/package.json +0 -11
- package/extensions/whatsapp/src/channel.ts +0 -500
- package/extensions/whatsapp/src/runtime.ts +0 -14
- package/extensions/zalo/CHANGELOG.md +0 -55
- package/extensions/zalo/README.md +0 -50
- package/extensions/zalo/clawdbot.plugin.json +0 -11
- package/extensions/zalo/index.ts +0 -20
- package/extensions/zalo/package.json +0 -33
- package/extensions/zalo/src/accounts.ts +0 -71
- package/extensions/zalo/src/actions.ts +0 -62
- package/extensions/zalo/src/api.ts +0 -206
- package/extensions/zalo/src/channel.directory.test.ts +0 -35
- package/extensions/zalo/src/channel.ts +0 -394
- package/extensions/zalo/src/config-schema.ts +0 -24
- package/extensions/zalo/src/monitor.ts +0 -760
- package/extensions/zalo/src/monitor.webhook.test.ts +0 -70
- package/extensions/zalo/src/onboarding.ts +0 -405
- package/extensions/zalo/src/probe.ts +0 -46
- package/extensions/zalo/src/proxy.ts +0 -18
- package/extensions/zalo/src/runtime.ts +0 -14
- package/extensions/zalo/src/send.ts +0 -117
- package/extensions/zalo/src/status-issues.ts +0 -50
- package/extensions/zalo/src/token.ts +0 -55
- package/extensions/zalo/src/types.ts +0 -42
- package/extensions/zalouser/CHANGELOG.md +0 -33
- package/extensions/zalouser/README.md +0 -221
- package/extensions/zalouser/clawdbot.plugin.json +0 -11
- package/extensions/zalouser/index.ts +0 -32
- package/extensions/zalouser/package.json +0 -33
- package/extensions/zalouser/src/accounts.ts +0 -117
- package/extensions/zalouser/src/channel.test.ts +0 -17
- package/extensions/zalouser/src/channel.ts +0 -641
- package/extensions/zalouser/src/config-schema.ts +0 -27
- package/extensions/zalouser/src/monitor.ts +0 -574
- package/extensions/zalouser/src/onboarding.ts +0 -488
- package/extensions/zalouser/src/probe.ts +0 -28
- package/extensions/zalouser/src/runtime.ts +0 -14
- package/extensions/zalouser/src/send.ts +0 -150
- package/extensions/zalouser/src/status-issues.test.ts +0 -58
- package/extensions/zalouser/src/status-issues.ts +0 -81
- package/extensions/zalouser/src/tool.ts +0 -156
- package/extensions/zalouser/src/types.ts +0 -102
- package/extensions/zalouser/src/zca.ts +0 -208
- package/skills/1password/SKILL.md +0 -53
- package/skills/1password/references/cli-examples.md +0 -29
- package/skills/1password/references/get-started.md +0 -17
- package/skills/apple-notes/SKILL.md +0 -50
- package/skills/apple-reminders/SKILL.md +0 -67
- package/skills/bear-notes/SKILL.md +0 -79
- package/skills/bird/SKILL.md +0 -197
- package/skills/blogwatcher/SKILL.md +0 -46
- package/skills/blucli/SKILL.md +0 -27
- package/skills/bluebubbles/SKILL.md +0 -39
- package/skills/camsnap/SKILL.md +0 -25
- package/skills/canvas/SKILL.md +0 -189
- package/skills/clawdhub/SKILL.md +0 -53
- package/skills/coding-agent/SKILL.md +0 -278
- package/skills/discord/SKILL.md +0 -475
- package/skills/eightctl/SKILL.md +0 -29
- package/skills/food-order/SKILL.md +0 -41
- package/skills/gemini/SKILL.md +0 -23
- package/skills/gifgrep/SKILL.md +0 -47
- package/skills/github/SKILL.md +0 -48
- package/skills/gog/SKILL.md +0 -92
- package/skills/goplaces/SKILL.md +0 -30
- package/skills/himalaya/SKILL.md +0 -217
- package/skills/himalaya/references/configuration.md +0 -174
- package/skills/himalaya/references/message-composition.md +0 -182
- package/skills/imsg/SKILL.md +0 -25
- package/skills/local-places/SERVER_README.md +0 -101
- package/skills/local-places/SKILL.md +0 -91
- package/skills/local-places/pyproject.toml +0 -27
- package/skills/local-places/src/local_places/__init__.py +0 -2
- package/skills/local-places/src/local_places/google_places.py +0 -314
- package/skills/local-places/src/local_places/main.py +0 -65
- package/skills/local-places/src/local_places/schemas.py +0 -107
- package/skills/mcporter/SKILL.md +0 -38
- package/skills/model-usage/SKILL.md +0 -45
- package/skills/model-usage/references/codexbar-cli.md +0 -28
- package/skills/model-usage/scripts/model_usage.py +0 -310
- package/skills/nano-banana-pro/SKILL.md +0 -30
- package/skills/nano-banana-pro/scripts/generate_image.py +0 -169
- package/skills/nano-pdf/SKILL.md +0 -20
- package/skills/notion/SKILL.md +0 -156
- package/skills/obsidian/SKILL.md +0 -55
- package/skills/openai-image-gen/SKILL.md +0 -71
- package/skills/openai-image-gen/scripts/gen.py +0 -240
- package/skills/openai-whisper/SKILL.md +0 -19
- package/skills/openai-whisper-api/SKILL.md +0 -43
- package/skills/openai-whisper-api/scripts/transcribe.sh +0 -85
- package/skills/openhue/SKILL.md +0 -30
- package/skills/oracle/SKILL.md +0 -105
- package/skills/ordercli/SKILL.md +0 -47
- package/skills/peekaboo/SKILL.md +0 -153
- package/skills/sag/SKILL.md +0 -62
- package/skills/session-logs/SKILL.md +0 -105
- package/skills/sherpa-onnx-tts/SKILL.md +0 -49
- package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +0 -178
- package/skills/skill-creator/SKILL.md +0 -371
- package/skills/skill-creator/license.txt +0 -202
- package/skills/skill-creator/scripts/init_skill.py +0 -378
- package/skills/skill-creator/scripts/package_skill.py +0 -111
- package/skills/skill-creator/scripts/quick_validate.py +0 -101
- package/skills/slack/SKILL.md +0 -144
- package/skills/songsee/SKILL.md +0 -29
- package/skills/sonoscli/SKILL.md +0 -26
- package/skills/spotify-player/SKILL.md +0 -34
- package/skills/summarize/SKILL.md +0 -67
- package/skills/things-mac/SKILL.md +0 -61
- package/skills/tmux/SKILL.md +0 -121
- package/skills/tmux/scripts/find-sessions.sh +0 -112
- package/skills/tmux/scripts/wait-for-text.sh +0 -83
- package/skills/trello/SKILL.md +0 -84
- package/skills/video-frames/SKILL.md +0 -29
- package/skills/video-frames/scripts/frame.sh +0 -81
- package/skills/voice-call/SKILL.md +0 -35
- package/skills/wacli/SKILL.md +0 -42
- package/skills/weather/SKILL.md +0 -49
|
@@ -1,2140 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
-
import { EventEmitter } from "node:events";
|
|
4
|
-
|
|
5
|
-
import { removeAckReactionAfterReply, shouldAckReaction } from "clawdbot/plugin-sdk";
|
|
6
|
-
import type { ClawdbotConfig, PluginRuntime } from "clawdbot/plugin-sdk";
|
|
7
|
-
import {
|
|
8
|
-
handleBlueBubblesWebhookRequest,
|
|
9
|
-
registerBlueBubblesWebhookTarget,
|
|
10
|
-
resolveBlueBubblesMessageId,
|
|
11
|
-
_resetBlueBubblesShortIdState,
|
|
12
|
-
} from "./monitor.js";
|
|
13
|
-
import { setBlueBubblesRuntime } from "./runtime.js";
|
|
14
|
-
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
|
15
|
-
|
|
16
|
-
// Mock dependencies
|
|
17
|
-
vi.mock("./send.js", () => ({
|
|
18
|
-
resolveChatGuidForTarget: vi.fn().mockResolvedValue("iMessage;-;+15551234567"),
|
|
19
|
-
sendMessageBlueBubbles: vi.fn().mockResolvedValue({ messageId: "msg-123" }),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
vi.mock("./chat.js", () => ({
|
|
23
|
-
markBlueBubblesChatRead: vi.fn().mockResolvedValue(undefined),
|
|
24
|
-
sendBlueBubblesTyping: vi.fn().mockResolvedValue(undefined),
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
vi.mock("./attachments.js", () => ({
|
|
28
|
-
downloadBlueBubblesAttachment: vi.fn().mockResolvedValue({
|
|
29
|
-
buffer: Buffer.from("test"),
|
|
30
|
-
contentType: "image/jpeg",
|
|
31
|
-
}),
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
vi.mock("./reactions.js", async () => {
|
|
35
|
-
const actual = await vi.importActual<typeof import("./reactions.js")>("./reactions.js");
|
|
36
|
-
return {
|
|
37
|
-
...actual,
|
|
38
|
-
sendBlueBubblesReaction: vi.fn().mockResolvedValue(undefined),
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Mock runtime
|
|
43
|
-
const mockEnqueueSystemEvent = vi.fn();
|
|
44
|
-
const mockBuildPairingReply = vi.fn(() => "Pairing code: TESTCODE");
|
|
45
|
-
const mockReadAllowFromStore = vi.fn().mockResolvedValue([]);
|
|
46
|
-
const mockUpsertPairingRequest = vi.fn().mockResolvedValue({ code: "TESTCODE", created: true });
|
|
47
|
-
const mockResolveAgentRoute = vi.fn(() => ({
|
|
48
|
-
agentId: "main",
|
|
49
|
-
accountId: "default",
|
|
50
|
-
sessionKey: "agent:main:bluebubbles:dm:+15551234567",
|
|
51
|
-
}));
|
|
52
|
-
const mockBuildMentionRegexes = vi.fn(() => [/\bbert\b/i]);
|
|
53
|
-
const mockMatchesMentionPatterns = vi.fn((text: string, regexes: RegExp[]) =>
|
|
54
|
-
regexes.some((r) => r.test(text)),
|
|
55
|
-
);
|
|
56
|
-
const mockResolveRequireMention = vi.fn(() => false);
|
|
57
|
-
const mockResolveGroupPolicy = vi.fn(() => "open");
|
|
58
|
-
const mockDispatchReplyWithBufferedBlockDispatcher = vi.fn(async () => undefined);
|
|
59
|
-
const mockHasControlCommand = vi.fn(() => false);
|
|
60
|
-
const mockResolveCommandAuthorizedFromAuthorizers = vi.fn(() => false);
|
|
61
|
-
const mockSaveMediaBuffer = vi.fn().mockResolvedValue({
|
|
62
|
-
path: "/tmp/test-media.jpg",
|
|
63
|
-
contentType: "image/jpeg",
|
|
64
|
-
});
|
|
65
|
-
const mockResolveStorePath = vi.fn(() => "/tmp/sessions.json");
|
|
66
|
-
const mockReadSessionUpdatedAt = vi.fn(() => undefined);
|
|
67
|
-
const mockResolveEnvelopeFormatOptions = vi.fn(() => ({
|
|
68
|
-
template: "channel+name+time",
|
|
69
|
-
}));
|
|
70
|
-
const mockFormatAgentEnvelope = vi.fn((opts: { body: string }) => opts.body);
|
|
71
|
-
const mockChunkMarkdownText = vi.fn((text: string) => [text]);
|
|
72
|
-
|
|
73
|
-
function createMockRuntime(): PluginRuntime {
|
|
74
|
-
return {
|
|
75
|
-
version: "1.0.0",
|
|
76
|
-
config: {
|
|
77
|
-
loadConfig: vi.fn(() => ({})) as unknown as PluginRuntime["config"]["loadConfig"],
|
|
78
|
-
writeConfigFile: vi.fn() as unknown as PluginRuntime["config"]["writeConfigFile"],
|
|
79
|
-
},
|
|
80
|
-
system: {
|
|
81
|
-
enqueueSystemEvent: mockEnqueueSystemEvent as unknown as PluginRuntime["system"]["enqueueSystemEvent"],
|
|
82
|
-
runCommandWithTimeout: vi.fn() as unknown as PluginRuntime["system"]["runCommandWithTimeout"],
|
|
83
|
-
},
|
|
84
|
-
media: {
|
|
85
|
-
loadWebMedia: vi.fn() as unknown as PluginRuntime["media"]["loadWebMedia"],
|
|
86
|
-
detectMime: vi.fn() as unknown as PluginRuntime["media"]["detectMime"],
|
|
87
|
-
mediaKindFromMime: vi.fn() as unknown as PluginRuntime["media"]["mediaKindFromMime"],
|
|
88
|
-
isVoiceCompatibleAudio: vi.fn() as unknown as PluginRuntime["media"]["isVoiceCompatibleAudio"],
|
|
89
|
-
getImageMetadata: vi.fn() as unknown as PluginRuntime["media"]["getImageMetadata"],
|
|
90
|
-
resizeToJpeg: vi.fn() as unknown as PluginRuntime["media"]["resizeToJpeg"],
|
|
91
|
-
},
|
|
92
|
-
tools: {
|
|
93
|
-
createMemoryGetTool: vi.fn() as unknown as PluginRuntime["tools"]["createMemoryGetTool"],
|
|
94
|
-
createMemorySearchTool: vi.fn() as unknown as PluginRuntime["tools"]["createMemorySearchTool"],
|
|
95
|
-
registerMemoryCli: vi.fn() as unknown as PluginRuntime["tools"]["registerMemoryCli"],
|
|
96
|
-
},
|
|
97
|
-
channel: {
|
|
98
|
-
text: {
|
|
99
|
-
chunkMarkdownText: mockChunkMarkdownText as unknown as PluginRuntime["channel"]["text"]["chunkMarkdownText"],
|
|
100
|
-
chunkText: vi.fn() as unknown as PluginRuntime["channel"]["text"]["chunkText"],
|
|
101
|
-
resolveTextChunkLimit: vi.fn(() => 4000) as unknown as PluginRuntime["channel"]["text"]["resolveTextChunkLimit"],
|
|
102
|
-
hasControlCommand: mockHasControlCommand as unknown as PluginRuntime["channel"]["text"]["hasControlCommand"],
|
|
103
|
-
resolveMarkdownTableMode: vi.fn(() => "code") as unknown as PluginRuntime["channel"]["text"]["resolveMarkdownTableMode"],
|
|
104
|
-
convertMarkdownTables: vi.fn((text: string) => text) as unknown as PluginRuntime["channel"]["text"]["convertMarkdownTables"],
|
|
105
|
-
},
|
|
106
|
-
reply: {
|
|
107
|
-
dispatchReplyWithBufferedBlockDispatcher: mockDispatchReplyWithBufferedBlockDispatcher as unknown as PluginRuntime["channel"]["reply"]["dispatchReplyWithBufferedBlockDispatcher"],
|
|
108
|
-
createReplyDispatcherWithTyping: vi.fn() as unknown as PluginRuntime["channel"]["reply"]["createReplyDispatcherWithTyping"],
|
|
109
|
-
resolveEffectiveMessagesConfig: vi.fn() as unknown as PluginRuntime["channel"]["reply"]["resolveEffectiveMessagesConfig"],
|
|
110
|
-
resolveHumanDelayConfig: vi.fn() as unknown as PluginRuntime["channel"]["reply"]["resolveHumanDelayConfig"],
|
|
111
|
-
dispatchReplyFromConfig: vi.fn() as unknown as PluginRuntime["channel"]["reply"]["dispatchReplyFromConfig"],
|
|
112
|
-
finalizeInboundContext: vi.fn() as unknown as PluginRuntime["channel"]["reply"]["finalizeInboundContext"],
|
|
113
|
-
formatAgentEnvelope: mockFormatAgentEnvelope as unknown as PluginRuntime["channel"]["reply"]["formatAgentEnvelope"],
|
|
114
|
-
formatInboundEnvelope: vi.fn() as unknown as PluginRuntime["channel"]["reply"]["formatInboundEnvelope"],
|
|
115
|
-
resolveEnvelopeFormatOptions: mockResolveEnvelopeFormatOptions as unknown as PluginRuntime["channel"]["reply"]["resolveEnvelopeFormatOptions"],
|
|
116
|
-
},
|
|
117
|
-
routing: {
|
|
118
|
-
resolveAgentRoute: mockResolveAgentRoute as unknown as PluginRuntime["channel"]["routing"]["resolveAgentRoute"],
|
|
119
|
-
},
|
|
120
|
-
pairing: {
|
|
121
|
-
buildPairingReply: mockBuildPairingReply as unknown as PluginRuntime["channel"]["pairing"]["buildPairingReply"],
|
|
122
|
-
readAllowFromStore: mockReadAllowFromStore as unknown as PluginRuntime["channel"]["pairing"]["readAllowFromStore"],
|
|
123
|
-
upsertPairingRequest: mockUpsertPairingRequest as unknown as PluginRuntime["channel"]["pairing"]["upsertPairingRequest"],
|
|
124
|
-
},
|
|
125
|
-
media: {
|
|
126
|
-
fetchRemoteMedia: vi.fn() as unknown as PluginRuntime["channel"]["media"]["fetchRemoteMedia"],
|
|
127
|
-
saveMediaBuffer: mockSaveMediaBuffer as unknown as PluginRuntime["channel"]["media"]["saveMediaBuffer"],
|
|
128
|
-
},
|
|
129
|
-
session: {
|
|
130
|
-
resolveStorePath: mockResolveStorePath as unknown as PluginRuntime["channel"]["session"]["resolveStorePath"],
|
|
131
|
-
readSessionUpdatedAt: mockReadSessionUpdatedAt as unknown as PluginRuntime["channel"]["session"]["readSessionUpdatedAt"],
|
|
132
|
-
recordInboundSession: vi.fn() as unknown as PluginRuntime["channel"]["session"]["recordInboundSession"],
|
|
133
|
-
recordSessionMetaFromInbound: vi.fn() as unknown as PluginRuntime["channel"]["session"]["recordSessionMetaFromInbound"],
|
|
134
|
-
updateLastRoute: vi.fn() as unknown as PluginRuntime["channel"]["session"]["updateLastRoute"],
|
|
135
|
-
},
|
|
136
|
-
mentions: {
|
|
137
|
-
buildMentionRegexes: mockBuildMentionRegexes as unknown as PluginRuntime["channel"]["mentions"]["buildMentionRegexes"],
|
|
138
|
-
matchesMentionPatterns: mockMatchesMentionPatterns as unknown as PluginRuntime["channel"]["mentions"]["matchesMentionPatterns"],
|
|
139
|
-
},
|
|
140
|
-
reactions: {
|
|
141
|
-
shouldAckReaction,
|
|
142
|
-
removeAckReactionAfterReply,
|
|
143
|
-
},
|
|
144
|
-
groups: {
|
|
145
|
-
resolveGroupPolicy: mockResolveGroupPolicy as unknown as PluginRuntime["channel"]["groups"]["resolveGroupPolicy"],
|
|
146
|
-
resolveRequireMention: mockResolveRequireMention as unknown as PluginRuntime["channel"]["groups"]["resolveRequireMention"],
|
|
147
|
-
},
|
|
148
|
-
debounce: {
|
|
149
|
-
createInboundDebouncer: vi.fn() as unknown as PluginRuntime["channel"]["debounce"]["createInboundDebouncer"],
|
|
150
|
-
resolveInboundDebounceMs: vi.fn() as unknown as PluginRuntime["channel"]["debounce"]["resolveInboundDebounceMs"],
|
|
151
|
-
},
|
|
152
|
-
commands: {
|
|
153
|
-
resolveCommandAuthorizedFromAuthorizers: mockResolveCommandAuthorizedFromAuthorizers as unknown as PluginRuntime["channel"]["commands"]["resolveCommandAuthorizedFromAuthorizers"],
|
|
154
|
-
isControlCommandMessage: vi.fn() as unknown as PluginRuntime["channel"]["commands"]["isControlCommandMessage"],
|
|
155
|
-
shouldComputeCommandAuthorized: vi.fn() as unknown as PluginRuntime["channel"]["commands"]["shouldComputeCommandAuthorized"],
|
|
156
|
-
shouldHandleTextCommands: vi.fn() as unknown as PluginRuntime["channel"]["commands"]["shouldHandleTextCommands"],
|
|
157
|
-
},
|
|
158
|
-
discord: {} as PluginRuntime["channel"]["discord"],
|
|
159
|
-
slack: {} as PluginRuntime["channel"]["slack"],
|
|
160
|
-
telegram: {} as PluginRuntime["channel"]["telegram"],
|
|
161
|
-
signal: {} as PluginRuntime["channel"]["signal"],
|
|
162
|
-
imessage: {} as PluginRuntime["channel"]["imessage"],
|
|
163
|
-
whatsapp: {} as PluginRuntime["channel"]["whatsapp"],
|
|
164
|
-
},
|
|
165
|
-
logging: {
|
|
166
|
-
shouldLogVerbose: vi.fn(() => false) as unknown as PluginRuntime["logging"]["shouldLogVerbose"],
|
|
167
|
-
getChildLogger: vi.fn(() => ({
|
|
168
|
-
info: vi.fn(),
|
|
169
|
-
warn: vi.fn(),
|
|
170
|
-
error: vi.fn(),
|
|
171
|
-
debug: vi.fn(),
|
|
172
|
-
})) as unknown as PluginRuntime["logging"]["getChildLogger"],
|
|
173
|
-
},
|
|
174
|
-
state: {
|
|
175
|
-
resolveStateDir: vi.fn(() => "/tmp/clawdbot") as unknown as PluginRuntime["state"]["resolveStateDir"],
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function createMockAccount(overrides: Partial<ResolvedBlueBubblesAccount["config"]> = {}): ResolvedBlueBubblesAccount {
|
|
181
|
-
return {
|
|
182
|
-
accountId: "default",
|
|
183
|
-
enabled: true,
|
|
184
|
-
configured: true,
|
|
185
|
-
config: {
|
|
186
|
-
serverUrl: "http://localhost:1234",
|
|
187
|
-
password: "test-password",
|
|
188
|
-
dmPolicy: "open",
|
|
189
|
-
groupPolicy: "open",
|
|
190
|
-
allowFrom: [],
|
|
191
|
-
groupAllowFrom: [],
|
|
192
|
-
...overrides,
|
|
193
|
-
},
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function createMockRequest(
|
|
198
|
-
method: string,
|
|
199
|
-
url: string,
|
|
200
|
-
body: unknown,
|
|
201
|
-
headers: Record<string, string> = {},
|
|
202
|
-
): IncomingMessage {
|
|
203
|
-
const req = new EventEmitter() as IncomingMessage;
|
|
204
|
-
req.method = method;
|
|
205
|
-
req.url = url;
|
|
206
|
-
req.headers = headers;
|
|
207
|
-
(req as unknown as { socket: { remoteAddress: string } }).socket = { remoteAddress: "127.0.0.1" };
|
|
208
|
-
|
|
209
|
-
// Emit body data after a microtask
|
|
210
|
-
Promise.resolve().then(() => {
|
|
211
|
-
const bodyStr = typeof body === "string" ? body : JSON.stringify(body);
|
|
212
|
-
req.emit("data", Buffer.from(bodyStr));
|
|
213
|
-
req.emit("end");
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
return req;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function createMockResponse(): ServerResponse & { body: string; statusCode: number } {
|
|
220
|
-
const res = {
|
|
221
|
-
statusCode: 200,
|
|
222
|
-
body: "",
|
|
223
|
-
setHeader: vi.fn(),
|
|
224
|
-
end: vi.fn((data?: string) => {
|
|
225
|
-
res.body = data ?? "";
|
|
226
|
-
}),
|
|
227
|
-
} as unknown as ServerResponse & { body: string; statusCode: number };
|
|
228
|
-
return res;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const flushAsync = async () => {
|
|
232
|
-
for (let i = 0; i < 2; i += 1) {
|
|
233
|
-
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
describe("BlueBubbles webhook monitor", () => {
|
|
238
|
-
let unregister: () => void;
|
|
239
|
-
|
|
240
|
-
beforeEach(() => {
|
|
241
|
-
vi.clearAllMocks();
|
|
242
|
-
// Reset short ID state between tests for predictable behavior
|
|
243
|
-
_resetBlueBubblesShortIdState();
|
|
244
|
-
mockReadAllowFromStore.mockResolvedValue([]);
|
|
245
|
-
mockUpsertPairingRequest.mockResolvedValue({ code: "TESTCODE", created: true });
|
|
246
|
-
mockResolveRequireMention.mockReturnValue(false);
|
|
247
|
-
mockHasControlCommand.mockReturnValue(false);
|
|
248
|
-
mockResolveCommandAuthorizedFromAuthorizers.mockReturnValue(false);
|
|
249
|
-
mockBuildMentionRegexes.mockReturnValue([/\bbert\b/i]);
|
|
250
|
-
|
|
251
|
-
setBlueBubblesRuntime(createMockRuntime());
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
afterEach(() => {
|
|
255
|
-
unregister?.();
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
describe("webhook parsing + auth handling", () => {
|
|
259
|
-
it("rejects non-POST requests", async () => {
|
|
260
|
-
const account = createMockAccount();
|
|
261
|
-
const config: ClawdbotConfig = {};
|
|
262
|
-
const core = createMockRuntime();
|
|
263
|
-
setBlueBubblesRuntime(core);
|
|
264
|
-
|
|
265
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
266
|
-
account,
|
|
267
|
-
config,
|
|
268
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
269
|
-
core,
|
|
270
|
-
path: "/bluebubbles-webhook",
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
const req = createMockRequest("GET", "/bluebubbles-webhook", {});
|
|
274
|
-
const res = createMockResponse();
|
|
275
|
-
|
|
276
|
-
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
277
|
-
|
|
278
|
-
expect(handled).toBe(true);
|
|
279
|
-
expect(res.statusCode).toBe(405);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it("accepts POST requests with valid JSON payload", async () => {
|
|
283
|
-
const account = createMockAccount();
|
|
284
|
-
const config: ClawdbotConfig = {};
|
|
285
|
-
const core = createMockRuntime();
|
|
286
|
-
setBlueBubblesRuntime(core);
|
|
287
|
-
|
|
288
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
289
|
-
account,
|
|
290
|
-
config,
|
|
291
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
292
|
-
core,
|
|
293
|
-
path: "/bluebubbles-webhook",
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
const payload = {
|
|
297
|
-
type: "new-message",
|
|
298
|
-
data: {
|
|
299
|
-
text: "hello",
|
|
300
|
-
handle: { address: "+15551234567" },
|
|
301
|
-
isGroup: false,
|
|
302
|
-
isFromMe: false,
|
|
303
|
-
guid: "msg-1",
|
|
304
|
-
date: Date.now(),
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
309
|
-
const res = createMockResponse();
|
|
310
|
-
|
|
311
|
-
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
312
|
-
|
|
313
|
-
expect(handled).toBe(true);
|
|
314
|
-
expect(res.statusCode).toBe(200);
|
|
315
|
-
expect(res.body).toBe("ok");
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it("rejects requests with invalid JSON", async () => {
|
|
319
|
-
const account = createMockAccount();
|
|
320
|
-
const config: ClawdbotConfig = {};
|
|
321
|
-
const core = createMockRuntime();
|
|
322
|
-
setBlueBubblesRuntime(core);
|
|
323
|
-
|
|
324
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
325
|
-
account,
|
|
326
|
-
config,
|
|
327
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
328
|
-
core,
|
|
329
|
-
path: "/bluebubbles-webhook",
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", "invalid json {{");
|
|
333
|
-
const res = createMockResponse();
|
|
334
|
-
|
|
335
|
-
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
336
|
-
|
|
337
|
-
expect(handled).toBe(true);
|
|
338
|
-
expect(res.statusCode).toBe(400);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it("authenticates via password query parameter", async () => {
|
|
342
|
-
const account = createMockAccount({ password: "secret-token" });
|
|
343
|
-
const config: ClawdbotConfig = {};
|
|
344
|
-
const core = createMockRuntime();
|
|
345
|
-
setBlueBubblesRuntime(core);
|
|
346
|
-
|
|
347
|
-
// Mock non-localhost request
|
|
348
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook?password=secret-token", {
|
|
349
|
-
type: "new-message",
|
|
350
|
-
data: {
|
|
351
|
-
text: "hello",
|
|
352
|
-
handle: { address: "+15551234567" },
|
|
353
|
-
isGroup: false,
|
|
354
|
-
isFromMe: false,
|
|
355
|
-
guid: "msg-1",
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
(req as unknown as { socket: { remoteAddress: string } }).socket = { remoteAddress: "192.168.1.100" };
|
|
359
|
-
|
|
360
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
361
|
-
account,
|
|
362
|
-
config,
|
|
363
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
364
|
-
core,
|
|
365
|
-
path: "/bluebubbles-webhook",
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const res = createMockResponse();
|
|
369
|
-
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
370
|
-
|
|
371
|
-
expect(handled).toBe(true);
|
|
372
|
-
expect(res.statusCode).toBe(200);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
it("authenticates via x-password header", async () => {
|
|
376
|
-
const account = createMockAccount({ password: "secret-token" });
|
|
377
|
-
const config: ClawdbotConfig = {};
|
|
378
|
-
const core = createMockRuntime();
|
|
379
|
-
setBlueBubblesRuntime(core);
|
|
380
|
-
|
|
381
|
-
const req = createMockRequest(
|
|
382
|
-
"POST",
|
|
383
|
-
"/bluebubbles-webhook",
|
|
384
|
-
{
|
|
385
|
-
type: "new-message",
|
|
386
|
-
data: {
|
|
387
|
-
text: "hello",
|
|
388
|
-
handle: { address: "+15551234567" },
|
|
389
|
-
isGroup: false,
|
|
390
|
-
isFromMe: false,
|
|
391
|
-
guid: "msg-1",
|
|
392
|
-
},
|
|
393
|
-
},
|
|
394
|
-
{ "x-password": "secret-token" },
|
|
395
|
-
);
|
|
396
|
-
(req as unknown as { socket: { remoteAddress: string } }).socket = { remoteAddress: "192.168.1.100" };
|
|
397
|
-
|
|
398
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
399
|
-
account,
|
|
400
|
-
config,
|
|
401
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
402
|
-
core,
|
|
403
|
-
path: "/bluebubbles-webhook",
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
const res = createMockResponse();
|
|
407
|
-
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
408
|
-
|
|
409
|
-
expect(handled).toBe(true);
|
|
410
|
-
expect(res.statusCode).toBe(200);
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it("rejects unauthorized requests with wrong password", async () => {
|
|
414
|
-
const account = createMockAccount({ password: "secret-token" });
|
|
415
|
-
const config: ClawdbotConfig = {};
|
|
416
|
-
const core = createMockRuntime();
|
|
417
|
-
setBlueBubblesRuntime(core);
|
|
418
|
-
|
|
419
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook?password=wrong-token", {
|
|
420
|
-
type: "new-message",
|
|
421
|
-
data: {
|
|
422
|
-
text: "hello",
|
|
423
|
-
handle: { address: "+15551234567" },
|
|
424
|
-
isGroup: false,
|
|
425
|
-
isFromMe: false,
|
|
426
|
-
guid: "msg-1",
|
|
427
|
-
},
|
|
428
|
-
});
|
|
429
|
-
(req as unknown as { socket: { remoteAddress: string } }).socket = { remoteAddress: "192.168.1.100" };
|
|
430
|
-
|
|
431
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
432
|
-
account,
|
|
433
|
-
config,
|
|
434
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
435
|
-
core,
|
|
436
|
-
path: "/bluebubbles-webhook",
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
const res = createMockResponse();
|
|
440
|
-
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
441
|
-
|
|
442
|
-
expect(handled).toBe(true);
|
|
443
|
-
expect(res.statusCode).toBe(401);
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it("allows localhost requests without authentication", async () => {
|
|
447
|
-
const account = createMockAccount({ password: "secret-token" });
|
|
448
|
-
const config: ClawdbotConfig = {};
|
|
449
|
-
const core = createMockRuntime();
|
|
450
|
-
setBlueBubblesRuntime(core);
|
|
451
|
-
|
|
452
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", {
|
|
453
|
-
type: "new-message",
|
|
454
|
-
data: {
|
|
455
|
-
text: "hello",
|
|
456
|
-
handle: { address: "+15551234567" },
|
|
457
|
-
isGroup: false,
|
|
458
|
-
isFromMe: false,
|
|
459
|
-
guid: "msg-1",
|
|
460
|
-
},
|
|
461
|
-
});
|
|
462
|
-
// Localhost address
|
|
463
|
-
(req as unknown as { socket: { remoteAddress: string } }).socket = { remoteAddress: "127.0.0.1" };
|
|
464
|
-
|
|
465
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
466
|
-
account,
|
|
467
|
-
config,
|
|
468
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
469
|
-
core,
|
|
470
|
-
path: "/bluebubbles-webhook",
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
const res = createMockResponse();
|
|
474
|
-
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
475
|
-
|
|
476
|
-
expect(handled).toBe(true);
|
|
477
|
-
expect(res.statusCode).toBe(200);
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
it("ignores unregistered webhook paths", async () => {
|
|
481
|
-
const req = createMockRequest("POST", "/unregistered-path", {});
|
|
482
|
-
const res = createMockResponse();
|
|
483
|
-
|
|
484
|
-
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
485
|
-
|
|
486
|
-
expect(handled).toBe(false);
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
it("parses chatId when provided as a string (webhook variant)", async () => {
|
|
490
|
-
const { resolveChatGuidForTarget } = await import("./send.js");
|
|
491
|
-
vi.mocked(resolveChatGuidForTarget).mockClear();
|
|
492
|
-
|
|
493
|
-
const account = createMockAccount({ groupPolicy: "open" });
|
|
494
|
-
const config: ClawdbotConfig = {};
|
|
495
|
-
const core = createMockRuntime();
|
|
496
|
-
setBlueBubblesRuntime(core);
|
|
497
|
-
|
|
498
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
499
|
-
account,
|
|
500
|
-
config,
|
|
501
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
502
|
-
core,
|
|
503
|
-
path: "/bluebubbles-webhook",
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
const payload = {
|
|
507
|
-
type: "new-message",
|
|
508
|
-
data: {
|
|
509
|
-
text: "hello from group",
|
|
510
|
-
handle: { address: "+15551234567" },
|
|
511
|
-
isGroup: true,
|
|
512
|
-
isFromMe: false,
|
|
513
|
-
guid: "msg-1",
|
|
514
|
-
chatId: "123",
|
|
515
|
-
date: Date.now(),
|
|
516
|
-
},
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
520
|
-
const res = createMockResponse();
|
|
521
|
-
|
|
522
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
523
|
-
await flushAsync();
|
|
524
|
-
|
|
525
|
-
expect(resolveChatGuidForTarget).toHaveBeenCalledWith(
|
|
526
|
-
expect.objectContaining({
|
|
527
|
-
target: { kind: "chat_id", chatId: 123 },
|
|
528
|
-
}),
|
|
529
|
-
);
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
it("extracts chatGuid from nested chat object fields (webhook variant)", async () => {
|
|
533
|
-
const { sendMessageBlueBubbles, resolveChatGuidForTarget } = await import("./send.js");
|
|
534
|
-
vi.mocked(sendMessageBlueBubbles).mockClear();
|
|
535
|
-
vi.mocked(resolveChatGuidForTarget).mockClear();
|
|
536
|
-
|
|
537
|
-
mockDispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(async (params) => {
|
|
538
|
-
await params.dispatcherOptions.deliver({ text: "replying now" }, { kind: "final" });
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
const account = createMockAccount({ groupPolicy: "open" });
|
|
542
|
-
const config: ClawdbotConfig = {};
|
|
543
|
-
const core = createMockRuntime();
|
|
544
|
-
setBlueBubblesRuntime(core);
|
|
545
|
-
|
|
546
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
547
|
-
account,
|
|
548
|
-
config,
|
|
549
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
550
|
-
core,
|
|
551
|
-
path: "/bluebubbles-webhook",
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
const payload = {
|
|
555
|
-
type: "new-message",
|
|
556
|
-
data: {
|
|
557
|
-
text: "hello from group",
|
|
558
|
-
handle: { address: "+15551234567" },
|
|
559
|
-
isGroup: true,
|
|
560
|
-
isFromMe: false,
|
|
561
|
-
guid: "msg-1",
|
|
562
|
-
chat: { chatGuid: "iMessage;+;chat123456" },
|
|
563
|
-
date: Date.now(),
|
|
564
|
-
},
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
568
|
-
const res = createMockResponse();
|
|
569
|
-
|
|
570
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
571
|
-
await flushAsync();
|
|
572
|
-
|
|
573
|
-
expect(resolveChatGuidForTarget).not.toHaveBeenCalled();
|
|
574
|
-
expect(sendMessageBlueBubbles).toHaveBeenCalledWith(
|
|
575
|
-
"chat_guid:iMessage;+;chat123456",
|
|
576
|
-
expect.any(String),
|
|
577
|
-
expect.any(Object),
|
|
578
|
-
);
|
|
579
|
-
});
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
describe("DM pairing behavior vs allowFrom", () => {
|
|
583
|
-
it("allows DM from sender in allowFrom list", async () => {
|
|
584
|
-
const account = createMockAccount({
|
|
585
|
-
dmPolicy: "allowlist",
|
|
586
|
-
allowFrom: ["+15551234567"],
|
|
587
|
-
});
|
|
588
|
-
const config: ClawdbotConfig = {};
|
|
589
|
-
const core = createMockRuntime();
|
|
590
|
-
setBlueBubblesRuntime(core);
|
|
591
|
-
|
|
592
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
593
|
-
account,
|
|
594
|
-
config,
|
|
595
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
596
|
-
core,
|
|
597
|
-
path: "/bluebubbles-webhook",
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
const payload = {
|
|
601
|
-
type: "new-message",
|
|
602
|
-
data: {
|
|
603
|
-
text: "hello from allowed sender",
|
|
604
|
-
handle: { address: "+15551234567" },
|
|
605
|
-
isGroup: false,
|
|
606
|
-
isFromMe: false,
|
|
607
|
-
guid: "msg-1",
|
|
608
|
-
date: Date.now(),
|
|
609
|
-
},
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
613
|
-
const res = createMockResponse();
|
|
614
|
-
|
|
615
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
616
|
-
|
|
617
|
-
// Wait for async processing
|
|
618
|
-
await flushAsync();
|
|
619
|
-
|
|
620
|
-
expect(res.statusCode).toBe(200);
|
|
621
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
it("blocks DM from sender not in allowFrom when dmPolicy=allowlist", async () => {
|
|
625
|
-
const account = createMockAccount({
|
|
626
|
-
dmPolicy: "allowlist",
|
|
627
|
-
allowFrom: ["+15559999999"], // Different number
|
|
628
|
-
});
|
|
629
|
-
const config: ClawdbotConfig = {};
|
|
630
|
-
const core = createMockRuntime();
|
|
631
|
-
setBlueBubblesRuntime(core);
|
|
632
|
-
|
|
633
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
634
|
-
account,
|
|
635
|
-
config,
|
|
636
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
637
|
-
core,
|
|
638
|
-
path: "/bluebubbles-webhook",
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
const payload = {
|
|
642
|
-
type: "new-message",
|
|
643
|
-
data: {
|
|
644
|
-
text: "hello from blocked sender",
|
|
645
|
-
handle: { address: "+15551234567" },
|
|
646
|
-
isGroup: false,
|
|
647
|
-
isFromMe: false,
|
|
648
|
-
guid: "msg-1",
|
|
649
|
-
date: Date.now(),
|
|
650
|
-
},
|
|
651
|
-
};
|
|
652
|
-
|
|
653
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
654
|
-
const res = createMockResponse();
|
|
655
|
-
|
|
656
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
657
|
-
await flushAsync();
|
|
658
|
-
|
|
659
|
-
expect(res.statusCode).toBe(200);
|
|
660
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
it("triggers pairing flow for unknown sender when dmPolicy=pairing", async () => {
|
|
664
|
-
// Note: empty allowFrom = allow all. To trigger pairing, we need a non-empty
|
|
665
|
-
// allowlist that doesn't include the sender
|
|
666
|
-
const account = createMockAccount({
|
|
667
|
-
dmPolicy: "pairing",
|
|
668
|
-
allowFrom: ["+15559999999"], // Different number than sender
|
|
669
|
-
});
|
|
670
|
-
const config: ClawdbotConfig = {};
|
|
671
|
-
const core = createMockRuntime();
|
|
672
|
-
setBlueBubblesRuntime(core);
|
|
673
|
-
|
|
674
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
675
|
-
account,
|
|
676
|
-
config,
|
|
677
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
678
|
-
core,
|
|
679
|
-
path: "/bluebubbles-webhook",
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
const payload = {
|
|
683
|
-
type: "new-message",
|
|
684
|
-
data: {
|
|
685
|
-
text: "hello",
|
|
686
|
-
handle: { address: "+15551234567" },
|
|
687
|
-
isGroup: false,
|
|
688
|
-
isFromMe: false,
|
|
689
|
-
guid: "msg-1",
|
|
690
|
-
date: Date.now(),
|
|
691
|
-
},
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
695
|
-
const res = createMockResponse();
|
|
696
|
-
|
|
697
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
698
|
-
await flushAsync();
|
|
699
|
-
|
|
700
|
-
expect(mockUpsertPairingRequest).toHaveBeenCalled();
|
|
701
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
it("does not resend pairing reply when request already exists", async () => {
|
|
705
|
-
mockUpsertPairingRequest.mockResolvedValue({ code: "TESTCODE", created: false });
|
|
706
|
-
|
|
707
|
-
// Note: empty allowFrom = allow all. To trigger pairing, we need a non-empty
|
|
708
|
-
// allowlist that doesn't include the sender
|
|
709
|
-
const account = createMockAccount({
|
|
710
|
-
dmPolicy: "pairing",
|
|
711
|
-
allowFrom: ["+15559999999"], // Different number than sender
|
|
712
|
-
});
|
|
713
|
-
const config: ClawdbotConfig = {};
|
|
714
|
-
const core = createMockRuntime();
|
|
715
|
-
setBlueBubblesRuntime(core);
|
|
716
|
-
|
|
717
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
718
|
-
account,
|
|
719
|
-
config,
|
|
720
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
721
|
-
core,
|
|
722
|
-
path: "/bluebubbles-webhook",
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
const payload = {
|
|
726
|
-
type: "new-message",
|
|
727
|
-
data: {
|
|
728
|
-
text: "hello again",
|
|
729
|
-
handle: { address: "+15551234567" },
|
|
730
|
-
isGroup: false,
|
|
731
|
-
isFromMe: false,
|
|
732
|
-
guid: "msg-2",
|
|
733
|
-
date: Date.now(),
|
|
734
|
-
},
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
738
|
-
const res = createMockResponse();
|
|
739
|
-
|
|
740
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
741
|
-
await flushAsync();
|
|
742
|
-
|
|
743
|
-
expect(mockUpsertPairingRequest).toHaveBeenCalled();
|
|
744
|
-
// Should not send pairing reply since created=false
|
|
745
|
-
const { sendMessageBlueBubbles } = await import("./send.js");
|
|
746
|
-
expect(sendMessageBlueBubbles).not.toHaveBeenCalled();
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
it("allows all DMs when dmPolicy=open", async () => {
|
|
750
|
-
const account = createMockAccount({
|
|
751
|
-
dmPolicy: "open",
|
|
752
|
-
allowFrom: [],
|
|
753
|
-
});
|
|
754
|
-
const config: ClawdbotConfig = {};
|
|
755
|
-
const core = createMockRuntime();
|
|
756
|
-
setBlueBubblesRuntime(core);
|
|
757
|
-
|
|
758
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
759
|
-
account,
|
|
760
|
-
config,
|
|
761
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
762
|
-
core,
|
|
763
|
-
path: "/bluebubbles-webhook",
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
const payload = {
|
|
767
|
-
type: "new-message",
|
|
768
|
-
data: {
|
|
769
|
-
text: "hello from anyone",
|
|
770
|
-
handle: { address: "+15559999999" },
|
|
771
|
-
isGroup: false,
|
|
772
|
-
isFromMe: false,
|
|
773
|
-
guid: "msg-1",
|
|
774
|
-
date: Date.now(),
|
|
775
|
-
},
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
779
|
-
const res = createMockResponse();
|
|
780
|
-
|
|
781
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
782
|
-
await flushAsync();
|
|
783
|
-
|
|
784
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
it("blocks all DMs when dmPolicy=disabled", async () => {
|
|
788
|
-
const account = createMockAccount({
|
|
789
|
-
dmPolicy: "disabled",
|
|
790
|
-
});
|
|
791
|
-
const config: ClawdbotConfig = {};
|
|
792
|
-
const core = createMockRuntime();
|
|
793
|
-
setBlueBubblesRuntime(core);
|
|
794
|
-
|
|
795
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
796
|
-
account,
|
|
797
|
-
config,
|
|
798
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
799
|
-
core,
|
|
800
|
-
path: "/bluebubbles-webhook",
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
const payload = {
|
|
804
|
-
type: "new-message",
|
|
805
|
-
data: {
|
|
806
|
-
text: "hello",
|
|
807
|
-
handle: { address: "+15551234567" },
|
|
808
|
-
isGroup: false,
|
|
809
|
-
isFromMe: false,
|
|
810
|
-
guid: "msg-1",
|
|
811
|
-
date: Date.now(),
|
|
812
|
-
},
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
816
|
-
const res = createMockResponse();
|
|
817
|
-
|
|
818
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
819
|
-
await flushAsync();
|
|
820
|
-
|
|
821
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
|
|
822
|
-
});
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
describe("group message gating", () => {
|
|
826
|
-
it("allows group messages when groupPolicy=open and no allowlist", async () => {
|
|
827
|
-
const account = createMockAccount({
|
|
828
|
-
groupPolicy: "open",
|
|
829
|
-
});
|
|
830
|
-
const config: ClawdbotConfig = {};
|
|
831
|
-
const core = createMockRuntime();
|
|
832
|
-
setBlueBubblesRuntime(core);
|
|
833
|
-
|
|
834
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
835
|
-
account,
|
|
836
|
-
config,
|
|
837
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
838
|
-
core,
|
|
839
|
-
path: "/bluebubbles-webhook",
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
const payload = {
|
|
843
|
-
type: "new-message",
|
|
844
|
-
data: {
|
|
845
|
-
text: "hello from group",
|
|
846
|
-
handle: { address: "+15551234567" },
|
|
847
|
-
isGroup: true,
|
|
848
|
-
isFromMe: false,
|
|
849
|
-
guid: "msg-1",
|
|
850
|
-
chatGuid: "iMessage;+;chat123456",
|
|
851
|
-
date: Date.now(),
|
|
852
|
-
},
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
856
|
-
const res = createMockResponse();
|
|
857
|
-
|
|
858
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
859
|
-
await flushAsync();
|
|
860
|
-
|
|
861
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
it("blocks group messages when groupPolicy=disabled", async () => {
|
|
865
|
-
const account = createMockAccount({
|
|
866
|
-
groupPolicy: "disabled",
|
|
867
|
-
});
|
|
868
|
-
const config: ClawdbotConfig = {};
|
|
869
|
-
const core = createMockRuntime();
|
|
870
|
-
setBlueBubblesRuntime(core);
|
|
871
|
-
|
|
872
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
873
|
-
account,
|
|
874
|
-
config,
|
|
875
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
876
|
-
core,
|
|
877
|
-
path: "/bluebubbles-webhook",
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
const payload = {
|
|
881
|
-
type: "new-message",
|
|
882
|
-
data: {
|
|
883
|
-
text: "hello from group",
|
|
884
|
-
handle: { address: "+15551234567" },
|
|
885
|
-
isGroup: true,
|
|
886
|
-
isFromMe: false,
|
|
887
|
-
guid: "msg-1",
|
|
888
|
-
chatGuid: "iMessage;+;chat123456",
|
|
889
|
-
date: Date.now(),
|
|
890
|
-
},
|
|
891
|
-
};
|
|
892
|
-
|
|
893
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
894
|
-
const res = createMockResponse();
|
|
895
|
-
|
|
896
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
897
|
-
await flushAsync();
|
|
898
|
-
|
|
899
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
it("treats chat_guid groups as group even when isGroup=false", async () => {
|
|
903
|
-
const account = createMockAccount({
|
|
904
|
-
groupPolicy: "allowlist",
|
|
905
|
-
dmPolicy: "open",
|
|
906
|
-
});
|
|
907
|
-
const config: ClawdbotConfig = {};
|
|
908
|
-
const core = createMockRuntime();
|
|
909
|
-
setBlueBubblesRuntime(core);
|
|
910
|
-
|
|
911
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
912
|
-
account,
|
|
913
|
-
config,
|
|
914
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
915
|
-
core,
|
|
916
|
-
path: "/bluebubbles-webhook",
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
const payload = {
|
|
920
|
-
type: "new-message",
|
|
921
|
-
data: {
|
|
922
|
-
text: "hello from group",
|
|
923
|
-
handle: { address: "+15551234567" },
|
|
924
|
-
isGroup: false,
|
|
925
|
-
isFromMe: false,
|
|
926
|
-
guid: "msg-1",
|
|
927
|
-
chatGuid: "iMessage;+;chat123456",
|
|
928
|
-
date: Date.now(),
|
|
929
|
-
},
|
|
930
|
-
};
|
|
931
|
-
|
|
932
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
933
|
-
const res = createMockResponse();
|
|
934
|
-
|
|
935
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
936
|
-
await flushAsync();
|
|
937
|
-
|
|
938
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
|
|
939
|
-
});
|
|
940
|
-
|
|
941
|
-
it("allows group messages from allowed chat_guid in groupAllowFrom", async () => {
|
|
942
|
-
const account = createMockAccount({
|
|
943
|
-
groupPolicy: "allowlist",
|
|
944
|
-
groupAllowFrom: ["chat_guid:iMessage;+;chat123456"],
|
|
945
|
-
});
|
|
946
|
-
const config: ClawdbotConfig = {};
|
|
947
|
-
const core = createMockRuntime();
|
|
948
|
-
setBlueBubblesRuntime(core);
|
|
949
|
-
|
|
950
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
951
|
-
account,
|
|
952
|
-
config,
|
|
953
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
954
|
-
core,
|
|
955
|
-
path: "/bluebubbles-webhook",
|
|
956
|
-
});
|
|
957
|
-
|
|
958
|
-
const payload = {
|
|
959
|
-
type: "new-message",
|
|
960
|
-
data: {
|
|
961
|
-
text: "hello from allowed group",
|
|
962
|
-
handle: { address: "+15551234567" },
|
|
963
|
-
isGroup: true,
|
|
964
|
-
isFromMe: false,
|
|
965
|
-
guid: "msg-1",
|
|
966
|
-
chatGuid: "iMessage;+;chat123456",
|
|
967
|
-
date: Date.now(),
|
|
968
|
-
},
|
|
969
|
-
};
|
|
970
|
-
|
|
971
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
972
|
-
const res = createMockResponse();
|
|
973
|
-
|
|
974
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
975
|
-
await flushAsync();
|
|
976
|
-
|
|
977
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
978
|
-
});
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
describe("mention gating (group messages)", () => {
|
|
982
|
-
it("processes group message when mentioned and requireMention=true", async () => {
|
|
983
|
-
mockResolveRequireMention.mockReturnValue(true);
|
|
984
|
-
mockMatchesMentionPatterns.mockReturnValue(true);
|
|
985
|
-
|
|
986
|
-
const account = createMockAccount({ groupPolicy: "open" });
|
|
987
|
-
const config: ClawdbotConfig = {};
|
|
988
|
-
const core = createMockRuntime();
|
|
989
|
-
setBlueBubblesRuntime(core);
|
|
990
|
-
|
|
991
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
992
|
-
account,
|
|
993
|
-
config,
|
|
994
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
995
|
-
core,
|
|
996
|
-
path: "/bluebubbles-webhook",
|
|
997
|
-
});
|
|
998
|
-
|
|
999
|
-
const payload = {
|
|
1000
|
-
type: "new-message",
|
|
1001
|
-
data: {
|
|
1002
|
-
text: "bert, can you help me?",
|
|
1003
|
-
handle: { address: "+15551234567" },
|
|
1004
|
-
isGroup: true,
|
|
1005
|
-
isFromMe: false,
|
|
1006
|
-
guid: "msg-1",
|
|
1007
|
-
chatGuid: "iMessage;+;chat123456",
|
|
1008
|
-
date: Date.now(),
|
|
1009
|
-
},
|
|
1010
|
-
};
|
|
1011
|
-
|
|
1012
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1013
|
-
const res = createMockResponse();
|
|
1014
|
-
|
|
1015
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1016
|
-
await flushAsync();
|
|
1017
|
-
|
|
1018
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1019
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
1020
|
-
expect(callArgs.ctx.WasMentioned).toBe(true);
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
it("skips group message when not mentioned and requireMention=true", async () => {
|
|
1024
|
-
mockResolveRequireMention.mockReturnValue(true);
|
|
1025
|
-
mockMatchesMentionPatterns.mockReturnValue(false);
|
|
1026
|
-
|
|
1027
|
-
const account = createMockAccount({ groupPolicy: "open" });
|
|
1028
|
-
const config: ClawdbotConfig = {};
|
|
1029
|
-
const core = createMockRuntime();
|
|
1030
|
-
setBlueBubblesRuntime(core);
|
|
1031
|
-
|
|
1032
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1033
|
-
account,
|
|
1034
|
-
config,
|
|
1035
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1036
|
-
core,
|
|
1037
|
-
path: "/bluebubbles-webhook",
|
|
1038
|
-
});
|
|
1039
|
-
|
|
1040
|
-
const payload = {
|
|
1041
|
-
type: "new-message",
|
|
1042
|
-
data: {
|
|
1043
|
-
text: "hello everyone",
|
|
1044
|
-
handle: { address: "+15551234567" },
|
|
1045
|
-
isGroup: true,
|
|
1046
|
-
isFromMe: false,
|
|
1047
|
-
guid: "msg-1",
|
|
1048
|
-
chatGuid: "iMessage;+;chat123456",
|
|
1049
|
-
date: Date.now(),
|
|
1050
|
-
},
|
|
1051
|
-
};
|
|
1052
|
-
|
|
1053
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1054
|
-
const res = createMockResponse();
|
|
1055
|
-
|
|
1056
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1057
|
-
await flushAsync();
|
|
1058
|
-
|
|
1059
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
it("processes group message without mention when requireMention=false", async () => {
|
|
1063
|
-
mockResolveRequireMention.mockReturnValue(false);
|
|
1064
|
-
|
|
1065
|
-
const account = createMockAccount({ groupPolicy: "open" });
|
|
1066
|
-
const config: ClawdbotConfig = {};
|
|
1067
|
-
const core = createMockRuntime();
|
|
1068
|
-
setBlueBubblesRuntime(core);
|
|
1069
|
-
|
|
1070
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1071
|
-
account,
|
|
1072
|
-
config,
|
|
1073
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1074
|
-
core,
|
|
1075
|
-
path: "/bluebubbles-webhook",
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
|
-
const payload = {
|
|
1079
|
-
type: "new-message",
|
|
1080
|
-
data: {
|
|
1081
|
-
text: "hello everyone",
|
|
1082
|
-
handle: { address: "+15551234567" },
|
|
1083
|
-
isGroup: true,
|
|
1084
|
-
isFromMe: false,
|
|
1085
|
-
guid: "msg-1",
|
|
1086
|
-
chatGuid: "iMessage;+;chat123456",
|
|
1087
|
-
date: Date.now(),
|
|
1088
|
-
},
|
|
1089
|
-
};
|
|
1090
|
-
|
|
1091
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1092
|
-
const res = createMockResponse();
|
|
1093
|
-
|
|
1094
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1095
|
-
await flushAsync();
|
|
1096
|
-
|
|
1097
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1098
|
-
});
|
|
1099
|
-
});
|
|
1100
|
-
|
|
1101
|
-
describe("group metadata", () => {
|
|
1102
|
-
it("includes group subject + members in ctx", async () => {
|
|
1103
|
-
const account = createMockAccount({ groupPolicy: "open" });
|
|
1104
|
-
const config: ClawdbotConfig = {};
|
|
1105
|
-
const core = createMockRuntime();
|
|
1106
|
-
setBlueBubblesRuntime(core);
|
|
1107
|
-
|
|
1108
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1109
|
-
account,
|
|
1110
|
-
config,
|
|
1111
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1112
|
-
core,
|
|
1113
|
-
path: "/bluebubbles-webhook",
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
const payload = {
|
|
1117
|
-
type: "new-message",
|
|
1118
|
-
data: {
|
|
1119
|
-
text: "hello group",
|
|
1120
|
-
handle: { address: "+15551234567" },
|
|
1121
|
-
isGroup: true,
|
|
1122
|
-
isFromMe: false,
|
|
1123
|
-
guid: "msg-1",
|
|
1124
|
-
chatGuid: "iMessage;+;chat123456",
|
|
1125
|
-
chatName: "Family",
|
|
1126
|
-
participants: [
|
|
1127
|
-
{ address: "+15551234567", displayName: "Alice" },
|
|
1128
|
-
{ address: "+15557654321", displayName: "Bob" },
|
|
1129
|
-
],
|
|
1130
|
-
date: Date.now(),
|
|
1131
|
-
},
|
|
1132
|
-
};
|
|
1133
|
-
|
|
1134
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1135
|
-
const res = createMockResponse();
|
|
1136
|
-
|
|
1137
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1138
|
-
await flushAsync();
|
|
1139
|
-
|
|
1140
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1141
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
1142
|
-
expect(callArgs.ctx.GroupSubject).toBe("Family");
|
|
1143
|
-
expect(callArgs.ctx.GroupMembers).toBe("Alice (+15551234567), Bob (+15557654321)");
|
|
1144
|
-
});
|
|
1145
|
-
});
|
|
1146
|
-
|
|
1147
|
-
describe("reply metadata", () => {
|
|
1148
|
-
it("surfaces reply fields in ctx when provided", async () => {
|
|
1149
|
-
const account = createMockAccount({ dmPolicy: "open" });
|
|
1150
|
-
const config: ClawdbotConfig = {};
|
|
1151
|
-
const core = createMockRuntime();
|
|
1152
|
-
setBlueBubblesRuntime(core);
|
|
1153
|
-
|
|
1154
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1155
|
-
account,
|
|
1156
|
-
config,
|
|
1157
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1158
|
-
core,
|
|
1159
|
-
path: "/bluebubbles-webhook",
|
|
1160
|
-
});
|
|
1161
|
-
|
|
1162
|
-
const payload = {
|
|
1163
|
-
type: "new-message",
|
|
1164
|
-
data: {
|
|
1165
|
-
text: "replying now",
|
|
1166
|
-
handle: { address: "+15551234567" },
|
|
1167
|
-
isGroup: false,
|
|
1168
|
-
isFromMe: false,
|
|
1169
|
-
guid: "msg-1",
|
|
1170
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1171
|
-
replyTo: {
|
|
1172
|
-
guid: "msg-0",
|
|
1173
|
-
text: "original message",
|
|
1174
|
-
handle: { address: "+15550000000", displayName: "Alice" },
|
|
1175
|
-
},
|
|
1176
|
-
date: Date.now(),
|
|
1177
|
-
},
|
|
1178
|
-
};
|
|
1179
|
-
|
|
1180
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1181
|
-
const res = createMockResponse();
|
|
1182
|
-
|
|
1183
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1184
|
-
await flushAsync();
|
|
1185
|
-
|
|
1186
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1187
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
1188
|
-
// ReplyToId is the full UUID since it wasn't previously cached
|
|
1189
|
-
expect(callArgs.ctx.ReplyToId).toBe("msg-0");
|
|
1190
|
-
expect(callArgs.ctx.ReplyToBody).toBe("original message");
|
|
1191
|
-
expect(callArgs.ctx.ReplyToSender).toBe("+15550000000");
|
|
1192
|
-
// Body uses inline [[reply_to:N]] tag format
|
|
1193
|
-
expect(callArgs.ctx.Body).toContain("[[reply_to:msg-0]]");
|
|
1194
|
-
});
|
|
1195
|
-
|
|
1196
|
-
it("preserves part index prefixes in reply tags when short IDs are unavailable", async () => {
|
|
1197
|
-
const account = createMockAccount({ dmPolicy: "open" });
|
|
1198
|
-
const config: ClawdbotConfig = {};
|
|
1199
|
-
const core = createMockRuntime();
|
|
1200
|
-
setBlueBubblesRuntime(core);
|
|
1201
|
-
|
|
1202
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1203
|
-
account,
|
|
1204
|
-
config,
|
|
1205
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1206
|
-
core,
|
|
1207
|
-
path: "/bluebubbles-webhook",
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
const payload = {
|
|
1211
|
-
type: "new-message",
|
|
1212
|
-
data: {
|
|
1213
|
-
text: "replying now",
|
|
1214
|
-
handle: { address: "+15551234567" },
|
|
1215
|
-
isGroup: false,
|
|
1216
|
-
isFromMe: false,
|
|
1217
|
-
guid: "msg-1",
|
|
1218
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1219
|
-
replyTo: {
|
|
1220
|
-
guid: "p:1/msg-0",
|
|
1221
|
-
text: "original message",
|
|
1222
|
-
handle: { address: "+15550000000", displayName: "Alice" },
|
|
1223
|
-
},
|
|
1224
|
-
date: Date.now(),
|
|
1225
|
-
},
|
|
1226
|
-
};
|
|
1227
|
-
|
|
1228
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1229
|
-
const res = createMockResponse();
|
|
1230
|
-
|
|
1231
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1232
|
-
await flushAsync();
|
|
1233
|
-
|
|
1234
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1235
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
1236
|
-
expect(callArgs.ctx.ReplyToId).toBe("p:1/msg-0");
|
|
1237
|
-
expect(callArgs.ctx.ReplyToIdFull).toBe("p:1/msg-0");
|
|
1238
|
-
expect(callArgs.ctx.Body).toContain("[[reply_to:p:1/msg-0]]");
|
|
1239
|
-
});
|
|
1240
|
-
|
|
1241
|
-
it("hydrates missing reply sender/body from the recent-message cache", async () => {
|
|
1242
|
-
const account = createMockAccount({ dmPolicy: "open", groupPolicy: "open" });
|
|
1243
|
-
const config: ClawdbotConfig = {};
|
|
1244
|
-
const core = createMockRuntime();
|
|
1245
|
-
setBlueBubblesRuntime(core);
|
|
1246
|
-
|
|
1247
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1248
|
-
account,
|
|
1249
|
-
config,
|
|
1250
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1251
|
-
core,
|
|
1252
|
-
path: "/bluebubbles-webhook",
|
|
1253
|
-
});
|
|
1254
|
-
|
|
1255
|
-
const chatGuid = "iMessage;+;chat-reply-cache";
|
|
1256
|
-
|
|
1257
|
-
const originalPayload = {
|
|
1258
|
-
type: "new-message",
|
|
1259
|
-
data: {
|
|
1260
|
-
text: "original message (cached)",
|
|
1261
|
-
handle: { address: "+15550000000" },
|
|
1262
|
-
isGroup: true,
|
|
1263
|
-
isFromMe: false,
|
|
1264
|
-
guid: "cache-msg-0",
|
|
1265
|
-
chatGuid,
|
|
1266
|
-
date: Date.now(),
|
|
1267
|
-
},
|
|
1268
|
-
};
|
|
1269
|
-
|
|
1270
|
-
const originalReq = createMockRequest("POST", "/bluebubbles-webhook", originalPayload);
|
|
1271
|
-
const originalRes = createMockResponse();
|
|
1272
|
-
|
|
1273
|
-
await handleBlueBubblesWebhookRequest(originalReq, originalRes);
|
|
1274
|
-
await flushAsync();
|
|
1275
|
-
|
|
1276
|
-
// Only assert the reply message behavior below.
|
|
1277
|
-
mockDispatchReplyWithBufferedBlockDispatcher.mockClear();
|
|
1278
|
-
|
|
1279
|
-
const replyPayload = {
|
|
1280
|
-
type: "new-message",
|
|
1281
|
-
data: {
|
|
1282
|
-
text: "replying now",
|
|
1283
|
-
handle: { address: "+15551234567" },
|
|
1284
|
-
isGroup: true,
|
|
1285
|
-
isFromMe: false,
|
|
1286
|
-
guid: "cache-msg-1",
|
|
1287
|
-
chatGuid,
|
|
1288
|
-
// Only the GUID is provided; sender/body must be hydrated.
|
|
1289
|
-
replyToMessageGuid: "cache-msg-0",
|
|
1290
|
-
date: Date.now(),
|
|
1291
|
-
},
|
|
1292
|
-
};
|
|
1293
|
-
|
|
1294
|
-
const replyReq = createMockRequest("POST", "/bluebubbles-webhook", replyPayload);
|
|
1295
|
-
const replyRes = createMockResponse();
|
|
1296
|
-
|
|
1297
|
-
await handleBlueBubblesWebhookRequest(replyReq, replyRes);
|
|
1298
|
-
await flushAsync();
|
|
1299
|
-
|
|
1300
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1301
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
1302
|
-
// ReplyToId uses short ID "1" (first cached message) for token savings
|
|
1303
|
-
expect(callArgs.ctx.ReplyToId).toBe("1");
|
|
1304
|
-
expect(callArgs.ctx.ReplyToIdFull).toBe("cache-msg-0");
|
|
1305
|
-
expect(callArgs.ctx.ReplyToBody).toBe("original message (cached)");
|
|
1306
|
-
expect(callArgs.ctx.ReplyToSender).toBe("+15550000000");
|
|
1307
|
-
// Body uses inline [[reply_to:N]] tag format with short ID
|
|
1308
|
-
expect(callArgs.ctx.Body).toContain("[[reply_to:1]]");
|
|
1309
|
-
});
|
|
1310
|
-
|
|
1311
|
-
it("falls back to threadOriginatorGuid when reply metadata is absent", async () => {
|
|
1312
|
-
const account = createMockAccount({ dmPolicy: "open" });
|
|
1313
|
-
const config: ClawdbotConfig = {};
|
|
1314
|
-
const core = createMockRuntime();
|
|
1315
|
-
setBlueBubblesRuntime(core);
|
|
1316
|
-
|
|
1317
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1318
|
-
account,
|
|
1319
|
-
config,
|
|
1320
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1321
|
-
core,
|
|
1322
|
-
path: "/bluebubbles-webhook",
|
|
1323
|
-
});
|
|
1324
|
-
|
|
1325
|
-
const payload = {
|
|
1326
|
-
type: "new-message",
|
|
1327
|
-
data: {
|
|
1328
|
-
text: "replying now",
|
|
1329
|
-
handle: { address: "+15551234567" },
|
|
1330
|
-
isGroup: false,
|
|
1331
|
-
isFromMe: false,
|
|
1332
|
-
guid: "msg-1",
|
|
1333
|
-
threadOriginatorGuid: "msg-0",
|
|
1334
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1335
|
-
date: Date.now(),
|
|
1336
|
-
},
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1340
|
-
const res = createMockResponse();
|
|
1341
|
-
|
|
1342
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1343
|
-
await flushAsync();
|
|
1344
|
-
|
|
1345
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1346
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
1347
|
-
expect(callArgs.ctx.ReplyToId).toBe("msg-0");
|
|
1348
|
-
});
|
|
1349
|
-
});
|
|
1350
|
-
|
|
1351
|
-
describe("tapback text parsing", () => {
|
|
1352
|
-
it("does not rewrite tapback-like text without metadata", async () => {
|
|
1353
|
-
const account = createMockAccount({ dmPolicy: "open" });
|
|
1354
|
-
const config: ClawdbotConfig = {};
|
|
1355
|
-
const core = createMockRuntime();
|
|
1356
|
-
setBlueBubblesRuntime(core);
|
|
1357
|
-
|
|
1358
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1359
|
-
account,
|
|
1360
|
-
config,
|
|
1361
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1362
|
-
core,
|
|
1363
|
-
path: "/bluebubbles-webhook",
|
|
1364
|
-
});
|
|
1365
|
-
|
|
1366
|
-
const payload = {
|
|
1367
|
-
type: "new-message",
|
|
1368
|
-
data: {
|
|
1369
|
-
text: "Loved this idea",
|
|
1370
|
-
handle: { address: "+15551234567" },
|
|
1371
|
-
isGroup: false,
|
|
1372
|
-
isFromMe: false,
|
|
1373
|
-
guid: "msg-1",
|
|
1374
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1375
|
-
date: Date.now(),
|
|
1376
|
-
},
|
|
1377
|
-
};
|
|
1378
|
-
|
|
1379
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1380
|
-
const res = createMockResponse();
|
|
1381
|
-
|
|
1382
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1383
|
-
await flushAsync();
|
|
1384
|
-
|
|
1385
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1386
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
1387
|
-
expect(callArgs.ctx.RawBody).toBe("Loved this idea");
|
|
1388
|
-
expect(callArgs.ctx.Body).toContain("Loved this idea");
|
|
1389
|
-
expect(callArgs.ctx.Body).not.toContain("reacted with");
|
|
1390
|
-
});
|
|
1391
|
-
|
|
1392
|
-
it("parses tapback text with custom emoji when metadata is present", async () => {
|
|
1393
|
-
const account = createMockAccount({ dmPolicy: "open" });
|
|
1394
|
-
const config: ClawdbotConfig = {};
|
|
1395
|
-
const core = createMockRuntime();
|
|
1396
|
-
setBlueBubblesRuntime(core);
|
|
1397
|
-
|
|
1398
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1399
|
-
account,
|
|
1400
|
-
config,
|
|
1401
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1402
|
-
core,
|
|
1403
|
-
path: "/bluebubbles-webhook",
|
|
1404
|
-
});
|
|
1405
|
-
|
|
1406
|
-
const payload = {
|
|
1407
|
-
type: "new-message",
|
|
1408
|
-
data: {
|
|
1409
|
-
text: 'Reacted 😅 to "nice one"',
|
|
1410
|
-
handle: { address: "+15551234567" },
|
|
1411
|
-
isGroup: false,
|
|
1412
|
-
isFromMe: false,
|
|
1413
|
-
guid: "msg-2",
|
|
1414
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1415
|
-
date: Date.now(),
|
|
1416
|
-
},
|
|
1417
|
-
};
|
|
1418
|
-
|
|
1419
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1420
|
-
const res = createMockResponse();
|
|
1421
|
-
|
|
1422
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1423
|
-
await flushAsync();
|
|
1424
|
-
|
|
1425
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1426
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
1427
|
-
expect(callArgs.ctx.RawBody).toBe("reacted with 😅");
|
|
1428
|
-
expect(callArgs.ctx.Body).toContain("reacted with 😅");
|
|
1429
|
-
expect(callArgs.ctx.Body).not.toContain("[[reply_to:");
|
|
1430
|
-
});
|
|
1431
|
-
});
|
|
1432
|
-
|
|
1433
|
-
describe("ack reactions", () => {
|
|
1434
|
-
it("sends ack reaction when configured", async () => {
|
|
1435
|
-
const { sendBlueBubblesReaction } = await import("./reactions.js");
|
|
1436
|
-
vi.mocked(sendBlueBubblesReaction).mockClear();
|
|
1437
|
-
|
|
1438
|
-
const account = createMockAccount({ dmPolicy: "open" });
|
|
1439
|
-
const config: ClawdbotConfig = {
|
|
1440
|
-
messages: {
|
|
1441
|
-
ackReaction: "❤️",
|
|
1442
|
-
ackReactionScope: "direct",
|
|
1443
|
-
},
|
|
1444
|
-
};
|
|
1445
|
-
const core = createMockRuntime();
|
|
1446
|
-
setBlueBubblesRuntime(core);
|
|
1447
|
-
|
|
1448
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1449
|
-
account,
|
|
1450
|
-
config,
|
|
1451
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1452
|
-
core,
|
|
1453
|
-
path: "/bluebubbles-webhook",
|
|
1454
|
-
});
|
|
1455
|
-
|
|
1456
|
-
const payload = {
|
|
1457
|
-
type: "new-message",
|
|
1458
|
-
data: {
|
|
1459
|
-
text: "hello",
|
|
1460
|
-
handle: { address: "+15551234567" },
|
|
1461
|
-
isGroup: false,
|
|
1462
|
-
isFromMe: false,
|
|
1463
|
-
guid: "msg-1",
|
|
1464
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1465
|
-
date: Date.now(),
|
|
1466
|
-
},
|
|
1467
|
-
};
|
|
1468
|
-
|
|
1469
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1470
|
-
const res = createMockResponse();
|
|
1471
|
-
|
|
1472
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1473
|
-
await flushAsync();
|
|
1474
|
-
|
|
1475
|
-
expect(sendBlueBubblesReaction).toHaveBeenCalledWith(
|
|
1476
|
-
expect.objectContaining({
|
|
1477
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1478
|
-
messageGuid: "msg-1",
|
|
1479
|
-
emoji: "❤️",
|
|
1480
|
-
opts: expect.objectContaining({ accountId: "default" }),
|
|
1481
|
-
}),
|
|
1482
|
-
);
|
|
1483
|
-
});
|
|
1484
|
-
});
|
|
1485
|
-
|
|
1486
|
-
describe("command gating", () => {
|
|
1487
|
-
it("allows control command to bypass mention gating when authorized", async () => {
|
|
1488
|
-
mockResolveRequireMention.mockReturnValue(true);
|
|
1489
|
-
mockMatchesMentionPatterns.mockReturnValue(false); // Not mentioned
|
|
1490
|
-
mockHasControlCommand.mockReturnValue(true); // Has control command
|
|
1491
|
-
mockResolveCommandAuthorizedFromAuthorizers.mockReturnValue(true); // Authorized
|
|
1492
|
-
|
|
1493
|
-
const account = createMockAccount({
|
|
1494
|
-
groupPolicy: "open",
|
|
1495
|
-
allowFrom: ["+15551234567"],
|
|
1496
|
-
});
|
|
1497
|
-
const config: ClawdbotConfig = {};
|
|
1498
|
-
const core = createMockRuntime();
|
|
1499
|
-
setBlueBubblesRuntime(core);
|
|
1500
|
-
|
|
1501
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1502
|
-
account,
|
|
1503
|
-
config,
|
|
1504
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1505
|
-
core,
|
|
1506
|
-
path: "/bluebubbles-webhook",
|
|
1507
|
-
});
|
|
1508
|
-
|
|
1509
|
-
const payload = {
|
|
1510
|
-
type: "new-message",
|
|
1511
|
-
data: {
|
|
1512
|
-
text: "/status",
|
|
1513
|
-
handle: { address: "+15551234567" },
|
|
1514
|
-
isGroup: true,
|
|
1515
|
-
isFromMe: false,
|
|
1516
|
-
guid: "msg-1",
|
|
1517
|
-
chatGuid: "iMessage;+;chat123456",
|
|
1518
|
-
date: Date.now(),
|
|
1519
|
-
},
|
|
1520
|
-
};
|
|
1521
|
-
|
|
1522
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1523
|
-
const res = createMockResponse();
|
|
1524
|
-
|
|
1525
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1526
|
-
await flushAsync();
|
|
1527
|
-
|
|
1528
|
-
// Should process even without mention because it's an authorized control command
|
|
1529
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
1530
|
-
});
|
|
1531
|
-
|
|
1532
|
-
it("blocks control command from unauthorized sender in group", async () => {
|
|
1533
|
-
mockHasControlCommand.mockReturnValue(true);
|
|
1534
|
-
mockResolveCommandAuthorizedFromAuthorizers.mockReturnValue(false);
|
|
1535
|
-
|
|
1536
|
-
const account = createMockAccount({
|
|
1537
|
-
groupPolicy: "open",
|
|
1538
|
-
allowFrom: [], // No one authorized
|
|
1539
|
-
});
|
|
1540
|
-
const config: ClawdbotConfig = {};
|
|
1541
|
-
const core = createMockRuntime();
|
|
1542
|
-
setBlueBubblesRuntime(core);
|
|
1543
|
-
|
|
1544
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1545
|
-
account,
|
|
1546
|
-
config,
|
|
1547
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1548
|
-
core,
|
|
1549
|
-
path: "/bluebubbles-webhook",
|
|
1550
|
-
});
|
|
1551
|
-
|
|
1552
|
-
const payload = {
|
|
1553
|
-
type: "new-message",
|
|
1554
|
-
data: {
|
|
1555
|
-
text: "/status",
|
|
1556
|
-
handle: { address: "+15559999999" },
|
|
1557
|
-
isGroup: true,
|
|
1558
|
-
isFromMe: false,
|
|
1559
|
-
guid: "msg-1",
|
|
1560
|
-
chatGuid: "iMessage;+;chat123456",
|
|
1561
|
-
date: Date.now(),
|
|
1562
|
-
},
|
|
1563
|
-
};
|
|
1564
|
-
|
|
1565
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1566
|
-
const res = createMockResponse();
|
|
1567
|
-
|
|
1568
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1569
|
-
await flushAsync();
|
|
1570
|
-
|
|
1571
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
|
|
1572
|
-
});
|
|
1573
|
-
});
|
|
1574
|
-
|
|
1575
|
-
describe("typing/read receipt toggles", () => {
|
|
1576
|
-
it("marks chat as read when sendReadReceipts=true (default)", async () => {
|
|
1577
|
-
const { markBlueBubblesChatRead } = await import("./chat.js");
|
|
1578
|
-
vi.mocked(markBlueBubblesChatRead).mockClear();
|
|
1579
|
-
|
|
1580
|
-
const account = createMockAccount({
|
|
1581
|
-
sendReadReceipts: true,
|
|
1582
|
-
});
|
|
1583
|
-
const config: ClawdbotConfig = {};
|
|
1584
|
-
const core = createMockRuntime();
|
|
1585
|
-
setBlueBubblesRuntime(core);
|
|
1586
|
-
|
|
1587
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1588
|
-
account,
|
|
1589
|
-
config,
|
|
1590
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1591
|
-
core,
|
|
1592
|
-
path: "/bluebubbles-webhook",
|
|
1593
|
-
});
|
|
1594
|
-
|
|
1595
|
-
const payload = {
|
|
1596
|
-
type: "new-message",
|
|
1597
|
-
data: {
|
|
1598
|
-
text: "hello",
|
|
1599
|
-
handle: { address: "+15551234567" },
|
|
1600
|
-
isGroup: false,
|
|
1601
|
-
isFromMe: false,
|
|
1602
|
-
guid: "msg-1",
|
|
1603
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1604
|
-
date: Date.now(),
|
|
1605
|
-
},
|
|
1606
|
-
};
|
|
1607
|
-
|
|
1608
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1609
|
-
const res = createMockResponse();
|
|
1610
|
-
|
|
1611
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1612
|
-
await flushAsync();
|
|
1613
|
-
|
|
1614
|
-
expect(markBlueBubblesChatRead).toHaveBeenCalled();
|
|
1615
|
-
});
|
|
1616
|
-
|
|
1617
|
-
it("does not mark chat as read when sendReadReceipts=false", async () => {
|
|
1618
|
-
const { markBlueBubblesChatRead } = await import("./chat.js");
|
|
1619
|
-
vi.mocked(markBlueBubblesChatRead).mockClear();
|
|
1620
|
-
|
|
1621
|
-
const account = createMockAccount({
|
|
1622
|
-
sendReadReceipts: false,
|
|
1623
|
-
});
|
|
1624
|
-
const config: ClawdbotConfig = {};
|
|
1625
|
-
const core = createMockRuntime();
|
|
1626
|
-
setBlueBubblesRuntime(core);
|
|
1627
|
-
|
|
1628
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1629
|
-
account,
|
|
1630
|
-
config,
|
|
1631
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1632
|
-
core,
|
|
1633
|
-
path: "/bluebubbles-webhook",
|
|
1634
|
-
});
|
|
1635
|
-
|
|
1636
|
-
const payload = {
|
|
1637
|
-
type: "new-message",
|
|
1638
|
-
data: {
|
|
1639
|
-
text: "hello",
|
|
1640
|
-
handle: { address: "+15551234567" },
|
|
1641
|
-
isGroup: false,
|
|
1642
|
-
isFromMe: false,
|
|
1643
|
-
guid: "msg-1",
|
|
1644
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1645
|
-
date: Date.now(),
|
|
1646
|
-
},
|
|
1647
|
-
};
|
|
1648
|
-
|
|
1649
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1650
|
-
const res = createMockResponse();
|
|
1651
|
-
|
|
1652
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1653
|
-
await flushAsync();
|
|
1654
|
-
|
|
1655
|
-
expect(markBlueBubblesChatRead).not.toHaveBeenCalled();
|
|
1656
|
-
});
|
|
1657
|
-
|
|
1658
|
-
it("sends typing indicator when processing message", async () => {
|
|
1659
|
-
const { sendBlueBubblesTyping } = await import("./chat.js");
|
|
1660
|
-
vi.mocked(sendBlueBubblesTyping).mockClear();
|
|
1661
|
-
|
|
1662
|
-
const account = createMockAccount();
|
|
1663
|
-
const config: ClawdbotConfig = {};
|
|
1664
|
-
const core = createMockRuntime();
|
|
1665
|
-
setBlueBubblesRuntime(core);
|
|
1666
|
-
|
|
1667
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1668
|
-
account,
|
|
1669
|
-
config,
|
|
1670
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1671
|
-
core,
|
|
1672
|
-
path: "/bluebubbles-webhook",
|
|
1673
|
-
});
|
|
1674
|
-
|
|
1675
|
-
const payload = {
|
|
1676
|
-
type: "new-message",
|
|
1677
|
-
data: {
|
|
1678
|
-
text: "hello",
|
|
1679
|
-
handle: { address: "+15551234567" },
|
|
1680
|
-
isGroup: false,
|
|
1681
|
-
isFromMe: false,
|
|
1682
|
-
guid: "msg-1",
|
|
1683
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1684
|
-
date: Date.now(),
|
|
1685
|
-
},
|
|
1686
|
-
};
|
|
1687
|
-
|
|
1688
|
-
mockDispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(async (params) => {
|
|
1689
|
-
await params.dispatcherOptions.onReplyStart?.();
|
|
1690
|
-
});
|
|
1691
|
-
|
|
1692
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1693
|
-
const res = createMockResponse();
|
|
1694
|
-
|
|
1695
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1696
|
-
await flushAsync();
|
|
1697
|
-
|
|
1698
|
-
// Should call typing start when reply flow triggers it.
|
|
1699
|
-
expect(sendBlueBubblesTyping).toHaveBeenCalledWith(
|
|
1700
|
-
expect.any(String),
|
|
1701
|
-
true,
|
|
1702
|
-
expect.any(Object),
|
|
1703
|
-
);
|
|
1704
|
-
});
|
|
1705
|
-
|
|
1706
|
-
it("stops typing on idle", async () => {
|
|
1707
|
-
const { sendBlueBubblesTyping } = await import("./chat.js");
|
|
1708
|
-
vi.mocked(sendBlueBubblesTyping).mockClear();
|
|
1709
|
-
|
|
1710
|
-
const account = createMockAccount();
|
|
1711
|
-
const config: ClawdbotConfig = {};
|
|
1712
|
-
const core = createMockRuntime();
|
|
1713
|
-
setBlueBubblesRuntime(core);
|
|
1714
|
-
|
|
1715
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1716
|
-
account,
|
|
1717
|
-
config,
|
|
1718
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1719
|
-
core,
|
|
1720
|
-
path: "/bluebubbles-webhook",
|
|
1721
|
-
});
|
|
1722
|
-
|
|
1723
|
-
const payload = {
|
|
1724
|
-
type: "new-message",
|
|
1725
|
-
data: {
|
|
1726
|
-
text: "hello",
|
|
1727
|
-
handle: { address: "+15551234567" },
|
|
1728
|
-
isGroup: false,
|
|
1729
|
-
isFromMe: false,
|
|
1730
|
-
guid: "msg-1",
|
|
1731
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1732
|
-
date: Date.now(),
|
|
1733
|
-
},
|
|
1734
|
-
};
|
|
1735
|
-
|
|
1736
|
-
mockDispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(async (params) => {
|
|
1737
|
-
await params.dispatcherOptions.onReplyStart?.();
|
|
1738
|
-
await params.dispatcherOptions.deliver({ text: "replying now" }, { kind: "final" });
|
|
1739
|
-
await params.dispatcherOptions.onIdle?.();
|
|
1740
|
-
});
|
|
1741
|
-
|
|
1742
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1743
|
-
const res = createMockResponse();
|
|
1744
|
-
|
|
1745
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1746
|
-
await flushAsync();
|
|
1747
|
-
|
|
1748
|
-
expect(sendBlueBubblesTyping).toHaveBeenCalledWith(
|
|
1749
|
-
expect.any(String),
|
|
1750
|
-
false,
|
|
1751
|
-
expect.any(Object),
|
|
1752
|
-
);
|
|
1753
|
-
});
|
|
1754
|
-
|
|
1755
|
-
it("stops typing when no reply is sent", async () => {
|
|
1756
|
-
const { sendBlueBubblesTyping } = await import("./chat.js");
|
|
1757
|
-
vi.mocked(sendBlueBubblesTyping).mockClear();
|
|
1758
|
-
|
|
1759
|
-
const account = createMockAccount();
|
|
1760
|
-
const config: ClawdbotConfig = {};
|
|
1761
|
-
const core = createMockRuntime();
|
|
1762
|
-
setBlueBubblesRuntime(core);
|
|
1763
|
-
|
|
1764
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1765
|
-
account,
|
|
1766
|
-
config,
|
|
1767
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1768
|
-
core,
|
|
1769
|
-
path: "/bluebubbles-webhook",
|
|
1770
|
-
});
|
|
1771
|
-
|
|
1772
|
-
const payload = {
|
|
1773
|
-
type: "new-message",
|
|
1774
|
-
data: {
|
|
1775
|
-
text: "hello",
|
|
1776
|
-
handle: { address: "+15551234567" },
|
|
1777
|
-
isGroup: false,
|
|
1778
|
-
isFromMe: false,
|
|
1779
|
-
guid: "msg-1",
|
|
1780
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1781
|
-
date: Date.now(),
|
|
1782
|
-
},
|
|
1783
|
-
};
|
|
1784
|
-
|
|
1785
|
-
mockDispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(async () => undefined);
|
|
1786
|
-
|
|
1787
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1788
|
-
const res = createMockResponse();
|
|
1789
|
-
|
|
1790
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1791
|
-
await flushAsync();
|
|
1792
|
-
|
|
1793
|
-
expect(sendBlueBubblesTyping).toHaveBeenCalledWith(
|
|
1794
|
-
expect.any(String),
|
|
1795
|
-
false,
|
|
1796
|
-
expect.any(Object),
|
|
1797
|
-
);
|
|
1798
|
-
});
|
|
1799
|
-
});
|
|
1800
|
-
|
|
1801
|
-
describe("outbound message ids", () => {
|
|
1802
|
-
it("enqueues system event for outbound message id", async () => {
|
|
1803
|
-
mockEnqueueSystemEvent.mockClear();
|
|
1804
|
-
|
|
1805
|
-
mockDispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(async (params) => {
|
|
1806
|
-
await params.dispatcherOptions.deliver({ text: "replying now" }, { kind: "final" });
|
|
1807
|
-
});
|
|
1808
|
-
|
|
1809
|
-
const account = createMockAccount();
|
|
1810
|
-
const config: ClawdbotConfig = {};
|
|
1811
|
-
const core = createMockRuntime();
|
|
1812
|
-
setBlueBubblesRuntime(core);
|
|
1813
|
-
|
|
1814
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1815
|
-
account,
|
|
1816
|
-
config,
|
|
1817
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1818
|
-
core,
|
|
1819
|
-
path: "/bluebubbles-webhook",
|
|
1820
|
-
});
|
|
1821
|
-
|
|
1822
|
-
const payload = {
|
|
1823
|
-
type: "new-message",
|
|
1824
|
-
data: {
|
|
1825
|
-
text: "hello",
|
|
1826
|
-
handle: { address: "+15551234567" },
|
|
1827
|
-
isGroup: false,
|
|
1828
|
-
isFromMe: false,
|
|
1829
|
-
guid: "msg-1",
|
|
1830
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
1831
|
-
date: Date.now(),
|
|
1832
|
-
},
|
|
1833
|
-
};
|
|
1834
|
-
|
|
1835
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1836
|
-
const res = createMockResponse();
|
|
1837
|
-
|
|
1838
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1839
|
-
await flushAsync();
|
|
1840
|
-
|
|
1841
|
-
// Outbound message ID uses short ID "2" (inbound msg-1 is "1", outbound msg-123 is "2")
|
|
1842
|
-
expect(mockEnqueueSystemEvent).toHaveBeenCalledWith(
|
|
1843
|
-
'Assistant sent "replying now" [message_id:2]',
|
|
1844
|
-
expect.objectContaining({
|
|
1845
|
-
sessionKey: "agent:main:bluebubbles:dm:+15551234567",
|
|
1846
|
-
}),
|
|
1847
|
-
);
|
|
1848
|
-
});
|
|
1849
|
-
});
|
|
1850
|
-
|
|
1851
|
-
describe("reaction events", () => {
|
|
1852
|
-
it("enqueues system event for reaction added", async () => {
|
|
1853
|
-
mockEnqueueSystemEvent.mockClear();
|
|
1854
|
-
|
|
1855
|
-
const account = createMockAccount();
|
|
1856
|
-
const config: ClawdbotConfig = {};
|
|
1857
|
-
const core = createMockRuntime();
|
|
1858
|
-
setBlueBubblesRuntime(core);
|
|
1859
|
-
|
|
1860
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1861
|
-
account,
|
|
1862
|
-
config,
|
|
1863
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1864
|
-
core,
|
|
1865
|
-
path: "/bluebubbles-webhook",
|
|
1866
|
-
});
|
|
1867
|
-
|
|
1868
|
-
const payload = {
|
|
1869
|
-
type: "message-reaction",
|
|
1870
|
-
data: {
|
|
1871
|
-
handle: { address: "+15551234567" },
|
|
1872
|
-
isGroup: false,
|
|
1873
|
-
isFromMe: false,
|
|
1874
|
-
associatedMessageGuid: "msg-original-123",
|
|
1875
|
-
associatedMessageType: 2000, // Heart reaction added
|
|
1876
|
-
date: Date.now(),
|
|
1877
|
-
},
|
|
1878
|
-
};
|
|
1879
|
-
|
|
1880
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1881
|
-
const res = createMockResponse();
|
|
1882
|
-
|
|
1883
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1884
|
-
await flushAsync();
|
|
1885
|
-
|
|
1886
|
-
expect(mockEnqueueSystemEvent).toHaveBeenCalledWith(
|
|
1887
|
-
expect.stringContaining("reacted with ❤️ [[reply_to:"),
|
|
1888
|
-
expect.any(Object),
|
|
1889
|
-
);
|
|
1890
|
-
});
|
|
1891
|
-
|
|
1892
|
-
it("enqueues system event for reaction removed", async () => {
|
|
1893
|
-
mockEnqueueSystemEvent.mockClear();
|
|
1894
|
-
|
|
1895
|
-
const account = createMockAccount();
|
|
1896
|
-
const config: ClawdbotConfig = {};
|
|
1897
|
-
const core = createMockRuntime();
|
|
1898
|
-
setBlueBubblesRuntime(core);
|
|
1899
|
-
|
|
1900
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1901
|
-
account,
|
|
1902
|
-
config,
|
|
1903
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1904
|
-
core,
|
|
1905
|
-
path: "/bluebubbles-webhook",
|
|
1906
|
-
});
|
|
1907
|
-
|
|
1908
|
-
const payload = {
|
|
1909
|
-
type: "message-reaction",
|
|
1910
|
-
data: {
|
|
1911
|
-
handle: { address: "+15551234567" },
|
|
1912
|
-
isGroup: false,
|
|
1913
|
-
isFromMe: false,
|
|
1914
|
-
associatedMessageGuid: "msg-original-123",
|
|
1915
|
-
associatedMessageType: 3000, // Heart reaction removed
|
|
1916
|
-
date: Date.now(),
|
|
1917
|
-
},
|
|
1918
|
-
};
|
|
1919
|
-
|
|
1920
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1921
|
-
const res = createMockResponse();
|
|
1922
|
-
|
|
1923
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1924
|
-
await flushAsync();
|
|
1925
|
-
|
|
1926
|
-
expect(mockEnqueueSystemEvent).toHaveBeenCalledWith(
|
|
1927
|
-
expect.stringContaining("removed ❤️ reaction [[reply_to:"),
|
|
1928
|
-
expect.any(Object),
|
|
1929
|
-
);
|
|
1930
|
-
});
|
|
1931
|
-
|
|
1932
|
-
it("ignores reaction from self (fromMe=true)", async () => {
|
|
1933
|
-
mockEnqueueSystemEvent.mockClear();
|
|
1934
|
-
|
|
1935
|
-
const account = createMockAccount();
|
|
1936
|
-
const config: ClawdbotConfig = {};
|
|
1937
|
-
const core = createMockRuntime();
|
|
1938
|
-
setBlueBubblesRuntime(core);
|
|
1939
|
-
|
|
1940
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1941
|
-
account,
|
|
1942
|
-
config,
|
|
1943
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1944
|
-
core,
|
|
1945
|
-
path: "/bluebubbles-webhook",
|
|
1946
|
-
});
|
|
1947
|
-
|
|
1948
|
-
const payload = {
|
|
1949
|
-
type: "message-reaction",
|
|
1950
|
-
data: {
|
|
1951
|
-
handle: { address: "+15551234567" },
|
|
1952
|
-
isGroup: false,
|
|
1953
|
-
isFromMe: true, // From self
|
|
1954
|
-
associatedMessageGuid: "msg-original-123",
|
|
1955
|
-
associatedMessageType: 2000,
|
|
1956
|
-
date: Date.now(),
|
|
1957
|
-
},
|
|
1958
|
-
};
|
|
1959
|
-
|
|
1960
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1961
|
-
const res = createMockResponse();
|
|
1962
|
-
|
|
1963
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
1964
|
-
await flushAsync();
|
|
1965
|
-
|
|
1966
|
-
expect(mockEnqueueSystemEvent).not.toHaveBeenCalled();
|
|
1967
|
-
});
|
|
1968
|
-
|
|
1969
|
-
it("maps reaction types to correct emojis", async () => {
|
|
1970
|
-
mockEnqueueSystemEvent.mockClear();
|
|
1971
|
-
|
|
1972
|
-
const account = createMockAccount();
|
|
1973
|
-
const config: ClawdbotConfig = {};
|
|
1974
|
-
const core = createMockRuntime();
|
|
1975
|
-
setBlueBubblesRuntime(core);
|
|
1976
|
-
|
|
1977
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
1978
|
-
account,
|
|
1979
|
-
config,
|
|
1980
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
1981
|
-
core,
|
|
1982
|
-
path: "/bluebubbles-webhook",
|
|
1983
|
-
});
|
|
1984
|
-
|
|
1985
|
-
// Test thumbs up reaction (2001)
|
|
1986
|
-
const payload = {
|
|
1987
|
-
type: "message-reaction",
|
|
1988
|
-
data: {
|
|
1989
|
-
handle: { address: "+15551234567" },
|
|
1990
|
-
isGroup: false,
|
|
1991
|
-
isFromMe: false,
|
|
1992
|
-
associatedMessageGuid: "msg-123",
|
|
1993
|
-
associatedMessageType: 2001, // Thumbs up
|
|
1994
|
-
date: Date.now(),
|
|
1995
|
-
},
|
|
1996
|
-
};
|
|
1997
|
-
|
|
1998
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
1999
|
-
const res = createMockResponse();
|
|
2000
|
-
|
|
2001
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
2002
|
-
await flushAsync();
|
|
2003
|
-
|
|
2004
|
-
expect(mockEnqueueSystemEvent).toHaveBeenCalledWith(
|
|
2005
|
-
expect.stringContaining("👍"),
|
|
2006
|
-
expect.any(Object),
|
|
2007
|
-
);
|
|
2008
|
-
});
|
|
2009
|
-
});
|
|
2010
|
-
|
|
2011
|
-
describe("short message ID mapping", () => {
|
|
2012
|
-
it("assigns sequential short IDs to messages", async () => {
|
|
2013
|
-
const account = createMockAccount({ dmPolicy: "open" });
|
|
2014
|
-
const config: ClawdbotConfig = {};
|
|
2015
|
-
const core = createMockRuntime();
|
|
2016
|
-
setBlueBubblesRuntime(core);
|
|
2017
|
-
|
|
2018
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
2019
|
-
account,
|
|
2020
|
-
config,
|
|
2021
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
2022
|
-
core,
|
|
2023
|
-
path: "/bluebubbles-webhook",
|
|
2024
|
-
});
|
|
2025
|
-
|
|
2026
|
-
const payload = {
|
|
2027
|
-
type: "new-message",
|
|
2028
|
-
data: {
|
|
2029
|
-
text: "hello",
|
|
2030
|
-
handle: { address: "+15551234567" },
|
|
2031
|
-
isGroup: false,
|
|
2032
|
-
isFromMe: false,
|
|
2033
|
-
guid: "p:1/msg-uuid-12345",
|
|
2034
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
2035
|
-
date: Date.now(),
|
|
2036
|
-
},
|
|
2037
|
-
};
|
|
2038
|
-
|
|
2039
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
2040
|
-
const res = createMockResponse();
|
|
2041
|
-
|
|
2042
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
2043
|
-
await flushAsync();
|
|
2044
|
-
|
|
2045
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
|
2046
|
-
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
|
|
2047
|
-
// MessageSid should be short ID "1" instead of full UUID
|
|
2048
|
-
expect(callArgs.ctx.MessageSid).toBe("1");
|
|
2049
|
-
expect(callArgs.ctx.MessageSidFull).toBe("p:1/msg-uuid-12345");
|
|
2050
|
-
});
|
|
2051
|
-
|
|
2052
|
-
it("resolves short ID back to UUID", async () => {
|
|
2053
|
-
const account = createMockAccount({ dmPolicy: "open" });
|
|
2054
|
-
const config: ClawdbotConfig = {};
|
|
2055
|
-
const core = createMockRuntime();
|
|
2056
|
-
setBlueBubblesRuntime(core);
|
|
2057
|
-
|
|
2058
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
2059
|
-
account,
|
|
2060
|
-
config,
|
|
2061
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
2062
|
-
core,
|
|
2063
|
-
path: "/bluebubbles-webhook",
|
|
2064
|
-
});
|
|
2065
|
-
|
|
2066
|
-
const payload = {
|
|
2067
|
-
type: "new-message",
|
|
2068
|
-
data: {
|
|
2069
|
-
text: "hello",
|
|
2070
|
-
handle: { address: "+15551234567" },
|
|
2071
|
-
isGroup: false,
|
|
2072
|
-
isFromMe: false,
|
|
2073
|
-
guid: "p:1/msg-uuid-12345",
|
|
2074
|
-
chatGuid: "iMessage;-;+15551234567",
|
|
2075
|
-
date: Date.now(),
|
|
2076
|
-
},
|
|
2077
|
-
};
|
|
2078
|
-
|
|
2079
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
2080
|
-
const res = createMockResponse();
|
|
2081
|
-
|
|
2082
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
2083
|
-
await flushAsync();
|
|
2084
|
-
|
|
2085
|
-
// The short ID "1" should resolve back to the full UUID
|
|
2086
|
-
expect(resolveBlueBubblesMessageId("1")).toBe("p:1/msg-uuid-12345");
|
|
2087
|
-
});
|
|
2088
|
-
|
|
2089
|
-
it("returns UUID unchanged when not in cache", () => {
|
|
2090
|
-
expect(resolveBlueBubblesMessageId("msg-not-cached")).toBe("msg-not-cached");
|
|
2091
|
-
});
|
|
2092
|
-
|
|
2093
|
-
it("returns short ID unchanged when numeric but not in cache", () => {
|
|
2094
|
-
expect(resolveBlueBubblesMessageId("999")).toBe("999");
|
|
2095
|
-
});
|
|
2096
|
-
|
|
2097
|
-
it("throws when numeric short ID is missing and requireKnownShortId is set", () => {
|
|
2098
|
-
expect(() =>
|
|
2099
|
-
resolveBlueBubblesMessageId("999", { requireKnownShortId: true }),
|
|
2100
|
-
).toThrow(/short message id/i);
|
|
2101
|
-
});
|
|
2102
|
-
});
|
|
2103
|
-
|
|
2104
|
-
describe("fromMe messages", () => {
|
|
2105
|
-
it("ignores messages from self (fromMe=true)", async () => {
|
|
2106
|
-
const account = createMockAccount();
|
|
2107
|
-
const config: ClawdbotConfig = {};
|
|
2108
|
-
const core = createMockRuntime();
|
|
2109
|
-
setBlueBubblesRuntime(core);
|
|
2110
|
-
|
|
2111
|
-
unregister = registerBlueBubblesWebhookTarget({
|
|
2112
|
-
account,
|
|
2113
|
-
config,
|
|
2114
|
-
runtime: { log: vi.fn(), error: vi.fn() },
|
|
2115
|
-
core,
|
|
2116
|
-
path: "/bluebubbles-webhook",
|
|
2117
|
-
});
|
|
2118
|
-
|
|
2119
|
-
const payload = {
|
|
2120
|
-
type: "new-message",
|
|
2121
|
-
data: {
|
|
2122
|
-
text: "my own message",
|
|
2123
|
-
handle: { address: "+15551234567" },
|
|
2124
|
-
isGroup: false,
|
|
2125
|
-
isFromMe: true,
|
|
2126
|
-
guid: "msg-1",
|
|
2127
|
-
date: Date.now(),
|
|
2128
|
-
},
|
|
2129
|
-
};
|
|
2130
|
-
|
|
2131
|
-
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
|
2132
|
-
const res = createMockResponse();
|
|
2133
|
-
|
|
2134
|
-
await handleBlueBubblesWebhookRequest(req, res);
|
|
2135
|
-
await flushAsync();
|
|
2136
|
-
|
|
2137
|
-
expect(mockDispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
|
|
2138
|
-
});
|
|
2139
|
-
});
|
|
2140
|
-
});
|