clawdbot 2026.1.4-1
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/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/README-header.png +0 -0
- package/README.md +297 -0
- package/dist/agents/agent-paths.js +17 -0
- package/dist/agents/bash-process-registry.js +126 -0
- package/dist/agents/bash-tools.js +837 -0
- package/dist/agents/clawdbot-tools.js +30 -0
- package/dist/agents/clawdis-tools.js +27 -0
- package/dist/agents/context.js +34 -0
- package/dist/agents/defaults.js +6 -0
- package/dist/agents/model-auth.js +112 -0
- package/dist/agents/model-catalog.js +55 -0
- package/dist/agents/model-fallback.js +191 -0
- package/dist/agents/model-scan.js +263 -0
- package/dist/agents/model-selection.js +116 -0
- package/dist/agents/models-config.js +49 -0
- package/dist/agents/pi-embedded-helpers.js +74 -0
- package/dist/agents/pi-embedded-runner.js +407 -0
- package/dist/agents/pi-embedded-subscribe.js +568 -0
- package/dist/agents/pi-embedded-utils.js +20 -0
- package/dist/agents/pi-embedded.js +1 -0
- package/dist/agents/pi-oauth.js +88 -0
- package/dist/agents/pi-tools.js +433 -0
- package/dist/agents/sandbox-paths.js +68 -0
- package/dist/agents/sandbox.js +644 -0
- package/dist/agents/shell-utils.js +53 -0
- package/dist/agents/skills-install.js +244 -0
- package/dist/agents/skills-status.js +157 -0
- package/dist/agents/skills.js +470 -0
- package/dist/agents/steerable-agent-loop.js +338 -0
- package/dist/agents/steerable-provider-transport.js +48 -0
- package/dist/agents/system-prompt.js +104 -0
- package/dist/agents/tool-display.js +162 -0
- package/dist/agents/tool-images.js +138 -0
- package/dist/agents/tools/browser-tool.js +339 -0
- package/dist/agents/tools/canvas-tool.js +193 -0
- package/dist/agents/tools/common.js +88 -0
- package/dist/agents/tools/cron-tool.js +124 -0
- package/dist/agents/tools/discord-actions-guild.js +186 -0
- package/dist/agents/tools/discord-actions-messaging.js +285 -0
- package/dist/agents/tools/discord-actions-moderation.js +70 -0
- package/dist/agents/tools/discord-actions.js +56 -0
- package/dist/agents/tools/discord-schema.js +199 -0
- package/dist/agents/tools/discord-tool.js +16 -0
- package/dist/agents/tools/gateway-tool.js +46 -0
- package/dist/agents/tools/gateway.js +27 -0
- package/dist/agents/tools/image-tool.js +132 -0
- package/dist/agents/tools/nodes-tool.js +413 -0
- package/dist/agents/tools/nodes-utils.js +92 -0
- package/dist/agents/tools/sessions-helpers.js +88 -0
- package/dist/agents/tools/sessions-history-tool.js +53 -0
- package/dist/agents/tools/sessions-list-tool.js +143 -0
- package/dist/agents/tools/sessions-send-helpers.js +100 -0
- package/dist/agents/tools/sessions-send-tool.js +347 -0
- package/dist/agents/tools/slack-actions.js +129 -0
- package/dist/agents/tools/slack-schema.js +59 -0
- package/dist/agents/tools/slack-tool.js +16 -0
- package/dist/agents/usage.js +39 -0
- package/dist/agents/workspace.js +241 -0
- package/dist/auto-reply/chunk.js +76 -0
- package/dist/auto-reply/envelope.js +38 -0
- package/dist/auto-reply/group-activation.js +20 -0
- package/dist/auto-reply/heartbeat.js +57 -0
- package/dist/auto-reply/model.js +14 -0
- package/dist/auto-reply/reply/abort.js +14 -0
- package/dist/auto-reply/reply/agent-runner.js +371 -0
- package/dist/auto-reply/reply/block-streaming.js +34 -0
- package/dist/auto-reply/reply/body.js +29 -0
- package/dist/auto-reply/reply/commands.js +207 -0
- package/dist/auto-reply/reply/directive-handling.js +361 -0
- package/dist/auto-reply/reply/directives.js +47 -0
- package/dist/auto-reply/reply/followup-runner.js +149 -0
- package/dist/auto-reply/reply/groups.js +91 -0
- package/dist/auto-reply/reply/mentions.js +38 -0
- package/dist/auto-reply/reply/model-selection.js +114 -0
- package/dist/auto-reply/reply/queue.js +399 -0
- package/dist/auto-reply/reply/reply-tags.js +26 -0
- package/dist/auto-reply/reply/session-updates.js +87 -0
- package/dist/auto-reply/reply/session.js +160 -0
- package/dist/auto-reply/reply/typing.js +75 -0
- package/dist/auto-reply/reply.js +535 -0
- package/dist/auto-reply/send-policy.js +28 -0
- package/dist/auto-reply/status.js +158 -0
- package/dist/auto-reply/templating.js +9 -0
- package/dist/auto-reply/thinking.js +49 -0
- package/dist/auto-reply/tokens.js +2 -0
- package/dist/auto-reply/tool-meta.js +74 -0
- package/dist/auto-reply/transcription.js +57 -0
- package/dist/auto-reply/types.js +1 -0
- package/dist/browser/bridge-server.js +37 -0
- package/dist/browser/cdp.js +382 -0
- package/dist/browser/chrome.js +432 -0
- package/dist/browser/client-actions-core.js +67 -0
- package/dist/browser/client-actions-observe.js +24 -0
- package/dist/browser/client-actions-types.js +1 -0
- package/dist/browser/client-actions.js +3 -0
- package/dist/browser/client-fetch.js +43 -0
- package/dist/browser/client.js +105 -0
- package/dist/browser/config.js +140 -0
- package/dist/browser/constants.js +4 -0
- package/dist/browser/profiles-service.js +122 -0
- package/dist/browser/profiles.js +85 -0
- package/dist/browser/pw-ai.js +2 -0
- package/dist/browser/pw-session.js +144 -0
- package/dist/browser/pw-tools-core.js +363 -0
- package/dist/browser/routes/agent.js +535 -0
- package/dist/browser/routes/basic.js +155 -0
- package/dist/browser/routes/index.js +8 -0
- package/dist/browser/routes/tabs.js +105 -0
- package/dist/browser/routes/utils.js +62 -0
- package/dist/browser/screenshot.js +40 -0
- package/dist/browser/server-context.js +377 -0
- package/dist/browser/server.js +81 -0
- package/dist/browser/target-id.js +18 -0
- package/dist/browser/trash.js +21 -0
- package/dist/canvas-host/a2ui/.bundle.hash +1 -0
- package/dist/canvas-host/a2ui/a2ui.bundle.js +17768 -0
- package/dist/canvas-host/a2ui/index.html +246 -0
- package/dist/canvas-host/a2ui.js +187 -0
- package/dist/canvas-host/server.js +382 -0
- package/dist/cli/browser-cli-actions-input.js +459 -0
- package/dist/cli/browser-cli-actions-observe.js +56 -0
- package/dist/cli/browser-cli-examples.js +31 -0
- package/dist/cli/browser-cli-inspect.js +97 -0
- package/dist/cli/browser-cli-manage.js +286 -0
- package/dist/cli/browser-cli-shared.js +1 -0
- package/dist/cli/browser-cli.js +26 -0
- package/dist/cli/canvas-cli.js +416 -0
- package/dist/cli/cron-cli.js +454 -0
- package/dist/cli/deps.js +17 -0
- package/dist/cli/dns-cli.js +180 -0
- package/dist/cli/gateway-cli.js +489 -0
- package/dist/cli/gateway-rpc.js +20 -0
- package/dist/cli/hooks-cli.js +135 -0
- package/dist/cli/models-cli.js +248 -0
- package/dist/cli/nodes-camera.js +57 -0
- package/dist/cli/nodes-canvas.js +26 -0
- package/dist/cli/nodes-cli.js +946 -0
- package/dist/cli/nodes-screen.js +37 -0
- package/dist/cli/parse-duration.js +20 -0
- package/dist/cli/ports.js +97 -0
- package/dist/cli/program.js +406 -0
- package/dist/cli/prompt.js +19 -0
- package/dist/cli/tui-cli.js +35 -0
- package/dist/cli/wait.js +8 -0
- package/dist/commands/agent.js +645 -0
- package/dist/commands/antigravity-oauth.js +327 -0
- package/dist/commands/configure.js +480 -0
- package/dist/commands/doctor.js +484 -0
- package/dist/commands/health.js +108 -0
- package/dist/commands/models/aliases.js +64 -0
- package/dist/commands/models/fallbacks.js +99 -0
- package/dist/commands/models/image-fallbacks.js +99 -0
- package/dist/commands/models/list.js +323 -0
- package/dist/commands/models/scan.js +266 -0
- package/dist/commands/models/set-image.js +23 -0
- package/dist/commands/models/set.js +23 -0
- package/dist/commands/models/shared.js +72 -0
- package/dist/commands/models.js +7 -0
- package/dist/commands/onboard-auth.js +70 -0
- package/dist/commands/onboard-helpers.js +295 -0
- package/dist/commands/onboard-interactive.js +17 -0
- package/dist/commands/onboard-non-interactive.js +202 -0
- package/dist/commands/onboard-providers.js +634 -0
- package/dist/commands/onboard-remote.js +120 -0
- package/dist/commands/onboard-skills.js +148 -0
- package/dist/commands/onboard-types.js +1 -0
- package/dist/commands/onboard.js +12 -0
- package/dist/commands/send.js +124 -0
- package/dist/commands/sessions.js +212 -0
- package/dist/commands/setup.js +58 -0
- package/dist/commands/signal-install.js +135 -0
- package/dist/commands/status.js +207 -0
- package/dist/commands/update.js +16 -0
- package/dist/config/config.js +6 -0
- package/dist/config/defaults.js +61 -0
- package/dist/config/io.js +147 -0
- package/dist/config/legacy-migrate.js +13 -0
- package/dist/config/legacy.js +159 -0
- package/dist/config/paths.js +71 -0
- package/dist/config/schema.js +150 -0
- package/dist/config/sessions.js +282 -0
- package/dist/config/talk.js +31 -0
- package/dist/config/types.js +1 -0
- package/dist/config/validation.js +29 -0
- package/dist/config/zod-schema.js +831 -0
- package/dist/control-ui/assets/index-BFID3yAA.css +1 -0
- package/dist/control-ui/assets/index-CE_axlTS.js +2235 -0
- package/dist/control-ui/assets/index-CE_axlTS.js.map +1 -0
- package/dist/control-ui/index.html +15 -0
- package/dist/cron/isolated-agent.js +499 -0
- package/dist/cron/run-log.js +72 -0
- package/dist/cron/schedule.js +24 -0
- package/dist/cron/service.js +471 -0
- package/dist/cron/store.js +43 -0
- package/dist/cron/types.js +1 -0
- package/dist/daemon/constants.js +10 -0
- package/dist/daemon/launchd.js +276 -0
- package/dist/daemon/legacy.js +63 -0
- package/dist/daemon/program-args.js +76 -0
- package/dist/daemon/schtasks.js +257 -0
- package/dist/daemon/service.js +60 -0
- package/dist/daemon/systemd.js +266 -0
- package/dist/discord/index.js +2 -0
- package/dist/discord/monitor.js +1188 -0
- package/dist/discord/probe.js +54 -0
- package/dist/discord/send.js +577 -0
- package/dist/discord/token.js +8 -0
- package/dist/gateway/auth.js +121 -0
- package/dist/gateway/call.js +94 -0
- package/dist/gateway/chat-attachments.js +41 -0
- package/dist/gateway/client.js +180 -0
- package/dist/gateway/config-reload.js +274 -0
- package/dist/gateway/control-ui.js +184 -0
- package/dist/gateway/hooks-mapping.js +282 -0
- package/dist/gateway/hooks.js +168 -0
- package/dist/gateway/net.js +29 -0
- package/dist/gateway/protocol/index.js +61 -0
- package/dist/gateway/protocol/schema.js +560 -0
- package/dist/gateway/server-bridge-subscriptions.js +93 -0
- package/dist/gateway/server-bridge.js +1013 -0
- package/dist/gateway/server-browser.js +12 -0
- package/dist/gateway/server-chat.js +159 -0
- package/dist/gateway/server-constants.js +8 -0
- package/dist/gateway/server-discovery.js +62 -0
- package/dist/gateway/server-http.js +165 -0
- package/dist/gateway/server-methods/agent-job.js +125 -0
- package/dist/gateway/server-methods/agent.js +250 -0
- package/dist/gateway/server-methods/chat.js +200 -0
- package/dist/gateway/server-methods/config.js +50 -0
- package/dist/gateway/server-methods/connect.js +6 -0
- package/dist/gateway/server-methods/cron.js +83 -0
- package/dist/gateway/server-methods/health.js +28 -0
- package/dist/gateway/server-methods/models.js +16 -0
- package/dist/gateway/server-methods/nodes.js +294 -0
- package/dist/gateway/server-methods/providers.js +217 -0
- package/dist/gateway/server-methods/send.js +166 -0
- package/dist/gateway/server-methods/sessions.js +305 -0
- package/dist/gateway/server-methods/skills.js +83 -0
- package/dist/gateway/server-methods/system.js +118 -0
- package/dist/gateway/server-methods/talk.js +22 -0
- package/dist/gateway/server-methods/types.js +1 -0
- package/dist/gateway/server-methods/voicewake.js +30 -0
- package/dist/gateway/server-methods/web.js +58 -0
- package/dist/gateway/server-methods/wizard.js +100 -0
- package/dist/gateway/server-methods.js +53 -0
- package/dist/gateway/server-providers.js +644 -0
- package/dist/gateway/server-shared.js +1 -0
- package/dist/gateway/server-utils.js +35 -0
- package/dist/gateway/server.js +1437 -0
- package/dist/gateway/session-utils.js +216 -0
- package/dist/gateway/ws-log.js +349 -0
- package/dist/gateway/ws-logging.js +8 -0
- package/dist/globals.js +41 -0
- package/dist/hooks/gmail-ops.js +236 -0
- package/dist/hooks/gmail-setup-utils.js +278 -0
- package/dist/hooks/gmail-watcher.js +175 -0
- package/dist/hooks/gmail.js +177 -0
- package/dist/imessage/client.js +165 -0
- package/dist/imessage/index.js +3 -0
- package/dist/imessage/monitor.js +272 -0
- package/dist/imessage/probe.js +26 -0
- package/dist/imessage/send.js +83 -0
- package/dist/imessage/targets.js +176 -0
- package/dist/index.js +50 -0
- package/dist/infra/agent-events.js +46 -0
- package/dist/infra/binaries.js +9 -0
- package/dist/infra/bonjour-discovery.js +163 -0
- package/dist/infra/bonjour.js +200 -0
- package/dist/infra/bridge/server.js +562 -0
- package/dist/infra/canvas-host-url.js +54 -0
- package/dist/infra/env.js +8 -0
- package/dist/infra/errors.js +28 -0
- package/dist/infra/gateway-lock.js +8 -0
- package/dist/infra/heartbeat-events.js +21 -0
- package/dist/infra/heartbeat-runner.js +453 -0
- package/dist/infra/heartbeat-wake.js +61 -0
- package/dist/infra/is-main.js +37 -0
- package/dist/infra/machine-name.js +40 -0
- package/dist/infra/node-pairing.js +211 -0
- package/dist/infra/pam.js +42 -0
- package/dist/infra/path-env.js +92 -0
- package/dist/infra/ports.js +87 -0
- package/dist/infra/provider-summary.js +80 -0
- package/dist/infra/restart.js +29 -0
- package/dist/infra/retry.js +16 -0
- package/dist/infra/runtime-guard.js +59 -0
- package/dist/infra/system-events.js +44 -0
- package/dist/infra/system-presence.js +216 -0
- package/dist/infra/tailnet.js +46 -0
- package/dist/infra/tailscale.js +149 -0
- package/dist/infra/voicewake.js +77 -0
- package/dist/infra/widearea-dns.js +123 -0
- package/dist/infra/ws.js +13 -0
- package/dist/logger.js +52 -0
- package/dist/logging.js +490 -0
- package/dist/macos/gateway-daemon.js +141 -0
- package/dist/macos/relay.js +46 -0
- package/dist/media/constants.js +33 -0
- package/dist/media/host.js +42 -0
- package/dist/media/image-ops.js +121 -0
- package/dist/media/mime.js +115 -0
- package/dist/media/parse.js +81 -0
- package/dist/media/server.js +64 -0
- package/dist/media/store.js +139 -0
- package/dist/process/command-queue.js +97 -0
- package/dist/process/exec.js +75 -0
- package/dist/protocol.schema.json +2918 -0
- package/dist/provider-web.js +8 -0
- package/dist/providers/web/index.js +2 -0
- package/dist/runtime.js +8 -0
- package/dist/sessions/send-policy.js +68 -0
- package/dist/signal/client.js +134 -0
- package/dist/signal/daemon.js +69 -0
- package/dist/signal/index.js +3 -0
- package/dist/signal/monitor.js +336 -0
- package/dist/signal/probe.js +46 -0
- package/dist/signal/send.js +91 -0
- package/dist/slack/actions.js +97 -0
- package/dist/slack/index.js +5 -0
- package/dist/slack/monitor.js +1029 -0
- package/dist/slack/probe.js +47 -0
- package/dist/slack/send.js +131 -0
- package/dist/slack/token.js +10 -0
- package/dist/telegram/bot.js +394 -0
- package/dist/telegram/download.js +34 -0
- package/dist/telegram/index.js +4 -0
- package/dist/telegram/monitor.js +47 -0
- package/dist/telegram/probe.js +63 -0
- package/dist/telegram/proxy.js +9 -0
- package/dist/telegram/send.js +138 -0
- package/dist/telegram/token.js +30 -0
- package/dist/telegram/webhook-set.js +12 -0
- package/dist/telegram/webhook.js +56 -0
- package/dist/tui/commands.js +74 -0
- package/dist/tui/components/assistant-message.js +16 -0
- package/dist/tui/components/chat-log.js +92 -0
- package/dist/tui/components/custom-editor.js +53 -0
- package/dist/tui/components/selectors.js +8 -0
- package/dist/tui/components/tool-execution.js +111 -0
- package/dist/tui/components/user-message.js +17 -0
- package/dist/tui/gateway-chat.js +140 -0
- package/dist/tui/layout.js +41 -0
- package/dist/tui/message-list.js +57 -0
- package/dist/tui/theme/theme.js +80 -0
- package/dist/tui/theme.js +25 -0
- package/dist/tui/tui.js +708 -0
- package/dist/utils.js +133 -0
- package/dist/version.js +18 -0
- package/dist/web/active-listener.js +7 -0
- package/dist/web/auto-reply.js +1203 -0
- package/dist/web/inbound.js +481 -0
- package/dist/web/login-qr.js +204 -0
- package/dist/web/login.js +59 -0
- package/dist/web/media.js +148 -0
- package/dist/web/outbound.js +67 -0
- package/dist/web/qr-image.js +97 -0
- package/dist/web/reconnect.js +60 -0
- package/dist/web/reply-heartbeat-wake.js +61 -0
- package/dist/web/session.js +346 -0
- package/dist/wizard/clack-prompter.js +56 -0
- package/dist/wizard/onboarding.js +452 -0
- package/dist/wizard/prompts.js +6 -0
- package/dist/wizard/session.js +203 -0
- package/docs/AGENTS.default.md +116 -0
- package/docs/CNAME +1 -0
- package/docs/RELEASING.md +64 -0
- package/docs/_config.yml +51 -0
- package/docs/_layouts/default.html +145 -0
- package/docs/agent-send.md +21 -0
- package/docs/agent.md +104 -0
- package/docs/android/connect.md +131 -0
- package/docs/architecture.md +89 -0
- package/docs/assets/markdown.css +130 -0
- package/docs/assets/pixel-lobster.svg +60 -0
- package/docs/assets/terminal.css +497 -0
- package/docs/assets/theme.js +55 -0
- package/docs/audio.md +50 -0
- package/docs/background-process.md +74 -0
- package/docs/bash.md +32 -0
- package/docs/bonjour.md +159 -0
- package/docs/browser.md +289 -0
- package/docs/camera.md +152 -0
- package/docs/clawd.md +199 -0
- package/docs/clawdbot-mac.md +104 -0
- package/docs/configuration.md +1177 -0
- package/docs/control-api.md +49 -0
- package/docs/control-ui.md +83 -0
- package/docs/cron.md +374 -0
- package/docs/dashboard.md +17 -0
- package/docs/device-models.md +46 -0
- package/docs/discord.md +293 -0
- package/docs/discovery.md +112 -0
- package/docs/docker.md +251 -0
- package/docs/docs.json +86 -0
- package/docs/doctor.md +47 -0
- package/docs/elevated.md +31 -0
- package/docs/faq.md +640 -0
- package/docs/gateway/pairing.md +109 -0
- package/docs/gateway-lock.md +28 -0
- package/docs/gateway.md +174 -0
- package/docs/gmail-pubsub.md +191 -0
- package/docs/grammy.md +27 -0
- package/docs/group-messages.md +71 -0
- package/docs/groups.md +78 -0
- package/docs/health.md +28 -0
- package/docs/heartbeat.md +64 -0
- package/docs/images.md +52 -0
- package/docs/imessage.md +63 -0
- package/docs/index.md +182 -0
- package/docs/ios/connect.md +177 -0
- package/docs/ios/spec.md +236 -0
- package/docs/location-command.md +95 -0
- package/docs/logging.md +99 -0
- package/docs/lore.md +131 -0
- package/docs/mac/bun.md +133 -0
- package/docs/mac/canvas.md +161 -0
- package/docs/mac/child-process.md +72 -0
- package/docs/mac/dev-setup.md +81 -0
- package/docs/mac/health.md +28 -0
- package/docs/mac/icon.md +26 -0
- package/docs/mac/logging.md +51 -0
- package/docs/mac/menu-bar.md +69 -0
- package/docs/mac/peekaboo.md +170 -0
- package/docs/mac/permissions.md +40 -0
- package/docs/mac/release.md +76 -0
- package/docs/mac/remote.md +57 -0
- package/docs/mac/signing.md +41 -0
- package/docs/mac/skills.md +27 -0
- package/docs/mac/voice-overlay.md +52 -0
- package/docs/mac/voicewake.md +56 -0
- package/docs/mac/webchat.md +27 -0
- package/docs/mac/xpc.md +40 -0
- package/docs/models.md +90 -0
- package/docs/nix.md +49 -0
- package/docs/nodes.md +157 -0
- package/docs/onboarding-config-protocol.md +29 -0
- package/docs/onboarding.md +185 -0
- package/docs/presence.md +133 -0
- package/docs/queue.md +78 -0
- package/docs/refactor/browser-control-simplification.md +58 -0
- package/docs/refactor/canvas-a2ui.md +93 -0
- package/docs/refactor/cli-unification.md +64 -0
- package/docs/refactor/gateway-client.md +31 -0
- package/docs/refactor/gateway.md +99 -0
- package/docs/refactor/new-arch.md +171 -0
- package/docs/refactor/tui.md +26 -0
- package/docs/refactor/web-gateway-troubleshooting.md +37 -0
- package/docs/refactor/webagent-session.md +46 -0
- package/docs/remote-gateway-readme.md +148 -0
- package/docs/remote.md +66 -0
- package/docs/research/memory.md +227 -0
- package/docs/rpc.md +35 -0
- package/docs/security.md +168 -0
- package/docs/session-tool.md +119 -0
- package/docs/session.md +84 -0
- package/docs/sessions.md +8 -0
- package/docs/setup.md +118 -0
- package/docs/signal.md +113 -0
- package/docs/skills-config.md +58 -0
- package/docs/skills.md +149 -0
- package/docs/slack.md +158 -0
- package/docs/surface.md +20 -0
- package/docs/tailscale.md +71 -0
- package/docs/talk.md +79 -0
- package/docs/telegram.md +90 -0
- package/docs/templates/AGENTS.md +126 -0
- package/docs/templates/BOOTSTRAP.md +53 -0
- package/docs/templates/IDENTITY.md +17 -0
- package/docs/templates/SOUL.md +41 -0
- package/docs/templates/TOOLS.md +41 -0
- package/docs/templates/USER.md +22 -0
- package/docs/test.md +35 -0
- package/docs/thinking.md +46 -0
- package/docs/tools.md +248 -0
- package/docs/troubleshooting.md +227 -0
- package/docs/tui.md +69 -0
- package/docs/typebox.md +42 -0
- package/docs/voicewake.md +61 -0
- package/docs/web.md +115 -0
- package/docs/webchat.md +34 -0
- package/docs/webhook.md +132 -0
- package/docs/whatsapp-clawd.jpg +0 -0
- package/docs/whatsapp.md +142 -0
- package/docs/wizard.md +158 -0
- package/package.json +186 -0
- package/skills/apple-notes/SKILL.md +50 -0
- package/skills/apple-reminders/SKILL.md +67 -0
- package/skills/bear-notes/SKILL.md +79 -0
- package/skills/bird/SKILL.md +25 -0
- package/skills/blogwatcher/SKILL.md +46 -0
- package/skills/blucli/SKILL.md +27 -0
- package/skills/brave-search/SKILL.md +30 -0
- package/skills/brave-search/scripts/content.mjs +53 -0
- package/skills/brave-search/scripts/search.mjs +79 -0
- package/skills/camsnap/SKILL.md +25 -0
- package/skills/clawdhub/SKILL.md +53 -0
- package/skills/coding-agent/SKILL.md +275 -0
- package/skills/discord/SKILL.md +369 -0
- package/skills/eightctl/SKILL.md +29 -0
- package/skills/food-order/SKILL.md +41 -0
- package/skills/gemini/SKILL.md +23 -0
- package/skills/gifgrep/SKILL.md +47 -0
- package/skills/github/SKILL.md +47 -0
- package/skills/gog/SKILL.md +36 -0
- package/skills/goplaces/SKILL.md +30 -0
- package/skills/imsg/SKILL.md +25 -0
- package/skills/local-places/SERVER_README.md +101 -0
- package/skills/local-places/SKILL.md +91 -0
- package/skills/local-places/pyproject.toml +27 -0
- package/skills/local-places/src/local_places/__init__.py +2 -0
- package/skills/local-places/src/local_places/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/google_places.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/main.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/schemas.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/google_places.py +314 -0
- package/skills/local-places/src/local_places/main.py +65 -0
- package/skills/local-places/src/local_places/schemas.py +107 -0
- package/skills/mcporter/SKILL.md +38 -0
- package/skills/nano-banana-pro/SKILL.md +29 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +167 -0
- package/skills/nano-pdf/SKILL.md +20 -0
- package/skills/notion/SKILL.md +156 -0
- package/skills/obsidian/SKILL.md +55 -0
- package/skills/openai-image-gen/SKILL.md +31 -0
- package/skills/openai-image-gen/scripts/gen.py +173 -0
- package/skills/openai-whisper/SKILL.md +19 -0
- package/skills/openai-whisper-api/SKILL.md +43 -0
- package/skills/openai-whisper-api/scripts/transcribe.sh +85 -0
- package/skills/openhue/SKILL.md +30 -0
- package/skills/oracle/SKILL.md +105 -0
- package/skills/ordercli/SKILL.md +47 -0
- package/skills/peekaboo/SKILL.md +153 -0
- package/skills/qmd/SKILL.md +26 -0
- package/skills/sag/SKILL.md +62 -0
- package/skills/slack/SKILL.md +143 -0
- package/skills/songsee/SKILL.md +29 -0
- package/skills/sonoscli/SKILL.md +26 -0
- package/skills/spotify-player/SKILL.md +34 -0
- package/skills/summarize/SKILL.md +49 -0
- package/skills/things-mac/SKILL.md +61 -0
- package/skills/tmux/SKILL.md +121 -0
- package/skills/tmux/scripts/find-sessions.sh +112 -0
- package/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/skills/trello/SKILL.md +84 -0
- package/skills/video-frames/SKILL.md +29 -0
- package/skills/video-frames/scripts/frame.sh +81 -0
- package/skills/wacli/SKILL.md +42 -0
- package/skills/weather/SKILL.md +49 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { DisconnectReason, downloadMediaMessage, extractMessageContent, getContentType, isJidGroup, normalizeMessageContent, } from "@whiskeysockets/baileys";
|
|
2
|
+
import { loadConfig } from "../config/config.js";
|
|
3
|
+
import { logVerbose, shouldLogVerbose } from "../globals.js";
|
|
4
|
+
import { createSubsystemLogger, getChildLogger } from "../logging.js";
|
|
5
|
+
import { saveMediaBuffer } from "../media/store.js";
|
|
6
|
+
import { isSelfChatMode, jidToE164, normalizeE164, toWhatsappJid, } from "../utils.js";
|
|
7
|
+
import { createWaSocket, getStatusCode, waitForWaConnection, } from "./session.js";
|
|
8
|
+
export async function monitorWebInbox(options) {
|
|
9
|
+
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
|
10
|
+
const inboundConsoleLog = createSubsystemLogger("gateway/providers/whatsapp").child("inbound");
|
|
11
|
+
const sock = await createWaSocket(false, options.verbose);
|
|
12
|
+
await waitForWaConnection(sock);
|
|
13
|
+
let onCloseResolve = null;
|
|
14
|
+
const onClose = new Promise((resolve) => {
|
|
15
|
+
onCloseResolve = resolve;
|
|
16
|
+
});
|
|
17
|
+
const resolveClose = (reason) => {
|
|
18
|
+
if (!onCloseResolve)
|
|
19
|
+
return;
|
|
20
|
+
const resolver = onCloseResolve;
|
|
21
|
+
onCloseResolve = null;
|
|
22
|
+
resolver(reason);
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
// Advertise that the gateway is online right after connecting.
|
|
26
|
+
await sock.sendPresenceUpdate("available");
|
|
27
|
+
if (shouldLogVerbose())
|
|
28
|
+
logVerbose("Sent global 'available' presence on connect");
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
logVerbose(`Failed to send 'available' presence on connect: ${String(err)}`);
|
|
32
|
+
}
|
|
33
|
+
const selfJid = sock.user?.id;
|
|
34
|
+
const selfE164 = selfJid ? jidToE164(selfJid) : null;
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const groupMetaCache = new Map();
|
|
37
|
+
const GROUP_META_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
38
|
+
const getGroupMeta = async (jid) => {
|
|
39
|
+
const cached = groupMetaCache.get(jid);
|
|
40
|
+
if (cached && cached.expires > Date.now())
|
|
41
|
+
return cached;
|
|
42
|
+
try {
|
|
43
|
+
const meta = await sock.groupMetadata(jid);
|
|
44
|
+
const participants = meta.participants
|
|
45
|
+
?.map((p) => jidToE164(p.id) ?? p.id)
|
|
46
|
+
.filter(Boolean) ?? [];
|
|
47
|
+
const entry = {
|
|
48
|
+
subject: meta.subject,
|
|
49
|
+
participants,
|
|
50
|
+
expires: Date.now() + GROUP_META_TTL_MS,
|
|
51
|
+
};
|
|
52
|
+
groupMetaCache.set(jid, entry);
|
|
53
|
+
return entry;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
logVerbose(`Failed to fetch group metadata for ${jid}: ${String(err)}`);
|
|
57
|
+
return { expires: Date.now() + GROUP_META_TTL_MS };
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const handleMessagesUpsert = async (upsert) => {
|
|
61
|
+
if (upsert.type !== "notify")
|
|
62
|
+
return;
|
|
63
|
+
for (const msg of upsert.messages ?? []) {
|
|
64
|
+
const id = msg.key?.id ?? undefined;
|
|
65
|
+
// De-dupe on message id; Baileys can emit retries.
|
|
66
|
+
if (id && seen.has(id))
|
|
67
|
+
continue;
|
|
68
|
+
if (id)
|
|
69
|
+
seen.add(id);
|
|
70
|
+
// Note: not filtering fromMe here - echo detection happens in auto-reply layer
|
|
71
|
+
const remoteJid = msg.key?.remoteJid;
|
|
72
|
+
if (!remoteJid)
|
|
73
|
+
continue;
|
|
74
|
+
// Ignore status/broadcast traffic; we only care about direct chats.
|
|
75
|
+
if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast"))
|
|
76
|
+
continue;
|
|
77
|
+
const group = isJidGroup(remoteJid);
|
|
78
|
+
const participantJid = msg.key?.participant ?? undefined;
|
|
79
|
+
const senderE164 = participantJid ? jidToE164(participantJid) : null;
|
|
80
|
+
const from = group ? remoteJid : jidToE164(remoteJid);
|
|
81
|
+
// Skip if we still can't resolve an id to key conversation
|
|
82
|
+
if (!from)
|
|
83
|
+
continue;
|
|
84
|
+
let groupSubject;
|
|
85
|
+
let groupParticipants;
|
|
86
|
+
if (group) {
|
|
87
|
+
const meta = await getGroupMeta(remoteJid);
|
|
88
|
+
groupSubject = meta.subject;
|
|
89
|
+
groupParticipants = meta.participants;
|
|
90
|
+
}
|
|
91
|
+
// Filter unauthorized senders early to prevent wasted processing
|
|
92
|
+
// and potential session corruption from Bad MAC errors
|
|
93
|
+
const cfg = loadConfig();
|
|
94
|
+
const configuredAllowFrom = cfg.whatsapp?.allowFrom;
|
|
95
|
+
// Without user config, default to self-only DM access so the owner can talk to themselves
|
|
96
|
+
const defaultAllowFrom = (!configuredAllowFrom || configuredAllowFrom.length === 0) && selfE164
|
|
97
|
+
? [selfE164]
|
|
98
|
+
: undefined;
|
|
99
|
+
const allowFrom = configuredAllowFrom && configuredAllowFrom.length > 0
|
|
100
|
+
? configuredAllowFrom
|
|
101
|
+
: defaultAllowFrom;
|
|
102
|
+
const isSamePhone = from === selfE164;
|
|
103
|
+
const isSelfChat = isSelfChatMode(selfE164, configuredAllowFrom);
|
|
104
|
+
const allowlistEnabled = !group && Array.isArray(allowFrom) && allowFrom.length > 0;
|
|
105
|
+
if (!isSamePhone && allowlistEnabled) {
|
|
106
|
+
const candidate = from;
|
|
107
|
+
const allowedList = allowFrom.map(normalizeE164);
|
|
108
|
+
if (!allowFrom.includes("*") && !allowedList.includes(candidate)) {
|
|
109
|
+
logVerbose(`Blocked unauthorized sender ${candidate} (not in allowFrom list)`);
|
|
110
|
+
continue; // Skip processing entirely
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (id && !isSelfChat) {
|
|
114
|
+
const participant = msg.key?.participant;
|
|
115
|
+
try {
|
|
116
|
+
await sock.readMessages([
|
|
117
|
+
{ remoteJid, id, participant, fromMe: false },
|
|
118
|
+
]);
|
|
119
|
+
if (shouldLogVerbose()) {
|
|
120
|
+
const suffix = participant ? ` (participant ${participant})` : "";
|
|
121
|
+
logVerbose(`Marked message ${id} as read for ${remoteJid}${suffix}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
logVerbose(`Failed to mark message ${id} read: ${String(err)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (id && isSelfChat && shouldLogVerbose()) {
|
|
129
|
+
// Self-chat mode: never auto-send read receipts (blue ticks) on behalf of the owner.
|
|
130
|
+
logVerbose(`Self-chat mode: skipping read receipt for ${id}`);
|
|
131
|
+
}
|
|
132
|
+
let body = extractText(msg.message ?? undefined);
|
|
133
|
+
if (!body) {
|
|
134
|
+
body = extractMediaPlaceholder(msg.message ?? undefined);
|
|
135
|
+
if (!body)
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const replyContext = describeReplyContext(msg.message);
|
|
139
|
+
let mediaPath;
|
|
140
|
+
let mediaType;
|
|
141
|
+
try {
|
|
142
|
+
const inboundMedia = await downloadInboundMedia(msg, sock);
|
|
143
|
+
if (inboundMedia) {
|
|
144
|
+
const saved = await saveMediaBuffer(inboundMedia.buffer, inboundMedia.mimetype);
|
|
145
|
+
mediaPath = saved.path;
|
|
146
|
+
mediaType = inboundMedia.mimetype;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
logVerbose(`Inbound media download failed: ${String(err)}`);
|
|
151
|
+
}
|
|
152
|
+
const chatJid = remoteJid;
|
|
153
|
+
const sendComposing = async () => {
|
|
154
|
+
try {
|
|
155
|
+
await sock.sendPresenceUpdate("composing", chatJid);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
logVerbose(`Presence update failed: ${String(err)}`);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const reply = async (text) => {
|
|
162
|
+
await sock.sendMessage(chatJid, { text });
|
|
163
|
+
};
|
|
164
|
+
const sendMedia = async (payload) => {
|
|
165
|
+
await sock.sendMessage(chatJid, payload);
|
|
166
|
+
};
|
|
167
|
+
const timestamp = msg.messageTimestamp
|
|
168
|
+
? Number(msg.messageTimestamp) * 1000
|
|
169
|
+
: undefined;
|
|
170
|
+
const mentionedJids = extractMentionedJids(msg.message);
|
|
171
|
+
const senderName = msg.pushName ?? undefined;
|
|
172
|
+
inboundLogger.info({
|
|
173
|
+
from,
|
|
174
|
+
to: selfE164 ?? "me",
|
|
175
|
+
body,
|
|
176
|
+
mediaPath,
|
|
177
|
+
mediaType,
|
|
178
|
+
timestamp,
|
|
179
|
+
}, "inbound message");
|
|
180
|
+
try {
|
|
181
|
+
const task = Promise.resolve(options.onMessage({
|
|
182
|
+
id,
|
|
183
|
+
from,
|
|
184
|
+
conversationId: from,
|
|
185
|
+
to: selfE164 ?? "me",
|
|
186
|
+
body,
|
|
187
|
+
pushName: senderName,
|
|
188
|
+
timestamp,
|
|
189
|
+
chatType: group ? "group" : "direct",
|
|
190
|
+
chatId: remoteJid,
|
|
191
|
+
senderJid: participantJid,
|
|
192
|
+
senderE164: senderE164 ?? undefined,
|
|
193
|
+
senderName,
|
|
194
|
+
replyToId: replyContext?.id,
|
|
195
|
+
replyToBody: replyContext?.body,
|
|
196
|
+
replyToSender: replyContext?.sender,
|
|
197
|
+
groupSubject,
|
|
198
|
+
groupParticipants,
|
|
199
|
+
mentionedJids: mentionedJids ?? undefined,
|
|
200
|
+
selfJid,
|
|
201
|
+
selfE164,
|
|
202
|
+
sendComposing,
|
|
203
|
+
reply,
|
|
204
|
+
sendMedia,
|
|
205
|
+
mediaPath,
|
|
206
|
+
mediaType,
|
|
207
|
+
}));
|
|
208
|
+
void task.catch((err) => {
|
|
209
|
+
inboundLogger.error({ error: String(err) }, "failed handling inbound web message");
|
|
210
|
+
inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
inboundLogger.error({ error: String(err) }, "failed handling inbound web message");
|
|
215
|
+
inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
sock.ev.on("messages.upsert", handleMessagesUpsert);
|
|
220
|
+
const handleConnectionUpdate = (update) => {
|
|
221
|
+
try {
|
|
222
|
+
if (update.connection === "close") {
|
|
223
|
+
const status = getStatusCode(update.lastDisconnect?.error);
|
|
224
|
+
resolveClose({
|
|
225
|
+
status,
|
|
226
|
+
isLoggedOut: status === DisconnectReason.loggedOut,
|
|
227
|
+
error: update.lastDisconnect?.error,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
inboundLogger.error({ error: String(err) }, "connection.update handler error");
|
|
233
|
+
resolveClose({
|
|
234
|
+
status: undefined,
|
|
235
|
+
isLoggedOut: false,
|
|
236
|
+
error: err,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
sock.ev.on("connection.update", handleConnectionUpdate);
|
|
241
|
+
return {
|
|
242
|
+
close: async () => {
|
|
243
|
+
try {
|
|
244
|
+
const ev = sock.ev;
|
|
245
|
+
const messagesUpsertHandler = handleMessagesUpsert;
|
|
246
|
+
const connectionUpdateHandler = handleConnectionUpdate;
|
|
247
|
+
if (typeof ev.off === "function") {
|
|
248
|
+
ev.off("messages.upsert", messagesUpsertHandler);
|
|
249
|
+
ev.off("connection.update", connectionUpdateHandler);
|
|
250
|
+
}
|
|
251
|
+
else if (typeof ev.removeListener === "function") {
|
|
252
|
+
ev.removeListener("messages.upsert", messagesUpsertHandler);
|
|
253
|
+
ev.removeListener("connection.update", connectionUpdateHandler);
|
|
254
|
+
}
|
|
255
|
+
sock.ws?.close();
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
logVerbose(`Socket close failed: ${String(err)}`);
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
onClose,
|
|
262
|
+
signalClose: (reason) => {
|
|
263
|
+
resolveClose(reason ?? { status: undefined, isLoggedOut: false, error: "closed" });
|
|
264
|
+
},
|
|
265
|
+
/**
|
|
266
|
+
* Send a message through this connection's socket.
|
|
267
|
+
* Used by IPC to avoid creating new connections.
|
|
268
|
+
*/
|
|
269
|
+
sendMessage: async (to, text, mediaBuffer, mediaType, options) => {
|
|
270
|
+
const jid = toWhatsappJid(to);
|
|
271
|
+
let payload;
|
|
272
|
+
if (mediaBuffer && mediaType) {
|
|
273
|
+
if (mediaType.startsWith("image/")) {
|
|
274
|
+
payload = {
|
|
275
|
+
image: mediaBuffer,
|
|
276
|
+
caption: text || undefined,
|
|
277
|
+
mimetype: mediaType,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
else if (mediaType.startsWith("audio/")) {
|
|
281
|
+
payload = {
|
|
282
|
+
audio: mediaBuffer,
|
|
283
|
+
ptt: true,
|
|
284
|
+
mimetype: mediaType,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
else if (mediaType.startsWith("video/")) {
|
|
288
|
+
const gifPlayback = options?.gifPlayback;
|
|
289
|
+
payload = {
|
|
290
|
+
video: mediaBuffer,
|
|
291
|
+
caption: text || undefined,
|
|
292
|
+
mimetype: mediaType,
|
|
293
|
+
...(gifPlayback ? { gifPlayback: true } : {}),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
payload = {
|
|
298
|
+
document: mediaBuffer,
|
|
299
|
+
fileName: "file",
|
|
300
|
+
caption: text || undefined,
|
|
301
|
+
mimetype: mediaType,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
payload = { text };
|
|
307
|
+
}
|
|
308
|
+
const result = await sock.sendMessage(jid, payload);
|
|
309
|
+
return { messageId: result?.key?.id ?? "unknown" };
|
|
310
|
+
},
|
|
311
|
+
/**
|
|
312
|
+
* Send typing indicator ("composing") to a chat.
|
|
313
|
+
* Used after IPC send to show more messages are coming.
|
|
314
|
+
*/
|
|
315
|
+
sendComposingTo: async (to) => {
|
|
316
|
+
const jid = toWhatsappJid(to);
|
|
317
|
+
await sock.sendPresenceUpdate("composing", jid);
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function unwrapMessage(message) {
|
|
322
|
+
const normalized = normalizeMessageContent(message);
|
|
323
|
+
return normalized;
|
|
324
|
+
}
|
|
325
|
+
function extractContextInfo(message) {
|
|
326
|
+
if (!message)
|
|
327
|
+
return undefined;
|
|
328
|
+
const contentType = getContentType(message);
|
|
329
|
+
const candidate = contentType
|
|
330
|
+
? message[contentType]
|
|
331
|
+
: undefined;
|
|
332
|
+
const contextInfo = candidate && typeof candidate === "object" && "contextInfo" in candidate
|
|
333
|
+
? candidate.contextInfo
|
|
334
|
+
: undefined;
|
|
335
|
+
if (contextInfo)
|
|
336
|
+
return contextInfo;
|
|
337
|
+
const fallback = message.extendedTextMessage?.contextInfo ??
|
|
338
|
+
message.imageMessage?.contextInfo ??
|
|
339
|
+
message.videoMessage?.contextInfo ??
|
|
340
|
+
message.documentMessage?.contextInfo ??
|
|
341
|
+
message.audioMessage?.contextInfo ??
|
|
342
|
+
message.stickerMessage?.contextInfo ??
|
|
343
|
+
message.buttonsResponseMessage?.contextInfo ??
|
|
344
|
+
message.listResponseMessage?.contextInfo ??
|
|
345
|
+
message.templateButtonReplyMessage?.contextInfo ??
|
|
346
|
+
message.interactiveResponseMessage?.contextInfo ??
|
|
347
|
+
message.buttonsMessage?.contextInfo ??
|
|
348
|
+
message.listMessage?.contextInfo;
|
|
349
|
+
if (fallback)
|
|
350
|
+
return fallback;
|
|
351
|
+
for (const value of Object.values(message)) {
|
|
352
|
+
if (!value || typeof value !== "object")
|
|
353
|
+
continue;
|
|
354
|
+
if (!("contextInfo" in value))
|
|
355
|
+
continue;
|
|
356
|
+
const candidateContext = value
|
|
357
|
+
.contextInfo;
|
|
358
|
+
if (candidateContext)
|
|
359
|
+
return candidateContext;
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
function extractMentionedJids(rawMessage) {
|
|
364
|
+
const message = unwrapMessage(rawMessage);
|
|
365
|
+
if (!message)
|
|
366
|
+
return undefined;
|
|
367
|
+
const candidates = [
|
|
368
|
+
message.extendedTextMessage?.contextInfo?.mentionedJid,
|
|
369
|
+
message.extendedTextMessage?.contextInfo?.quotedMessage?.extendedTextMessage
|
|
370
|
+
?.contextInfo?.mentionedJid,
|
|
371
|
+
message.imageMessage?.contextInfo?.mentionedJid,
|
|
372
|
+
message.videoMessage?.contextInfo?.mentionedJid,
|
|
373
|
+
message.documentMessage?.contextInfo?.mentionedJid,
|
|
374
|
+
message.audioMessage?.contextInfo?.mentionedJid,
|
|
375
|
+
message.stickerMessage?.contextInfo?.mentionedJid,
|
|
376
|
+
message.buttonsResponseMessage?.contextInfo?.mentionedJid,
|
|
377
|
+
message.listResponseMessage?.contextInfo?.mentionedJid,
|
|
378
|
+
];
|
|
379
|
+
const flattened = candidates.flatMap((arr) => arr ?? []).filter(Boolean);
|
|
380
|
+
if (flattened.length === 0)
|
|
381
|
+
return undefined;
|
|
382
|
+
// De-dupe
|
|
383
|
+
return Array.from(new Set(flattened));
|
|
384
|
+
}
|
|
385
|
+
export function extractText(rawMessage) {
|
|
386
|
+
const message = unwrapMessage(rawMessage);
|
|
387
|
+
if (!message)
|
|
388
|
+
return undefined;
|
|
389
|
+
const extracted = extractMessageContent(message);
|
|
390
|
+
const candidates = [
|
|
391
|
+
message,
|
|
392
|
+
extracted && extracted !== message ? extracted : undefined,
|
|
393
|
+
];
|
|
394
|
+
for (const candidate of candidates) {
|
|
395
|
+
if (!candidate)
|
|
396
|
+
continue;
|
|
397
|
+
if (typeof candidate.conversation === "string" &&
|
|
398
|
+
candidate.conversation.trim()) {
|
|
399
|
+
return candidate.conversation.trim();
|
|
400
|
+
}
|
|
401
|
+
const extended = candidate.extendedTextMessage?.text;
|
|
402
|
+
if (extended?.trim())
|
|
403
|
+
return extended.trim();
|
|
404
|
+
const caption = candidate.imageMessage?.caption ??
|
|
405
|
+
candidate.videoMessage?.caption ??
|
|
406
|
+
candidate.documentMessage?.caption;
|
|
407
|
+
if (caption?.trim())
|
|
408
|
+
return caption.trim();
|
|
409
|
+
}
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
export function extractMediaPlaceholder(rawMessage) {
|
|
413
|
+
const message = unwrapMessage(rawMessage);
|
|
414
|
+
if (!message)
|
|
415
|
+
return undefined;
|
|
416
|
+
if (message.imageMessage)
|
|
417
|
+
return "<media:image>";
|
|
418
|
+
if (message.videoMessage)
|
|
419
|
+
return "<media:video>";
|
|
420
|
+
if (message.audioMessage)
|
|
421
|
+
return "<media:audio>";
|
|
422
|
+
if (message.documentMessage)
|
|
423
|
+
return "<media:document>";
|
|
424
|
+
if (message.stickerMessage)
|
|
425
|
+
return "<media:sticker>";
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
function describeReplyContext(rawMessage) {
|
|
429
|
+
const message = unwrapMessage(rawMessage);
|
|
430
|
+
if (!message)
|
|
431
|
+
return null;
|
|
432
|
+
const contextInfo = extractContextInfo(message);
|
|
433
|
+
const quoted = normalizeMessageContent(contextInfo?.quotedMessage);
|
|
434
|
+
if (!quoted)
|
|
435
|
+
return null;
|
|
436
|
+
const body = extractText(quoted) ?? extractMediaPlaceholder(quoted);
|
|
437
|
+
if (!body) {
|
|
438
|
+
const quotedType = quoted ? getContentType(quoted) : undefined;
|
|
439
|
+
logVerbose(`Quoted message missing extractable body${quotedType ? ` (type ${quotedType})` : ""}`);
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
const senderJid = contextInfo?.participant ?? undefined;
|
|
443
|
+
const senderE164 = senderJid
|
|
444
|
+
? (jidToE164(senderJid) ?? senderJid)
|
|
445
|
+
: undefined;
|
|
446
|
+
const sender = senderE164 ?? "unknown sender";
|
|
447
|
+
return {
|
|
448
|
+
id: contextInfo?.stanzaId ? String(contextInfo.stanzaId) : undefined,
|
|
449
|
+
body,
|
|
450
|
+
sender,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
async function downloadInboundMedia(msg, sock) {
|
|
454
|
+
const message = unwrapMessage(msg.message);
|
|
455
|
+
if (!message)
|
|
456
|
+
return undefined;
|
|
457
|
+
const mimetype = message.imageMessage?.mimetype ??
|
|
458
|
+
message.videoMessage?.mimetype ??
|
|
459
|
+
message.documentMessage?.mimetype ??
|
|
460
|
+
message.audioMessage?.mimetype ??
|
|
461
|
+
message.stickerMessage?.mimetype ??
|
|
462
|
+
undefined;
|
|
463
|
+
if (!message.imageMessage &&
|
|
464
|
+
!message.videoMessage &&
|
|
465
|
+
!message.documentMessage &&
|
|
466
|
+
!message.audioMessage &&
|
|
467
|
+
!message.stickerMessage) {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
const buffer = (await downloadMediaMessage(msg, "buffer", {}, {
|
|
472
|
+
reuploadRequest: sock.updateMediaMessage,
|
|
473
|
+
logger: sock.logger,
|
|
474
|
+
}));
|
|
475
|
+
return { buffer, mimetype };
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
logVerbose(`downloadMediaMessage failed: ${String(err)}`);
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { DisconnectReason } from "@whiskeysockets/baileys";
|
|
3
|
+
import { danger, info, success } from "../globals.js";
|
|
4
|
+
import { logInfo } from "../logger.js";
|
|
5
|
+
import { defaultRuntime } from "../runtime.js";
|
|
6
|
+
import { renderQrPngBase64 } from "./qr-image.js";
|
|
7
|
+
import { createWaSocket, formatError, getStatusCode, logoutWeb, readWebSelfId, waitForWaConnection, webAuthExists, } from "./session.js";
|
|
8
|
+
const ACTIVE_LOGIN_TTL_MS = 3 * 60_000;
|
|
9
|
+
let activeLogin = null;
|
|
10
|
+
function closeSocket(sock) {
|
|
11
|
+
try {
|
|
12
|
+
sock.ws?.close();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// ignore
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function resetActiveLogin(reason) {
|
|
19
|
+
if (activeLogin) {
|
|
20
|
+
closeSocket(activeLogin.sock);
|
|
21
|
+
activeLogin = null;
|
|
22
|
+
}
|
|
23
|
+
if (reason) {
|
|
24
|
+
logInfo(reason);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isLoginFresh(login) {
|
|
28
|
+
return Date.now() - login.startedAt < ACTIVE_LOGIN_TTL_MS;
|
|
29
|
+
}
|
|
30
|
+
function attachLoginWaiter(login) {
|
|
31
|
+
login.waitPromise = waitForWaConnection(login.sock)
|
|
32
|
+
.then(() => {
|
|
33
|
+
if (activeLogin?.id === login.id) {
|
|
34
|
+
activeLogin.connected = true;
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
.catch((err) => {
|
|
38
|
+
if (activeLogin?.id === login.id) {
|
|
39
|
+
activeLogin.error = formatError(err);
|
|
40
|
+
activeLogin.errorStatus = getStatusCode(err);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async function restartLoginSocket(login, runtime) {
|
|
45
|
+
if (login.restartAttempted)
|
|
46
|
+
return false;
|
|
47
|
+
login.restartAttempted = true;
|
|
48
|
+
runtime.log(info("WhatsApp asked for a restart after pairing (code 515); retrying connection once…"));
|
|
49
|
+
closeSocket(login.sock);
|
|
50
|
+
try {
|
|
51
|
+
const sock = await createWaSocket(false, login.verbose);
|
|
52
|
+
login.sock = sock;
|
|
53
|
+
login.connected = false;
|
|
54
|
+
login.error = undefined;
|
|
55
|
+
login.errorStatus = undefined;
|
|
56
|
+
attachLoginWaiter(login);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
login.error = formatError(err);
|
|
61
|
+
login.errorStatus = getStatusCode(err);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function startWebLoginWithQr(opts = {}) {
|
|
66
|
+
const runtime = opts.runtime ?? defaultRuntime;
|
|
67
|
+
const hasWeb = await webAuthExists();
|
|
68
|
+
const selfId = readWebSelfId();
|
|
69
|
+
if (hasWeb && !opts.force) {
|
|
70
|
+
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
71
|
+
return {
|
|
72
|
+
message: `WhatsApp is already linked (${who}). Say “relink” if you want a fresh QR.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (activeLogin && isLoginFresh(activeLogin) && activeLogin.qrDataUrl) {
|
|
76
|
+
return {
|
|
77
|
+
qrDataUrl: activeLogin.qrDataUrl,
|
|
78
|
+
message: "QR already active. Scan it in WhatsApp → Linked Devices.",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
await resetActiveLogin();
|
|
82
|
+
let resolveQr = null;
|
|
83
|
+
let rejectQr = null;
|
|
84
|
+
const qrPromise = new Promise((resolve, reject) => {
|
|
85
|
+
resolveQr = resolve;
|
|
86
|
+
rejectQr = reject;
|
|
87
|
+
});
|
|
88
|
+
const qrTimer = setTimeout(() => {
|
|
89
|
+
rejectQr?.(new Error("Timed out waiting for WhatsApp QR"));
|
|
90
|
+
}, Math.max(opts.timeoutMs ?? 30_000, 5000));
|
|
91
|
+
let sock;
|
|
92
|
+
try {
|
|
93
|
+
sock = await createWaSocket(false, Boolean(opts.verbose), {
|
|
94
|
+
onQr: (qr) => {
|
|
95
|
+
if (!activeLogin || activeLogin.qr)
|
|
96
|
+
return;
|
|
97
|
+
activeLogin.qr = qr;
|
|
98
|
+
clearTimeout(qrTimer);
|
|
99
|
+
runtime.log(info("WhatsApp QR received."));
|
|
100
|
+
resolveQr?.(qr);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
clearTimeout(qrTimer);
|
|
106
|
+
await resetActiveLogin();
|
|
107
|
+
return {
|
|
108
|
+
message: `Failed to start WhatsApp login: ${String(err)}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const login = {
|
|
112
|
+
id: randomUUID(),
|
|
113
|
+
sock,
|
|
114
|
+
startedAt: Date.now(),
|
|
115
|
+
connected: false,
|
|
116
|
+
waitPromise: Promise.resolve(),
|
|
117
|
+
restartAttempted: false,
|
|
118
|
+
verbose: Boolean(opts.verbose),
|
|
119
|
+
};
|
|
120
|
+
activeLogin = login;
|
|
121
|
+
attachLoginWaiter(login);
|
|
122
|
+
let qr;
|
|
123
|
+
try {
|
|
124
|
+
qr = await qrPromise;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
clearTimeout(qrTimer);
|
|
128
|
+
await resetActiveLogin();
|
|
129
|
+
return {
|
|
130
|
+
message: `Failed to get QR: ${String(err)}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const base64 = await renderQrPngBase64(qr);
|
|
134
|
+
login.qrDataUrl = `data:image/png;base64,${base64}`;
|
|
135
|
+
return {
|
|
136
|
+
qrDataUrl: login.qrDataUrl,
|
|
137
|
+
message: "Scan this QR in WhatsApp → Linked Devices.",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export async function waitForWebLogin(opts = {}) {
|
|
141
|
+
const runtime = opts.runtime ?? defaultRuntime;
|
|
142
|
+
if (!activeLogin) {
|
|
143
|
+
return {
|
|
144
|
+
connected: false,
|
|
145
|
+
message: "No active WhatsApp login in progress.",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const login = activeLogin;
|
|
149
|
+
if (!isLoginFresh(login)) {
|
|
150
|
+
await resetActiveLogin();
|
|
151
|
+
return {
|
|
152
|
+
connected: false,
|
|
153
|
+
message: "The login QR expired. Ask me to generate a new one.",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const timeoutMs = Math.max(opts.timeoutMs ?? 120_000, 1000);
|
|
157
|
+
const deadline = Date.now() + timeoutMs;
|
|
158
|
+
while (true) {
|
|
159
|
+
const remaining = deadline - Date.now();
|
|
160
|
+
if (remaining <= 0) {
|
|
161
|
+
return {
|
|
162
|
+
connected: false,
|
|
163
|
+
message: "Still waiting for the QR scan. Let me know when you’ve scanned it.",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const timeout = new Promise((resolve) => setTimeout(() => resolve("timeout"), remaining));
|
|
167
|
+
const result = await Promise.race([
|
|
168
|
+
login.waitPromise.then(() => "done"),
|
|
169
|
+
timeout,
|
|
170
|
+
]);
|
|
171
|
+
if (result === "timeout") {
|
|
172
|
+
return {
|
|
173
|
+
connected: false,
|
|
174
|
+
message: "Still waiting for the QR scan. Let me know when you’ve scanned it.",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (login.error) {
|
|
178
|
+
if (login.errorStatus === DisconnectReason.loggedOut) {
|
|
179
|
+
await logoutWeb(runtime);
|
|
180
|
+
const message = "WhatsApp reported the session is logged out. Cleared cached web session; please scan a new QR.";
|
|
181
|
+
await resetActiveLogin(message);
|
|
182
|
+
runtime.log(danger(message));
|
|
183
|
+
return { connected: false, message };
|
|
184
|
+
}
|
|
185
|
+
if (login.errorStatus === 515) {
|
|
186
|
+
const restarted = await restartLoginSocket(login, runtime);
|
|
187
|
+
if (restarted && isLoginFresh(login)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const message = `WhatsApp login failed: ${login.error}`;
|
|
192
|
+
await resetActiveLogin(message);
|
|
193
|
+
runtime.log(danger(message));
|
|
194
|
+
return { connected: false, message };
|
|
195
|
+
}
|
|
196
|
+
if (login.connected) {
|
|
197
|
+
const message = "✅ Linked! WhatsApp is ready.";
|
|
198
|
+
runtime.log(success(message));
|
|
199
|
+
await resetActiveLogin();
|
|
200
|
+
return { connected: true, message };
|
|
201
|
+
}
|
|
202
|
+
return { connected: false, message: "Login ended without a connection." };
|
|
203
|
+
}
|
|
204
|
+
}
|