machinaos 0.0.78 → 0.0.80
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/.env.template +74 -5
- package/{workflows/AI Assistant_workflow-1778504793388-ou1m1tz2x.json → .machina/workflows/AI Assistant_example_workflow-1779017037684-e2e5da7a.json } +164 -105
- package/{workflows/AI Employee_example_workflow-1777720598005-u4cm858dv.json → .machina/workflows/AI Employee_example_workflow-1779102911870-cbc76c82.json } +582 -328
- package/{workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json → .machina/workflows/Claude Assistant_example_workflow-1779095939967-2369cff4.json } +152 -83
- package/README.md +5 -2
- package/bin/cli.js +2 -2
- package/{machina → cli}/__main__.py +11 -7
- package/cli/_common.py +122 -0
- package/cli/buildenv.py +40 -0
- package/cli/cli.py +204 -0
- package/{machina → cli}/colors.py +10 -2
- package/cli/commands/__init__.py +1 -0
- package/cli/commands/_temporal_specs.py +59 -0
- package/{machina → cli}/commands/build.py +35 -45
- package/cli/commands/clean.py +141 -0
- package/cli/commands/daemon/__init__.py +47 -0
- package/cli/commands/daemon/_state.py +97 -0
- package/cli/commands/daemon/restart.py +14 -0
- package/cli/commands/daemon/start.py +49 -0
- package/cli/commands/daemon/status.py +20 -0
- package/cli/commands/daemon/stop.py +22 -0
- package/{machina → cli}/commands/dev.py +32 -42
- package/{machina → cli}/commands/docs.py +13 -11
- package/{machina → cli}/commands/start.py +69 -62
- package/{machina → cli}/commands/stop.py +7 -10
- package/{machina → cli}/commands/version.py +12 -6
- package/cli/config.py +170 -0
- package/cli/platform_.py +169 -0
- package/{machina → cli}/ports.py +42 -3
- package/{machina → cli}/run.py +29 -2
- package/{machina → cli}/supervisor.py +29 -12
- package/{machina → cli}/tcp.py +6 -2
- package/{machina → cli}/tree.py +38 -11
- package/client/dist/assets/{ActionBar-Du2MSFSz.js → ActionBar-Cjr3TF7g.js} +1 -1
- package/client/dist/assets/{ApiKeyInput-k2LBmBjb.js → ApiKeyInput-DIJE2PVA.js} +1 -1
- package/client/dist/assets/{ApiKeyPanel-C_bV9U0X.js → ApiKeyPanel-CPmye7uh.js} +1 -1
- package/client/dist/assets/{ApiUsageSection-CmVfwZzL.js → ApiUsageSection-TF_7gH2D.js} +1 -1
- package/client/dist/assets/{EmailPanel-CeKIMGu-.js → EmailPanel-Bs-xvbKR.js} +1 -1
- package/client/dist/assets/{OAuthPanel-KA3t3Q2K.js → OAuthPanel-BDtVJhAV.js} +1 -1
- package/client/dist/assets/{QrPairingPanel-NgNpJNuk.js → QrPairingPanel-BwJehTuZ.js} +1 -1
- package/client/dist/assets/{RateLimitSection-Du5YNVIA.js → RateLimitSection-CfNOoPIS.js} +1 -1
- package/client/dist/assets/{StatusCard-DNLyayXc.js → StatusCard-DkwIrgdP.js} +1 -1
- package/client/dist/assets/index-P2FzntoL.js +165 -0
- package/client/dist/index.html +1 -1
- package/client/package.json +1 -1
- package/client/src/Dashboard.tsx +128 -76
- package/client/src/adapters/nodeSpecToDescription.ts +7 -0
- package/client/src/assets/icons/index.test.ts +10 -0
- package/client/src/assets/icons/index.ts +16 -3
- package/client/src/components/AIAgentNode.tsx +8 -8
- package/client/src/components/ParameterRenderer.tsx +6 -3
- package/client/src/components/SkillEditorModal.tsx +1 -0
- package/client/src/components/credentials/panels/EmailPanel.tsx +2 -0
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +2 -0
- package/client/src/components/credentials/sections/RateLimitSection.tsx +1 -0
- package/client/src/components/icons/AIProviderIcons.tsx +1 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +1 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1 -0
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +1 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +1 -0
- package/client/src/components/ui/ComponentPalette.tsx +1 -0
- package/client/src/components/ui/MapSelector.tsx +1 -0
- package/client/src/components/ui/NodeContextMenu.tsx +3 -3
- package/client/src/components/ui/SettingsPanel.tsx +1 -0
- package/client/src/components/ui/action-button.tsx +1 -0
- package/client/src/components/ui/badge.tsx +1 -0
- package/client/src/components/ui/button.tsx +1 -0
- package/client/src/components/ui/form.tsx +1 -0
- package/client/src/components/ui/tabs.tsx +1 -0
- package/client/src/contexts/AuthContext.tsx +1 -0
- package/client/src/contexts/ThemeContext.tsx +1 -0
- package/client/src/contexts/WebSocketContext.tsx +104 -34
- package/client/src/hooks/__tests__/useApiKeys.test.ts +2 -2
- package/client/src/hooks/useReactFlowNodes.ts +1 -0
- package/client/src/hooks/useWorkflowValidation.ts +142 -0
- package/client/src/lib/nodeSpec.ts +1 -0
- package/client/src/test/providers.tsx +1 -0
- package/client/src/types/__tests__/cloudEvents.test.ts +5 -2
- package/client/src/types/cloudEvents.ts +19 -7
- package/client/src/utils/nodeUtils.ts +1 -1
- package/client/src/utils/workflow.ts +8 -2
- package/client/src/utils/workflowExport.ts +60 -3
- package/package.json +24 -23
- package/scripts/install.js +16 -27
- package/scripts/migrate_icons.py +3 -1
- package/scripts/migrate_skill_icons.py +6 -7
- package/scripts/postinstall.js +11 -9
- package/server/config/ai_cli_providers.json +2 -3
- package/server/config/credential_providers.json +15 -15
- package/server/config/llm_defaults.json +1 -1
- package/server/config/model_registry.json +416 -611
- package/server/constants.py +285 -223
- package/server/core/__init__.py +1 -1
- package/server/core/cache.py +9 -29
- package/server/core/cleanup.py +12 -24
- package/server/core/config.py +148 -24
- package/server/core/container.py +68 -59
- package/server/core/credential_backends.py +5 -13
- package/server/core/credentials_database.py +13 -43
- package/server/core/database.py +292 -353
- package/server/core/health.py +4 -5
- package/server/core/logging.py +241 -87
- package/server/core/paths.py +285 -0
- package/server/core/tracing.py +2 -8
- package/server/gunicorn.conf.py +1 -0
- package/server/main.py +150 -74
- package/server/middleware/auth.py +18 -24
- package/server/models/__init__.py +1 -1
- package/server/models/auth.py +5 -12
- package/server/models/database.py +36 -68
- package/server/models/node_metadata.py +25 -18
- package/server/nodejs/dist/index.js +107 -0
- package/server/nodes/README.md +11 -5
- package/server/nodes/__init__.py +1 -1
- package/server/nodes/_visuals.py +146 -14
- package/server/nodes/agent/_events.py +124 -0
- package/server/nodes/agent/_handles.py +15 -29
- package/server/nodes/agent/_inline.py +28 -25
- package/server/nodes/agent/_specialized.py +30 -15
- package/server/nodes/agent/{ai_agent.py → ai_agent/__init__.py} +33 -17
- package/server/nodes/agent/ai_agent/meta.json +3 -0
- package/server/nodes/agent/{ai_employee.py → ai_employee/__init__.py} +5 -2
- package/server/nodes/agent/ai_employee/meta.json +3 -0
- package/server/nodes/agent/{android_agent.py → android_agent/__init__.py} +1 -1
- package/server/nodes/agent/android_agent/meta.json +3 -0
- package/server/nodes/agent/{autonomous_agent.py → autonomous_agent/__init__.py} +2 -1
- package/server/nodes/agent/autonomous_agent/meta.json +3 -0
- package/server/nodes/agent/{chat_agent.py → chat_agent/__init__.py} +29 -12
- package/server/nodes/agent/chat_agent/meta.json +3 -0
- package/server/nodes/agent/{claude_code_agent.py → claude_code_agent/__init__.py} +192 -95
- package/server/nodes/agent/claude_code_agent/_handlers.py +169 -0
- package/server/{services/claude_oauth.py → nodes/agent/claude_code_agent/_oauth.py} +26 -13
- package/server/nodes/agent/claude_code_agent/_pool.py +1020 -0
- package/server/nodes/agent/claude_code_agent/_provider.py +513 -0
- package/server/nodes/agent/claude_code_agent/_skills.py +245 -0
- package/server/nodes/agent/claude_code_agent/meta.json +3 -0
- package/server/nodes/agent/{codex_agent.py → codex_agent/__init__.py} +26 -35
- package/server/nodes/agent/codex_agent/meta.json +3 -0
- package/server/nodes/agent/{coding_agent.py → coding_agent/__init__.py} +1 -1
- package/server/nodes/agent/coding_agent/meta.json +3 -0
- package/server/nodes/agent/{consumer_agent.py → consumer_agent/__init__.py} +1 -1
- package/server/nodes/agent/consumer_agent/meta.json +3 -0
- package/server/nodes/agent/{orchestrator_agent.py → orchestrator_agent/__init__.py} +5 -2
- package/server/nodes/agent/orchestrator_agent/meta.json +3 -0
- package/server/nodes/agent/{payments_agent.py → payments_agent/__init__.py} +1 -1
- package/server/nodes/agent/payments_agent/meta.json +3 -0
- package/server/nodes/agent/{productivity_agent.py → productivity_agent/__init__.py} +1 -1
- package/server/nodes/agent/productivity_agent/meta.json +3 -0
- package/server/nodes/agent/{rlm_agent.py → rlm_agent/__init__.py} +18 -17
- package/server/nodes/agent/rlm_agent/meta.json +3 -0
- package/server/nodes/agent/{social_agent.py → social_agent/__init__.py} +1 -1
- package/server/nodes/agent/social_agent/meta.json +3 -0
- package/server/nodes/agent/{task_agent.py → task_agent/__init__.py} +1 -1
- package/server/nodes/agent/task_agent/meta.json +3 -0
- package/server/nodes/agent/{tool_agent.py → tool_agent/__init__.py} +1 -1
- package/server/nodes/agent/tool_agent/meta.json +3 -0
- package/server/nodes/agent/{travel_agent.py → travel_agent/__init__.py} +1 -1
- package/server/nodes/agent/travel_agent/meta.json +3 -0
- package/server/nodes/agent/{web_agent.py → web_agent/__init__.py} +1 -1
- package/server/nodes/agent/web_agent/meta.json +3 -0
- package/server/nodes/android/__init__.py +24 -0
- package/server/nodes/android/_base.py +93 -76
- package/server/nodes/android/_dispatcher.py +140 -223
- package/server/nodes/android/_events.py +154 -0
- package/server/nodes/android/_handlers.py +13 -7
- package/server/nodes/android/_option_loaders.py +1 -4
- package/server/nodes/android/_refresh.py +27 -37
- package/server/nodes/android/_relay/broadcaster.py +25 -41
- package/server/nodes/android/_relay/client.py +23 -42
- package/server/nodes/android/_relay/manager.py +1 -0
- package/server/nodes/android/_relay/protocol.py +6 -0
- package/server/nodes/android/_router.py +48 -133
- package/server/nodes/android/{airplane_mode_control.py → airplane_mode_control/__init__.py} +2 -1
- package/server/nodes/android/airplane_mode_control/meta.json +3 -0
- package/server/nodes/android/{app_launcher.py → app_launcher/__init__.py} +2 -1
- package/server/nodes/android/app_launcher/meta.json +3 -0
- package/server/nodes/android/{app_list.py → app_list/__init__.py} +2 -1
- package/server/nodes/android/app_list/meta.json +3 -0
- package/server/nodes/android/{audio_automation.py → audio_automation/__init__.py} +2 -1
- package/server/nodes/android/audio_automation/meta.json +3 -0
- package/server/nodes/android/{battery_monitor.py → battery_monitor/__init__.py} +2 -1
- package/server/nodes/android/battery_monitor/meta.json +3 -0
- package/server/nodes/android/{bluetooth_automation.py → bluetooth_automation/__init__.py} +2 -1
- package/server/nodes/android/bluetooth_automation/meta.json +3 -0
- package/server/nodes/android/{camera_control.py → camera_control/__init__.py} +2 -1
- package/server/nodes/android/camera_control/meta.json +3 -0
- package/server/nodes/android/{device_state_automation.py → device_state_automation/__init__.py} +2 -1
- package/server/nodes/android/device_state_automation/meta.json +3 -0
- package/server/nodes/android/{environmental_sensors.py → environmental_sensors/__init__.py} +2 -1
- package/server/nodes/android/environmental_sensors/meta.json +3 -0
- package/server/nodes/android/{location.py → location/__init__.py} +2 -1
- package/server/nodes/android/location/meta.json +3 -0
- package/server/nodes/android/{media_control.py → media_control/__init__.py} +2 -1
- package/server/nodes/android/media_control/meta.json +3 -0
- package/server/nodes/android/{motion_detection.py → motion_detection/__init__.py} +2 -1
- package/server/nodes/android/motion_detection/meta.json +3 -0
- package/server/nodes/android/{network_monitor.py → network_monitor/__init__.py} +2 -1
- package/server/nodes/android/network_monitor/meta.json +3 -0
- package/server/nodes/android/{screen_control_automation.py → screen_control_automation/__init__.py} +2 -1
- package/server/nodes/android/screen_control_automation/meta.json +3 -0
- package/server/nodes/android/{system_info.py → system_info/__init__.py} +2 -1
- package/server/nodes/android/system_info/meta.json +3 -0
- package/server/nodes/android/{wifi_automation.py → wifi_automation/__init__.py} +2 -1
- package/server/nodes/android/wifi_automation/meta.json +3 -0
- package/server/nodes/browser/__init__.py +22 -1
- package/server/nodes/browser/_install.py +63 -0
- package/server/nodes/browser/_service.py +21 -25
- package/server/nodes/browser/{browser.py → browser/__init__.py} +58 -25
- package/server/nodes/browser/browser/meta.json +3 -0
- package/server/nodes/chat/{chat_history.py → chat_history/__init__.py} +2 -4
- package/server/nodes/chat/chat_history/meta.json +3 -0
- package/server/nodes/chat/{chat_send.py → chat_send/__init__.py} +2 -4
- package/server/nodes/chat/chat_send/icon.svg +1 -0
- package/server/nodes/chat/chat_send/meta.json +3 -0
- package/server/nodes/code/_base.py +1 -1
- package/server/nodes/code/{javascript_executor.py → javascript_executor/__init__.py} +5 -5
- package/server/nodes/code/javascript_executor/meta.json +3 -0
- package/server/nodes/code/{python_executor.py → python_executor/__init__.py} +32 -14
- package/server/nodes/code/python_executor/meta.json +3 -0
- package/server/nodes/code/{typescript_executor.py → typescript_executor/__init__.py} +5 -5
- package/server/nodes/code/typescript_executor/meta.json +3 -0
- package/server/nodes/document/{document_parser.py → document_parser/__init__.py} +26 -15
- package/server/nodes/document/document_parser/meta.json +3 -0
- package/server/nodes/document/{embedding_generator.py → embedding_generator/__init__.py} +16 -9
- package/server/nodes/document/embedding_generator/meta.json +3 -0
- package/server/nodes/document/{file_downloader.py → file_downloader/__init__.py} +30 -20
- package/server/nodes/document/file_downloader/meta.json +3 -0
- package/server/nodes/document/{http_scraper.py → http_scraper/__init__.py} +31 -21
- package/server/nodes/document/http_scraper/meta.json +3 -0
- package/server/nodes/document/{text_chunker.py → text_chunker/__init__.py} +17 -12
- package/server/nodes/document/text_chunker/meta.json +3 -0
- package/server/nodes/document/{vector_store.py → vector_store/__init__.py} +88 -72
- package/server/nodes/document/vector_store/meta.json +3 -0
- package/server/nodes/email/__init__.py +9 -2
- package/server/nodes/email/_events.py +54 -0
- package/server/nodes/email/_filters.py +3 -3
- package/server/nodes/email/_himalaya.py +95 -50
- package/server/nodes/email/_service.py +23 -13
- package/server/nodes/email/{email_read.py → email_read/__init__.py} +23 -11
- package/server/nodes/email/email_read/icon.svg +6 -0
- package/server/nodes/email/email_read/meta.json +3 -0
- package/server/nodes/email/{email_receive.py → email_receive/__init__.py} +45 -23
- package/server/nodes/email/email_receive/meta.json +3 -0
- package/server/nodes/email/{email_send.py → email_send/__init__.py} +13 -7
- package/server/nodes/email/email_send/meta.json +3 -0
- package/server/nodes/filesystem/_backend.py +1 -5
- package/server/nodes/filesystem/{file_modify.py → file_modify/__init__.py} +10 -5
- package/server/nodes/filesystem/file_modify/meta.json +3 -0
- package/server/nodes/filesystem/{file_read.py → file_read/__init__.py} +7 -3
- package/server/nodes/filesystem/file_read/meta.json +3 -0
- package/server/nodes/filesystem/{fs_search.py → fs_search/__init__.py} +11 -3
- package/server/nodes/filesystem/fs_search/meta.json +3 -0
- package/server/nodes/filesystem/{shell.py → shell/__init__.py} +12 -5
- package/server/nodes/filesystem/shell/meta.json +3 -0
- package/server/nodes/google/__init__.py +12 -0
- package/server/nodes/google/_auth_helper.py +7 -13
- package/server/nodes/google/_base.py +14 -11
- package/server/nodes/google/_credentials.py +2 -1
- package/server/nodes/google/_events.py +47 -0
- package/server/nodes/google/_filters.py +3 -3
- package/server/nodes/google/_gmail.py +70 -47
- package/server/nodes/google/_handlers.py +3 -1
- package/server/nodes/google/_oauth.py +25 -11
- package/server/nodes/google/_option_loaders.py +9 -30
- package/server/nodes/google/_refresh.py +8 -12
- package/server/nodes/google/_router.py +4 -5
- package/server/nodes/google/{calendar.py → calendar/__init__.py} +87 -64
- package/server/nodes/google/calendar/meta.json +3 -0
- package/server/nodes/google/{contacts.py → contacts/__init__.py} +84 -72
- package/server/nodes/google/contacts/meta.json +3 -0
- package/server/nodes/google/{drive.py → drive/__init__.py} +87 -72
- package/server/nodes/google/drive/meta.json +3 -0
- package/server/nodes/google/{gmail.py → gmail/__init__.py} +73 -39
- package/server/nodes/google/gmail/meta.json +3 -0
- package/server/nodes/google/{gmail_receive.py → gmail_receive/__init__.py} +31 -24
- package/server/nodes/google/gmail_receive/icon.svg +7 -0
- package/server/nodes/google/gmail_receive/meta.json +3 -0
- package/server/nodes/google/google.svg +7 -0
- package/server/nodes/google/{sheets.py → sheets/__init__.py} +54 -42
- package/server/nodes/google/sheets/meta.json +3 -0
- package/server/nodes/google/{tasks.py → tasks/__init__.py} +56 -43
- package/server/nodes/google/tasks/meta.json +3 -0
- package/server/nodes/groups.py +28 -28
- package/server/nodes/location/__init__.py +31 -1
- package/server/nodes/location/_credentials.py +1 -6
- package/server/nodes/location/_service.py +88 -107
- package/server/nodes/location/{gmaps_create.py → gmaps_create/__init__.py} +6 -6
- package/server/nodes/location/gmaps_create/meta.json +3 -0
- package/server/nodes/location/{gmaps_locations.py → gmaps_locations/__init__.py} +8 -6
- package/server/nodes/location/gmaps_locations/meta.json +3 -0
- package/server/nodes/location/{gmaps_nearby_places.py → gmaps_nearby_places/__init__.py} +8 -6
- package/server/nodes/location/gmaps_nearby_places/meta.json +3 -0
- package/server/nodes/model/_base.py +10 -7
- package/server/nodes/model/_credentials.py +10 -10
- package/server/nodes/model/_local_validator.py +28 -24
- package/server/nodes/model/{anthropic_chat_model.py → anthropic_chat_model/__init__.py} +5 -3
- package/server/nodes/model/anthropic_chat_model/meta.json +3 -0
- package/server/nodes/model/{cerebras_chat_model.py → cerebras_chat_model/__init__.py} +5 -3
- package/server/nodes/model/cerebras_chat_model/meta.json +3 -0
- package/server/nodes/model/{deepseek_chat_model.py → deepseek_chat_model/__init__.py} +8 -4
- package/server/nodes/model/deepseek_chat_model/meta.json +3 -0
- package/server/nodes/model/{gemini_chat_model.py → gemini_chat_model/__init__.py} +5 -3
- package/server/nodes/model/gemini_chat_model/meta.json +3 -0
- package/server/nodes/model/{groq_chat_model.py → groq_chat_model/__init__.py} +2 -2
- package/server/nodes/model/groq_chat_model/meta.json +3 -0
- package/server/nodes/model/{kimi_chat_model.py → kimi_chat_model/__init__.py} +2 -2
- package/server/nodes/model/kimi_chat_model/meta.json +3 -0
- package/server/nodes/model/{lmstudio_chat_model.py → lmstudio_chat_model/__init__.py} +2 -2
- package/server/nodes/model/lmstudio_chat_model/meta.json +3 -0
- package/server/nodes/model/{mistral_chat_model.py → mistral_chat_model/__init__.py} +2 -2
- package/server/nodes/model/mistral_chat_model/meta.json +3 -0
- package/server/nodes/model/{ollama_chat_model.py → ollama_chat_model/__init__.py} +2 -2
- package/server/nodes/model/ollama_chat_model/meta.json +3 -0
- package/server/nodes/model/{openai_chat_model.py → openai_chat_model/__init__.py} +8 -4
- package/server/nodes/model/openai_chat_model/meta.json +3 -0
- package/server/nodes/model/{openrouter_chat_model.py → openrouter_chat_model/__init__.py} +8 -4
- package/server/nodes/model/openrouter_chat_model/meta.json +3 -0
- package/server/nodes/proxy/_usage.py +14 -15
- package/server/nodes/proxy/{proxy_config.py → proxy_config/__init__.py} +39 -30
- package/server/nodes/proxy/proxy_config/meta.json +3 -0
- package/server/nodes/proxy/{proxy_request.py → proxy_request/__init__.py} +30 -16
- package/server/nodes/proxy/proxy_request/meta.json +3 -0
- package/server/nodes/proxy/{proxy_status.py → proxy_status/__init__.py} +2 -0
- package/server/nodes/proxy/proxy_status/meta.json +3 -0
- package/server/nodes/scheduler/{cron_scheduler.py → cron_scheduler/__init__.py} +96 -23
- package/server/nodes/scheduler/cron_scheduler/_workflow.py +155 -0
- package/server/nodes/scheduler/cron_scheduler/meta.json +3 -0
- package/server/nodes/scheduler/{timer.py → timer/__init__.py} +6 -5
- package/server/nodes/scheduler/timer/meta.json +3 -0
- package/server/nodes/scraper/_credentials.py +0 -1
- package/server/nodes/scraper/{apify_actor.py → apify_actor/__init__.py} +44 -35
- package/server/nodes/scraper/apify_actor/icon.svg +5 -0
- package/server/nodes/scraper/apify_actor/meta.json +3 -0
- package/server/nodes/scraper/{crawlee_scraper.py → crawlee_scraper/__init__.py} +96 -57
- package/server/nodes/scraper/crawlee_scraper/meta.json +3 -0
- package/server/nodes/search/{brave_search.py → brave_search/__init__.py} +6 -5
- package/server/nodes/search/brave_search/icon.svg +3 -0
- package/server/nodes/search/brave_search/meta.json +3 -0
- package/server/nodes/search/{duckduckgo_search.py → duckduckgo_search/__init__.py} +17 -6
- package/server/nodes/search/duckduckgo_search/meta.json +3 -0
- package/server/nodes/search/{perplexity_search.py → perplexity_search/__init__.py} +4 -5
- package/server/nodes/search/perplexity_search/icon.svg +3 -0
- package/server/nodes/search/perplexity_search/meta.json +3 -0
- package/server/nodes/search/{serper_search.py → serper_search/__init__.py} +32 -25
- package/server/nodes/search/serper_search/icon.svg +3 -0
- package/server/nodes/search/serper_search/meta.json +3 -0
- package/server/nodes/skill/__init__.py +21 -1
- package/server/nodes/skill/_expander.py +75 -0
- package/server/nodes/skill/{master_skill.py → master_skill/__init__.py} +2 -8
- package/server/nodes/skill/master_skill/_events.py +84 -0
- package/server/nodes/skill/master_skill/meta.json +3 -0
- package/server/nodes/skill/{simple_memory.py → simple_memory/__init__.py} +8 -16
- package/server/nodes/skill/simple_memory/meta.json +3 -0
- package/server/nodes/social/_base.py +223 -231
- package/server/nodes/social/{social_receive.py → social_receive/__init__.py} +38 -13
- package/server/nodes/social/social_receive/meta.json +3 -0
- package/server/nodes/social/{social_send.py → social_send/__init__.py} +71 -29
- package/server/nodes/social/social_send/icon.svg +1 -0
- package/server/nodes/social/social_send/meta.json +3 -0
- package/server/nodes/stripe/__init__.py +7 -3
- package/server/nodes/stripe/_credentials.py +0 -1
- package/server/nodes/stripe/_handlers.py +18 -7
- package/server/nodes/stripe/_install.py +14 -15
- package/server/nodes/stripe/_source.py +5 -5
- package/server/nodes/stripe/icon.svg +1 -0
- package/server/nodes/stripe/meta.json +3 -0
- package/server/nodes/stripe/stripe_action.py +4 -4
- package/server/nodes/stripe/stripe_receive.py +6 -9
- package/server/nodes/telegram/__init__.py +13 -0
- package/server/nodes/telegram/_credentials.py +2 -7
- package/server/nodes/telegram/_events.py +167 -0
- package/server/nodes/telegram/_filters.py +3 -11
- package/server/nodes/telegram/_handlers.py +17 -7
- package/server/nodes/telegram/_refresh.py +24 -34
- package/server/nodes/telegram/_service.py +29 -45
- package/server/nodes/telegram/meta.json +3 -0
- package/server/nodes/telegram/telegram.svg +3 -0
- package/server/nodes/telegram/telegram_receive.py +38 -18
- package/server/nodes/telegram/telegram_send.py +21 -19
- package/server/nodes/text/{file_handler.py → file_handler/__init__.py} +7 -1
- package/server/nodes/text/file_handler/meta.json +3 -0
- package/server/nodes/text/{text_generator.py → text_generator/__init__.py} +2 -1
- package/server/nodes/text/text_generator/meta.json +3 -0
- package/server/nodes/tool/{agent_builder.py → agent_builder/__init__.py} +105 -100
- package/server/nodes/tool/agent_builder/_events.py +91 -0
- package/server/nodes/tool/agent_builder/meta.json +3 -0
- package/server/nodes/tool/{calculator_tool.py → calculator_tool/__init__.py} +19 -7
- package/server/nodes/tool/calculator_tool/meta.json +3 -0
- package/server/nodes/tool/{current_time_tool.py → current_time_tool/__init__.py} +6 -4
- package/server/nodes/tool/current_time_tool/meta.json +3 -0
- package/server/nodes/tool/{task_manager.py → task_manager/__init__.py} +17 -18
- package/server/nodes/tool/task_manager/meta.json +3 -0
- package/server/nodes/tool/{write_todos.py → write_todos/__init__.py} +20 -6
- package/server/nodes/tool/write_todos/meta.json +3 -0
- package/server/nodes/trigger/{chat_trigger.py → chat_trigger/__init__.py} +11 -7
- package/server/nodes/trigger/chat_trigger/_events.py +53 -0
- package/server/nodes/trigger/chat_trigger/meta.json +3 -0
- package/server/nodes/trigger/{task_trigger.py → task_trigger/__init__.py} +10 -7
- package/server/nodes/trigger/task_trigger/meta.json +3 -0
- package/server/nodes/trigger/{webhook_trigger.py → webhook_trigger/__init__.py} +10 -7
- package/server/nodes/trigger/webhook_trigger/_events.py +54 -0
- package/server/nodes/trigger/webhook_trigger/meta.json +3 -0
- package/server/nodes/twitter/__init__.py +7 -1
- package/server/nodes/twitter/_base.py +86 -61
- package/server/nodes/twitter/_credentials.py +7 -5
- package/server/nodes/twitter/_events.py +101 -0
- package/server/nodes/twitter/_filters.py +9 -9
- package/server/nodes/twitter/_handlers.py +3 -1
- package/server/nodes/twitter/_oauth.py +1 -2
- package/server/nodes/twitter/_refresh.py +8 -12
- package/server/nodes/twitter/{twitter_receive.py → twitter_receive/__init__.py} +7 -7
- package/server/nodes/twitter/twitter_receive/icon.svg +1 -0
- package/server/nodes/twitter/twitter_receive/meta.json +3 -0
- package/server/nodes/twitter/{twitter_search.py → twitter_search/__init__.py} +16 -11
- package/server/nodes/twitter/twitter_search/icon.svg +1 -0
- package/server/nodes/twitter/twitter_search/meta.json +3 -0
- package/server/nodes/twitter/{twitter_send.py → twitter_send/__init__.py} +60 -27
- package/server/nodes/twitter/twitter_send/icon.svg +1 -0
- package/server/nodes/twitter/twitter_send/meta.json +3 -0
- package/server/nodes/twitter/{twitter_user.py → twitter_user/__init__.py} +34 -19
- package/server/nodes/twitter/twitter_user/icon.svg +1 -0
- package/server/nodes/twitter/twitter_user/meta.json +3 -0
- package/server/nodes/utility/{console.py → console/__init__.py} +17 -22
- package/server/nodes/utility/console/meta.json +3 -0
- package/server/nodes/utility/{http_request.py → http_request/__init__.py} +9 -6
- package/server/nodes/utility/http_request/meta.json +3 -0
- package/server/nodes/utility/{process_manager.py → process_manager/__init__.py} +10 -6
- package/server/nodes/utility/process_manager/meta.json +3 -0
- package/server/nodes/utility/team_monitor/meta.json +3 -0
- package/server/nodes/utility/{webhook_response.py → webhook_response/__init__.py} +12 -7
- package/server/nodes/utility/webhook_response/meta.json +3 -0
- package/server/nodes/visuals.json +69 -251
- package/server/nodes/whatsapp/__init__.py +24 -0
- package/server/nodes/whatsapp/_base.py +283 -338
- package/server/nodes/whatsapp/_credentials.py +44 -0
- package/server/nodes/whatsapp/_events.py +277 -0
- package/server/nodes/whatsapp/_filters.py +36 -37
- package/server/nodes/whatsapp/_handlers.py +2 -0
- package/server/nodes/whatsapp/_option_loaders.py +1 -3
- package/server/nodes/whatsapp/_refresh.py +13 -18
- package/server/nodes/whatsapp/_runtime.py +9 -6
- package/server/nodes/whatsapp/_service.py +89 -152
- package/server/nodes/whatsapp/meta.json +3 -0
- package/server/nodes/whatsapp/whatsapp_db.py +116 -54
- package/server/nodes/whatsapp/whatsapp_receive.py +30 -13
- package/server/nodes/whatsapp/whatsapp_send.py +60 -37
- package/server/nodes/workflow/{start.py → start/__init__.py} +1 -4
- package/server/nodes/workflow/start/meta.json +3 -0
- package/server/package-lock.json +3 -3
- package/server/package.json +3 -0
- package/server/pyproject.toml +39 -10
- package/server/requirements.txt +3 -5
- package/server/routers/__init__.py +1 -1
- package/server/routers/auth.py +16 -56
- package/server/routers/database.py +27 -50
- package/server/routers/nodejs_compat.py +25 -87
- package/server/routers/schemas.py +66 -2
- package/server/routers/webhook.py +12 -12
- package/server/routers/websocket.py +312 -1716
- package/server/routers/workflow.py +28 -53
- package/server/scripts/smoke_test_skills.py +178 -0
- package/server/services/__init__.py +1 -1
- package/server/services/_supervisor/process.py +9 -3
- package/server/services/_supervisor/registry.py +3 -3
- package/server/services/_supervisor/util.py +1 -1
- package/server/services/agent_team.py +15 -43
- package/server/services/agent_teams/__init__.py +17 -0
- package/server/services/agent_teams/handlers.py +195 -0
- package/server/services/ai.py +853 -1108
- package/server/services/auth.py +10 -34
- package/server/services/chat_client.py +5 -34
- package/server/services/circuit_breaker.py +2 -6
- package/server/services/cli_agent/__init__.py +28 -4
- package/server/services/cli_agent/_cli_auth.py +61 -0
- package/server/services/cli_agent/_handlers.py +24 -183
- package/server/services/cli_agent/config.py +5 -8
- package/server/services/cli_agent/factory.py +168 -22
- package/server/services/cli_agent/jsonl_watcher.py +380 -0
- package/server/services/cli_agent/lockfile.py +9 -2
- package/server/services/cli_agent/mcp_server.py +110 -34
- package/server/services/cli_agent/protocol.py +37 -19
- package/server/services/cli_agent/providers/__init__.py +8 -4
- package/server/services/cli_agent/providers/google_gemini.py +11 -5
- package/server/services/cli_agent/providers/openai_codex.py +34 -34
- package/server/services/cli_agent/service.py +245 -83
- package/server/services/cli_agent/session.py +409 -229
- package/server/services/cli_agent/transports/__init__.py +47 -0
- package/server/services/cli_agent/transports/base.py +111 -0
- package/server/services/cli_agent/transports/posix.py +196 -0
- package/server/services/cli_agent/transports/windows.py +189 -0
- package/server/services/cli_agent/types.py +45 -18
- package/server/services/cli_agent/workflow_tools.py +28 -15
- package/server/services/compaction.py +68 -52
- package/server/services/credential_registry.py +6 -20
- package/server/services/credentials/__init__.py +18 -0
- package/server/services/credentials/handlers.py +196 -0
- package/server/services/deployment/__init__.py +12 -1
- package/server/services/deployment/canary_registry.py +137 -0
- package/server/services/deployment/handlers.py +382 -0
- package/server/services/deployment/manager.py +653 -163
- package/server/services/deployment/poll_registry.py +2 -6
- package/server/services/deployment/state.py +2 -0
- package/server/services/deployment/triggers.py +87 -93
- package/server/services/event_waiter.py +47 -54
- package/server/services/events/__init__.py +11 -0
- package/server/services/events/admin_handlers.py +368 -0
- package/server/services/events/daemon.py +3 -1
- package/server/services/events/dispatch.py +188 -0
- package/server/services/events/envelope.py +264 -45
- package/server/services/events/oauth_lifecycle.py +98 -42
- package/server/services/events/triggers.py +3 -13
- package/server/services/events/verifiers/hmac_basic.py +1 -1
- package/server/services/events/verifiers/standard_webhooks.py +2 -4
- package/server/services/events/webhook.py +2 -3
- package/server/services/example_loader.py +73 -15
- package/server/services/execution/cache.py +36 -76
- package/server/services/execution/conditions.py +7 -20
- package/server/services/execution/dlq.py +20 -24
- package/server/services/execution/executor.py +234 -265
- package/server/services/execution/models.py +40 -46
- package/server/services/execution/recovery.py +23 -46
- package/server/services/handlers/__init__.py +12 -16
- package/server/services/handlers/todo.py +3 -6
- package/server/services/handlers/tools.py +143 -194
- package/server/services/handlers/triggers.py +24 -23
- package/server/services/llm/config.py +10 -1
- package/server/services/llm/factory.py +16 -4
- package/server/services/llm/messages.py +1 -5
- package/server/services/llm/protocol.py +9 -1
- package/server/services/llm/providers/anthropic.py +23 -12
- package/server/services/llm/providers/gemini.py +43 -22
- package/server/services/llm/providers/openai.py +14 -6
- package/server/services/llm/providers/openrouter.py +6 -1
- package/server/services/markdown_formatter.py +1 -2
- package/server/services/memory/__init__.py +2 -2
- package/server/services/memory/jsonl.py +6 -2
- package/server/services/memory/markdown.py +6 -6
- package/server/services/memory/state.py +6 -5
- package/server/services/memory_store.py +8 -12
- package/server/services/model_registry.py +22 -20
- package/server/services/node_executor.py +85 -80
- package/server/services/node_output_schemas.py +4 -7
- package/server/services/node_registry.py +40 -4
- package/server/services/node_spec.py +3 -7
- package/server/services/nodejs_client.py +4 -14
- package/server/services/oauth_utils.py +11 -7
- package/server/services/parameter_resolver.py +30 -36
- package/server/services/plugin/base.py +321 -38
- package/server/services/plugin/connection.py +12 -7
- package/server/services/plugin/credential.py +80 -22
- package/server/services/plugin/edge_walker.py +128 -105
- package/server/services/plugin/identifiers.py +48 -0
- package/server/services/plugin/interceptor.py +1 -1
- package/server/services/plugin/oauth.py +25 -21
- package/server/services/plugin/operation.py +1 -1
- package/server/services/plugin/polling.py +151 -26
- package/server/services/plugin/registry.py +52 -4
- package/server/services/plugin/routing.py +6 -9
- package/server/services/plugin/scaling.py +36 -18
- package/server/services/plugin/service_factories.py +95 -0
- package/server/services/plugin/shutdown_hooks.py +103 -0
- package/server/services/plugin/social_provider_registry.py +80 -0
- package/server/services/plugin/ws.py +2 -1
- package/server/services/pricing.py +26 -40
- package/server/services/pricing_handlers.py +90 -0
- package/server/services/process_service.py +33 -32
- package/server/services/proxy/models.py +15 -9
- package/server/services/proxy/service.py +26 -40
- package/server/services/rlm/adapters.py +43 -40
- package/server/services/rlm/constants.py +9 -9
- package/server/services/rlm/service.py +57 -45
- package/server/services/scheduler.py +8 -39
- package/server/services/settings/__init__.py +16 -0
- package/server/services/settings/handlers.py +275 -0
- package/server/services/skill_loader.py +53 -45
- package/server/services/skill_prompt.py +8 -6
- package/server/services/skills/__init__.py +23 -0
- package/server/services/skills/handlers.py +479 -0
- package/server/services/status_broadcaster.py +314 -291
- package/server/services/temporal/__init__.py +22 -1
- package/server/services/temporal/_handlers.py +65 -0
- package/server/services/temporal/_install.py +158 -0
- package/server/services/temporal/_refresh.py +57 -0
- package/server/services/temporal/_retry_policies.py +85 -0
- package/server/services/temporal/_runtime.py +181 -0
- package/server/services/temporal/_supervised_runtime.py +102 -0
- package/server/services/temporal/activities.py +168 -11
- package/server/services/temporal/agent_activities.py +683 -0
- package/server/services/temporal/agent_workflow.py +601 -0
- package/server/services/temporal/client.py +58 -13
- package/server/services/temporal/executor.py +2 -3
- package/server/services/temporal/plugin_activities.py +37 -2
- package/server/services/temporal/plugin_registry.py +82 -0
- package/server/services/temporal/polling_trigger_workflow.py +267 -0
- package/server/services/temporal/schedules.py +220 -0
- package/server/services/temporal/search_attributes.py +177 -0
- package/server/services/temporal/trigger_listener_workflow.py +378 -0
- package/server/services/temporal/worker.py +111 -18
- package/server/services/temporal/workflow.py +259 -40
- package/server/services/temporal/ws_client.py +22 -11
- package/server/services/text.py +14 -28
- package/server/services/tracked_http.py +29 -49
- package/server/services/user_auth.py +7 -21
- package/server/services/workflow.py +28 -20
- package/server/services/workflow_import.py +351 -0
- package/server/services/workflow_ops.py +4 -0
- package/server/services/workflow_storage/__init__.py +18 -0
- package/server/services/workflow_storage/handlers.py +132 -0
- package/server/services/workflow_validator.py +209 -0
- package/server/services/ws_handler_registry.py +80 -9
- package/server/skills/assistant/agent-builder-skill/SKILL.md +6 -6
- package/server/tests/conftest.py +54 -3
- package/server/tests/credentials/test_auth_service.py +9 -21
- package/server/tests/credentials/test_credential_broadcasts.py +116 -22
- package/server/tests/credentials/test_credentials_database.py +12 -38
- package/server/tests/credentials/test_encryption.py +3 -9
- package/server/tests/credentials/test_google_oauth.py +1 -3
- package/server/tests/credentials/test_oauth_utils.py +31 -38
- package/server/tests/credentials/test_twitter_oauth.py +1 -3
- package/server/tests/credentials/test_websocket_handlers.py +37 -72
- package/server/tests/fixtures/tool_names_snapshot.json +78 -0
- package/server/tests/llm/test_factory.py +12 -4
- package/server/tests/llm/test_providers.py +25 -32
- package/server/tests/llm/test_wiring.py +27 -22
- package/server/tests/nodes/_compat.py +4 -5
- package/server/tests/nodes/_harness.py +31 -24
- package/server/tests/nodes/_mocks.py +2 -6
- package/server/tests/nodes/test_agent_builder.py +43 -35
- package/server/tests/nodes/test_ai_agents.py +29 -24
- package/server/tests/nodes/test_ai_chat_models.py +3 -9
- package/server/tests/nodes/test_ai_tools.py +29 -24
- package/server/tests/nodes/test_android.py +34 -64
- package/server/tests/nodes/test_chat_utility.py +2 -2
- package/server/tests/nodes/test_code_fs_process.py +26 -84
- package/server/tests/nodes/test_document.py +23 -47
- package/server/tests/nodes/test_email.py +88 -51
- package/server/tests/nodes/test_google_workspace.py +26 -20
- package/server/tests/nodes/test_http_proxy.py +43 -89
- package/server/tests/nodes/test_search.py +3 -9
- package/server/tests/nodes/test_specialized_agents.py +58 -162
- package/server/tests/nodes/test_stripe_plugin.py +25 -5
- package/server/tests/nodes/test_telegram_social.py +33 -37
- package/server/tests/nodes/test_twitter.py +59 -150
- package/server/tests/nodes/test_web_automation.py +21 -51
- package/server/tests/nodes/test_whatsapp.py +13 -19
- package/server/tests/nodes/test_workflow_triggers.py +16 -45
- package/server/tests/services/cli_agent/test_claude_session_events.py +201 -0
- package/server/tests/services/cli_agent/test_jsonl_watcher.py +190 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +67 -29
- package/server/tests/services/cli_agent/test_providers.py +236 -47
- package/server/tests/services/cli_agent/test_service.py +9 -7
- package/server/tests/services/memory/test_jsonl.py +30 -25
- package/server/tests/services/test_events.py +26 -7
- package/server/tests/services/test_identifiers.py +122 -0
- package/server/tests/services/test_process_lifecycle.py +129 -0
- package/server/tests/services/test_supervisor.py +0 -1
- package/server/tests/temporal/__init__.py +0 -0
- package/server/tests/temporal/test_agent_workflow.py +215 -0
- package/server/tests/temporal/test_dispatch.py +231 -0
- package/server/tests/test_admin_handlers.py +394 -0
- package/server/tests/test_auto_skill.py +4 -2
- package/server/tests/test_canary_registry.py +310 -0
- package/server/tests/test_chat_trigger_canary_producer.py +101 -0
- package/server/tests/test_cloudevents_node_parameters.py +129 -0
- package/server/tests/test_credential_icon.py +115 -0
- package/server/tests/test_cron_canary.py +511 -0
- package/server/tests/test_deployment_canary_listener.py +692 -0
- package/server/tests/test_event_framework_phase_a.py +537 -0
- package/server/tests/test_no_raw_prints.py +131 -0
- package/server/tests/test_node_spec.py +196 -103
- package/server/tests/test_parameter_resolver.py +20 -20
- package/server/tests/test_plugin_contract.py +76 -49
- package/server/tests/test_plugin_helpers.py +0 -1
- package/server/tests/test_plugin_self_containment.py +40 -47
- package/server/tests/test_polling_trigger_workflow.py +572 -0
- package/server/tests/test_retry_policies.py +146 -0
- package/server/tests/test_service_factories.py +168 -0
- package/server/tests/test_shutdown_hooks.py +199 -0
- package/server/tests/test_social_provider_registry.py +177 -0
- package/server/tests/test_status_broadcasts.py +214 -63
- package/server/tests/test_task_trigger_canary_producer.py +131 -0
- package/server/tests/test_telegram_trigger_canary_producer.py +113 -0
- package/server/tests/test_tool_registry.py +110 -0
- package/server/tests/test_trigger_listener_workflow.py +365 -0
- package/server/tests/test_whatsapp_trigger_canary_producer.py +164 -0
- package/server/tests/test_workflow_ops.py +1 -3
- package/server/tests/test_workflow_validator.py +791 -0
- package/server/uv.lock +3539 -0
- package/client/dist/assets/index-DQ0nwhec.js +0 -257
- package/client/src/assets/icons/apify/index.ts +0 -19
- package/client/src/assets/icons/browser/index.ts +0 -17
- package/client/src/assets/icons/email/index.ts +0 -22
- package/client/src/assets/icons/google/index.ts +0 -34
- package/client/src/assets/icons/llm/deepseek.svg +0 -1
- package/client/src/assets/icons/llm/index.ts +0 -18
- package/client/src/assets/icons/llm/kimi.svg +0 -1
- package/client/src/assets/icons/llm/mistral.svg +0 -1
- package/client/src/assets/icons/search/index.ts +0 -28
- package/client/src/assets/icons/telegram/index.ts +0 -19
- package/machina/buildenv.py +0 -44
- package/machina/cli.py +0 -55
- package/machina/commands/__init__.py +0 -1
- package/machina/commands/clean.py +0 -80
- package/machina/commands/daemon.py +0 -150
- package/machina/config.py +0 -93
- package/machina/platform_.py +0 -37
- package/machina/pyproject.toml +0 -33
- package/server/nodes/agent/deep_agent.py +0 -103
- package/server/services/agents/__init__.py +0 -9
- package/server/services/agents/adapters.py +0 -199
- package/server/services/agents/constants.py +0 -10
- package/server/services/agents/service.py +0 -297
- package/server/services/cli_agent/providers/anthropic_claude.py +0 -419
- /package/{machina → cli}/README.md +0 -0
- /package/{machina → cli}/__init__.py +0 -0
- /package/{client/src/assets/icons/apify → server/credentials/icons}/apify.svg +0 -0
- /package/{client/src/assets/icons/search/brave.svg → server/credentials/icons/brave_search.svg} +0 -0
- /package/{client/src/assets/icons/email/read.svg → server/credentials/icons/email_himalaya.svg} +0 -0
- /package/{client/src/assets/icons/search → server/credentials/icons}/perplexity.svg +0 -0
- /package/{client/src/assets/icons/search/google.svg → server/credentials/icons/serper.svg} +0 -0
- /package/{client/src/assets → server/credentials}/icons/stripe.svg +0 -0
- /package/{client/src/assets/icons/twitter/x.svg → server/credentials/icons/twitter.svg} +0 -0
- /package/{client/src/assets/icons/browser/chrome.svg → server/nodes/browser/browser/icon.svg} +0 -0
- /package/{client/src/assets/icons/chat/chat.svg → server/nodes/chat/chat_history/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/javascript.svg → server/nodes/code/javascript_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/python.svg → server/nodes/code/python_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/typescript.svg → server/nodes/code/typescript_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/email/receive.svg → server/nodes/email/email_receive/icon.svg} +0 -0
- /package/{client/src/assets/icons/email/send.svg → server/nodes/email/email_send/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/calendar.svg → server/nodes/google/calendar/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/contacts.svg → server/nodes/google/contacts/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/drive.svg → server/nodes/google/drive/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/gmail.svg → server/nodes/google/gmail/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/sheets.svg → server/nodes/google/sheets/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/tasks.svg → server/nodes/google/tasks/icon.svg} +0 -0
- /package/{client/src/assets/icons/search/duckduckgo.svg → server/nodes/search/duckduckgo_search/icon.svg} +0 -0
- /package/{client/src/assets/icons/social/social.svg → server/nodes/social/social_receive/icon.svg} +0 -0
- /package/{client/src/assets/icons/telegram/telegram.svg → server/nodes/telegram/icon.svg} +0 -0
- /package/server/nodes/utility/{team_monitor.py → team_monitor/__init__.py} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-db.svg → server/nodes/whatsapp/icon_whatsappDb.svg} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-receive.svg → server/nodes/whatsapp/icon_whatsappReceive.svg} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-send.svg → server/nodes/whatsapp/icon_whatsappSend.svg} +0 -0
- /package/{client/src/assets/icons → server/nodes}/whatsapp/whatsapp.svg +0 -0
|
@@ -17,7 +17,7 @@ logger = get_logger(__name__)
|
|
|
17
17
|
|
|
18
18
|
def _is_invite_link(value: str) -> bool:
|
|
19
19
|
"""Check if a channel identifier is an invite link URL."""
|
|
20
|
-
return value.strip().startswith(
|
|
20
|
+
return value.strip().startswith("http://") or value.strip().startswith("https://")
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _resolve_channel_identifier(value: str) -> Dict[str, str]:
|
|
@@ -30,8 +30,8 @@ def _resolve_channel_identifier(value: str) -> Dict[str, str]:
|
|
|
30
30
|
return {}
|
|
31
31
|
value = value.strip()
|
|
32
32
|
if _is_invite_link(value):
|
|
33
|
-
return {
|
|
34
|
-
return {
|
|
33
|
+
return {"invite": value}
|
|
34
|
+
return {"jid": value}
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
async def _resolve_to_jid(value: str, rpc_call) -> str:
|
|
@@ -48,44 +48,40 @@ async def _resolve_to_jid(value: str, rpc_call) -> str:
|
|
|
48
48
|
if not _is_invite_link(value):
|
|
49
49
|
return value
|
|
50
50
|
# Resolve invite link to JID via newsletter_info
|
|
51
|
-
data = await rpc_call(
|
|
51
|
+
data = await rpc_call("newsletter_info", {"invite": value})
|
|
52
52
|
if isinstance(data, dict):
|
|
53
|
-
result = data.get(
|
|
54
|
-
jid = result.get(
|
|
53
|
+
result = data.get("result", data)
|
|
54
|
+
jid = result.get("jid") or result.get("id")
|
|
55
55
|
if jid:
|
|
56
56
|
return jid
|
|
57
57
|
raise ValueError(f"Could not resolve invite link to channel JID: {value}")
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
# Media types that can be downloaded via the media RPC
|
|
61
|
-
MEDIA_MESSAGE_TYPES = frozenset({
|
|
61
|
+
MEDIA_MESSAGE_TYPES = frozenset({"image", "video", "audio", "document", "sticker"})
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
async def _download_single_media(message: Dict[str, Any], rpc_call) -> None:
|
|
65
65
|
"""Download media for a single message in-place. Mutates the message dict."""
|
|
66
|
-
message_id = message.get(
|
|
66
|
+
message_id = message.get("message_id") or message.get("id")
|
|
67
67
|
if not message_id:
|
|
68
68
|
return
|
|
69
69
|
try:
|
|
70
|
-
data = await rpc_call(
|
|
70
|
+
data = await rpc_call("media", {"message_id": message_id})
|
|
71
71
|
if isinstance(data, dict):
|
|
72
|
-
result = data.get(
|
|
73
|
-
if result.get(
|
|
74
|
-
message[
|
|
75
|
-
message[
|
|
72
|
+
result = data.get("result", data)
|
|
73
|
+
if result.get("data"):
|
|
74
|
+
message["media_data"] = result["data"]
|
|
75
|
+
message["media_mime_type"] = result.get("mime_type", "")
|
|
76
76
|
else:
|
|
77
|
-
message[
|
|
77
|
+
message["media_error"] = result.get("error", "No media data returned")
|
|
78
78
|
else:
|
|
79
|
-
message[
|
|
79
|
+
message["media_error"] = "Unexpected response format"
|
|
80
80
|
except Exception as e:
|
|
81
|
-
message[
|
|
81
|
+
message["media_error"] = str(e)
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
async def _enrich_messages_with_media(
|
|
85
|
-
messages: List[Dict[str, Any]],
|
|
86
|
-
rpc_call,
|
|
87
|
-
max_concurrent: int = 5
|
|
88
|
-
) -> List[Dict[str, Any]]:
|
|
84
|
+
async def _enrich_messages_with_media(messages: List[Dict[str, Any]], rpc_call, max_concurrent: int = 5) -> List[Dict[str, Any]]:
|
|
89
85
|
"""Download media for messages that contain media types.
|
|
90
86
|
|
|
91
87
|
Uses asyncio.Semaphore for concurrency control and asyncio.gather
|
|
@@ -106,30 +102,19 @@ async def _enrich_messages_with_media(
|
|
|
106
102
|
await _download_single_media(msg, rpc_call)
|
|
107
103
|
|
|
108
104
|
# Filter to only media messages
|
|
109
|
-
media_messages = [
|
|
110
|
-
msg for msg in messages
|
|
111
|
-
if msg.get('message_type', msg.get('type', '')) in MEDIA_MESSAGE_TYPES
|
|
112
|
-
]
|
|
105
|
+
media_messages = [msg for msg in messages if msg.get("message_type", msg.get("type", "")) in MEDIA_MESSAGE_TYPES]
|
|
113
106
|
|
|
114
107
|
if not media_messages:
|
|
115
108
|
return messages
|
|
116
109
|
|
|
117
110
|
logger.info(f"Downloading media for {len(media_messages)} messages (max concurrent: {max_concurrent})")
|
|
118
111
|
|
|
119
|
-
await asyncio.gather(
|
|
120
|
-
*(download_with_semaphore(msg) for msg in media_messages),
|
|
121
|
-
return_exceptions=True
|
|
122
|
-
)
|
|
112
|
+
await asyncio.gather(*(download_with_semaphore(msg) for msg in media_messages), return_exceptions=True)
|
|
123
113
|
|
|
124
114
|
return messages
|
|
125
115
|
|
|
126
116
|
|
|
127
|
-
async def handle_whatsapp_send(
|
|
128
|
-
node_id: str,
|
|
129
|
-
node_type: str,
|
|
130
|
-
parameters: Dict[str, Any],
|
|
131
|
-
context: Dict[str, Any]
|
|
132
|
-
) -> Dict[str, Any]:
|
|
117
|
+
async def handle_whatsapp_send(node_id: str, node_type: str, parameters: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
|
|
133
118
|
"""Handle WhatsApp send message node via Go RPC service.
|
|
134
119
|
|
|
135
120
|
Supports all message types: text, image, video, audio, document, sticker, location, contact
|
|
@@ -146,49 +131,51 @@ async def handle_whatsapp_send(
|
|
|
146
131
|
Execution result dict
|
|
147
132
|
"""
|
|
148
133
|
from nodes.whatsapp._service import handle_whatsapp_send as whatsapp_send_handler
|
|
134
|
+
|
|
149
135
|
start_time = time.time()
|
|
150
136
|
|
|
151
137
|
try:
|
|
152
138
|
# Determine recipient (snake_case parameters)
|
|
153
|
-
recipient_type = parameters.get(
|
|
154
|
-
message_type = parameters.get(
|
|
139
|
+
recipient_type = parameters.get("recipient_type", "self")
|
|
140
|
+
message_type = parameters.get("message_type", "text")
|
|
155
141
|
|
|
156
142
|
# Determine recipient based on type
|
|
157
|
-
if recipient_type ==
|
|
143
|
+
if recipient_type == "self":
|
|
158
144
|
# Self will be resolved by the router using connected phone
|
|
159
|
-
recipient =
|
|
160
|
-
elif recipient_type ==
|
|
161
|
-
recipient = parameters.get(
|
|
145
|
+
recipient = "self"
|
|
146
|
+
elif recipient_type == "channel":
|
|
147
|
+
recipient = parameters.get("channel_jid")
|
|
162
148
|
if not recipient:
|
|
163
149
|
raise ValueError("Channel JID is required")
|
|
164
150
|
# Validate channel-supported message types
|
|
165
|
-
channel_types = {
|
|
151
|
+
channel_types = {"text", "image", "video", "audio", "document"}
|
|
166
152
|
if message_type not in channel_types:
|
|
167
153
|
raise ValueError(f"Channels only support: {', '.join(sorted(channel_types))}. Got: {message_type}")
|
|
168
|
-
elif recipient_type ==
|
|
169
|
-
recipient = parameters.get(
|
|
154
|
+
elif recipient_type == "group":
|
|
155
|
+
recipient = parameters.get("group_id")
|
|
170
156
|
if not recipient:
|
|
171
157
|
raise ValueError("Group ID is required")
|
|
172
158
|
else: # phone
|
|
173
|
-
recipient = parameters.get(
|
|
159
|
+
recipient = parameters.get("phone")
|
|
174
160
|
if not recipient:
|
|
175
161
|
raise ValueError("Phone number is required")
|
|
176
162
|
|
|
177
163
|
# For text messages, validate message content
|
|
178
|
-
if message_type ==
|
|
164
|
+
if message_type == "text" and not parameters.get("message"):
|
|
179
165
|
raise ValueError("Message content is required for text messages")
|
|
180
166
|
|
|
181
167
|
# Convert GFM markdown to WhatsApp-native formatting if enabled
|
|
182
|
-
if message_type ==
|
|
168
|
+
if message_type == "text" and parameters.get("format_markdown", False):
|
|
183
169
|
from services.markdown_formatter import to_whatsapp
|
|
184
|
-
|
|
170
|
+
|
|
171
|
+
parameters["message"] = to_whatsapp(parameters["message"])
|
|
185
172
|
|
|
186
173
|
# Call WhatsApp Go RPC service via handler - pass full params
|
|
187
174
|
data = await whatsapp_send_handler(parameters)
|
|
188
175
|
|
|
189
|
-
success = data.get(
|
|
176
|
+
success = data.get("success", False)
|
|
190
177
|
if not success:
|
|
191
|
-
raise Exception(data.get(
|
|
178
|
+
raise Exception(data.get("error", "Send failed"))
|
|
192
179
|
|
|
193
180
|
# Build informative result based on message type (snake_case output)
|
|
194
181
|
result = {
|
|
@@ -196,37 +183,37 @@ async def handle_whatsapp_send(
|
|
|
196
183
|
"recipient": recipient,
|
|
197
184
|
"recipient_type": recipient_type,
|
|
198
185
|
"message_type": message_type,
|
|
199
|
-
"timestamp": datetime.now().isoformat()
|
|
186
|
+
"timestamp": datetime.now().isoformat(),
|
|
200
187
|
}
|
|
201
188
|
|
|
202
189
|
# Add type-specific details using match statement
|
|
203
190
|
match message_type:
|
|
204
|
-
case
|
|
205
|
-
msg_content = parameters.get(
|
|
191
|
+
case "text":
|
|
192
|
+
msg_content = parameters.get("message", "")
|
|
206
193
|
result["preview"] = msg_content[:100] + "..." if len(msg_content) > 100 else msg_content
|
|
207
|
-
case
|
|
208
|
-
media_source = parameters.get(
|
|
194
|
+
case "image" | "video" | "audio" | "document" | "sticker":
|
|
195
|
+
media_source = parameters.get("media_source", "base64")
|
|
209
196
|
result["media_source"] = media_source
|
|
210
|
-
if parameters.get(
|
|
211
|
-
result["caption"] = parameters.get(
|
|
212
|
-
if parameters.get(
|
|
213
|
-
result["filename"] = parameters.get(
|
|
214
|
-
if parameters.get(
|
|
215
|
-
result["mime_type"] = parameters.get(
|
|
197
|
+
if parameters.get("caption"):
|
|
198
|
+
result["caption"] = parameters.get("caption")
|
|
199
|
+
if parameters.get("filename"):
|
|
200
|
+
result["filename"] = parameters.get("filename")
|
|
201
|
+
if parameters.get("mime_type"):
|
|
202
|
+
result["mime_type"] = parameters.get("mime_type")
|
|
216
203
|
# For file uploads, include the uploaded filename
|
|
217
|
-
file_param = parameters.get(
|
|
218
|
-
if isinstance(file_param, dict) and file_param.get(
|
|
219
|
-
result["uploaded_file"] = file_param.get(
|
|
220
|
-
result["mime_type"] = file_param.get(
|
|
221
|
-
case
|
|
204
|
+
file_param = parameters.get("file_path")
|
|
205
|
+
if isinstance(file_param, dict) and file_param.get("type") == "upload":
|
|
206
|
+
result["uploaded_file"] = file_param.get("filename")
|
|
207
|
+
result["mime_type"] = file_param.get("mimeType")
|
|
208
|
+
case "location":
|
|
222
209
|
result["location"] = {
|
|
223
|
-
"latitude": parameters.get(
|
|
224
|
-
"longitude": parameters.get(
|
|
225
|
-
"name": parameters.get(
|
|
226
|
-
"address": parameters.get(
|
|
210
|
+
"latitude": parameters.get("latitude"),
|
|
211
|
+
"longitude": parameters.get("longitude"),
|
|
212
|
+
"name": parameters.get("location_name"),
|
|
213
|
+
"address": parameters.get("address"),
|
|
227
214
|
}
|
|
228
|
-
case
|
|
229
|
-
result["contact_name"] = parameters.get(
|
|
215
|
+
case "contact":
|
|
216
|
+
result["contact_name"] = parameters.get("contact_name")
|
|
230
217
|
|
|
231
218
|
return {
|
|
232
219
|
"success": success,
|
|
@@ -234,7 +221,7 @@ async def handle_whatsapp_send(
|
|
|
234
221
|
"node_type": "whatsappSend",
|
|
235
222
|
"result": result,
|
|
236
223
|
"execution_time": time.time() - start_time,
|
|
237
|
-
"timestamp": datetime.now().isoformat()
|
|
224
|
+
"timestamp": datetime.now().isoformat(),
|
|
238
225
|
}
|
|
239
226
|
|
|
240
227
|
except Exception as e:
|
|
@@ -245,16 +232,11 @@ async def handle_whatsapp_send(
|
|
|
245
232
|
"node_type": "whatsappSend",
|
|
246
233
|
"error": str(e),
|
|
247
234
|
"execution_time": time.time() - start_time,
|
|
248
|
-
"timestamp": datetime.now().isoformat()
|
|
235
|
+
"timestamp": datetime.now().isoformat(),
|
|
249
236
|
}
|
|
250
237
|
|
|
251
238
|
|
|
252
|
-
async def handle_whatsapp_db(
|
|
253
|
-
node_id: str,
|
|
254
|
-
node_type: str,
|
|
255
|
-
parameters: Dict[str, Any],
|
|
256
|
-
context: Dict[str, Any]
|
|
257
|
-
) -> Dict[str, Any]:
|
|
239
|
+
async def handle_whatsapp_db(node_id: str, node_type: str, parameters: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
|
|
258
240
|
"""Handle WhatsApp DB node - query contacts, groups, messages.
|
|
259
241
|
|
|
260
242
|
Operations:
|
|
@@ -289,105 +271,103 @@ async def handle_whatsapp_db(
|
|
|
289
271
|
Returns:
|
|
290
272
|
Execution result dict
|
|
291
273
|
"""
|
|
292
|
-
from nodes.whatsapp._service import
|
|
293
|
-
|
|
294
|
-
whatsapp_rpc_call
|
|
295
|
-
)
|
|
274
|
+
from nodes.whatsapp._service import handle_whatsapp_chat_history as whatsapp_chat_history_handler, whatsapp_rpc_call
|
|
275
|
+
|
|
296
276
|
start_time = time.time()
|
|
297
277
|
|
|
298
278
|
try:
|
|
299
|
-
operation = parameters.get(
|
|
279
|
+
operation = parameters.get("operation", "chat_history")
|
|
300
280
|
|
|
301
|
-
if operation ==
|
|
281
|
+
if operation == "chat_history":
|
|
302
282
|
return await _handle_chat_history(node_id, parameters, start_time, whatsapp_chat_history_handler, whatsapp_rpc_call)
|
|
303
|
-
elif operation ==
|
|
283
|
+
elif operation == "search_groups":
|
|
304
284
|
return await _handle_search_groups(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
305
|
-
elif operation ==
|
|
285
|
+
elif operation == "get_group_info":
|
|
306
286
|
return await _handle_get_group_info(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
307
|
-
elif operation ==
|
|
287
|
+
elif operation == "get_contact_info":
|
|
308
288
|
return await _handle_get_contact_info(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
309
|
-
elif operation ==
|
|
289
|
+
elif operation == "list_contacts":
|
|
310
290
|
return await _handle_list_contacts(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
311
|
-
elif operation ==
|
|
291
|
+
elif operation == "check_contacts":
|
|
312
292
|
return await _handle_check_contacts(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
313
|
-
elif operation ==
|
|
293
|
+
elif operation == "list_channels":
|
|
314
294
|
return await _handle_list_channels(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
315
|
-
elif operation ==
|
|
295
|
+
elif operation == "get_channel_info":
|
|
316
296
|
return await _handle_get_channel_info(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
317
|
-
elif operation ==
|
|
297
|
+
elif operation == "channel_messages":
|
|
318
298
|
return await _handle_channel_messages(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
319
|
-
elif operation ==
|
|
299
|
+
elif operation == "channel_stats":
|
|
320
300
|
return await _handle_channel_stats(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
321
|
-
elif operation ==
|
|
301
|
+
elif operation == "channel_follow":
|
|
322
302
|
return await _handle_channel_follow(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
323
|
-
elif operation ==
|
|
303
|
+
elif operation == "channel_unfollow":
|
|
324
304
|
return await _handle_channel_unfollow(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
325
|
-
elif operation ==
|
|
305
|
+
elif operation == "channel_create":
|
|
326
306
|
return await _handle_channel_create(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
327
|
-
elif operation ==
|
|
307
|
+
elif operation == "channel_mute":
|
|
328
308
|
return await _handle_channel_mute(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
329
|
-
elif operation ==
|
|
309
|
+
elif operation == "channel_mark_viewed":
|
|
330
310
|
return await _handle_channel_mark_viewed(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
331
|
-
elif operation ==
|
|
311
|
+
elif operation == "newsletter_react":
|
|
332
312
|
return await _handle_newsletter_react(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
333
|
-
elif operation ==
|
|
313
|
+
elif operation == "newsletter_live_updates":
|
|
334
314
|
return await _handle_newsletter_live_updates(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
335
|
-
elif operation ==
|
|
315
|
+
elif operation == "contact_profile_pic":
|
|
336
316
|
return await _handle_contact_profile_pic(node_id, parameters, start_time, whatsapp_rpc_call)
|
|
337
317
|
else:
|
|
338
318
|
raise ValueError(f"Unknown operation: {operation}")
|
|
339
319
|
|
|
340
320
|
except Exception as e:
|
|
341
|
-
logger.error("WhatsApp DB failed", node_id=node_id, operation=parameters.get(
|
|
321
|
+
logger.error("WhatsApp DB failed", node_id=node_id, operation=parameters.get("operation"), error=str(e))
|
|
342
322
|
return {
|
|
343
323
|
"success": False,
|
|
344
324
|
"node_id": node_id,
|
|
345
325
|
"node_type": "whatsappDb",
|
|
346
326
|
"error": str(e),
|
|
347
327
|
"execution_time": time.time() - start_time,
|
|
348
|
-
"timestamp": datetime.now().isoformat()
|
|
328
|
+
"timestamp": datetime.now().isoformat(),
|
|
349
329
|
}
|
|
350
330
|
|
|
351
331
|
|
|
352
332
|
async def _handle_chat_history(node_id: str, parameters: Dict[str, Any], start_time: float, handler, rpc_call=None) -> Dict[str, Any]:
|
|
353
333
|
"""Handle chat_history operation."""
|
|
354
|
-
chat_type = parameters.get(
|
|
334
|
+
chat_type = parameters.get("chat_type", "individual")
|
|
355
335
|
rpc_params: Dict[str, Any] = {}
|
|
356
336
|
|
|
357
|
-
if chat_type ==
|
|
358
|
-
phone = parameters.get(
|
|
337
|
+
if chat_type == "individual":
|
|
338
|
+
phone = parameters.get("phone")
|
|
359
339
|
if not phone:
|
|
360
340
|
raise ValueError("Phone number is required for individual chats")
|
|
361
|
-
rpc_params[
|
|
341
|
+
rpc_params["phone"] = phone
|
|
362
342
|
else:
|
|
363
|
-
group_id = parameters.get(
|
|
343
|
+
group_id = parameters.get("group_id")
|
|
364
344
|
if not group_id:
|
|
365
345
|
raise ValueError("Group ID is required for group chats")
|
|
366
|
-
rpc_params[
|
|
346
|
+
rpc_params["group_id"] = group_id
|
|
367
347
|
|
|
368
|
-
group_filter = parameters.get(
|
|
369
|
-
if group_filter ==
|
|
370
|
-
sender_phone = parameters.get(
|
|
348
|
+
group_filter = parameters.get("group_filter", "all")
|
|
349
|
+
if group_filter == "contact":
|
|
350
|
+
sender_phone = parameters.get("sender_phone")
|
|
371
351
|
if sender_phone:
|
|
372
|
-
rpc_params[
|
|
352
|
+
rpc_params["sender_phone"] = sender_phone
|
|
373
353
|
|
|
374
|
-
message_filter = parameters.get(
|
|
375
|
-
rpc_params[
|
|
376
|
-
rpc_params[
|
|
377
|
-
rpc_params[
|
|
354
|
+
message_filter = parameters.get("message_filter", "all")
|
|
355
|
+
rpc_params["text_only"] = message_filter == "text_only"
|
|
356
|
+
rpc_params["limit"] = parameters.get("limit", 50)
|
|
357
|
+
rpc_params["offset"] = parameters.get("offset", 0)
|
|
378
358
|
|
|
379
359
|
data = await handler(rpc_params)
|
|
380
360
|
|
|
381
|
-
if not data.get(
|
|
382
|
-
raise Exception(data.get(
|
|
361
|
+
if not data.get("success", False):
|
|
362
|
+
raise Exception(data.get("error", "Failed to retrieve chat history"))
|
|
383
363
|
|
|
384
|
-
messages = data.get(
|
|
385
|
-
base_offset = rpc_params.get(
|
|
364
|
+
messages = data.get("messages", [])
|
|
365
|
+
base_offset = rpc_params.get("offset", 0)
|
|
386
366
|
for i, msg in enumerate(messages):
|
|
387
|
-
msg[
|
|
367
|
+
msg["index"] = base_offset + i + 1
|
|
388
368
|
|
|
389
369
|
# Enrich with media data if requested
|
|
390
|
-
if parameters.get(
|
|
370
|
+
if parameters.get("include_media_data") and rpc_call and messages:
|
|
391
371
|
await _enrich_messages_with_media(messages, rpc_call)
|
|
392
372
|
|
|
393
373
|
return {
|
|
@@ -397,41 +377,38 @@ async def _handle_chat_history(node_id: str, parameters: Dict[str, Any], start_t
|
|
|
397
377
|
"result": {
|
|
398
378
|
"operation": "chat_history",
|
|
399
379
|
"messages": messages,
|
|
400
|
-
"total": data.get(
|
|
401
|
-
"has_more": data.get(
|
|
380
|
+
"total": data.get("total", 0),
|
|
381
|
+
"has_more": data.get("has_more", False),
|
|
402
382
|
"count": len(messages),
|
|
403
383
|
"chat_type": chat_type,
|
|
404
|
-
"timestamp": datetime.now().isoformat()
|
|
384
|
+
"timestamp": datetime.now().isoformat(),
|
|
405
385
|
},
|
|
406
386
|
"execution_time": time.time() - start_time,
|
|
407
|
-
"timestamp": datetime.now().isoformat()
|
|
387
|
+
"timestamp": datetime.now().isoformat(),
|
|
408
388
|
}
|
|
409
389
|
|
|
410
390
|
|
|
411
391
|
async def _handle_search_groups(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
412
392
|
"""Handle search_groups operation."""
|
|
413
|
-
query = parameters.get(
|
|
414
|
-
limit = parameters.get(
|
|
415
|
-
data = await rpc_call(
|
|
393
|
+
query = parameters.get("query", "")
|
|
394
|
+
limit = parameters.get("limit", 20) # Default limit to prevent context overflow
|
|
395
|
+
data = await rpc_call("groups", {})
|
|
416
396
|
|
|
417
|
-
if not data.get(
|
|
418
|
-
raise Exception(data.get(
|
|
397
|
+
if not data.get("success", True):
|
|
398
|
+
raise Exception(data.get("error", "Failed to get groups"))
|
|
419
399
|
|
|
420
|
-
groups = data if isinstance(data, list) else data.get(
|
|
400
|
+
groups = data if isinstance(data, list) else data.get("result", [])
|
|
421
401
|
|
|
422
402
|
# Filter by query if provided
|
|
423
403
|
if query:
|
|
424
404
|
query_lower = query.lower()
|
|
425
|
-
groups = [g for g in groups if query_lower in g.get(
|
|
405
|
+
groups = [g for g in groups if query_lower in g.get("name", "").lower()]
|
|
426
406
|
|
|
427
407
|
total_found = len(groups)
|
|
428
408
|
|
|
429
409
|
# Apply limit to prevent context overflow (51 groups * ~4KB = 200KB+ tokens)
|
|
430
410
|
# Only return essential fields: jid and name
|
|
431
|
-
groups_limited = [
|
|
432
|
-
{"jid": g.get("jid", ""), "name": g.get("name", "")}
|
|
433
|
-
for g in groups[:limit]
|
|
434
|
-
]
|
|
411
|
+
groups_limited = [{"jid": g.get("jid", ""), "name": g.get("name", "")} for g in groups[:limit]]
|
|
435
412
|
|
|
436
413
|
return {
|
|
437
414
|
"success": True,
|
|
@@ -444,31 +421,33 @@ async def _handle_search_groups(node_id: str, parameters: Dict[str, Any], start_
|
|
|
444
421
|
"returned": len(groups_limited),
|
|
445
422
|
"has_more": total_found > limit,
|
|
446
423
|
"query": query,
|
|
447
|
-
"hint": f"Showing {len(groups_limited)} of {total_found} groups. Use a more specific query or get_group_info for details."
|
|
448
|
-
|
|
424
|
+
"hint": f"Showing {len(groups_limited)} of {total_found} groups. Use a more specific query or get_group_info for details."
|
|
425
|
+
if total_found > limit
|
|
426
|
+
else None,
|
|
427
|
+
"timestamp": datetime.now().isoformat(),
|
|
449
428
|
},
|
|
450
429
|
"execution_time": time.time() - start_time,
|
|
451
|
-
"timestamp": datetime.now().isoformat()
|
|
430
|
+
"timestamp": datetime.now().isoformat(),
|
|
452
431
|
}
|
|
453
432
|
|
|
454
433
|
|
|
455
434
|
async def _handle_get_group_info(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
456
435
|
"""Handle get_group_info operation."""
|
|
457
|
-
group_id = parameters.get(
|
|
436
|
+
group_id = parameters.get("group_id_for_info") or parameters.get("group_id")
|
|
458
437
|
if not group_id:
|
|
459
438
|
raise ValueError("Group ID is required")
|
|
460
439
|
|
|
461
|
-
participant_limit = parameters.get(
|
|
440
|
+
participant_limit = parameters.get("participant_limit", 50) # Limit participants to prevent overflow
|
|
462
441
|
|
|
463
|
-
data = await rpc_call(
|
|
442
|
+
data = await rpc_call("group_info", {"group_id": group_id})
|
|
464
443
|
|
|
465
|
-
if not data.get(
|
|
466
|
-
raise Exception(data.get(
|
|
444
|
+
if not data.get("success", True) if isinstance(data, dict) else True:
|
|
445
|
+
raise Exception(data.get("error", "Failed to get group info") if isinstance(data, dict) else "Failed")
|
|
467
446
|
|
|
468
|
-
result = data if not isinstance(data, dict) or
|
|
447
|
+
result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
|
|
469
448
|
|
|
470
449
|
# Limit participants and return only essential fields
|
|
471
|
-
participants = result.get(
|
|
450
|
+
participants = result.get("participants", [])
|
|
472
451
|
total_participants = len(participants)
|
|
473
452
|
participants_limited = [
|
|
474
453
|
{"phone": p.get("phone", ""), "name": p.get("name", ""), "is_admin": p.get("is_admin", False)}
|
|
@@ -491,63 +470,52 @@ async def _handle_get_group_info(node_id: str, parameters: Dict[str, Any], start
|
|
|
491
470
|
"success": True,
|
|
492
471
|
"node_id": node_id,
|
|
493
472
|
"node_type": "whatsappDb",
|
|
494
|
-
"result": {
|
|
495
|
-
"operation": "get_group_info",
|
|
496
|
-
**limited_result,
|
|
497
|
-
"timestamp": datetime.now().isoformat()
|
|
498
|
-
},
|
|
473
|
+
"result": {"operation": "get_group_info", **limited_result, "timestamp": datetime.now().isoformat()},
|
|
499
474
|
"execution_time": time.time() - start_time,
|
|
500
|
-
"timestamp": datetime.now().isoformat()
|
|
475
|
+
"timestamp": datetime.now().isoformat(),
|
|
501
476
|
}
|
|
502
477
|
|
|
503
478
|
|
|
504
479
|
async def _handle_get_contact_info(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
505
480
|
"""Handle get_contact_info operation."""
|
|
506
|
-
phone = parameters.get(
|
|
481
|
+
phone = parameters.get("contact_phone") or parameters.get("phone")
|
|
507
482
|
if not phone:
|
|
508
483
|
raise ValueError("Phone number is required")
|
|
509
484
|
|
|
510
|
-
data = await rpc_call(
|
|
485
|
+
data = await rpc_call("contact_info", {"phone": phone})
|
|
511
486
|
|
|
512
|
-
if isinstance(data, dict) and not data.get(
|
|
513
|
-
raise Exception(data.get(
|
|
487
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
488
|
+
raise Exception(data.get("error", "Failed to get contact info"))
|
|
514
489
|
|
|
515
|
-
result = data if not isinstance(data, dict) or
|
|
490
|
+
result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
|
|
516
491
|
|
|
517
492
|
return {
|
|
518
493
|
"success": True,
|
|
519
494
|
"node_id": node_id,
|
|
520
495
|
"node_type": "whatsappDb",
|
|
521
|
-
"result": {
|
|
522
|
-
"operation": "get_contact_info",
|
|
523
|
-
**result,
|
|
524
|
-
"timestamp": datetime.now().isoformat()
|
|
525
|
-
},
|
|
496
|
+
"result": {"operation": "get_contact_info", **result, "timestamp": datetime.now().isoformat()},
|
|
526
497
|
"execution_time": time.time() - start_time,
|
|
527
|
-
"timestamp": datetime.now().isoformat()
|
|
498
|
+
"timestamp": datetime.now().isoformat(),
|
|
528
499
|
}
|
|
529
500
|
|
|
530
501
|
|
|
531
502
|
async def _handle_list_contacts(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
532
503
|
"""Handle list_contacts operation."""
|
|
533
|
-
query = parameters.get(
|
|
534
|
-
limit = parameters.get(
|
|
504
|
+
query = parameters.get("query", "")
|
|
505
|
+
limit = parameters.get("limit", 50) # Default limit to prevent context overflow
|
|
535
506
|
|
|
536
|
-
data = await rpc_call(
|
|
507
|
+
data = await rpc_call("contacts", {"query": query})
|
|
537
508
|
|
|
538
|
-
if isinstance(data, dict) and not data.get(
|
|
539
|
-
raise Exception(data.get(
|
|
509
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
510
|
+
raise Exception(data.get("error", "Failed to list contacts"))
|
|
540
511
|
|
|
541
|
-
result = data if not isinstance(data, dict) or
|
|
542
|
-
contacts = result.get(
|
|
512
|
+
result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
|
|
513
|
+
contacts = result.get("contacts", []) if isinstance(result, dict) else result
|
|
543
514
|
|
|
544
515
|
total_found = len(contacts)
|
|
545
516
|
|
|
546
517
|
# Apply limit and return only essential fields: phone, name, jid
|
|
547
|
-
contacts_limited = [
|
|
548
|
-
{"phone": c.get("phone", ""), "name": c.get("name", ""), "jid": c.get("jid", "")}
|
|
549
|
-
for c in contacts[:limit]
|
|
550
|
-
]
|
|
518
|
+
contacts_limited = [{"phone": c.get("phone", ""), "name": c.get("name", ""), "jid": c.get("jid", "")} for c in contacts[:limit]]
|
|
551
519
|
|
|
552
520
|
return {
|
|
553
521
|
"success": True,
|
|
@@ -560,64 +528,61 @@ async def _handle_list_contacts(node_id: str, parameters: Dict[str, Any], start_
|
|
|
560
528
|
"returned": len(contacts_limited),
|
|
561
529
|
"has_more": total_found > limit,
|
|
562
530
|
"query": query,
|
|
563
|
-
"hint": f"Showing {len(contacts_limited)} of {total_found} contacts. Use a more specific query to narrow results."
|
|
564
|
-
|
|
531
|
+
"hint": f"Showing {len(contacts_limited)} of {total_found} contacts. Use a more specific query to narrow results."
|
|
532
|
+
if total_found > limit
|
|
533
|
+
else None,
|
|
534
|
+
"timestamp": datetime.now().isoformat(),
|
|
565
535
|
},
|
|
566
536
|
"execution_time": time.time() - start_time,
|
|
567
|
-
"timestamp": datetime.now().isoformat()
|
|
537
|
+
"timestamp": datetime.now().isoformat(),
|
|
568
538
|
}
|
|
569
539
|
|
|
570
540
|
|
|
571
541
|
async def _handle_check_contacts(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
572
542
|
"""Handle check_contacts operation."""
|
|
573
|
-
phones_str = parameters.get(
|
|
543
|
+
phones_str = parameters.get("phones", "")
|
|
574
544
|
if not phones_str:
|
|
575
545
|
raise ValueError("Phone numbers are required")
|
|
576
546
|
|
|
577
547
|
# Parse comma-separated phones
|
|
578
|
-
phones = [p.strip() for p in phones_str.split(
|
|
548
|
+
phones = [p.strip() for p in phones_str.split(",") if p.strip()]
|
|
579
549
|
if not phones:
|
|
580
550
|
raise ValueError("At least one phone number is required")
|
|
581
551
|
|
|
582
|
-
data = await rpc_call(
|
|
552
|
+
data = await rpc_call("contact_check", {"phones": phones})
|
|
583
553
|
|
|
584
|
-
if isinstance(data, dict) and not data.get(
|
|
585
|
-
raise Exception(data.get(
|
|
554
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
555
|
+
raise Exception(data.get("error", "Failed to check contacts"))
|
|
586
556
|
|
|
587
|
-
results = data if isinstance(data, list) else data.get(
|
|
557
|
+
results = data if isinstance(data, list) else data.get("result", [])
|
|
588
558
|
|
|
589
559
|
return {
|
|
590
560
|
"success": True,
|
|
591
561
|
"node_id": node_id,
|
|
592
562
|
"node_type": "whatsappDb",
|
|
593
|
-
"result": {
|
|
594
|
-
"operation": "check_contacts",
|
|
595
|
-
"results": results,
|
|
596
|
-
"total": len(results),
|
|
597
|
-
"timestamp": datetime.now().isoformat()
|
|
598
|
-
},
|
|
563
|
+
"result": {"operation": "check_contacts", "results": results, "total": len(results), "timestamp": datetime.now().isoformat()},
|
|
599
564
|
"execution_time": time.time() - start_time,
|
|
600
|
-
"timestamp": datetime.now().isoformat()
|
|
565
|
+
"timestamp": datetime.now().isoformat(),
|
|
601
566
|
}
|
|
602
567
|
|
|
603
568
|
|
|
604
569
|
async def _handle_contact_profile_pic(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
605
570
|
"""Handle contact_profile_pic operation - get profile picture for a contact/group."""
|
|
606
|
-
jid = parameters.get(
|
|
571
|
+
jid = parameters.get("profile_pic_jid") or parameters.get("phone")
|
|
607
572
|
if not jid:
|
|
608
573
|
raise ValueError("JID or phone number is required")
|
|
609
574
|
|
|
610
|
-
rpc_params: Dict[str, Any] = {
|
|
611
|
-
preview = parameters.get(
|
|
575
|
+
rpc_params: Dict[str, Any] = {"jid": jid}
|
|
576
|
+
preview = parameters.get("preview", False)
|
|
612
577
|
if preview:
|
|
613
|
-
rpc_params[
|
|
578
|
+
rpc_params["preview"] = True
|
|
614
579
|
|
|
615
|
-
data = await rpc_call(
|
|
580
|
+
data = await rpc_call("contact_profile_pic", rpc_params)
|
|
616
581
|
|
|
617
|
-
if isinstance(data, dict) and not data.get(
|
|
618
|
-
raise Exception(data.get(
|
|
582
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
583
|
+
raise Exception(data.get("error", "Failed to get profile picture"))
|
|
619
584
|
|
|
620
|
-
result = data if not isinstance(data, dict) or
|
|
585
|
+
result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
|
|
621
586
|
|
|
622
587
|
return {
|
|
623
588
|
"success": True,
|
|
@@ -626,34 +591,33 @@ async def _handle_contact_profile_pic(node_id: str, parameters: Dict[str, Any],
|
|
|
626
591
|
"result": {
|
|
627
592
|
"operation": "contact_profile_pic",
|
|
628
593
|
**(result if isinstance(result, dict) else {"url": result}),
|
|
629
|
-
"timestamp": datetime.now().isoformat()
|
|
594
|
+
"timestamp": datetime.now().isoformat(),
|
|
630
595
|
},
|
|
631
596
|
"execution_time": time.time() - start_time,
|
|
632
|
-
"timestamp": datetime.now().isoformat()
|
|
597
|
+
"timestamp": datetime.now().isoformat(),
|
|
633
598
|
}
|
|
634
599
|
|
|
635
600
|
|
|
636
601
|
async def _handle_list_channels(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
637
602
|
"""Handle list_channels operation - list subscribed newsletter channels."""
|
|
638
|
-
refresh = parameters.get(
|
|
639
|
-
limit = parameters.get(
|
|
603
|
+
refresh = parameters.get("refresh", False)
|
|
604
|
+
limit = parameters.get("limit", 20)
|
|
640
605
|
|
|
641
606
|
rpc_params: Dict[str, Any] = {}
|
|
642
607
|
if refresh:
|
|
643
|
-
rpc_params[
|
|
608
|
+
rpc_params["refresh"] = True
|
|
644
609
|
|
|
645
|
-
data = await rpc_call(
|
|
610
|
+
data = await rpc_call("newsletters", rpc_params)
|
|
646
611
|
|
|
647
|
-
if isinstance(data, dict) and not data.get(
|
|
648
|
-
raise Exception(data.get(
|
|
612
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
613
|
+
raise Exception(data.get("error", "Failed to list channels"))
|
|
649
614
|
|
|
650
|
-
channels = data if isinstance(data, list) else data.get(
|
|
615
|
+
channels = data if isinstance(data, list) else data.get("result", [])
|
|
651
616
|
total_found = len(channels)
|
|
652
617
|
|
|
653
618
|
# Return essential fields only
|
|
654
619
|
channels_limited = [
|
|
655
|
-
{"jid": c.get("jid", ""), "name": c.get("name", ""), "subscriber_count": c.get("subscriber_count", 0)}
|
|
656
|
-
for c in channels[:limit]
|
|
620
|
+
{"jid": c.get("jid", ""), "name": c.get("name", ""), "subscriber_count": c.get("subscriber_count", 0)} for c in channels[:limit]
|
|
657
621
|
]
|
|
658
622
|
|
|
659
623
|
return {
|
|
@@ -667,41 +631,37 @@ async def _handle_list_channels(node_id: str, parameters: Dict[str, Any], start_
|
|
|
667
631
|
"returned": len(channels_limited),
|
|
668
632
|
"has_more": total_found > limit,
|
|
669
633
|
"hint": f"Showing {len(channels_limited)} of {total_found} channels." if total_found > limit else None,
|
|
670
|
-
"timestamp": datetime.now().isoformat()
|
|
634
|
+
"timestamp": datetime.now().isoformat(),
|
|
671
635
|
},
|
|
672
636
|
"execution_time": time.time() - start_time,
|
|
673
|
-
"timestamp": datetime.now().isoformat()
|
|
637
|
+
"timestamp": datetime.now().isoformat(),
|
|
674
638
|
}
|
|
675
639
|
|
|
676
640
|
|
|
677
641
|
async def _handle_get_channel_info(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
678
642
|
"""Handle get_channel_info operation - get channel details."""
|
|
679
|
-
channel_jid = parameters.get(
|
|
643
|
+
channel_jid = parameters.get("channel_jid")
|
|
680
644
|
if not channel_jid:
|
|
681
645
|
raise ValueError("Channel JID is required")
|
|
682
646
|
|
|
683
647
|
rpc_params: Dict[str, Any] = _resolve_channel_identifier(channel_jid)
|
|
684
|
-
if parameters.get(
|
|
685
|
-
rpc_params[
|
|
648
|
+
if parameters.get("refresh"):
|
|
649
|
+
rpc_params["refresh"] = True
|
|
686
650
|
|
|
687
|
-
data = await rpc_call(
|
|
651
|
+
data = await rpc_call("newsletter_info", rpc_params)
|
|
688
652
|
|
|
689
|
-
if isinstance(data, dict) and not data.get(
|
|
690
|
-
raise Exception(data.get(
|
|
653
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
654
|
+
raise Exception(data.get("error", "Failed to get channel info"))
|
|
691
655
|
|
|
692
|
-
result = data if not isinstance(data, dict) or
|
|
656
|
+
result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
|
|
693
657
|
|
|
694
658
|
return {
|
|
695
659
|
"success": True,
|
|
696
660
|
"node_id": node_id,
|
|
697
661
|
"node_type": "whatsappDb",
|
|
698
|
-
"result": {
|
|
699
|
-
"operation": "get_channel_info",
|
|
700
|
-
**result,
|
|
701
|
-
"timestamp": datetime.now().isoformat()
|
|
702
|
-
},
|
|
662
|
+
"result": {"operation": "get_channel_info", **result, "timestamp": datetime.now().isoformat()},
|
|
703
663
|
"execution_time": time.time() - start_time,
|
|
704
|
-
"timestamp": datetime.now().isoformat()
|
|
664
|
+
"timestamp": datetime.now().isoformat(),
|
|
705
665
|
}
|
|
706
666
|
|
|
707
667
|
|
|
@@ -711,53 +671,53 @@ async def _handle_channel_messages(node_id: str, parameters: Dict[str, Any], sta
|
|
|
711
671
|
Schema params (newsletter_messages RPC):
|
|
712
672
|
jid (required), count, offset, before, since, until, media_type, search, refresh
|
|
713
673
|
"""
|
|
714
|
-
channel_jid = parameters.get(
|
|
674
|
+
channel_jid = parameters.get("channel_jid")
|
|
715
675
|
jid = await _resolve_to_jid(channel_jid, rpc_call)
|
|
716
676
|
|
|
717
|
-
count = parameters.get(
|
|
718
|
-
rpc_params: Dict[str, Any] = {
|
|
677
|
+
count = parameters.get("channel_count", 20)
|
|
678
|
+
rpc_params: Dict[str, Any] = {"jid": jid, "count": count}
|
|
719
679
|
|
|
720
|
-
before_server_id = parameters.get(
|
|
680
|
+
before_server_id = parameters.get("before_server_id")
|
|
721
681
|
if before_server_id:
|
|
722
|
-
rpc_params[
|
|
682
|
+
rpc_params["before"] = int(before_server_id)
|
|
723
683
|
|
|
724
684
|
# Pagination offset
|
|
725
|
-
msg_offset = parameters.get(
|
|
685
|
+
msg_offset = parameters.get("message_offset")
|
|
726
686
|
if msg_offset:
|
|
727
|
-
rpc_params[
|
|
687
|
+
rpc_params["offset"] = int(msg_offset)
|
|
728
688
|
|
|
729
689
|
# Date range filters (unix timestamps as strings)
|
|
730
|
-
since = parameters.get(
|
|
690
|
+
since = parameters.get("since")
|
|
731
691
|
if since:
|
|
732
|
-
rpc_params[
|
|
692
|
+
rpc_params["since"] = str(since)
|
|
733
693
|
|
|
734
|
-
until = parameters.get(
|
|
694
|
+
until = parameters.get("until")
|
|
735
695
|
if until:
|
|
736
|
-
rpc_params[
|
|
696
|
+
rpc_params["until"] = str(until)
|
|
737
697
|
|
|
738
698
|
# Media type filter
|
|
739
|
-
media_type = parameters.get(
|
|
740
|
-
if media_type and media_type !=
|
|
741
|
-
rpc_params[
|
|
699
|
+
media_type = parameters.get("media_type")
|
|
700
|
+
if media_type and media_type != "all":
|
|
701
|
+
rpc_params["media_type"] = media_type
|
|
742
702
|
|
|
743
703
|
# Text search
|
|
744
|
-
search = parameters.get(
|
|
704
|
+
search = parameters.get("search")
|
|
745
705
|
if search:
|
|
746
|
-
rpc_params[
|
|
706
|
+
rpc_params["search"] = search
|
|
747
707
|
|
|
748
708
|
# Force refresh bypassing cache
|
|
749
|
-
if parameters.get(
|
|
750
|
-
rpc_params[
|
|
709
|
+
if parameters.get("refresh"):
|
|
710
|
+
rpc_params["refresh"] = True
|
|
751
711
|
|
|
752
|
-
data = await rpc_call(
|
|
712
|
+
data = await rpc_call("newsletter_messages", rpc_params)
|
|
753
713
|
|
|
754
|
-
if isinstance(data, dict) and not data.get(
|
|
755
|
-
raise Exception(data.get(
|
|
714
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
715
|
+
raise Exception(data.get("error", "Failed to get channel messages"))
|
|
756
716
|
|
|
757
|
-
messages = data if isinstance(data, list) else data.get(
|
|
717
|
+
messages = data if isinstance(data, list) else data.get("result", [])
|
|
758
718
|
|
|
759
719
|
# Enrich with media data if requested
|
|
760
|
-
if parameters.get(
|
|
720
|
+
if parameters.get("include_media_data") and messages:
|
|
761
721
|
await _enrich_messages_with_media(messages, rpc_call)
|
|
762
722
|
|
|
763
723
|
return {
|
|
@@ -769,53 +729,48 @@ async def _handle_channel_messages(node_id: str, parameters: Dict[str, Any], sta
|
|
|
769
729
|
"messages": messages,
|
|
770
730
|
"count": len(messages),
|
|
771
731
|
"channel_jid": channel_jid,
|
|
772
|
-
"timestamp": datetime.now().isoformat()
|
|
732
|
+
"timestamp": datetime.now().isoformat(),
|
|
773
733
|
},
|
|
774
734
|
"execution_time": time.time() - start_time,
|
|
775
|
-
"timestamp": datetime.now().isoformat()
|
|
735
|
+
"timestamp": datetime.now().isoformat(),
|
|
776
736
|
}
|
|
777
737
|
|
|
778
738
|
|
|
779
739
|
async def _handle_channel_stats(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
780
740
|
"""Handle channel_stats operation - get channel subscriber/view stats."""
|
|
781
|
-
channel_jid = parameters.get(
|
|
741
|
+
channel_jid = parameters.get("channel_jid")
|
|
782
742
|
if not channel_jid:
|
|
783
743
|
raise ValueError("Channel JID is required")
|
|
784
744
|
|
|
785
|
-
count = parameters.get(
|
|
786
|
-
rpc_params: Dict[str, Any] = {**_resolve_channel_identifier(channel_jid),
|
|
745
|
+
count = parameters.get("channel_count", 10)
|
|
746
|
+
rpc_params: Dict[str, Any] = {**_resolve_channel_identifier(channel_jid), "count": count}
|
|
787
747
|
|
|
788
|
-
data = await rpc_call(
|
|
748
|
+
data = await rpc_call("newsletter_stats", rpc_params)
|
|
789
749
|
|
|
790
|
-
if isinstance(data, dict) and not data.get(
|
|
791
|
-
raise Exception(data.get(
|
|
750
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
751
|
+
raise Exception(data.get("error", "Failed to get channel stats"))
|
|
792
752
|
|
|
793
|
-
result = data if not isinstance(data, dict) or
|
|
753
|
+
result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
|
|
794
754
|
|
|
795
755
|
return {
|
|
796
756
|
"success": True,
|
|
797
757
|
"node_id": node_id,
|
|
798
758
|
"node_type": "whatsappDb",
|
|
799
|
-
"result": {
|
|
800
|
-
"operation": "channel_stats",
|
|
801
|
-
**result,
|
|
802
|
-
"channel_jid": channel_jid,
|
|
803
|
-
"timestamp": datetime.now().isoformat()
|
|
804
|
-
},
|
|
759
|
+
"result": {"operation": "channel_stats", **result, "channel_jid": channel_jid, "timestamp": datetime.now().isoformat()},
|
|
805
760
|
"execution_time": time.time() - start_time,
|
|
806
|
-
"timestamp": datetime.now().isoformat()
|
|
761
|
+
"timestamp": datetime.now().isoformat(),
|
|
807
762
|
}
|
|
808
763
|
|
|
809
764
|
|
|
810
765
|
async def _handle_channel_follow(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
811
766
|
"""Handle channel_follow operation - follow/subscribe to a channel."""
|
|
812
|
-
channel_jid = parameters.get(
|
|
767
|
+
channel_jid = parameters.get("channel_jid")
|
|
813
768
|
jid = await _resolve_to_jid(channel_jid, rpc_call)
|
|
814
769
|
|
|
815
|
-
data = await rpc_call(
|
|
770
|
+
data = await rpc_call("newsletter_follow", {"jid": jid})
|
|
816
771
|
|
|
817
|
-
if isinstance(data, dict) and not data.get(
|
|
818
|
-
raise Exception(data.get(
|
|
772
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
773
|
+
raise Exception(data.get("error", "Failed to follow channel"))
|
|
819
774
|
|
|
820
775
|
return {
|
|
821
776
|
"success": True,
|
|
@@ -825,22 +780,22 @@ async def _handle_channel_follow(node_id: str, parameters: Dict[str, Any], start
|
|
|
825
780
|
"operation": "channel_follow",
|
|
826
781
|
"channel_jid": channel_jid,
|
|
827
782
|
"status": "followed",
|
|
828
|
-
"timestamp": datetime.now().isoformat()
|
|
783
|
+
"timestamp": datetime.now().isoformat(),
|
|
829
784
|
},
|
|
830
785
|
"execution_time": time.time() - start_time,
|
|
831
|
-
"timestamp": datetime.now().isoformat()
|
|
786
|
+
"timestamp": datetime.now().isoformat(),
|
|
832
787
|
}
|
|
833
788
|
|
|
834
789
|
|
|
835
790
|
async def _handle_channel_unfollow(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
836
791
|
"""Handle channel_unfollow operation - unfollow/unsubscribe from a channel."""
|
|
837
|
-
channel_jid = parameters.get(
|
|
792
|
+
channel_jid = parameters.get("channel_jid")
|
|
838
793
|
jid = await _resolve_to_jid(channel_jid, rpc_call)
|
|
839
794
|
|
|
840
|
-
data = await rpc_call(
|
|
795
|
+
data = await rpc_call("newsletter_unfollow", {"jid": jid})
|
|
841
796
|
|
|
842
|
-
if isinstance(data, dict) and not data.get(
|
|
843
|
-
raise Exception(data.get(
|
|
797
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
798
|
+
raise Exception(data.get("error", "Failed to unfollow channel"))
|
|
844
799
|
|
|
845
800
|
return {
|
|
846
801
|
"success": True,
|
|
@@ -850,60 +805,56 @@ async def _handle_channel_unfollow(node_id: str, parameters: Dict[str, Any], sta
|
|
|
850
805
|
"operation": "channel_unfollow",
|
|
851
806
|
"channel_jid": channel_jid,
|
|
852
807
|
"status": "unfollowed",
|
|
853
|
-
"timestamp": datetime.now().isoformat()
|
|
808
|
+
"timestamp": datetime.now().isoformat(),
|
|
854
809
|
},
|
|
855
810
|
"execution_time": time.time() - start_time,
|
|
856
|
-
"timestamp": datetime.now().isoformat()
|
|
811
|
+
"timestamp": datetime.now().isoformat(),
|
|
857
812
|
}
|
|
858
813
|
|
|
859
814
|
|
|
860
815
|
async def _handle_channel_create(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
861
816
|
"""Handle channel_create operation - create a new newsletter channel."""
|
|
862
|
-
channel_name = parameters.get(
|
|
817
|
+
channel_name = parameters.get("channel_name")
|
|
863
818
|
if not channel_name:
|
|
864
819
|
raise ValueError("Channel name is required")
|
|
865
820
|
|
|
866
|
-
rpc_params: Dict[str, Any] = {
|
|
867
|
-
description = parameters.get(
|
|
821
|
+
rpc_params: Dict[str, Any] = {"name": channel_name}
|
|
822
|
+
description = parameters.get("channel_description")
|
|
868
823
|
if description:
|
|
869
|
-
rpc_params[
|
|
870
|
-
picture = parameters.get(
|
|
824
|
+
rpc_params["description"] = description
|
|
825
|
+
picture = parameters.get("picture")
|
|
871
826
|
if picture:
|
|
872
|
-
rpc_params[
|
|
827
|
+
rpc_params["picture"] = picture
|
|
873
828
|
|
|
874
|
-
data = await rpc_call(
|
|
829
|
+
data = await rpc_call("newsletter_create", rpc_params)
|
|
875
830
|
|
|
876
|
-
if isinstance(data, dict) and not data.get(
|
|
877
|
-
raise Exception(data.get(
|
|
831
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
832
|
+
raise Exception(data.get("error", "Failed to create channel"))
|
|
878
833
|
|
|
879
|
-
result = data if not isinstance(data, dict) or
|
|
834
|
+
result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
|
|
880
835
|
|
|
881
836
|
return {
|
|
882
837
|
"success": True,
|
|
883
838
|
"node_id": node_id,
|
|
884
839
|
"node_type": "whatsappDb",
|
|
885
|
-
"result": {
|
|
886
|
-
"operation": "channel_create",
|
|
887
|
-
**result,
|
|
888
|
-
"timestamp": datetime.now().isoformat()
|
|
889
|
-
},
|
|
840
|
+
"result": {"operation": "channel_create", **result, "timestamp": datetime.now().isoformat()},
|
|
890
841
|
"execution_time": time.time() - start_time,
|
|
891
|
-
"timestamp": datetime.now().isoformat()
|
|
842
|
+
"timestamp": datetime.now().isoformat(),
|
|
892
843
|
}
|
|
893
844
|
|
|
894
845
|
|
|
895
846
|
async def _handle_channel_mute(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
896
847
|
"""Handle channel_mute operation - mute/unmute a newsletter channel."""
|
|
897
|
-
channel_jid = parameters.get(
|
|
848
|
+
channel_jid = parameters.get("channel_jid")
|
|
898
849
|
jid = await _resolve_to_jid(channel_jid, rpc_call)
|
|
899
850
|
|
|
900
|
-
mute = parameters.get(
|
|
901
|
-
rpc_params = {
|
|
851
|
+
mute = parameters.get("mute", True)
|
|
852
|
+
rpc_params = {"jid": jid, "mute": bool(mute)}
|
|
902
853
|
|
|
903
|
-
data = await rpc_call(
|
|
854
|
+
data = await rpc_call("newsletter_mute", rpc_params)
|
|
904
855
|
|
|
905
|
-
if isinstance(data, dict) and not data.get(
|
|
906
|
-
raise Exception(data.get(
|
|
856
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
857
|
+
raise Exception(data.get("error", "Failed to mute/unmute channel"))
|
|
907
858
|
|
|
908
859
|
return {
|
|
909
860
|
"success": True,
|
|
@@ -914,32 +865,32 @@ async def _handle_channel_mute(node_id: str, parameters: Dict[str, Any], start_t
|
|
|
914
865
|
"channel_jid": channel_jid,
|
|
915
866
|
"muted": mute,
|
|
916
867
|
"status": "muted" if mute else "unmuted",
|
|
917
|
-
"timestamp": datetime.now().isoformat()
|
|
868
|
+
"timestamp": datetime.now().isoformat(),
|
|
918
869
|
},
|
|
919
870
|
"execution_time": time.time() - start_time,
|
|
920
|
-
"timestamp": datetime.now().isoformat()
|
|
871
|
+
"timestamp": datetime.now().isoformat(),
|
|
921
872
|
}
|
|
922
873
|
|
|
923
874
|
|
|
924
875
|
async def _handle_channel_mark_viewed(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
925
876
|
"""Handle channel_mark_viewed operation - mark channel messages as viewed."""
|
|
926
|
-
channel_jid = parameters.get(
|
|
877
|
+
channel_jid = parameters.get("channel_jid")
|
|
927
878
|
jid = await _resolve_to_jid(channel_jid, rpc_call)
|
|
928
879
|
|
|
929
|
-
server_ids_raw = parameters.get(
|
|
880
|
+
server_ids_raw = parameters.get("server_ids", "")
|
|
930
881
|
if not server_ids_raw:
|
|
931
882
|
raise ValueError("server_ids is required (comma-separated message IDs)")
|
|
932
883
|
|
|
933
|
-
server_ids = [int(sid.strip()) for sid in str(server_ids_raw).split(
|
|
884
|
+
server_ids = [int(sid.strip()) for sid in str(server_ids_raw).split(",") if sid.strip()]
|
|
934
885
|
if not server_ids:
|
|
935
886
|
raise ValueError("At least one server_id is required")
|
|
936
887
|
|
|
937
|
-
rpc_params = {
|
|
888
|
+
rpc_params = {"jid": jid, "server_ids": server_ids}
|
|
938
889
|
|
|
939
|
-
data = await rpc_call(
|
|
890
|
+
data = await rpc_call("newsletter_mark_viewed", rpc_params)
|
|
940
891
|
|
|
941
|
-
if isinstance(data, dict) and not data.get(
|
|
942
|
-
raise Exception(data.get(
|
|
892
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
893
|
+
raise Exception(data.get("error", "Failed to mark channel as viewed"))
|
|
943
894
|
|
|
944
895
|
return {
|
|
945
896
|
"success": True,
|
|
@@ -948,36 +899,32 @@ async def _handle_channel_mark_viewed(node_id: str, parameters: Dict[str, Any],
|
|
|
948
899
|
"result": {
|
|
949
900
|
"operation": "channel_mark_viewed",
|
|
950
901
|
"channel_jid": channel_jid,
|
|
951
|
-
"server_ids":
|
|
902
|
+
"server_ids": ",".join(str(s) for s in server_ids),
|
|
952
903
|
"status": "marked_viewed",
|
|
953
|
-
"timestamp": datetime.now().isoformat()
|
|
904
|
+
"timestamp": datetime.now().isoformat(),
|
|
954
905
|
},
|
|
955
906
|
"execution_time": time.time() - start_time,
|
|
956
|
-
"timestamp": datetime.now().isoformat()
|
|
907
|
+
"timestamp": datetime.now().isoformat(),
|
|
957
908
|
}
|
|
958
909
|
|
|
959
910
|
|
|
960
911
|
async def _handle_newsletter_react(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
961
912
|
"""Handle newsletter_react operation - react to a channel message."""
|
|
962
|
-
channel_jid = parameters.get(
|
|
913
|
+
channel_jid = parameters.get("channel_jid")
|
|
963
914
|
jid = await _resolve_to_jid(channel_jid, rpc_call)
|
|
964
915
|
|
|
965
|
-
server_id = parameters.get(
|
|
916
|
+
server_id = parameters.get("react_server_id")
|
|
966
917
|
if not server_id:
|
|
967
918
|
raise ValueError("Message server ID is required")
|
|
968
919
|
|
|
969
|
-
reaction = parameters.get(
|
|
920
|
+
reaction = parameters.get("reaction", "")
|
|
970
921
|
|
|
971
|
-
rpc_params: Dict[str, Any] = {
|
|
972
|
-
'jid': jid,
|
|
973
|
-
'server_id': int(server_id),
|
|
974
|
-
'reaction': reaction
|
|
975
|
-
}
|
|
922
|
+
rpc_params: Dict[str, Any] = {"jid": jid, "server_id": int(server_id), "reaction": reaction}
|
|
976
923
|
|
|
977
|
-
data = await rpc_call(
|
|
924
|
+
data = await rpc_call("newsletter_react", rpc_params)
|
|
978
925
|
|
|
979
|
-
if isinstance(data, dict) and not data.get(
|
|
980
|
-
raise Exception(data.get(
|
|
926
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
927
|
+
raise Exception(data.get("error", "Failed to react to channel message"))
|
|
981
928
|
|
|
982
929
|
return {
|
|
983
930
|
"success": True,
|
|
@@ -988,34 +935,34 @@ async def _handle_newsletter_react(node_id: str, parameters: Dict[str, Any], sta
|
|
|
988
935
|
"channel_jid": channel_jid,
|
|
989
936
|
"server_id": int(server_id),
|
|
990
937
|
"reaction": reaction,
|
|
991
|
-
"timestamp": datetime.now().isoformat()
|
|
938
|
+
"timestamp": datetime.now().isoformat(),
|
|
992
939
|
},
|
|
993
940
|
"execution_time": time.time() - start_time,
|
|
994
|
-
"timestamp": datetime.now().isoformat()
|
|
941
|
+
"timestamp": datetime.now().isoformat(),
|
|
995
942
|
}
|
|
996
943
|
|
|
997
944
|
|
|
998
945
|
async def _handle_newsletter_live_updates(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
|
|
999
946
|
"""Handle newsletter_live_updates operation - subscribe to live view/reaction counts."""
|
|
1000
|
-
channel_jid = parameters.get(
|
|
947
|
+
channel_jid = parameters.get("channel_jid")
|
|
1001
948
|
jid = await _resolve_to_jid(channel_jid, rpc_call)
|
|
1002
949
|
|
|
1003
|
-
server_ids_raw = parameters.get(
|
|
950
|
+
server_ids_raw = parameters.get("server_ids", "")
|
|
1004
951
|
if not server_ids_raw:
|
|
1005
952
|
raise ValueError("server_ids is required (comma-separated message IDs)")
|
|
1006
953
|
|
|
1007
|
-
server_ids = [int(sid.strip()) for sid in str(server_ids_raw).split(
|
|
954
|
+
server_ids = [int(sid.strip()) for sid in str(server_ids_raw).split(",") if sid.strip()]
|
|
1008
955
|
if not server_ids:
|
|
1009
956
|
raise ValueError("At least one server_id is required")
|
|
1010
957
|
|
|
1011
|
-
rpc_params = {
|
|
958
|
+
rpc_params = {"jid": jid, "server_ids": server_ids}
|
|
1012
959
|
|
|
1013
|
-
data = await rpc_call(
|
|
960
|
+
data = await rpc_call("newsletter_live_updates", rpc_params)
|
|
1014
961
|
|
|
1015
|
-
if isinstance(data, dict) and not data.get(
|
|
1016
|
-
raise Exception(data.get(
|
|
962
|
+
if isinstance(data, dict) and not data.get("success", True):
|
|
963
|
+
raise Exception(data.get("error", "Failed to subscribe to live updates"))
|
|
1017
964
|
|
|
1018
|
-
result = data if not isinstance(data, dict) or
|
|
965
|
+
result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
|
|
1019
966
|
|
|
1020
967
|
return {
|
|
1021
968
|
"success": True,
|
|
@@ -1025,10 +972,8 @@ async def _handle_newsletter_live_updates(node_id: str, parameters: Dict[str, An
|
|
|
1025
972
|
"operation": "newsletter_live_updates",
|
|
1026
973
|
"channel_jid": channel_jid,
|
|
1027
974
|
**(result if isinstance(result, dict) else {}),
|
|
1028
|
-
"timestamp": datetime.now().isoformat()
|
|
975
|
+
"timestamp": datetime.now().isoformat(),
|
|
1029
976
|
},
|
|
1030
977
|
"execution_time": time.time() - start_time,
|
|
1031
|
-
"timestamp": datetime.now().isoformat()
|
|
978
|
+
"timestamp": datetime.now().isoformat(),
|
|
1032
979
|
}
|
|
1033
|
-
|
|
1034
|
-
|