machinaos 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +71 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/cli.js +159 -0
- package/client/.dockerignore +45 -0
- package/client/Dockerfile +68 -0
- package/client/eslint.config.js +29 -0
- package/client/index.html +13 -0
- package/client/nginx.conf +66 -0
- package/client/package.json +48 -0
- package/client/src/App.tsx +27 -0
- package/client/src/Dashboard.tsx +1173 -0
- package/client/src/ParameterPanel.tsx +301 -0
- package/client/src/components/AIAgentNode.tsx +321 -0
- package/client/src/components/APIKeyValidator.tsx +118 -0
- package/client/src/components/ClaudeChatModelNode.tsx +18 -0
- package/client/src/components/ConditionalEdge.tsx +189 -0
- package/client/src/components/CredentialsModal.tsx +306 -0
- package/client/src/components/EdgeConditionEditor.tsx +443 -0
- package/client/src/components/GeminiChatModelNode.tsx +18 -0
- package/client/src/components/GenericNode.tsx +357 -0
- package/client/src/components/LocationParameterPanel.tsx +154 -0
- package/client/src/components/ModelNode.tsx +286 -0
- package/client/src/components/OpenAIChatModelNode.tsx +18 -0
- package/client/src/components/OutputPanel.tsx +471 -0
- package/client/src/components/ParameterRenderer.tsx +1874 -0
- package/client/src/components/SkillEditorModal.tsx +417 -0
- package/client/src/components/SquareNode.tsx +797 -0
- package/client/src/components/StartNode.tsx +250 -0
- package/client/src/components/ToolkitNode.tsx +365 -0
- package/client/src/components/TriggerNode.tsx +463 -0
- package/client/src/components/auth/LoginPage.tsx +247 -0
- package/client/src/components/auth/ProtectedRoute.tsx +59 -0
- package/client/src/components/base/BaseChatModelNode.tsx +271 -0
- package/client/src/components/icons/AIProviderIcons.tsx +50 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
- package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
- package/client/src/components/maps/index.ts +26 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
- package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
- package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
- package/client/src/components/parameterPanel/index.ts +42 -0
- package/client/src/components/shared/DataPanel.tsx +142 -0
- package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
- package/client/src/components/ui/AIResultModal.tsx +204 -0
- package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -0
- package/client/src/components/ui/CollapsibleSection.tsx +88 -0
- package/client/src/components/ui/ComponentItem.tsx +154 -0
- package/client/src/components/ui/ComponentPalette.tsx +321 -0
- package/client/src/components/ui/ConsolePanel.tsx +1074 -0
- package/client/src/components/ui/ErrorBoundary.tsx +196 -0
- package/client/src/components/ui/InputNodesPanel.tsx +204 -0
- package/client/src/components/ui/MapSelector.tsx +314 -0
- package/client/src/components/ui/Modal.tsx +149 -0
- package/client/src/components/ui/NodeContextMenu.tsx +192 -0
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
- package/client/src/components/ui/SettingsPanel.tsx +243 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -0
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
- package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
- package/client/src/config/antdTheme.ts +186 -0
- package/client/src/config/api.ts +54 -0
- package/client/src/contexts/AuthContext.tsx +221 -0
- package/client/src/contexts/ThemeContext.tsx +42 -0
- package/client/src/contexts/WebSocketContext.tsx +1971 -0
- package/client/src/factories/baseChatModelFactory.ts +256 -0
- package/client/src/hooks/useAndroidOperations.ts +164 -0
- package/client/src/hooks/useApiKeyValidation.ts +107 -0
- package/client/src/hooks/useApiKeys.ts +238 -0
- package/client/src/hooks/useAppTheme.ts +17 -0
- package/client/src/hooks/useComponentPalette.ts +51 -0
- package/client/src/hooks/useCopyPaste.ts +155 -0
- package/client/src/hooks/useDragAndDrop.ts +124 -0
- package/client/src/hooks/useDragVariable.ts +88 -0
- package/client/src/hooks/useExecution.ts +313 -0
- package/client/src/hooks/useParameterPanel.ts +176 -0
- package/client/src/hooks/useReactFlowNodes.ts +189 -0
- package/client/src/hooks/useToolSchema.ts +209 -0
- package/client/src/hooks/useWhatsApp.ts +196 -0
- package/client/src/hooks/useWorkflowManagement.ts +46 -0
- package/client/src/index.css +315 -0
- package/client/src/main.tsx +19 -0
- package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
- package/client/src/nodeDefinitions/chatNodes.ts +135 -0
- package/client/src/nodeDefinitions/codeNodes.ts +54 -0
- package/client/src/nodeDefinitions/documentNodes.ts +379 -0
- package/client/src/nodeDefinitions/index.ts +15 -0
- package/client/src/nodeDefinitions/locationNodes.ts +463 -0
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
- package/client/src/nodeDefinitions/skillNodes.ts +211 -0
- package/client/src/nodeDefinitions/toolNodes.ts +198 -0
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
- package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
- package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
- package/client/src/nodeDefinitions.ts +104 -0
- package/client/src/schemas/workflowSchema.ts +264 -0
- package/client/src/services/dynamicParameterService.ts +96 -0
- package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
- package/client/src/services/executionService.ts +232 -0
- package/client/src/services/workflowApi.ts +91 -0
- package/client/src/store/useAppStore.ts +582 -0
- package/client/src/styles/theme.ts +508 -0
- package/client/src/styles/zIndex.ts +17 -0
- package/client/src/types/ComponentTypes.ts +39 -0
- package/client/src/types/EdgeCondition.ts +231 -0
- package/client/src/types/INodeProperties.ts +288 -0
- package/client/src/types/NodeTypes.ts +28 -0
- package/client/src/utils/formatters.ts +33 -0
- package/client/src/utils/googleMapsLoader.ts +140 -0
- package/client/src/utils/locationUtils.ts +85 -0
- package/client/src/utils/nodeUtils.ts +31 -0
- package/client/src/utils/workflow.ts +30 -0
- package/client/src/utils/workflowExport.ts +120 -0
- package/client/src/vite-env.d.ts +12 -0
- package/client/tailwind.config.js +60 -0
- package/client/tsconfig.json +25 -0
- package/client/tsconfig.node.json +11 -0
- package/client/vite.config.js +35 -0
- package/docker-compose.prod.yml +107 -0
- package/docker-compose.yml +104 -0
- package/docs-MachinaOs/README.md +85 -0
- package/docs-MachinaOs/deployment/docker.mdx +228 -0
- package/docs-MachinaOs/deployment/production.mdx +345 -0
- package/docs-MachinaOs/docs.json +75 -0
- package/docs-MachinaOs/faq.mdx +309 -0
- package/docs-MachinaOs/favicon.svg +5 -0
- package/docs-MachinaOs/installation.mdx +160 -0
- package/docs-MachinaOs/introduction.mdx +114 -0
- package/docs-MachinaOs/logo/dark.svg +6 -0
- package/docs-MachinaOs/logo/light.svg +6 -0
- package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
- package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
- package/docs-MachinaOs/nodes/android.mdx +411 -0
- package/docs-MachinaOs/nodes/overview.mdx +181 -0
- package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
- package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
- package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
- package/docs-MachinaOs/quickstart.mdx +119 -0
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
- package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
- package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
- package/nul +0 -0
- package/package.json +70 -0
- package/scripts/build.js +158 -0
- package/scripts/check-ports.ps1 +33 -0
- package/scripts/clean.js +40 -0
- package/scripts/docker.js +93 -0
- package/scripts/kill-port.ps1 +154 -0
- package/scripts/start.js +210 -0
- package/scripts/stop.js +325 -0
- package/server/.dockerignore +44 -0
- package/server/Dockerfile +45 -0
- package/server/constants.py +249 -0
- package/server/core/__init__.py +1 -0
- package/server/core/cache.py +461 -0
- package/server/core/config.py +128 -0
- package/server/core/container.py +99 -0
- package/server/core/database.py +1211 -0
- package/server/core/logging.py +314 -0
- package/server/main.py +289 -0
- package/server/middleware/__init__.py +5 -0
- package/server/middleware/auth.py +89 -0
- package/server/models/__init__.py +1 -0
- package/server/models/auth.py +52 -0
- package/server/models/cache.py +24 -0
- package/server/models/database.py +211 -0
- package/server/models/nodes.py +455 -0
- package/server/package.json +9 -0
- package/server/pyproject.toml +72 -0
- package/server/requirements.txt +83 -0
- package/server/routers/__init__.py +1 -0
- package/server/routers/android.py +294 -0
- package/server/routers/auth.py +203 -0
- package/server/routers/database.py +151 -0
- package/server/routers/maps.py +142 -0
- package/server/routers/nodejs_compat.py +289 -0
- package/server/routers/webhook.py +90 -0
- package/server/routers/websocket.py +2127 -0
- package/server/routers/whatsapp.py +761 -0
- package/server/routers/workflow.py +200 -0
- package/server/services/__init__.py +1 -0
- package/server/services/ai.py +2415 -0
- package/server/services/android/__init__.py +27 -0
- package/server/services/android/broadcaster.py +114 -0
- package/server/services/android/client.py +608 -0
- package/server/services/android/manager.py +78 -0
- package/server/services/android/protocol.py +165 -0
- package/server/services/android_service.py +588 -0
- package/server/services/auth.py +131 -0
- package/server/services/chat_client.py +160 -0
- package/server/services/deployment/__init__.py +12 -0
- package/server/services/deployment/manager.py +706 -0
- package/server/services/deployment/state.py +47 -0
- package/server/services/deployment/triggers.py +275 -0
- package/server/services/event_waiter.py +785 -0
- package/server/services/execution/__init__.py +77 -0
- package/server/services/execution/cache.py +769 -0
- package/server/services/execution/conditions.py +373 -0
- package/server/services/execution/dlq.py +132 -0
- package/server/services/execution/executor.py +1351 -0
- package/server/services/execution/models.py +531 -0
- package/server/services/execution/recovery.py +235 -0
- package/server/services/handlers/__init__.py +126 -0
- package/server/services/handlers/ai.py +355 -0
- package/server/services/handlers/android.py +260 -0
- package/server/services/handlers/code.py +278 -0
- package/server/services/handlers/document.py +598 -0
- package/server/services/handlers/http.py +193 -0
- package/server/services/handlers/polyglot.py +105 -0
- package/server/services/handlers/tools.py +845 -0
- package/server/services/handlers/triggers.py +107 -0
- package/server/services/handlers/utility.py +822 -0
- package/server/services/handlers/whatsapp.py +476 -0
- package/server/services/maps.py +289 -0
- package/server/services/memory_store.py +103 -0
- package/server/services/node_executor.py +375 -0
- package/server/services/parameter_resolver.py +218 -0
- package/server/services/polyglot_client.py +169 -0
- package/server/services/scheduler.py +155 -0
- package/server/services/skill_loader.py +417 -0
- package/server/services/status_broadcaster.py +826 -0
- package/server/services/temporal/__init__.py +23 -0
- package/server/services/temporal/activities.py +344 -0
- package/server/services/temporal/client.py +76 -0
- package/server/services/temporal/executor.py +147 -0
- package/server/services/temporal/worker.py +251 -0
- package/server/services/temporal/workflow.py +355 -0
- package/server/services/temporal/ws_client.py +236 -0
- package/server/services/text.py +111 -0
- package/server/services/user_auth.py +172 -0
- package/server/services/websocket_client.py +29 -0
- package/server/services/workflow.py +597 -0
- package/server/skills/android-skill/SKILL.md +82 -0
- package/server/skills/assistant-personality/SKILL.md +45 -0
- package/server/skills/code-skill/SKILL.md +140 -0
- package/server/skills/http-skill/SKILL.md +161 -0
- package/server/skills/maps-skill/SKILL.md +170 -0
- package/server/skills/memory-skill/SKILL.md +154 -0
- package/server/skills/scheduler-skill/SKILL.md +84 -0
- package/server/skills/whatsapp-skill/SKILL.md +283 -0
- package/server/uv.lock +2916 -0
- package/server/whatsapp-rpc/.dockerignore +30 -0
- package/server/whatsapp-rpc/Dockerfile +44 -0
- package/server/whatsapp-rpc/Dockerfile.web +17 -0
- package/server/whatsapp-rpc/README.md +139 -0
- package/server/whatsapp-rpc/cli.js +95 -0
- package/server/whatsapp-rpc/configs/config.yaml +7 -0
- package/server/whatsapp-rpc/docker-compose.yml +35 -0
- package/server/whatsapp-rpc/docs/API.md +410 -0
- package/server/whatsapp-rpc/go.mod +67 -0
- package/server/whatsapp-rpc/go.sum +203 -0
- package/server/whatsapp-rpc/package.json +30 -0
- package/server/whatsapp-rpc/schema.json +1294 -0
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
- package/server/whatsapp-rpc/scripts/cli.js +162 -0
- package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
- package/server/whatsapp-rpc/src/go/config/config.go +49 -0
- package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
- package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
- package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
- package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
- package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
- package/server/whatsapp-rpc/web/app.py +609 -0
- package/server/whatsapp-rpc/web/requirements.txt +6 -0
- package/server/whatsapp-rpc/web/rpc_client.py +427 -0
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
- package/server/whatsapp-rpc/web/templates/base.html +150 -0
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
- package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
- package/server/whatsapp-rpc/web/templates/groups.html +328 -0
- package/server/whatsapp-rpc/web/templates/messages.html +465 -0
- package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
- package/server/whatsapp-rpc/web/templates/send.html +259 -0
- package/server/whatsapp-rpc/web/templates/settings.html +459 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WhatsApp RPC Client - Async JSON-RPC 2.0 client over WebSocket
|
|
3
|
+
|
|
4
|
+
Uses the official `websockets` library for stable async WebSocket communication.
|
|
5
|
+
Implements JSON-RPC 2.0 protocol for bidirectional communication with Go backend.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Callable, Optional
|
|
12
|
+
|
|
13
|
+
import websockets
|
|
14
|
+
from websockets.exceptions import ConnectionClosed
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WhatsAppRPCClient:
|
|
20
|
+
"""Async JSON-RPC 2.0 client using official websockets library."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, ws_url: str):
|
|
23
|
+
"""
|
|
24
|
+
Initialize RPC client.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
ws_url: WebSocket URL (e.g., 'ws://localhost:9400/ws/rpc')
|
|
28
|
+
"""
|
|
29
|
+
self.ws_url = ws_url
|
|
30
|
+
self.ws = None
|
|
31
|
+
self.request_id = 0
|
|
32
|
+
self.pending: dict[int, asyncio.Future] = {}
|
|
33
|
+
self.event_callback: Optional[Callable[[dict], None]] = None
|
|
34
|
+
self._recv_task: Optional[asyncio.Task] = None
|
|
35
|
+
self._connected = False
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def connected(self) -> bool:
|
|
39
|
+
"""Check if WebSocket is connected."""
|
|
40
|
+
return self._connected and self.ws is not None
|
|
41
|
+
|
|
42
|
+
async def connect(self) -> None:
|
|
43
|
+
"""Connect to WebSocket RPC endpoint."""
|
|
44
|
+
try:
|
|
45
|
+
self.ws = await websockets.connect(
|
|
46
|
+
self.ws_url,
|
|
47
|
+
ping_interval=300, # 5 minutes, same as Go server
|
|
48
|
+
ping_timeout=60,
|
|
49
|
+
max_size=100 * 1024 * 1024, # 100 MB max message size for large media
|
|
50
|
+
close_timeout=10,
|
|
51
|
+
)
|
|
52
|
+
self._connected = True
|
|
53
|
+
self._recv_task = asyncio.create_task(self._receive_loop())
|
|
54
|
+
logger.info(f"Connected to RPC endpoint: {self.ws_url}")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.error(f"Failed to connect to RPC endpoint: {e}")
|
|
57
|
+
raise
|
|
58
|
+
|
|
59
|
+
async def close(self) -> None:
|
|
60
|
+
"""Close connection."""
|
|
61
|
+
self._connected = False
|
|
62
|
+
if self._recv_task:
|
|
63
|
+
self._recv_task.cancel()
|
|
64
|
+
try:
|
|
65
|
+
await self._recv_task
|
|
66
|
+
except asyncio.CancelledError:
|
|
67
|
+
pass
|
|
68
|
+
if self.ws:
|
|
69
|
+
await self.ws.close()
|
|
70
|
+
self.ws = None
|
|
71
|
+
logger.info("RPC connection closed")
|
|
72
|
+
|
|
73
|
+
async def _receive_loop(self) -> None:
|
|
74
|
+
"""Handle incoming messages (responses and events)."""
|
|
75
|
+
try:
|
|
76
|
+
async for message in self.ws:
|
|
77
|
+
try:
|
|
78
|
+
data = json.loads(message)
|
|
79
|
+
|
|
80
|
+
if "id" in data and data["id"] is not None:
|
|
81
|
+
# Response to a request
|
|
82
|
+
req_id = data["id"]
|
|
83
|
+
if req_id in self.pending:
|
|
84
|
+
self.pending[req_id].set_result(data)
|
|
85
|
+
elif data.get("method", "").startswith("event."):
|
|
86
|
+
# Event notification from server
|
|
87
|
+
if self.event_callback:
|
|
88
|
+
try:
|
|
89
|
+
self.event_callback(data)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Error in event callback: {e}")
|
|
92
|
+
except json.JSONDecodeError as e:
|
|
93
|
+
logger.error(f"Invalid JSON received: {e}")
|
|
94
|
+
except ConnectionClosed:
|
|
95
|
+
logger.warning("WebSocket connection closed")
|
|
96
|
+
self._connected = False
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.error(f"Error in receive loop: {e}")
|
|
99
|
+
self._connected = False
|
|
100
|
+
|
|
101
|
+
async def call(self, method: str, params: Any = None, timeout: float = 30) -> Any:
|
|
102
|
+
"""
|
|
103
|
+
Call RPC method and wait for response.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
method: RPC method name (e.g., 'status', 'send')
|
|
107
|
+
params: Method parameters (optional)
|
|
108
|
+
timeout: Response timeout in seconds
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Result from the RPC call
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
Exception: If RPC call fails or times out
|
|
115
|
+
"""
|
|
116
|
+
if not self.connected:
|
|
117
|
+
raise Exception("Not connected to RPC endpoint")
|
|
118
|
+
|
|
119
|
+
self.request_id += 1
|
|
120
|
+
req_id = self.request_id
|
|
121
|
+
|
|
122
|
+
request = {"jsonrpc": "2.0", "id": req_id, "method": method}
|
|
123
|
+
if params is not None:
|
|
124
|
+
request["params"] = params
|
|
125
|
+
|
|
126
|
+
future = asyncio.get_event_loop().create_future()
|
|
127
|
+
self.pending[req_id] = future
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
await self.ws.send(json.dumps(request))
|
|
131
|
+
response = await asyncio.wait_for(future, timeout)
|
|
132
|
+
|
|
133
|
+
if "error" in response and response["error"]:
|
|
134
|
+
error = response["error"]
|
|
135
|
+
raise Exception(f"RPC Error {error.get('code', -1)}: {error.get('message', 'Unknown error')}")
|
|
136
|
+
|
|
137
|
+
return response.get("result")
|
|
138
|
+
except asyncio.TimeoutError:
|
|
139
|
+
raise Exception(f"RPC call '{method}' timed out after {timeout}s")
|
|
140
|
+
finally:
|
|
141
|
+
self.pending.pop(req_id, None)
|
|
142
|
+
|
|
143
|
+
# Convenience methods for each RPC command
|
|
144
|
+
async def status(self) -> dict:
|
|
145
|
+
"""Get WhatsApp connection status."""
|
|
146
|
+
return await self.call("status")
|
|
147
|
+
|
|
148
|
+
async def start(self) -> dict:
|
|
149
|
+
"""Start WhatsApp service."""
|
|
150
|
+
return await self.call("start")
|
|
151
|
+
|
|
152
|
+
async def stop(self) -> dict:
|
|
153
|
+
"""Stop WhatsApp service."""
|
|
154
|
+
return await self.call("stop")
|
|
155
|
+
|
|
156
|
+
async def restart(self) -> dict:
|
|
157
|
+
"""Restart WhatsApp service."""
|
|
158
|
+
return await self.call("restart")
|
|
159
|
+
|
|
160
|
+
async def reset(self) -> dict:
|
|
161
|
+
"""Reset WhatsApp session."""
|
|
162
|
+
return await self.call("reset")
|
|
163
|
+
|
|
164
|
+
async def diagnostics(self) -> dict:
|
|
165
|
+
"""Get diagnostics information."""
|
|
166
|
+
return await self.call("diagnostics")
|
|
167
|
+
|
|
168
|
+
async def qr(self) -> dict:
|
|
169
|
+
"""Get QR code for pairing."""
|
|
170
|
+
return await self.call("qr")
|
|
171
|
+
|
|
172
|
+
async def send(self, **kwargs) -> dict:
|
|
173
|
+
"""
|
|
174
|
+
Send WhatsApp message.
|
|
175
|
+
|
|
176
|
+
Supports all message types: text, image, video, audio, document,
|
|
177
|
+
sticker, location, contact.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
phone: Recipient phone number (or use group_id)
|
|
181
|
+
group_id: Group JID (or use phone)
|
|
182
|
+
type: Message type (text, image, etc.)
|
|
183
|
+
message: Text content (for text messages)
|
|
184
|
+
media_data: Media content (for media messages)
|
|
185
|
+
location: Location data (for location messages)
|
|
186
|
+
contact: Contact data (for contact messages)
|
|
187
|
+
reply: Reply context (optional)
|
|
188
|
+
"""
|
|
189
|
+
return await self.call("send", kwargs)
|
|
190
|
+
|
|
191
|
+
async def media(self, message_id: str) -> dict:
|
|
192
|
+
"""
|
|
193
|
+
Download media from a received message.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
message_id: ID of the message containing media
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Dict with 'data' (base64) and 'mime_type'
|
|
200
|
+
"""
|
|
201
|
+
# Use longer timeout for media downloads (videos can be large)
|
|
202
|
+
return await self.call("media", {"message_id": message_id}, timeout=120)
|
|
203
|
+
|
|
204
|
+
async def groups(self) -> list:
|
|
205
|
+
"""
|
|
206
|
+
Get all groups the user is a member of.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
List of group info dicts with jid, name, topic, participants, etc.
|
|
210
|
+
"""
|
|
211
|
+
return await self.call("groups")
|
|
212
|
+
|
|
213
|
+
async def group_info(self, group_id: str) -> dict:
|
|
214
|
+
"""
|
|
215
|
+
Get detailed information about a specific group.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
group_id: Group JID (e.g., '123456789@g.us')
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Dict with group details including participants
|
|
222
|
+
"""
|
|
223
|
+
return await self.call("group_info", {"group_id": group_id})
|
|
224
|
+
|
|
225
|
+
async def group_update(self, group_id: str, name: str = None, topic: str = None) -> dict:
|
|
226
|
+
"""
|
|
227
|
+
Update group name and/or topic (description).
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
group_id: Group JID
|
|
231
|
+
name: New group name (optional)
|
|
232
|
+
topic: New group description (optional)
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Success message
|
|
236
|
+
"""
|
|
237
|
+
params = {"group_id": group_id}
|
|
238
|
+
if name is not None:
|
|
239
|
+
params["name"] = name
|
|
240
|
+
if topic is not None:
|
|
241
|
+
params["topic"] = topic
|
|
242
|
+
return await self.call("group_update", params)
|
|
243
|
+
|
|
244
|
+
async def contact_check(self, phones: list) -> list:
|
|
245
|
+
"""
|
|
246
|
+
Check if phone numbers are registered on WhatsApp.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
phones: List of phone numbers (without + prefix)
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
List of dicts with query, jid, is_registered, is_business, business_name
|
|
253
|
+
"""
|
|
254
|
+
return await self.call("contact_check", {"phones": phones})
|
|
255
|
+
|
|
256
|
+
async def contact_profile_pic(self, jid: str, preview: bool = False) -> dict:
|
|
257
|
+
"""
|
|
258
|
+
Get profile picture for a user or group.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
jid: User or group JID (e.g., '1234567890@s.whatsapp.net')
|
|
262
|
+
preview: Get smaller preview image instead of full size
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Dict with exists, url, id, data (base64 if available)
|
|
266
|
+
"""
|
|
267
|
+
return await self.call("contact_profile_pic", {"jid": jid, "preview": preview})
|
|
268
|
+
|
|
269
|
+
async def typing(self, jid: str, state: str = "composing", media: str = "") -> dict:
|
|
270
|
+
"""
|
|
271
|
+
Send typing indicator to a chat.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
jid: Chat JID (individual or group)
|
|
275
|
+
state: 'composing' (typing) or 'paused' (stopped typing)
|
|
276
|
+
media: '' for text typing, 'audio' for recording voice
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Success message
|
|
280
|
+
"""
|
|
281
|
+
params = {"jid": jid, "state": state}
|
|
282
|
+
if media:
|
|
283
|
+
params["media"] = media
|
|
284
|
+
return await self.call("typing", params)
|
|
285
|
+
|
|
286
|
+
async def presence(self, status: str) -> dict:
|
|
287
|
+
"""
|
|
288
|
+
Set online/offline presence status.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
status: 'available' (online) or 'unavailable' (offline)
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Success message
|
|
295
|
+
"""
|
|
296
|
+
return await self.call("presence", {"status": status})
|
|
297
|
+
|
|
298
|
+
async def mark_read(self, message_ids: list, chat_jid: str, sender_jid: str = None) -> dict:
|
|
299
|
+
"""
|
|
300
|
+
Mark messages as read.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
message_ids: List of message IDs to mark as read
|
|
304
|
+
chat_jid: Chat JID where messages are from
|
|
305
|
+
sender_jid: Sender JID (required for group messages)
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Success message
|
|
309
|
+
"""
|
|
310
|
+
params = {"message_ids": message_ids, "chat_jid": chat_jid}
|
|
311
|
+
if sender_jid:
|
|
312
|
+
params["sender_jid"] = sender_jid
|
|
313
|
+
return await self.call("mark_read", params)
|
|
314
|
+
|
|
315
|
+
async def group_participants_add(self, group_id: str, participants: list) -> dict:
|
|
316
|
+
"""
|
|
317
|
+
Add participants to a group.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
group_id: Group JID (e.g., '123456789@g.us')
|
|
321
|
+
participants: List of phone numbers or JIDs to add
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Dict with group_id, action, results (list with success/error per participant),
|
|
325
|
+
added count, and failed count
|
|
326
|
+
"""
|
|
327
|
+
return await self.call("group_participants_add", {
|
|
328
|
+
"group_id": group_id,
|
|
329
|
+
"participants": participants
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
async def group_participants_remove(self, group_id: str, participants: list) -> dict:
|
|
333
|
+
"""
|
|
334
|
+
Remove participants from a group.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
group_id: Group JID (e.g., '123456789@g.us')
|
|
338
|
+
participants: List of phone numbers or JIDs to remove
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Dict with group_id, action, results (list with success/error per participant),
|
|
342
|
+
removed count, and failed count
|
|
343
|
+
"""
|
|
344
|
+
return await self.call("group_participants_remove", {
|
|
345
|
+
"group_id": group_id,
|
|
346
|
+
"participants": participants
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
async def group_invite_link(self, group_id: str) -> dict:
|
|
350
|
+
"""
|
|
351
|
+
Get the invite link for a group.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
group_id: Group JID (e.g., '123456789@g.us')
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Dict with group_id, invite_link
|
|
358
|
+
"""
|
|
359
|
+
return await self.call("group_invite_link", {"group_id": group_id})
|
|
360
|
+
|
|
361
|
+
async def group_revoke_invite(self, group_id: str) -> dict:
|
|
362
|
+
"""
|
|
363
|
+
Revoke and regenerate the group invite link.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
group_id: Group JID (e.g., '123456789@g.us')
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Dict with group_id, invite_link (new), revoked=true
|
|
370
|
+
"""
|
|
371
|
+
return await self.call("group_revoke_invite", {"group_id": group_id})
|
|
372
|
+
|
|
373
|
+
# ========================================================================
|
|
374
|
+
# Rate Limiting Methods (Anti-Ban Protection)
|
|
375
|
+
# ========================================================================
|
|
376
|
+
|
|
377
|
+
async def rate_limit_get(self) -> dict:
|
|
378
|
+
"""
|
|
379
|
+
Get current rate limit configuration and statistics.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
Dict with 'config' (RateLimitConfig) and 'stats' (RateLimitStats)
|
|
383
|
+
"""
|
|
384
|
+
return await self.call("rate_limit_get")
|
|
385
|
+
|
|
386
|
+
async def rate_limit_set(self, **config) -> dict:
|
|
387
|
+
"""
|
|
388
|
+
Update rate limit configuration dynamically.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
enabled: Enable/disable rate limiting
|
|
392
|
+
min_delay_ms: Minimum delay between messages (ms, default: 3000)
|
|
393
|
+
max_delay_ms: Maximum delay for randomization (ms, default: 8000)
|
|
394
|
+
typing_delay_ms: Typing indicator duration (ms, default: 2000)
|
|
395
|
+
link_extra_delay_ms: Extra delay for messages with links (ms, default: 5000)
|
|
396
|
+
max_messages_per_minute: Per-minute message limit (default: 10)
|
|
397
|
+
max_messages_per_hour: Per-hour message limit (default: 60)
|
|
398
|
+
max_new_contacts_per_day: Daily new contact limit (default: 20)
|
|
399
|
+
simulate_typing: Send typing indicator before messages (default: true)
|
|
400
|
+
randomize_delays: Add random variance to delays (default: true)
|
|
401
|
+
pause_on_low_response: Pause if response rate < threshold (default: false)
|
|
402
|
+
response_rate_threshold: Min response rate 0.0-1.0 (default: 0.3)
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Dict with 'message' and updated 'config'
|
|
406
|
+
"""
|
|
407
|
+
return await self.call("rate_limit_set", config)
|
|
408
|
+
|
|
409
|
+
async def rate_limit_stats(self) -> dict:
|
|
410
|
+
"""
|
|
411
|
+
Get current rate limiting statistics.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Dict with messages_sent_last_minute, messages_sent_last_hour,
|
|
415
|
+
messages_sent_today, new_contacts_today, response_rate,
|
|
416
|
+
is_paused, pause_reason, last_message_time, next_allowed_time
|
|
417
|
+
"""
|
|
418
|
+
return await self.call("rate_limit_stats")
|
|
419
|
+
|
|
420
|
+
async def rate_limit_unpause(self) -> dict:
|
|
421
|
+
"""
|
|
422
|
+
Unpause rate limiting after it was paused due to low response rate.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Dict with 'message' and current 'stats'
|
|
426
|
+
"""
|
|
427
|
+
return await self.call("rate_limit_unpause")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
openapi: 3.0.0
|
|
2
|
+
info:
|
|
3
|
+
title: WhatsApp Controller API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: WhatsApp Web API with enhanced messaging capabilities
|
|
6
|
+
|
|
7
|
+
servers:
|
|
8
|
+
- url: http://localhost:5000
|
|
9
|
+
description: Flask Server
|
|
10
|
+
|
|
11
|
+
paths:
|
|
12
|
+
/api/status:
|
|
13
|
+
get:
|
|
14
|
+
summary: Get WhatsApp status
|
|
15
|
+
tags: [Status]
|
|
16
|
+
responses:
|
|
17
|
+
'200':
|
|
18
|
+
description: Success
|
|
19
|
+
|
|
20
|
+
/api/start:
|
|
21
|
+
post:
|
|
22
|
+
summary: Start WhatsApp service
|
|
23
|
+
tags: [Status]
|
|
24
|
+
responses:
|
|
25
|
+
'200':
|
|
26
|
+
description: Service started
|
|
27
|
+
|
|
28
|
+
/api/send/enhanced:
|
|
29
|
+
post:
|
|
30
|
+
summary: Send message
|
|
31
|
+
tags: [Messaging]
|
|
32
|
+
requestBody:
|
|
33
|
+
content:
|
|
34
|
+
application/json:
|
|
35
|
+
schema:
|
|
36
|
+
type: object
|
|
37
|
+
properties:
|
|
38
|
+
phone:
|
|
39
|
+
type: string
|
|
40
|
+
type:
|
|
41
|
+
type: string
|
|
42
|
+
message:
|
|
43
|
+
type: string
|
|
44
|
+
responses:
|
|
45
|
+
'200':
|
|
46
|
+
description: Message sent
|
|
47
|
+
|
|
48
|
+
/api/messages:
|
|
49
|
+
get:
|
|
50
|
+
summary: Get received messages
|
|
51
|
+
tags: [Messages]
|
|
52
|
+
parameters:
|
|
53
|
+
- name: limit
|
|
54
|
+
in: query
|
|
55
|
+
schema:
|
|
56
|
+
type: integer
|
|
57
|
+
responses:
|
|
58
|
+
'200':
|
|
59
|
+
description: Messages retrieved
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{% block title %}WhatsApp Controller{% endblock %}</title>
|
|
7
|
+
|
|
8
|
+
<!-- Tailwind CSS -->
|
|
9
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
10
|
+
|
|
11
|
+
<!-- Socket.IO -->
|
|
12
|
+
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
|
|
13
|
+
|
|
14
|
+
<!-- QR Code Library -->
|
|
15
|
+
<script src="https://unpkg.com/qrious@4.0.2/dist/qrious.min.js"></script>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
.status-indicator {
|
|
19
|
+
@apply inline-block w-3 h-3 rounded-full mr-2;
|
|
20
|
+
}
|
|
21
|
+
.status-connected { @apply bg-green-500; }
|
|
22
|
+
.status-disconnected { @apply bg-red-500; }
|
|
23
|
+
.status-connecting { @apply bg-yellow-500 animate-pulse; }
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body class="bg-gray-100 min-h-screen">
|
|
27
|
+
<!-- Navigation -->
|
|
28
|
+
<nav class="bg-green-600 shadow-lg">
|
|
29
|
+
<div class="max-w-7xl mx-auto px-4">
|
|
30
|
+
<div class="flex justify-between items-center h-16">
|
|
31
|
+
<div class="flex items-center">
|
|
32
|
+
<h1 class="text-white text-xl font-bold">
|
|
33
|
+
<a href="{{ url_for('index') }}">WhatsApp Controller</a>
|
|
34
|
+
</h1>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="flex items-center space-x-4">
|
|
38
|
+
<!-- Status indicator -->
|
|
39
|
+
<div id="status-indicator" class="flex items-center text-white">
|
|
40
|
+
<span class="status-indicator status-disconnected" id="status-dot"></span>
|
|
41
|
+
<span id="status-text">Disconnected</span>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- Navigation links -->
|
|
45
|
+
<div class="flex items-center space-x-1">
|
|
46
|
+
<a href="{{ url_for('index') }}" class="text-white hover:text-green-200 px-3 py-2 rounded text-sm">
|
|
47
|
+
Dashboard
|
|
48
|
+
</a>
|
|
49
|
+
|
|
50
|
+
<!-- Messaging Dropdown -->
|
|
51
|
+
<div class="relative group">
|
|
52
|
+
<button class="text-white hover:text-green-200 px-3 py-2 rounded text-sm flex items-center">
|
|
53
|
+
Messaging
|
|
54
|
+
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
55
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
56
|
+
</svg>
|
|
57
|
+
</button>
|
|
58
|
+
<div class="absolute left-0 mt-2 w-48 bg-white rounded-md shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50">
|
|
59
|
+
<a href="{{ url_for('send_page') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Simple Send</a>
|
|
60
|
+
<a href="{{ url_for('messaging_page') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Enhanced Messaging</a>
|
|
61
|
+
<a href="{{ url_for('messages_page') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Received Messages</a>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<a href="{{ url_for('groups_page') }}" class="text-white hover:text-green-200 px-3 py-2 rounded text-sm">
|
|
66
|
+
Groups
|
|
67
|
+
</a>
|
|
68
|
+
<a href="{{ url_for('contacts_page') }}" class="text-white hover:text-green-200 px-3 py-2 rounded text-sm">
|
|
69
|
+
Contacts
|
|
70
|
+
</a>
|
|
71
|
+
<a href="{{ url_for('settings_page') }}" class="text-white hover:text-green-200 px-3 py-2 rounded text-sm">
|
|
72
|
+
Settings
|
|
73
|
+
</a>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</nav>
|
|
79
|
+
|
|
80
|
+
<!-- Flash messages -->
|
|
81
|
+
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
82
|
+
{% if messages %}
|
|
83
|
+
<div class="max-w-7xl mx-auto px-4 py-4">
|
|
84
|
+
{% for category, message in messages %}
|
|
85
|
+
<div class="alert alert-{{ 'success' if category == 'success' else 'error' }} mb-4 p-4 rounded-lg
|
|
86
|
+
{{ 'bg-green-100 border border-green-400 text-green-700' if category == 'success' else 'bg-red-100 border border-red-400 text-red-700' }}">
|
|
87
|
+
{{ message }}
|
|
88
|
+
</div>
|
|
89
|
+
{% endfor %}
|
|
90
|
+
</div>
|
|
91
|
+
{% endif %}
|
|
92
|
+
{% endwith %}
|
|
93
|
+
|
|
94
|
+
<!-- Main content -->
|
|
95
|
+
<main class="max-w-7xl mx-auto px-4 py-8">
|
|
96
|
+
{% block content %}{% endblock %}
|
|
97
|
+
</main>
|
|
98
|
+
|
|
99
|
+
<!-- Footer -->
|
|
100
|
+
<footer class="bg-white border-t mt-12">
|
|
101
|
+
<div class="max-w-7xl mx-auto px-4 py-6">
|
|
102
|
+
<p class="text-center text-gray-600">
|
|
103
|
+
WhatsApp Controller - Modern Web Interface
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
</footer>
|
|
107
|
+
|
|
108
|
+
<!-- JavaScript -->
|
|
109
|
+
<script>
|
|
110
|
+
// Initialize Socket.IO connection
|
|
111
|
+
const socket = io();
|
|
112
|
+
|
|
113
|
+
// Status elements
|
|
114
|
+
const statusDot = document.getElementById('status-dot');
|
|
115
|
+
const statusText = document.getElementById('status-text');
|
|
116
|
+
|
|
117
|
+
// Socket events
|
|
118
|
+
socket.on('connect', function() {
|
|
119
|
+
console.log('Connected to WebSocket');
|
|
120
|
+
socket.emit('request_status');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
socket.on('disconnect', function() {
|
|
124
|
+
console.log('Disconnected from WebSocket');
|
|
125
|
+
updateStatus('disconnected', 'Disconnected');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
socket.on('status_update', function(data) {
|
|
129
|
+
console.log('Status update:', data);
|
|
130
|
+
if (data.success && data.data) {
|
|
131
|
+
const connected = data.data.connected;
|
|
132
|
+
updateStatus(connected ? 'connected' : 'disconnected',
|
|
133
|
+
connected ? 'Connected' : 'Disconnected');
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
function updateStatus(status, text) {
|
|
138
|
+
statusDot.className = `status-indicator status-${status}`;
|
|
139
|
+
statusText.textContent = text;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Auto-refresh status every 30 seconds
|
|
143
|
+
setInterval(() => {
|
|
144
|
+
socket.emit('request_status');
|
|
145
|
+
}, 30000);
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
{% block scripts %}{% endblock %}
|
|
149
|
+
</body>
|
|
150
|
+
</html>
|