cowork-os 0.3.21
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/LICENSE +21 -0
- package/README.md +1638 -0
- package/bin/cowork.js +42 -0
- package/build/entitlements.mac.plist +16 -0
- package/build/icon.icns +0 -0
- package/build/icon.png +0 -0
- package/dist/electron/electron/activity/ActivityRepository.js +190 -0
- package/dist/electron/electron/agent/browser/browser-service.js +639 -0
- package/dist/electron/electron/agent/context-manager.js +225 -0
- package/dist/electron/electron/agent/custom-skill-loader.js +566 -0
- package/dist/electron/electron/agent/daemon.js +975 -0
- package/dist/electron/electron/agent/executor.js +3561 -0
- package/dist/electron/electron/agent/llm/anthropic-provider.js +155 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +202 -0
- package/dist/electron/electron/agent/llm/gemini-provider.js +375 -0
- package/dist/electron/electron/agent/llm/index.js +34 -0
- package/dist/electron/electron/agent/llm/ollama-provider.js +263 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +101 -0
- package/dist/electron/electron/agent/llm/openai-provider.js +657 -0
- package/dist/electron/electron/agent/llm/openrouter-provider.js +232 -0
- package/dist/electron/electron/agent/llm/pricing.js +160 -0
- package/dist/electron/electron/agent/llm/provider-factory.js +880 -0
- package/dist/electron/electron/agent/llm/types.js +178 -0
- package/dist/electron/electron/agent/queue-manager.js +378 -0
- package/dist/electron/electron/agent/sandbox/docker-sandbox.js +402 -0
- package/dist/electron/electron/agent/sandbox/macos-sandbox.js +407 -0
- package/dist/electron/electron/agent/sandbox/runner.js +410 -0
- package/dist/electron/electron/agent/sandbox/sandbox-factory.js +228 -0
- package/dist/electron/electron/agent/sandbox/security-utils.js +258 -0
- package/dist/electron/electron/agent/search/brave-provider.js +119 -0
- package/dist/electron/electron/agent/search/google-provider.js +100 -0
- package/dist/electron/electron/agent/search/index.js +28 -0
- package/dist/electron/electron/agent/search/provider-factory.js +395 -0
- package/dist/electron/electron/agent/search/serpapi-provider.js +112 -0
- package/dist/electron/electron/agent/search/tavily-provider.js +90 -0
- package/dist/electron/electron/agent/search/types.js +40 -0
- package/dist/electron/electron/agent/security/index.js +12 -0
- package/dist/electron/electron/agent/security/input-sanitizer.js +303 -0
- package/dist/electron/electron/agent/security/output-filter.js +217 -0
- package/dist/electron/electron/agent/skill-eligibility.js +281 -0
- package/dist/electron/electron/agent/skill-registry.js +396 -0
- package/dist/electron/electron/agent/skills/document.js +878 -0
- package/dist/electron/electron/agent/skills/image-generator.js +225 -0
- package/dist/electron/electron/agent/skills/organizer.js +141 -0
- package/dist/electron/electron/agent/skills/presentation.js +367 -0
- package/dist/electron/electron/agent/skills/spreadsheet.js +165 -0
- package/dist/electron/electron/agent/tools/browser-tools.js +523 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +384 -0
- package/dist/electron/electron/agent/tools/canvas-tools.js +530 -0
- package/dist/electron/electron/agent/tools/cron-tools.js +577 -0
- package/dist/electron/electron/agent/tools/edit-tools.js +194 -0
- package/dist/electron/electron/agent/tools/file-tools.js +719 -0
- package/dist/electron/electron/agent/tools/glob-tools.js +283 -0
- package/dist/electron/electron/agent/tools/grep-tools.js +387 -0
- package/dist/electron/electron/agent/tools/image-tools.js +111 -0
- package/dist/electron/electron/agent/tools/mention-tools.js +282 -0
- package/dist/electron/electron/agent/tools/node-tools.js +476 -0
- package/dist/electron/electron/agent/tools/registry.js +2719 -0
- package/dist/electron/electron/agent/tools/search-tools.js +91 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +574 -0
- package/dist/electron/electron/agent/tools/skill-tools.js +274 -0
- package/dist/electron/electron/agent/tools/system-tools.js +578 -0
- package/dist/electron/electron/agent/tools/web-fetch-tools.js +444 -0
- package/dist/electron/electron/agent/tools/x-tools.js +264 -0
- package/dist/electron/electron/agents/AgentRoleRepository.js +420 -0
- package/dist/electron/electron/agents/HeartbeatService.js +356 -0
- package/dist/electron/electron/agents/MentionRepository.js +197 -0
- package/dist/electron/electron/agents/TaskSubscriptionRepository.js +168 -0
- package/dist/electron/electron/agents/WorkingStateRepository.js +229 -0
- package/dist/electron/electron/canvas/canvas-manager.js +714 -0
- package/dist/electron/electron/canvas/canvas-preload.js +53 -0
- package/dist/electron/electron/canvas/canvas-protocol.js +195 -0
- package/dist/electron/electron/canvas/canvas-store.js +174 -0
- package/dist/electron/electron/canvas/index.js +13 -0
- package/dist/electron/electron/control-plane/client.js +364 -0
- package/dist/electron/electron/control-plane/handlers.js +572 -0
- package/dist/electron/electron/control-plane/index.js +41 -0
- package/dist/electron/electron/control-plane/node-manager.js +264 -0
- package/dist/electron/electron/control-plane/protocol.js +194 -0
- package/dist/electron/electron/control-plane/remote-client.js +437 -0
- package/dist/electron/electron/control-plane/server.js +640 -0
- package/dist/electron/electron/control-plane/settings.js +369 -0
- package/dist/electron/electron/control-plane/ssh-tunnel.js +549 -0
- package/dist/electron/electron/cron/index.js +30 -0
- package/dist/electron/electron/cron/schedule.js +190 -0
- package/dist/electron/electron/cron/service.js +614 -0
- package/dist/electron/electron/cron/store.js +155 -0
- package/dist/electron/electron/cron/types.js +82 -0
- package/dist/electron/electron/cron/webhook.js +258 -0
- package/dist/electron/electron/database/SecureSettingsRepository.js +444 -0
- package/dist/electron/electron/database/TaskLabelRepository.js +120 -0
- package/dist/electron/electron/database/repositories.js +1781 -0
- package/dist/electron/electron/database/schema.js +978 -0
- package/dist/electron/electron/extensions/index.js +33 -0
- package/dist/electron/electron/extensions/loader.js +313 -0
- package/dist/electron/electron/extensions/registry.js +485 -0
- package/dist/electron/electron/extensions/types.js +11 -0
- package/dist/electron/electron/gateway/channel-registry.js +1102 -0
- package/dist/electron/electron/gateway/channels/bluebubbles-client.js +479 -0
- package/dist/electron/electron/gateway/channels/bluebubbles.js +432 -0
- package/dist/electron/electron/gateway/channels/discord.js +975 -0
- package/dist/electron/electron/gateway/channels/email-client.js +593 -0
- package/dist/electron/electron/gateway/channels/email.js +443 -0
- package/dist/electron/electron/gateway/channels/google-chat.js +631 -0
- package/dist/electron/electron/gateway/channels/imessage-client.js +363 -0
- package/dist/electron/electron/gateway/channels/imessage.js +465 -0
- package/dist/electron/electron/gateway/channels/index.js +36 -0
- package/dist/electron/electron/gateway/channels/line-client.js +470 -0
- package/dist/electron/electron/gateway/channels/line.js +479 -0
- package/dist/electron/electron/gateway/channels/matrix-client.js +432 -0
- package/dist/electron/electron/gateway/channels/matrix.js +592 -0
- package/dist/electron/electron/gateway/channels/mattermost-client.js +394 -0
- package/dist/electron/electron/gateway/channels/mattermost.js +496 -0
- package/dist/electron/electron/gateway/channels/signal-client.js +500 -0
- package/dist/electron/electron/gateway/channels/signal.js +582 -0
- package/dist/electron/electron/gateway/channels/slack.js +415 -0
- package/dist/electron/electron/gateway/channels/teams.js +596 -0
- package/dist/electron/electron/gateway/channels/telegram.js +1390 -0
- package/dist/electron/electron/gateway/channels/twitch-client.js +502 -0
- package/dist/electron/electron/gateway/channels/twitch.js +396 -0
- package/dist/electron/electron/gateway/channels/types.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +953 -0
- package/dist/electron/electron/gateway/context-policy.js +268 -0
- package/dist/electron/electron/gateway/index.js +1063 -0
- package/dist/electron/electron/gateway/infrastructure.js +496 -0
- package/dist/electron/electron/gateway/router.js +2700 -0
- package/dist/electron/electron/gateway/security.js +375 -0
- package/dist/electron/electron/gateway/session.js +115 -0
- package/dist/electron/electron/gateway/tunnel.js +503 -0
- package/dist/electron/electron/guardrails/guardrail-manager.js +348 -0
- package/dist/electron/electron/hooks/gmail-watcher.js +300 -0
- package/dist/electron/electron/hooks/index.js +46 -0
- package/dist/electron/electron/hooks/mappings.js +381 -0
- package/dist/electron/electron/hooks/server.js +480 -0
- package/dist/electron/electron/hooks/settings.js +447 -0
- package/dist/electron/electron/hooks/types.js +41 -0
- package/dist/electron/electron/ipc/canvas-handlers.js +158 -0
- package/dist/electron/electron/ipc/handlers.js +3138 -0
- package/dist/electron/electron/ipc/mission-control-handlers.js +141 -0
- package/dist/electron/electron/main.js +448 -0
- package/dist/electron/electron/mcp/client/MCPClientManager.js +330 -0
- package/dist/electron/electron/mcp/client/MCPServerConnection.js +437 -0
- package/dist/electron/electron/mcp/client/transports/SSETransport.js +304 -0
- package/dist/electron/electron/mcp/client/transports/StdioTransport.js +307 -0
- package/dist/electron/electron/mcp/client/transports/WebSocketTransport.js +329 -0
- package/dist/electron/electron/mcp/host/MCPHostServer.js +354 -0
- package/dist/electron/electron/mcp/host/ToolAdapter.js +100 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +497 -0
- package/dist/electron/electron/mcp/settings.js +446 -0
- package/dist/electron/electron/mcp/types.js +59 -0
- package/dist/electron/electron/memory/MemoryService.js +435 -0
- package/dist/electron/electron/notifications/index.js +17 -0
- package/dist/electron/electron/notifications/service.js +118 -0
- package/dist/electron/electron/notifications/store.js +144 -0
- package/dist/electron/electron/preload.js +842 -0
- package/dist/electron/electron/reports/StandupReportService.js +272 -0
- package/dist/electron/electron/security/concurrency.js +293 -0
- package/dist/electron/electron/security/index.js +15 -0
- package/dist/electron/electron/security/policy-manager.js +435 -0
- package/dist/electron/electron/settings/appearance-manager.js +193 -0
- package/dist/electron/electron/settings/personality-manager.js +724 -0
- package/dist/electron/electron/settings/x-manager.js +58 -0
- package/dist/electron/electron/tailscale/exposure.js +188 -0
- package/dist/electron/electron/tailscale/index.js +28 -0
- package/dist/electron/electron/tailscale/settings.js +205 -0
- package/dist/electron/electron/tailscale/tailscale.js +355 -0
- package/dist/electron/electron/tray/QuickInputWindow.js +568 -0
- package/dist/electron/electron/tray/TrayManager.js +895 -0
- package/dist/electron/electron/tray/index.js +9 -0
- package/dist/electron/electron/updater/index.js +6 -0
- package/dist/electron/electron/updater/update-manager.js +418 -0
- package/dist/electron/electron/utils/env-migration.js +209 -0
- package/dist/electron/electron/utils/process.js +102 -0
- package/dist/electron/electron/utils/rate-limiter.js +104 -0
- package/dist/electron/electron/utils/validation.js +419 -0
- package/dist/electron/electron/utils/x-cli.js +177 -0
- package/dist/electron/electron/voice/VoiceService.js +507 -0
- package/dist/electron/electron/voice/index.js +14 -0
- package/dist/electron/electron/voice/voice-settings-manager.js +359 -0
- package/dist/electron/shared/channelMessages.js +170 -0
- package/dist/electron/shared/types.js +1185 -0
- package/package.json +159 -0
- package/resources/skills/1password.json +10 -0
- package/resources/skills/add-documentation.json +31 -0
- package/resources/skills/analyze-csv.json +17 -0
- package/resources/skills/apple-notes.json +10 -0
- package/resources/skills/apple-reminders.json +10 -0
- package/resources/skills/auto-commenter.json +10 -0
- package/resources/skills/bear-notes.json +10 -0
- package/resources/skills/bird.json +35 -0
- package/resources/skills/blogwatcher.json +10 -0
- package/resources/skills/blucli.json +10 -0
- package/resources/skills/bluebubbles.json +10 -0
- package/resources/skills/camsnap.json +10 -0
- package/resources/skills/clean-imports.json +18 -0
- package/resources/skills/code-review.json +18 -0
- package/resources/skills/coding-agent.json +10 -0
- package/resources/skills/compare-files.json +23 -0
- package/resources/skills/convert-code.json +34 -0
- package/resources/skills/create-changelog.json +24 -0
- package/resources/skills/debug-error.json +17 -0
- package/resources/skills/dependency-check.json +10 -0
- package/resources/skills/discord.json +10 -0
- package/resources/skills/eightctl.json +10 -0
- package/resources/skills/explain-code.json +29 -0
- package/resources/skills/extract-todos.json +18 -0
- package/resources/skills/food-order.json +10 -0
- package/resources/skills/gemini.json +10 -0
- package/resources/skills/generate-readme.json +10 -0
- package/resources/skills/gifgrep.json +10 -0
- package/resources/skills/git-commit.json +10 -0
- package/resources/skills/github.json +10 -0
- package/resources/skills/gog.json +10 -0
- package/resources/skills/goplaces.json +10 -0
- package/resources/skills/himalaya.json +10 -0
- package/resources/skills/imsg.json +10 -0
- package/resources/skills/karpathy-guidelines.json +12 -0
- package/resources/skills/last30days.json +26 -0
- package/resources/skills/local-places.json +10 -0
- package/resources/skills/mcporter.json +10 -0
- package/resources/skills/model-usage.json +10 -0
- package/resources/skills/nano-banana-pro.json +10 -0
- package/resources/skills/nano-pdf.json +10 -0
- package/resources/skills/notion.json +10 -0
- package/resources/skills/obsidian.json +10 -0
- package/resources/skills/openai-image-gen.json +10 -0
- package/resources/skills/openai-whisper-api.json +10 -0
- package/resources/skills/openai-whisper.json +10 -0
- package/resources/skills/openhue.json +10 -0
- package/resources/skills/oracle.json +10 -0
- package/resources/skills/ordercli.json +10 -0
- package/resources/skills/peekaboo.json +10 -0
- package/resources/skills/project-structure.json +10 -0
- package/resources/skills/proofread.json +17 -0
- package/resources/skills/refactor-code.json +31 -0
- package/resources/skills/rename-symbol.json +23 -0
- package/resources/skills/sag.json +10 -0
- package/resources/skills/security-audit.json +18 -0
- package/resources/skills/session-logs.json +10 -0
- package/resources/skills/sherpa-onnx-tts.json +10 -0
- package/resources/skills/skill-creator.json +15 -0
- package/resources/skills/skill-hub.json +29 -0
- package/resources/skills/slack.json +10 -0
- package/resources/skills/songsee.json +10 -0
- package/resources/skills/sonoscli.json +10 -0
- package/resources/skills/spotify-player.json +10 -0
- package/resources/skills/startup-cfo.json +55 -0
- package/resources/skills/summarize-folder.json +18 -0
- package/resources/skills/summarize.json +10 -0
- package/resources/skills/things-mac.json +10 -0
- package/resources/skills/tmux.json +10 -0
- package/resources/skills/translate.json +36 -0
- package/resources/skills/trello.json +10 -0
- package/resources/skills/video-frames.json +10 -0
- package/resources/skills/voice-call.json +10 -0
- package/resources/skills/wacli.json +10 -0
- package/resources/skills/weather.json +10 -0
- package/resources/skills/write-tests.json +31 -0
- package/src/electron/activity/ActivityRepository.ts +238 -0
- package/src/electron/agent/browser/browser-service.ts +721 -0
- package/src/electron/agent/context-manager.ts +257 -0
- package/src/electron/agent/custom-skill-loader.ts +634 -0
- package/src/electron/agent/daemon.ts +1097 -0
- package/src/electron/agent/executor.ts +4017 -0
- package/src/electron/agent/llm/anthropic-provider.ts +175 -0
- package/src/electron/agent/llm/bedrock-provider.ts +236 -0
- package/src/electron/agent/llm/gemini-provider.ts +422 -0
- package/src/electron/agent/llm/index.ts +9 -0
- package/src/electron/agent/llm/ollama-provider.ts +347 -0
- package/src/electron/agent/llm/openai-oauth.ts +127 -0
- package/src/electron/agent/llm/openai-provider.ts +686 -0
- package/src/electron/agent/llm/openrouter-provider.ts +273 -0
- package/src/electron/agent/llm/pricing.ts +180 -0
- package/src/electron/agent/llm/provider-factory.ts +971 -0
- package/src/electron/agent/llm/types.ts +291 -0
- package/src/electron/agent/queue-manager.ts +408 -0
- package/src/electron/agent/sandbox/docker-sandbox.ts +453 -0
- package/src/electron/agent/sandbox/macos-sandbox.ts +426 -0
- package/src/electron/agent/sandbox/runner.ts +453 -0
- package/src/electron/agent/sandbox/sandbox-factory.ts +337 -0
- package/src/electron/agent/sandbox/security-utils.ts +251 -0
- package/src/electron/agent/search/brave-provider.ts +141 -0
- package/src/electron/agent/search/google-provider.ts +131 -0
- package/src/electron/agent/search/index.ts +6 -0
- package/src/electron/agent/search/provider-factory.ts +450 -0
- package/src/electron/agent/search/serpapi-provider.ts +138 -0
- package/src/electron/agent/search/tavily-provider.ts +108 -0
- package/src/electron/agent/search/types.ts +118 -0
- package/src/electron/agent/security/index.ts +20 -0
- package/src/electron/agent/security/input-sanitizer.ts +380 -0
- package/src/electron/agent/security/output-filter.ts +259 -0
- package/src/electron/agent/skill-eligibility.ts +334 -0
- package/src/electron/agent/skill-registry.ts +457 -0
- package/src/electron/agent/skills/document.ts +1070 -0
- package/src/electron/agent/skills/image-generator.ts +272 -0
- package/src/electron/agent/skills/organizer.ts +131 -0
- package/src/electron/agent/skills/presentation.ts +418 -0
- package/src/electron/agent/skills/spreadsheet.ts +166 -0
- package/src/electron/agent/tools/browser-tools.ts +546 -0
- package/src/electron/agent/tools/builtin-settings.ts +422 -0
- package/src/electron/agent/tools/canvas-tools.ts +572 -0
- package/src/electron/agent/tools/cron-tools.ts +723 -0
- package/src/electron/agent/tools/edit-tools.ts +196 -0
- package/src/electron/agent/tools/file-tools.ts +811 -0
- package/src/electron/agent/tools/glob-tools.ts +303 -0
- package/src/electron/agent/tools/grep-tools.ts +432 -0
- package/src/electron/agent/tools/image-tools.ts +126 -0
- package/src/electron/agent/tools/mention-tools.ts +371 -0
- package/src/electron/agent/tools/node-tools.ts +550 -0
- package/src/electron/agent/tools/registry.ts +3052 -0
- package/src/electron/agent/tools/search-tools.ts +111 -0
- package/src/electron/agent/tools/shell-tools.ts +651 -0
- package/src/electron/agent/tools/skill-tools.ts +340 -0
- package/src/electron/agent/tools/system-tools.ts +665 -0
- package/src/electron/agent/tools/web-fetch-tools.ts +528 -0
- package/src/electron/agent/tools/x-tools.ts +267 -0
- package/src/electron/agents/AgentRoleRepository.ts +557 -0
- package/src/electron/agents/HeartbeatService.ts +469 -0
- package/src/electron/agents/MentionRepository.ts +242 -0
- package/src/electron/agents/TaskSubscriptionRepository.ts +231 -0
- package/src/electron/agents/WorkingStateRepository.ts +278 -0
- package/src/electron/canvas/canvas-manager.ts +818 -0
- package/src/electron/canvas/canvas-preload.ts +102 -0
- package/src/electron/canvas/canvas-protocol.ts +174 -0
- package/src/electron/canvas/canvas-store.ts +200 -0
- package/src/electron/canvas/index.ts +8 -0
- package/src/electron/control-plane/client.ts +527 -0
- package/src/electron/control-plane/handlers.ts +723 -0
- package/src/electron/control-plane/index.ts +51 -0
- package/src/electron/control-plane/node-manager.ts +322 -0
- package/src/electron/control-plane/protocol.ts +269 -0
- package/src/electron/control-plane/remote-client.ts +517 -0
- package/src/electron/control-plane/server.ts +853 -0
- package/src/electron/control-plane/settings.ts +401 -0
- package/src/electron/control-plane/ssh-tunnel.ts +624 -0
- package/src/electron/cron/index.ts +9 -0
- package/src/electron/cron/schedule.ts +217 -0
- package/src/electron/cron/service.ts +743 -0
- package/src/electron/cron/store.ts +165 -0
- package/src/electron/cron/types.ts +291 -0
- package/src/electron/cron/webhook.ts +303 -0
- package/src/electron/database/SecureSettingsRepository.ts +514 -0
- package/src/electron/database/TaskLabelRepository.ts +148 -0
- package/src/electron/database/repositories.ts +2397 -0
- package/src/electron/database/schema.ts +1017 -0
- package/src/electron/extensions/index.ts +18 -0
- package/src/electron/extensions/loader.ts +336 -0
- package/src/electron/extensions/registry.ts +546 -0
- package/src/electron/extensions/types.ts +372 -0
- package/src/electron/gateway/channel-registry.ts +1267 -0
- package/src/electron/gateway/channels/bluebubbles-client.ts +641 -0
- package/src/electron/gateway/channels/bluebubbles.ts +509 -0
- package/src/electron/gateway/channels/discord.ts +1150 -0
- package/src/electron/gateway/channels/email-client.ts +708 -0
- package/src/electron/gateway/channels/email.ts +516 -0
- package/src/electron/gateway/channels/google-chat.ts +760 -0
- package/src/electron/gateway/channels/imessage-client.ts +473 -0
- package/src/electron/gateway/channels/imessage.ts +520 -0
- package/src/electron/gateway/channels/index.ts +21 -0
- package/src/electron/gateway/channels/line-client.ts +598 -0
- package/src/electron/gateway/channels/line.ts +559 -0
- package/src/electron/gateway/channels/matrix-client.ts +632 -0
- package/src/electron/gateway/channels/matrix.ts +655 -0
- package/src/electron/gateway/channels/mattermost-client.ts +526 -0
- package/src/electron/gateway/channels/mattermost.ts +550 -0
- package/src/electron/gateway/channels/signal-client.ts +722 -0
- package/src/electron/gateway/channels/signal.ts +666 -0
- package/src/electron/gateway/channels/slack.ts +458 -0
- package/src/electron/gateway/channels/teams.ts +681 -0
- package/src/electron/gateway/channels/telegram.ts +1727 -0
- package/src/electron/gateway/channels/twitch-client.ts +665 -0
- package/src/electron/gateway/channels/twitch.ts +468 -0
- package/src/electron/gateway/channels/types.ts +1002 -0
- package/src/electron/gateway/channels/whatsapp.ts +1101 -0
- package/src/electron/gateway/context-policy.ts +382 -0
- package/src/electron/gateway/index.ts +1274 -0
- package/src/electron/gateway/infrastructure.ts +645 -0
- package/src/electron/gateway/router.ts +3206 -0
- package/src/electron/gateway/security.ts +422 -0
- package/src/electron/gateway/session.ts +144 -0
- package/src/electron/gateway/tunnel.ts +626 -0
- package/src/electron/guardrails/guardrail-manager.ts +380 -0
- package/src/electron/hooks/gmail-watcher.ts +355 -0
- package/src/electron/hooks/index.ts +30 -0
- package/src/electron/hooks/mappings.ts +404 -0
- package/src/electron/hooks/server.ts +574 -0
- package/src/electron/hooks/settings.ts +466 -0
- package/src/electron/hooks/types.ts +245 -0
- package/src/electron/ipc/canvas-handlers.ts +223 -0
- package/src/electron/ipc/handlers.ts +3661 -0
- package/src/electron/ipc/mission-control-handlers.ts +182 -0
- package/src/electron/main.ts +496 -0
- package/src/electron/mcp/client/MCPClientManager.ts +406 -0
- package/src/electron/mcp/client/MCPServerConnection.ts +514 -0
- package/src/electron/mcp/client/transports/SSETransport.ts +360 -0
- package/src/electron/mcp/client/transports/StdioTransport.ts +355 -0
- package/src/electron/mcp/client/transports/WebSocketTransport.ts +384 -0
- package/src/electron/mcp/host/MCPHostServer.ts +388 -0
- package/src/electron/mcp/host/ToolAdapter.ts +140 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +565 -0
- package/src/electron/mcp/settings.ts +468 -0
- package/src/electron/mcp/types.ts +371 -0
- package/src/electron/memory/MemoryService.ts +523 -0
- package/src/electron/notifications/index.ts +16 -0
- package/src/electron/notifications/service.ts +161 -0
- package/src/electron/notifications/store.ts +163 -0
- package/src/electron/preload.ts +2845 -0
- package/src/electron/reports/StandupReportService.ts +356 -0
- package/src/electron/security/concurrency.ts +333 -0
- package/src/electron/security/index.ts +17 -0
- package/src/electron/security/policy-manager.ts +539 -0
- package/src/electron/settings/appearance-manager.ts +182 -0
- package/src/electron/settings/personality-manager.ts +800 -0
- package/src/electron/settings/x-manager.ts +62 -0
- package/src/electron/tailscale/exposure.ts +262 -0
- package/src/electron/tailscale/index.ts +34 -0
- package/src/electron/tailscale/settings.ts +218 -0
- package/src/electron/tailscale/tailscale.ts +379 -0
- package/src/electron/tray/QuickInputWindow.ts +609 -0
- package/src/electron/tray/TrayManager.ts +1005 -0
- package/src/electron/tray/index.ts +6 -0
- package/src/electron/updater/index.ts +1 -0
- package/src/electron/updater/update-manager.ts +447 -0
- package/src/electron/utils/env-migration.ts +203 -0
- package/src/electron/utils/process.ts +124 -0
- package/src/electron/utils/rate-limiter.ts +130 -0
- package/src/electron/utils/validation.ts +493 -0
- package/src/electron/utils/x-cli.ts +198 -0
- package/src/electron/voice/VoiceService.ts +583 -0
- package/src/electron/voice/index.ts +9 -0
- package/src/electron/voice/voice-settings-manager.ts +403 -0
- package/src/renderer/App.tsx +775 -0
- package/src/renderer/components/ActivityFeed.tsx +407 -0
- package/src/renderer/components/ActivityFeedItem.tsx +285 -0
- package/src/renderer/components/AgentRoleCard.tsx +343 -0
- package/src/renderer/components/AgentRoleEditor.tsx +805 -0
- package/src/renderer/components/AgentSquadSettings.tsx +295 -0
- package/src/renderer/components/AgentWorkingStatePanel.tsx +411 -0
- package/src/renderer/components/AppearanceSettings.tsx +122 -0
- package/src/renderer/components/ApprovalDialog.tsx +100 -0
- package/src/renderer/components/BlueBubblesSettings.tsx +505 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +307 -0
- package/src/renderer/components/CanvasPreview.tsx +1189 -0
- package/src/renderer/components/CommandOutput.tsx +202 -0
- package/src/renderer/components/ContextPolicySettings.tsx +523 -0
- package/src/renderer/components/ControlPlaneSettings.tsx +1134 -0
- package/src/renderer/components/DisclaimerModal.tsx +124 -0
- package/src/renderer/components/DiscordSettings.tsx +436 -0
- package/src/renderer/components/EmailSettings.tsx +606 -0
- package/src/renderer/components/ExtensionsSettings.tsx +542 -0
- package/src/renderer/components/FileViewer.tsx +224 -0
- package/src/renderer/components/GoogleChatSettings.tsx +535 -0
- package/src/renderer/components/GuardrailSettings.tsx +487 -0
- package/src/renderer/components/HooksSettings.tsx +581 -0
- package/src/renderer/components/ImessageSettings.tsx +484 -0
- package/src/renderer/components/LineSettings.tsx +483 -0
- package/src/renderer/components/MCPRegistryBrowser.tsx +386 -0
- package/src/renderer/components/MCPSettings.tsx +943 -0
- package/src/renderer/components/MainContent.tsx +2433 -0
- package/src/renderer/components/MatrixSettings.tsx +510 -0
- package/src/renderer/components/MattermostSettings.tsx +473 -0
- package/src/renderer/components/MemorySettings.tsx +247 -0
- package/src/renderer/components/MentionBadge.tsx +87 -0
- package/src/renderer/components/MentionInput.tsx +409 -0
- package/src/renderer/components/MentionList.tsx +476 -0
- package/src/renderer/components/MissionControlPanel.tsx +1995 -0
- package/src/renderer/components/NodesSettings.tsx +316 -0
- package/src/renderer/components/NotificationPanel.tsx +481 -0
- package/src/renderer/components/Onboarding/AwakeningOrb.tsx +44 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +443 -0
- package/src/renderer/components/Onboarding/TypewriterText.tsx +102 -0
- package/src/renderer/components/Onboarding/index.ts +3 -0
- package/src/renderer/components/OnboardingModal.tsx +698 -0
- package/src/renderer/components/PairingCodeDisplay.tsx +324 -0
- package/src/renderer/components/PersonalitySettings.tsx +597 -0
- package/src/renderer/components/QueueSettings.tsx +119 -0
- package/src/renderer/components/QuickTaskFAB.tsx +71 -0
- package/src/renderer/components/RightPanel.tsx +413 -0
- package/src/renderer/components/ScheduledTasksSettings.tsx +1328 -0
- package/src/renderer/components/SearchSettings.tsx +328 -0
- package/src/renderer/components/Settings.tsx +1504 -0
- package/src/renderer/components/Sidebar.tsx +344 -0
- package/src/renderer/components/SignalSettings.tsx +673 -0
- package/src/renderer/components/SkillHubBrowser.tsx +458 -0
- package/src/renderer/components/SkillParameterModal.tsx +185 -0
- package/src/renderer/components/SkillsSettings.tsx +451 -0
- package/src/renderer/components/SlackSettings.tsx +442 -0
- package/src/renderer/components/StandupReportViewer.tsx +614 -0
- package/src/renderer/components/TaskBoard.tsx +498 -0
- package/src/renderer/components/TaskBoardCard.tsx +357 -0
- package/src/renderer/components/TaskBoardColumn.tsx +211 -0
- package/src/renderer/components/TaskLabelManager.tsx +472 -0
- package/src/renderer/components/TaskQueuePanel.tsx +144 -0
- package/src/renderer/components/TaskQuickActions.tsx +492 -0
- package/src/renderer/components/TaskTimeline.tsx +216 -0
- package/src/renderer/components/TaskView.tsx +162 -0
- package/src/renderer/components/TeamsSettings.tsx +518 -0
- package/src/renderer/components/TelegramSettings.tsx +421 -0
- package/src/renderer/components/Toast.tsx +76 -0
- package/src/renderer/components/TraySettings.tsx +189 -0
- package/src/renderer/components/TwitchSettings.tsx +511 -0
- package/src/renderer/components/UpdateSettings.tsx +295 -0
- package/src/renderer/components/VoiceIndicator.tsx +270 -0
- package/src/renderer/components/VoiceSettings.tsx +867 -0
- package/src/renderer/components/WhatsAppSettings.tsx +721 -0
- package/src/renderer/components/WorkingStateEditor.tsx +309 -0
- package/src/renderer/components/WorkingStateHistory.tsx +481 -0
- package/src/renderer/components/WorkspaceSelector.tsx +150 -0
- package/src/renderer/components/XSettings.tsx +311 -0
- package/src/renderer/global.d.ts +9 -0
- package/src/renderer/hooks/useAgentContext.ts +153 -0
- package/src/renderer/hooks/useOnboardingFlow.ts +548 -0
- package/src/renderer/hooks/useVoiceInput.ts +268 -0
- package/src/renderer/index.html +12 -0
- package/src/renderer/main.tsx +10 -0
- package/src/renderer/public/cowork-os-logo.png +0 -0
- package/src/renderer/quick-input.html +164 -0
- package/src/renderer/styles/index.css +14504 -0
- package/src/renderer/utils/agentMessages.ts +749 -0
- package/src/renderer/utils/voice-directives.ts +169 -0
- package/src/shared/channelMessages.ts +213 -0
- package/src/shared/types.ts +3608 -0
- package/tsconfig.electron.json +26 -0
- package/tsconfig.json +26 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +23 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WhatsApp Channel Adapter
|
|
4
|
+
*
|
|
5
|
+
* Implements the ChannelAdapter interface using Baileys for WhatsApp Web API.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - QR code authentication for WhatsApp Web
|
|
9
|
+
* - Multi-file auth state persistence
|
|
10
|
+
* - Message deduplication
|
|
11
|
+
* - Group and DM message handling
|
|
12
|
+
* - Media message support (images, documents, audio, video)
|
|
13
|
+
* - Typing indicators (composing presence)
|
|
14
|
+
* - Message reactions
|
|
15
|
+
* - Auto-reconnection with exponential backoff
|
|
16
|
+
* - Read receipts
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.WhatsAppAdapter = void 0;
|
|
53
|
+
exports.createWhatsAppAdapter = createWhatsAppAdapter;
|
|
54
|
+
const baileys_1 = require("@whiskeysockets/baileys");
|
|
55
|
+
const fs = __importStar(require("fs"));
|
|
56
|
+
const path = __importStar(require("path"));
|
|
57
|
+
const electron_1 = require("electron");
|
|
58
|
+
class WhatsAppAdapter {
|
|
59
|
+
constructor(config) {
|
|
60
|
+
this.type = 'whatsapp';
|
|
61
|
+
this.sock = null;
|
|
62
|
+
this._status = 'disconnected';
|
|
63
|
+
this.messageHandlers = [];
|
|
64
|
+
this.errorHandlers = [];
|
|
65
|
+
this.statusHandlers = [];
|
|
66
|
+
this.qrCodeHandlers = [];
|
|
67
|
+
// Message deduplication
|
|
68
|
+
this.processedMessages = new Map();
|
|
69
|
+
this.DEDUP_CACHE_TTL = 60000; // 1 minute
|
|
70
|
+
this.DEDUP_CACHE_MAX_SIZE = 1000;
|
|
71
|
+
// Connection state
|
|
72
|
+
this.connectedAtMs = 0;
|
|
73
|
+
this.isReconnecting = false;
|
|
74
|
+
this.backoffAttempt = 0;
|
|
75
|
+
this.DEFAULT_BACKOFF = {
|
|
76
|
+
initialDelay: 2000,
|
|
77
|
+
maxDelay: 30000,
|
|
78
|
+
multiplier: 1.8,
|
|
79
|
+
jitter: 0.25,
|
|
80
|
+
maxAttempts: 10,
|
|
81
|
+
};
|
|
82
|
+
// Group metadata cache
|
|
83
|
+
this.groupMetaCache = new Map();
|
|
84
|
+
this.GROUP_META_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
85
|
+
this.config = {
|
|
86
|
+
deduplicationEnabled: true,
|
|
87
|
+
sendReadReceipts: true,
|
|
88
|
+
printQrToTerminal: false,
|
|
89
|
+
selfChatMode: true, // Default to self-chat mode since most users use their own number
|
|
90
|
+
responsePrefix: '🤖', // Default prefix for bot responses
|
|
91
|
+
...config,
|
|
92
|
+
};
|
|
93
|
+
// In self-chat mode, disable read receipts by default
|
|
94
|
+
if (this.config.selfChatMode && config.sendReadReceipts === undefined) {
|
|
95
|
+
this.config.sendReadReceipts = false;
|
|
96
|
+
}
|
|
97
|
+
// Set auth directory
|
|
98
|
+
this.authDir = config.authDir || path.join(electron_1.app.getPath('userData'), 'whatsapp-auth');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if self-chat mode is enabled
|
|
102
|
+
*/
|
|
103
|
+
get isSelfChatMode() {
|
|
104
|
+
return this.config.selfChatMode === true;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get the response prefix for bot messages
|
|
108
|
+
*/
|
|
109
|
+
get responsePrefix() {
|
|
110
|
+
return this.config.responsePrefix || '🤖';
|
|
111
|
+
}
|
|
112
|
+
get status() {
|
|
113
|
+
return this._status;
|
|
114
|
+
}
|
|
115
|
+
get botUsername() {
|
|
116
|
+
return this._selfE164;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get the current QR code (if in login state)
|
|
120
|
+
*/
|
|
121
|
+
get qrCode() {
|
|
122
|
+
return this.currentQr;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if WhatsApp auth credentials exist
|
|
126
|
+
*/
|
|
127
|
+
async hasCredentials() {
|
|
128
|
+
const credsPath = path.join(this.authDir, 'creds.json');
|
|
129
|
+
return fs.existsSync(credsPath);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Connect to WhatsApp Web
|
|
133
|
+
*/
|
|
134
|
+
async connect() {
|
|
135
|
+
if (this._status === 'connected' || this._status === 'connecting') {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.setStatus('connecting');
|
|
139
|
+
this.resetBackoff();
|
|
140
|
+
try {
|
|
141
|
+
// Ensure auth directory exists
|
|
142
|
+
await this.ensureDir(this.authDir);
|
|
143
|
+
// Load auth state
|
|
144
|
+
const { state, saveCreds } = await (0, baileys_1.useMultiFileAuthState)(this.authDir);
|
|
145
|
+
// Get latest Baileys version
|
|
146
|
+
const { version } = await (0, baileys_1.fetchLatestBaileysVersion)();
|
|
147
|
+
// Create silent logger to suppress Baileys logs
|
|
148
|
+
const logger = {
|
|
149
|
+
level: 'silent',
|
|
150
|
+
trace: () => { },
|
|
151
|
+
debug: () => { },
|
|
152
|
+
info: () => { },
|
|
153
|
+
warn: () => { },
|
|
154
|
+
error: () => { },
|
|
155
|
+
fatal: () => { },
|
|
156
|
+
child: () => logger,
|
|
157
|
+
};
|
|
158
|
+
// Create WhatsApp socket
|
|
159
|
+
this.sock = (0, baileys_1.makeWASocket)({
|
|
160
|
+
auth: {
|
|
161
|
+
creds: state.creds,
|
|
162
|
+
keys: (0, baileys_1.makeCacheableSignalKeyStore)(state.keys, logger),
|
|
163
|
+
},
|
|
164
|
+
version,
|
|
165
|
+
logger: logger,
|
|
166
|
+
// Note: printQRInTerminal is deprecated - QR codes are handled via connection.update event
|
|
167
|
+
browser: ['CoWork-OS', 'Desktop', '1.0.0'],
|
|
168
|
+
syncFullHistory: false,
|
|
169
|
+
markOnlineOnConnect: false,
|
|
170
|
+
});
|
|
171
|
+
// Handle credential updates
|
|
172
|
+
this.sock.ev.on('creds.update', saveCreds);
|
|
173
|
+
// Handle connection updates
|
|
174
|
+
this.sock.ev.on('connection.update', (update) => {
|
|
175
|
+
this.handleConnectionUpdate(update);
|
|
176
|
+
});
|
|
177
|
+
// Handle incoming messages
|
|
178
|
+
this.sock.ev.on('messages.upsert', (upsert) => {
|
|
179
|
+
this.handleMessagesUpsert(upsert);
|
|
180
|
+
});
|
|
181
|
+
// Start deduplication cleanup
|
|
182
|
+
if (this.config.deduplicationEnabled) {
|
|
183
|
+
this.startDedupCleanup();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
188
|
+
this.setStatus('error', err);
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Handle connection state updates
|
|
194
|
+
*/
|
|
195
|
+
handleConnectionUpdate(update) {
|
|
196
|
+
const { connection, lastDisconnect, qr } = update;
|
|
197
|
+
// Handle QR code for authentication
|
|
198
|
+
if (qr) {
|
|
199
|
+
this.currentQr = qr;
|
|
200
|
+
console.log('WhatsApp QR code received - scan with WhatsApp mobile app');
|
|
201
|
+
// Notify QR handlers
|
|
202
|
+
for (const handler of this.qrCodeHandlers) {
|
|
203
|
+
try {
|
|
204
|
+
handler(qr);
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
console.error('Error in QR code handler:', e);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Handle connection open
|
|
212
|
+
if (connection === 'open') {
|
|
213
|
+
this.currentQr = undefined;
|
|
214
|
+
this.connectedAtMs = Date.now();
|
|
215
|
+
this._selfJid = this.sock?.user?.id;
|
|
216
|
+
this._selfE164 = this._selfJid ? this.jidToE164(this._selfJid) ?? undefined : undefined;
|
|
217
|
+
console.log(`WhatsApp connected as ${this._selfE164 || this._selfJid}`);
|
|
218
|
+
this.setStatus('connected');
|
|
219
|
+
this.resetBackoff();
|
|
220
|
+
// Send available presence
|
|
221
|
+
this.sock?.sendPresenceUpdate('available').catch(() => { });
|
|
222
|
+
}
|
|
223
|
+
// Handle connection close
|
|
224
|
+
if (connection === 'close') {
|
|
225
|
+
this.currentQr = undefined;
|
|
226
|
+
const statusCode = this.getStatusCode(lastDisconnect?.error);
|
|
227
|
+
if (statusCode === baileys_1.DisconnectReason.loggedOut) {
|
|
228
|
+
console.error('WhatsApp session logged out');
|
|
229
|
+
this.setStatus('error', new Error('WhatsApp session logged out. Please re-authenticate.'));
|
|
230
|
+
// Clear credentials on logout
|
|
231
|
+
this.clearCredentials().catch(() => { });
|
|
232
|
+
}
|
|
233
|
+
else if (statusCode === baileys_1.DisconnectReason.restartRequired) {
|
|
234
|
+
console.log('WhatsApp restart required, reconnecting...');
|
|
235
|
+
this.attemptReconnection();
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
console.log(`WhatsApp connection closed (status: ${statusCode}), attempting reconnection...`);
|
|
239
|
+
this.attemptReconnection();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Handle incoming messages
|
|
245
|
+
*/
|
|
246
|
+
async handleMessagesUpsert(upsert) {
|
|
247
|
+
if (upsert.type !== 'notify' && upsert.type !== 'append')
|
|
248
|
+
return;
|
|
249
|
+
for (const msg of upsert.messages ?? []) {
|
|
250
|
+
try {
|
|
251
|
+
await this.processInboundMessage(msg, upsert.type);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
console.error('Error processing WhatsApp message:', error);
|
|
255
|
+
this.handleError(error instanceof Error ? error : new Error(String(error)), 'messageProcessing');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Process a single inbound message
|
|
261
|
+
*/
|
|
262
|
+
async processInboundMessage(msg, upsertType) {
|
|
263
|
+
const id = msg.key?.id;
|
|
264
|
+
const remoteJid = msg.key?.remoteJid;
|
|
265
|
+
if (!remoteJid)
|
|
266
|
+
return;
|
|
267
|
+
// Skip status and broadcast messages
|
|
268
|
+
if (remoteJid.endsWith('@status') || remoteJid.endsWith('@broadcast'))
|
|
269
|
+
return;
|
|
270
|
+
// CRITICAL: In self-chat mode, ONLY process messages from self-chat
|
|
271
|
+
// This prevents the bot from responding to messages sent to other people
|
|
272
|
+
if (this.isSelfChatMode && this._selfJid) {
|
|
273
|
+
// Normalize JIDs by removing device suffix (e.g., "123:5@s.whatsapp.net" -> "123@s.whatsapp.net")
|
|
274
|
+
const normalizeJid = (jid) => jid.replace(/:[\d]+@/, '@');
|
|
275
|
+
const selfJidNormalized = normalizeJid(this._selfJid);
|
|
276
|
+
const remoteJidNormalized = normalizeJid(remoteJid);
|
|
277
|
+
if (remoteJidNormalized !== selfJidNormalized) {
|
|
278
|
+
// Message is NOT in self-chat, silently ignore it
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Deduplication
|
|
283
|
+
if (id && this.config.deduplicationEnabled) {
|
|
284
|
+
const dedupeKey = `${remoteJid}:${id}`;
|
|
285
|
+
if (this.processedMessages.has(dedupeKey))
|
|
286
|
+
return;
|
|
287
|
+
this.processedMessages.set(dedupeKey, Date.now());
|
|
288
|
+
// Cleanup if cache is too large
|
|
289
|
+
if (this.processedMessages.size > this.DEDUP_CACHE_MAX_SIZE) {
|
|
290
|
+
this.cleanupDedupCache();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const isGroup = (0, baileys_1.isJidGroup)(remoteJid) === true;
|
|
294
|
+
const participantJid = msg.key?.participant;
|
|
295
|
+
const from = isGroup ? remoteJid : this.jidToE164(remoteJid) || remoteJid;
|
|
296
|
+
const senderE164 = isGroup
|
|
297
|
+
? participantJid ? this.jidToE164(participantJid) : null
|
|
298
|
+
: from;
|
|
299
|
+
// Check access control
|
|
300
|
+
if (this.config.allowedNumbers && this.config.allowedNumbers.length > 0) {
|
|
301
|
+
const senderNumber = senderE164?.replace(/[^0-9]/g, '');
|
|
302
|
+
if (senderNumber && !this.config.allowedNumbers.includes(senderNumber)) {
|
|
303
|
+
console.log(`WhatsApp: Ignoring message from unauthorized number: ${senderNumber}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Get group metadata if applicable
|
|
308
|
+
let groupSubject;
|
|
309
|
+
if (isGroup && this.sock) {
|
|
310
|
+
const meta = await this.getGroupMeta(remoteJid);
|
|
311
|
+
groupSubject = meta.subject;
|
|
312
|
+
}
|
|
313
|
+
// Extract message text
|
|
314
|
+
const body = this.extractText(msg.message);
|
|
315
|
+
if (!body) {
|
|
316
|
+
// Check for media placeholder
|
|
317
|
+
const mediaPlaceholder = this.extractMediaPlaceholder(msg.message);
|
|
318
|
+
if (!mediaPlaceholder)
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// Send read receipt
|
|
322
|
+
if (id && this.config.sendReadReceipts && upsertType === 'notify') {
|
|
323
|
+
try {
|
|
324
|
+
await this.sock?.readMessages([{
|
|
325
|
+
remoteJid,
|
|
326
|
+
id,
|
|
327
|
+
participant: participantJid,
|
|
328
|
+
fromMe: false,
|
|
329
|
+
}]);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// Ignore read receipt errors
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Skip history/offline catch-up messages
|
|
336
|
+
if (upsertType === 'append')
|
|
337
|
+
return;
|
|
338
|
+
const messageTimestampMs = msg.messageTimestamp
|
|
339
|
+
? Number(msg.messageTimestamp) * 1000
|
|
340
|
+
: undefined;
|
|
341
|
+
// Download audio attachment if present
|
|
342
|
+
const attachments = [];
|
|
343
|
+
if (msg.message?.audioMessage) {
|
|
344
|
+
const audioAttachment = await this.downloadAudioAttachment(msg.message);
|
|
345
|
+
if (audioAttachment) {
|
|
346
|
+
attachments.push(audioAttachment);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Create incoming message
|
|
350
|
+
const incomingMessage = {
|
|
351
|
+
messageId: id || `wa-${Date.now()}`,
|
|
352
|
+
channel: 'whatsapp',
|
|
353
|
+
userId: senderE164 || participantJid || remoteJid,
|
|
354
|
+
userName: msg.pushName || senderE164 || 'Unknown',
|
|
355
|
+
chatId: remoteJid,
|
|
356
|
+
text: body || (attachments.length === 0 ? this.extractMediaPlaceholder(msg.message) : '') || '',
|
|
357
|
+
timestamp: messageTimestampMs ? new Date(messageTimestampMs) : new Date(),
|
|
358
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
359
|
+
raw: msg,
|
|
360
|
+
};
|
|
361
|
+
// Notify message handlers
|
|
362
|
+
await this.handleIncomingMessage(incomingMessage);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Disconnect from WhatsApp
|
|
366
|
+
*/
|
|
367
|
+
async disconnect() {
|
|
368
|
+
this.resetBackoff();
|
|
369
|
+
// Clear timers
|
|
370
|
+
if (this.dedupCleanupTimer) {
|
|
371
|
+
clearInterval(this.dedupCleanupTimer);
|
|
372
|
+
this.dedupCleanupTimer = undefined;
|
|
373
|
+
}
|
|
374
|
+
// Clear caches
|
|
375
|
+
this.processedMessages.clear();
|
|
376
|
+
this.groupMetaCache.clear();
|
|
377
|
+
this.currentQr = undefined;
|
|
378
|
+
if (this.sock) {
|
|
379
|
+
try {
|
|
380
|
+
this.sock.ws?.close();
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Ignore close errors
|
|
384
|
+
}
|
|
385
|
+
this.sock = null;
|
|
386
|
+
}
|
|
387
|
+
this._selfJid = undefined;
|
|
388
|
+
this._selfE164 = undefined;
|
|
389
|
+
this.setStatus('disconnected');
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Convert standard Markdown to WhatsApp-compatible formatting
|
|
393
|
+
* WhatsApp supports: *bold*, _italic_, ~strikethrough~, ```monospace```
|
|
394
|
+
*/
|
|
395
|
+
convertMarkdownToWhatsApp(text) {
|
|
396
|
+
let result = text;
|
|
397
|
+
// Convert headers (### Header) to bold text
|
|
398
|
+
result = result.replace(/^#{1,6}\s+(.+)$/gm, '*$1*');
|
|
399
|
+
// Convert **bold** to *bold* (WhatsApp style)
|
|
400
|
+
result = result.replace(/\*\*(.+?)\*\*/g, '*$1*');
|
|
401
|
+
// Convert __bold__ to *bold*
|
|
402
|
+
result = result.replace(/__(.+?)__/g, '*$1*');
|
|
403
|
+
// Convert _italic_ - already WhatsApp compatible, but handle markdown style
|
|
404
|
+
// Note: Single underscores are already WhatsApp italic
|
|
405
|
+
// Convert ~~strikethrough~~ to ~strikethrough~
|
|
406
|
+
result = result.replace(/~~(.+?)~~/g, '~$1~');
|
|
407
|
+
// Convert inline code `code` to monospace (WhatsApp uses triple backticks but single works in some clients)
|
|
408
|
+
// Keep as-is since WhatsApp renders `code` reasonably
|
|
409
|
+
// Convert code blocks ```code``` - already WhatsApp compatible
|
|
410
|
+
// Convert [link text](url) to "link text (url)" since WhatsApp auto-links URLs
|
|
411
|
+
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ($2)');
|
|
412
|
+
// Convert horizontal rules (---, ***, ___) to a line
|
|
413
|
+
result = result.replace(/^[-*_]{3,}$/gm, '───────────');
|
|
414
|
+
// Clean up excessive newlines
|
|
415
|
+
result = result.replace(/\n{3,}/g, '\n\n');
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Send a message to a WhatsApp chat
|
|
420
|
+
*/
|
|
421
|
+
async sendMessage(message) {
|
|
422
|
+
if (!this.sock || this._status !== 'connected') {
|
|
423
|
+
throw new Error('WhatsApp is not connected');
|
|
424
|
+
}
|
|
425
|
+
const jid = this.toWhatsAppJid(message.chatId);
|
|
426
|
+
let messageId = '';
|
|
427
|
+
// Convert markdown to WhatsApp formatting and apply response prefix
|
|
428
|
+
let textToSend = message.text ? this.convertMarkdownToWhatsApp(message.text) : message.text;
|
|
429
|
+
if (this.isSelfChatMode && textToSend && textToSend.trim()) {
|
|
430
|
+
const prefix = this.responsePrefix;
|
|
431
|
+
// Only add prefix if not already present
|
|
432
|
+
if (!textToSend.startsWith(prefix)) {
|
|
433
|
+
textToSend = `${prefix} ${textToSend}`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Send media attachments first
|
|
437
|
+
if (message.attachments && message.attachments.length > 0) {
|
|
438
|
+
for (const attachment of message.attachments) {
|
|
439
|
+
const result = await this.sendMediaAttachment(jid, attachment, textToSend);
|
|
440
|
+
messageId = result;
|
|
441
|
+
// Clear text after first media with caption
|
|
442
|
+
if (textToSend) {
|
|
443
|
+
textToSend = '';
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Send text message if no media or text remains
|
|
448
|
+
if (textToSend && textToSend.trim()) {
|
|
449
|
+
// Send composing presence
|
|
450
|
+
await this.sendComposingTo(jid);
|
|
451
|
+
const result = await this.sock.sendMessage(jid, { text: textToSend });
|
|
452
|
+
messageId = result?.key?.id || `wa-${Date.now()}`;
|
|
453
|
+
}
|
|
454
|
+
return messageId;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Send a media attachment
|
|
458
|
+
*/
|
|
459
|
+
async sendMediaAttachment(jid, attachment, caption) {
|
|
460
|
+
if (!this.sock)
|
|
461
|
+
throw new Error('WhatsApp is not connected');
|
|
462
|
+
let content;
|
|
463
|
+
if (attachment.type === 'image' && attachment.url) {
|
|
464
|
+
const buffer = fs.readFileSync(attachment.url);
|
|
465
|
+
content = {
|
|
466
|
+
image: buffer,
|
|
467
|
+
caption,
|
|
468
|
+
mimetype: attachment.mimeType || 'image/jpeg',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
else if (attachment.type === 'document' && attachment.url) {
|
|
472
|
+
const buffer = fs.readFileSync(attachment.url);
|
|
473
|
+
content = {
|
|
474
|
+
document: buffer,
|
|
475
|
+
fileName: attachment.fileName || path.basename(attachment.url),
|
|
476
|
+
mimetype: attachment.mimeType || 'application/octet-stream',
|
|
477
|
+
caption,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
else if (attachment.type === 'audio' && attachment.url) {
|
|
481
|
+
const buffer = fs.readFileSync(attachment.url);
|
|
482
|
+
content = {
|
|
483
|
+
audio: buffer,
|
|
484
|
+
mimetype: attachment.mimeType || 'audio/mpeg',
|
|
485
|
+
ptt: true, // Voice note
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
else if (attachment.type === 'video' && attachment.url) {
|
|
489
|
+
const buffer = fs.readFileSync(attachment.url);
|
|
490
|
+
content = {
|
|
491
|
+
video: buffer,
|
|
492
|
+
caption,
|
|
493
|
+
mimetype: attachment.mimeType || 'video/mp4',
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
throw new Error(`Unsupported attachment type: ${attachment.type}`);
|
|
498
|
+
}
|
|
499
|
+
const result = await this.sock.sendMessage(jid, content);
|
|
500
|
+
return result?.key?.id || `wa-${Date.now()}`;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Send composing (typing) indicator
|
|
504
|
+
*/
|
|
505
|
+
async sendComposingTo(chatId) {
|
|
506
|
+
if (!this.sock)
|
|
507
|
+
return;
|
|
508
|
+
const jid = this.toWhatsAppJid(chatId);
|
|
509
|
+
try {
|
|
510
|
+
await this.sock.sendPresenceUpdate('composing', jid);
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
// Ignore presence errors
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Send typing indicator (alias for sendComposingTo)
|
|
518
|
+
*/
|
|
519
|
+
async sendTyping(chatId) {
|
|
520
|
+
await this.sendComposingTo(chatId);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Edit an existing message (not supported by WhatsApp Web API)
|
|
524
|
+
*/
|
|
525
|
+
async editMessage(_chatId, _messageId, _text) {
|
|
526
|
+
throw new Error('WhatsApp does not support message editing');
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Delete a message
|
|
530
|
+
*/
|
|
531
|
+
async deleteMessage(chatId, messageId) {
|
|
532
|
+
if (!this.sock)
|
|
533
|
+
throw new Error('WhatsApp is not connected');
|
|
534
|
+
const jid = this.toWhatsAppJid(chatId);
|
|
535
|
+
await this.sock.sendMessage(jid, {
|
|
536
|
+
delete: {
|
|
537
|
+
remoteJid: jid,
|
|
538
|
+
fromMe: true,
|
|
539
|
+
id: messageId,
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Send a document/file
|
|
545
|
+
*/
|
|
546
|
+
async sendDocument(chatId, filePath, caption) {
|
|
547
|
+
if (!this.sock)
|
|
548
|
+
throw new Error('WhatsApp is not connected');
|
|
549
|
+
const jid = this.toWhatsAppJid(chatId);
|
|
550
|
+
const buffer = fs.readFileSync(filePath);
|
|
551
|
+
const fileName = path.basename(filePath);
|
|
552
|
+
const result = await this.sock.sendMessage(jid, {
|
|
553
|
+
document: buffer,
|
|
554
|
+
fileName,
|
|
555
|
+
mimetype: 'application/octet-stream',
|
|
556
|
+
caption,
|
|
557
|
+
});
|
|
558
|
+
return result?.key?.id || `wa-${Date.now()}`;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Send a photo/image
|
|
562
|
+
*/
|
|
563
|
+
async sendPhoto(chatId, filePath, caption) {
|
|
564
|
+
if (!this.sock)
|
|
565
|
+
throw new Error('WhatsApp is not connected');
|
|
566
|
+
const jid = this.toWhatsAppJid(chatId);
|
|
567
|
+
const buffer = fs.readFileSync(filePath);
|
|
568
|
+
const result = await this.sock.sendMessage(jid, {
|
|
569
|
+
image: buffer,
|
|
570
|
+
caption,
|
|
571
|
+
});
|
|
572
|
+
return result?.key?.id || `wa-${Date.now()}`;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Add a reaction to a message
|
|
576
|
+
*/
|
|
577
|
+
async addReaction(chatId, messageId, emoji) {
|
|
578
|
+
if (!this.sock)
|
|
579
|
+
throw new Error('WhatsApp is not connected');
|
|
580
|
+
const jid = this.toWhatsAppJid(chatId);
|
|
581
|
+
await this.sock.sendMessage(jid, {
|
|
582
|
+
react: {
|
|
583
|
+
text: emoji,
|
|
584
|
+
key: {
|
|
585
|
+
remoteJid: jid,
|
|
586
|
+
id: messageId,
|
|
587
|
+
fromMe: false,
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Remove a reaction from a message
|
|
594
|
+
*/
|
|
595
|
+
async removeReaction(chatId, messageId) {
|
|
596
|
+
await this.addReaction(chatId, messageId, ''); // Empty string removes reaction
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Register a message handler
|
|
600
|
+
*/
|
|
601
|
+
onMessage(handler) {
|
|
602
|
+
this.messageHandlers.push(handler);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Register a callback query handler (not supported by WhatsApp)
|
|
606
|
+
*/
|
|
607
|
+
onCallbackQuery(_handler) {
|
|
608
|
+
// WhatsApp doesn't support inline keyboards/callback queries
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Register an error handler
|
|
612
|
+
*/
|
|
613
|
+
onError(handler) {
|
|
614
|
+
this.errorHandlers.push(handler);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Register a status change handler
|
|
618
|
+
*/
|
|
619
|
+
onStatusChange(handler) {
|
|
620
|
+
this.statusHandlers.push(handler);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Register a QR code handler
|
|
624
|
+
*/
|
|
625
|
+
onQrCode(handler) {
|
|
626
|
+
this.qrCodeHandlers.push(handler);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Get channel info
|
|
630
|
+
*/
|
|
631
|
+
async getInfo() {
|
|
632
|
+
return {
|
|
633
|
+
type: 'whatsapp',
|
|
634
|
+
status: this._status,
|
|
635
|
+
botId: this._selfJid,
|
|
636
|
+
botUsername: this._selfE164,
|
|
637
|
+
botDisplayName: this._selfE164,
|
|
638
|
+
extra: {
|
|
639
|
+
qrCode: this.currentQr,
|
|
640
|
+
hasCredentials: await this.hasCredentials(),
|
|
641
|
+
},
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Logout and clear credentials
|
|
646
|
+
*/
|
|
647
|
+
async logout() {
|
|
648
|
+
await this.disconnect();
|
|
649
|
+
await this.clearCredentials();
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Clear stored credentials
|
|
653
|
+
*/
|
|
654
|
+
async clearCredentials() {
|
|
655
|
+
try {
|
|
656
|
+
if (fs.existsSync(this.authDir)) {
|
|
657
|
+
const files = fs.readdirSync(this.authDir);
|
|
658
|
+
for (const file of files) {
|
|
659
|
+
fs.unlinkSync(path.join(this.authDir, file));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
console.error('Error clearing WhatsApp credentials:', error);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
// ============================================================================
|
|
668
|
+
// Private Helper Methods
|
|
669
|
+
// ============================================================================
|
|
670
|
+
/**
|
|
671
|
+
* Handle incoming message notification
|
|
672
|
+
*/
|
|
673
|
+
async handleIncomingMessage(message) {
|
|
674
|
+
for (const handler of this.messageHandlers) {
|
|
675
|
+
try {
|
|
676
|
+
await handler(message);
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
console.error('Error in WhatsApp message handler:', error);
|
|
680
|
+
this.handleError(error instanceof Error ? error : new Error(String(error)), 'messageHandler');
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Handle errors
|
|
686
|
+
*/
|
|
687
|
+
handleError(error, context) {
|
|
688
|
+
for (const handler of this.errorHandlers) {
|
|
689
|
+
try {
|
|
690
|
+
handler(error, context);
|
|
691
|
+
}
|
|
692
|
+
catch (e) {
|
|
693
|
+
console.error('Error in error handler:', e);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Set status and notify handlers
|
|
699
|
+
*/
|
|
700
|
+
setStatus(status, error) {
|
|
701
|
+
this._status = status;
|
|
702
|
+
for (const handler of this.statusHandlers) {
|
|
703
|
+
try {
|
|
704
|
+
handler(status, error);
|
|
705
|
+
}
|
|
706
|
+
catch (e) {
|
|
707
|
+
console.error('Error in status handler:', e);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Attempt reconnection with exponential backoff
|
|
713
|
+
*/
|
|
714
|
+
async attemptReconnection() {
|
|
715
|
+
if (this.isReconnecting)
|
|
716
|
+
return;
|
|
717
|
+
const config = this.DEFAULT_BACKOFF;
|
|
718
|
+
if (this.backoffAttempt >= config.maxAttempts) {
|
|
719
|
+
console.error(`WhatsApp: Max reconnection attempts (${config.maxAttempts}) reached`);
|
|
720
|
+
this.setStatus('error', new Error('Max reconnection attempts reached'));
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
this.isReconnecting = true;
|
|
724
|
+
this.backoffAttempt++;
|
|
725
|
+
const delay = this.calculateBackoffDelay(config);
|
|
726
|
+
console.log(`WhatsApp: Reconnection attempt ${this.backoffAttempt}/${config.maxAttempts} in ${delay}ms`);
|
|
727
|
+
this.backoffTimer = setTimeout(async () => {
|
|
728
|
+
try {
|
|
729
|
+
this.sock = null;
|
|
730
|
+
this.isReconnecting = false;
|
|
731
|
+
this.setStatus('disconnected');
|
|
732
|
+
await this.connect();
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
this.isReconnecting = false;
|
|
736
|
+
console.error('WhatsApp reconnection attempt failed:', error);
|
|
737
|
+
await this.attemptReconnection();
|
|
738
|
+
}
|
|
739
|
+
}, delay);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Calculate backoff delay with jitter
|
|
743
|
+
*/
|
|
744
|
+
calculateBackoffDelay(config) {
|
|
745
|
+
let delay = config.initialDelay * Math.pow(config.multiplier, this.backoffAttempt - 1);
|
|
746
|
+
delay = Math.min(delay, config.maxDelay);
|
|
747
|
+
const jitterAmount = delay * config.jitter;
|
|
748
|
+
const jitter = (Math.random() * 2 - 1) * jitterAmount;
|
|
749
|
+
delay = Math.round(delay + jitter);
|
|
750
|
+
return Math.max(1000, delay);
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Reset backoff state
|
|
754
|
+
*/
|
|
755
|
+
resetBackoff() {
|
|
756
|
+
this.backoffAttempt = 0;
|
|
757
|
+
this.isReconnecting = false;
|
|
758
|
+
if (this.backoffTimer) {
|
|
759
|
+
clearTimeout(this.backoffTimer);
|
|
760
|
+
this.backoffTimer = undefined;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Start deduplication cache cleanup
|
|
765
|
+
*/
|
|
766
|
+
startDedupCleanup() {
|
|
767
|
+
this.dedupCleanupTimer = setInterval(() => {
|
|
768
|
+
this.cleanupDedupCache();
|
|
769
|
+
}, this.DEDUP_CACHE_TTL);
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Clean up old dedup cache entries
|
|
773
|
+
*/
|
|
774
|
+
cleanupDedupCache() {
|
|
775
|
+
const now = Date.now();
|
|
776
|
+
for (const [key, timestamp] of this.processedMessages) {
|
|
777
|
+
if (now - timestamp > this.DEDUP_CACHE_TTL) {
|
|
778
|
+
this.processedMessages.delete(key);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Get group metadata with caching
|
|
784
|
+
*/
|
|
785
|
+
async getGroupMeta(jid) {
|
|
786
|
+
const cached = this.groupMetaCache.get(jid);
|
|
787
|
+
if (cached && cached.expires > Date.now()) {
|
|
788
|
+
return cached;
|
|
789
|
+
}
|
|
790
|
+
try {
|
|
791
|
+
const meta = await this.sock?.groupMetadata(jid);
|
|
792
|
+
const entry = {
|
|
793
|
+
subject: meta?.subject,
|
|
794
|
+
expires: Date.now() + this.GROUP_META_TTL_MS,
|
|
795
|
+
};
|
|
796
|
+
this.groupMetaCache.set(jid, entry);
|
|
797
|
+
return entry;
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
return { subject: undefined };
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Convert JID to E.164 phone number format
|
|
805
|
+
*/
|
|
806
|
+
jidToE164(jid) {
|
|
807
|
+
if (!jid)
|
|
808
|
+
return null;
|
|
809
|
+
// Remove @s.whatsapp.net or @c.us suffix
|
|
810
|
+
const match = jid.match(/^(\d+)@/);
|
|
811
|
+
if (!match)
|
|
812
|
+
return null;
|
|
813
|
+
return match[1];
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Convert phone number/chat ID to WhatsApp JID
|
|
817
|
+
*/
|
|
818
|
+
toWhatsAppJid(chatId) {
|
|
819
|
+
// Already a JID
|
|
820
|
+
if (chatId.includes('@'))
|
|
821
|
+
return chatId;
|
|
822
|
+
// Group ID
|
|
823
|
+
if (chatId.includes('-')) {
|
|
824
|
+
return `${chatId}@g.us`;
|
|
825
|
+
}
|
|
826
|
+
// Phone number - remove any non-numeric characters
|
|
827
|
+
const cleaned = chatId.replace(/[^0-9]/g, '');
|
|
828
|
+
return `${cleaned}@s.whatsapp.net`;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Extract text from WhatsApp message
|
|
832
|
+
*/
|
|
833
|
+
extractText(message) {
|
|
834
|
+
if (!message)
|
|
835
|
+
return undefined;
|
|
836
|
+
// Direct text message
|
|
837
|
+
if (message.conversation) {
|
|
838
|
+
return message.conversation.trim();
|
|
839
|
+
}
|
|
840
|
+
// Extended text message
|
|
841
|
+
if (message.extendedTextMessage?.text) {
|
|
842
|
+
return message.extendedTextMessage.text.trim();
|
|
843
|
+
}
|
|
844
|
+
// Image/video/document caption
|
|
845
|
+
const caption = message.imageMessage?.caption ||
|
|
846
|
+
message.videoMessage?.caption ||
|
|
847
|
+
message.documentMessage?.caption;
|
|
848
|
+
if (caption)
|
|
849
|
+
return caption.trim();
|
|
850
|
+
return undefined;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Extract media placeholder from message
|
|
854
|
+
*/
|
|
855
|
+
extractMediaPlaceholder(message) {
|
|
856
|
+
if (!message)
|
|
857
|
+
return undefined;
|
|
858
|
+
if (message.imageMessage)
|
|
859
|
+
return '<media:image>';
|
|
860
|
+
if (message.videoMessage)
|
|
861
|
+
return '<media:video>';
|
|
862
|
+
if (message.audioMessage)
|
|
863
|
+
return '<media:audio>';
|
|
864
|
+
if (message.documentMessage)
|
|
865
|
+
return '<media:document>';
|
|
866
|
+
if (message.stickerMessage)
|
|
867
|
+
return '<media:sticker>';
|
|
868
|
+
if (message.contactMessage)
|
|
869
|
+
return '<contact>';
|
|
870
|
+
if (message.locationMessage)
|
|
871
|
+
return '<location>';
|
|
872
|
+
return undefined;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Download audio from a WhatsApp message and return as attachment
|
|
876
|
+
*/
|
|
877
|
+
async downloadAudioAttachment(message) {
|
|
878
|
+
const audioMessage = message.audioMessage;
|
|
879
|
+
if (!audioMessage)
|
|
880
|
+
return null;
|
|
881
|
+
try {
|
|
882
|
+
console.log('[WhatsApp] Downloading audio message...');
|
|
883
|
+
// Download the audio content
|
|
884
|
+
const stream = await (0, baileys_1.downloadContentFromMessage)(audioMessage, 'audio');
|
|
885
|
+
// Collect the stream into a buffer
|
|
886
|
+
const chunks = [];
|
|
887
|
+
for await (const chunk of stream) {
|
|
888
|
+
chunks.push(chunk);
|
|
889
|
+
}
|
|
890
|
+
const audioBuffer = Buffer.concat(chunks);
|
|
891
|
+
console.log(`[WhatsApp] Downloaded audio: ${audioBuffer.length} bytes`);
|
|
892
|
+
// Determine mime type (usually audio/ogg for voice messages)
|
|
893
|
+
const mimeType = audioMessage.mimetype || 'audio/ogg; codecs=opus';
|
|
894
|
+
const isVoiceNote = audioMessage.ptt === true;
|
|
895
|
+
const fileName = isVoiceNote
|
|
896
|
+
? `voice_message_${Date.now()}.ogg`
|
|
897
|
+
: `audio_${Date.now()}.${this.getAudioExtension(mimeType)}`;
|
|
898
|
+
return {
|
|
899
|
+
type: 'audio',
|
|
900
|
+
data: audioBuffer,
|
|
901
|
+
mimeType,
|
|
902
|
+
fileName,
|
|
903
|
+
size: audioBuffer.length,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
catch (error) {
|
|
907
|
+
console.error('[WhatsApp] Failed to download audio:', error);
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Get file extension from mime type
|
|
913
|
+
*/
|
|
914
|
+
getAudioExtension(mimeType) {
|
|
915
|
+
if (mimeType.includes('ogg'))
|
|
916
|
+
return 'ogg';
|
|
917
|
+
if (mimeType.includes('mp3') || mimeType.includes('mpeg'))
|
|
918
|
+
return 'mp3';
|
|
919
|
+
if (mimeType.includes('wav'))
|
|
920
|
+
return 'wav';
|
|
921
|
+
if (mimeType.includes('m4a') || mimeType.includes('mp4'))
|
|
922
|
+
return 'm4a';
|
|
923
|
+
if (mimeType.includes('webm'))
|
|
924
|
+
return 'webm';
|
|
925
|
+
return 'audio';
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Get status code from disconnect error
|
|
929
|
+
*/
|
|
930
|
+
getStatusCode(err) {
|
|
931
|
+
if (!err)
|
|
932
|
+
return undefined;
|
|
933
|
+
const asAny = err;
|
|
934
|
+
return (asAny?.output?.statusCode ||
|
|
935
|
+
asAny?.status ||
|
|
936
|
+
undefined);
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Ensure directory exists
|
|
940
|
+
*/
|
|
941
|
+
async ensureDir(dirPath) {
|
|
942
|
+
if (!fs.existsSync(dirPath)) {
|
|
943
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
exports.WhatsAppAdapter = WhatsAppAdapter;
|
|
948
|
+
/**
|
|
949
|
+
* Create a WhatsApp adapter from configuration
|
|
950
|
+
*/
|
|
951
|
+
function createWhatsAppAdapter(config) {
|
|
952
|
+
return new WhatsAppAdapter(config);
|
|
953
|
+
}
|