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.
Files changed (288) hide show
  1. package/.env.template +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/cli.js +159 -0
  5. package/client/.dockerignore +45 -0
  6. package/client/Dockerfile +68 -0
  7. package/client/eslint.config.js +29 -0
  8. package/client/index.html +13 -0
  9. package/client/nginx.conf +66 -0
  10. package/client/package.json +48 -0
  11. package/client/src/App.tsx +27 -0
  12. package/client/src/Dashboard.tsx +1173 -0
  13. package/client/src/ParameterPanel.tsx +301 -0
  14. package/client/src/components/AIAgentNode.tsx +321 -0
  15. package/client/src/components/APIKeyValidator.tsx +118 -0
  16. package/client/src/components/ClaudeChatModelNode.tsx +18 -0
  17. package/client/src/components/ConditionalEdge.tsx +189 -0
  18. package/client/src/components/CredentialsModal.tsx +306 -0
  19. package/client/src/components/EdgeConditionEditor.tsx +443 -0
  20. package/client/src/components/GeminiChatModelNode.tsx +18 -0
  21. package/client/src/components/GenericNode.tsx +357 -0
  22. package/client/src/components/LocationParameterPanel.tsx +154 -0
  23. package/client/src/components/ModelNode.tsx +286 -0
  24. package/client/src/components/OpenAIChatModelNode.tsx +18 -0
  25. package/client/src/components/OutputPanel.tsx +471 -0
  26. package/client/src/components/ParameterRenderer.tsx +1874 -0
  27. package/client/src/components/SkillEditorModal.tsx +417 -0
  28. package/client/src/components/SquareNode.tsx +797 -0
  29. package/client/src/components/StartNode.tsx +250 -0
  30. package/client/src/components/ToolkitNode.tsx +365 -0
  31. package/client/src/components/TriggerNode.tsx +463 -0
  32. package/client/src/components/auth/LoginPage.tsx +247 -0
  33. package/client/src/components/auth/ProtectedRoute.tsx +59 -0
  34. package/client/src/components/base/BaseChatModelNode.tsx +271 -0
  35. package/client/src/components/icons/AIProviderIcons.tsx +50 -0
  36. package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
  37. package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
  38. package/client/src/components/maps/index.ts +26 -0
  39. package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
  40. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
  41. package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
  42. package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
  43. package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
  44. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
  45. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
  46. package/client/src/components/parameterPanel/index.ts +42 -0
  47. package/client/src/components/shared/DataPanel.tsx +142 -0
  48. package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
  49. package/client/src/components/ui/AIResultModal.tsx +204 -0
  50. package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
  51. package/client/src/components/ui/CodeEditor.tsx +81 -0
  52. package/client/src/components/ui/CollapsibleSection.tsx +88 -0
  53. package/client/src/components/ui/ComponentItem.tsx +154 -0
  54. package/client/src/components/ui/ComponentPalette.tsx +321 -0
  55. package/client/src/components/ui/ConsolePanel.tsx +1074 -0
  56. package/client/src/components/ui/ErrorBoundary.tsx +196 -0
  57. package/client/src/components/ui/InputNodesPanel.tsx +204 -0
  58. package/client/src/components/ui/MapSelector.tsx +314 -0
  59. package/client/src/components/ui/Modal.tsx +149 -0
  60. package/client/src/components/ui/NodeContextMenu.tsx +192 -0
  61. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
  62. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
  63. package/client/src/components/ui/SettingsPanel.tsx +243 -0
  64. package/client/src/components/ui/TopToolbar.tsx +736 -0
  65. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
  66. package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
  67. package/client/src/config/antdTheme.ts +186 -0
  68. package/client/src/config/api.ts +54 -0
  69. package/client/src/contexts/AuthContext.tsx +221 -0
  70. package/client/src/contexts/ThemeContext.tsx +42 -0
  71. package/client/src/contexts/WebSocketContext.tsx +1971 -0
  72. package/client/src/factories/baseChatModelFactory.ts +256 -0
  73. package/client/src/hooks/useAndroidOperations.ts +164 -0
  74. package/client/src/hooks/useApiKeyValidation.ts +107 -0
  75. package/client/src/hooks/useApiKeys.ts +238 -0
  76. package/client/src/hooks/useAppTheme.ts +17 -0
  77. package/client/src/hooks/useComponentPalette.ts +51 -0
  78. package/client/src/hooks/useCopyPaste.ts +155 -0
  79. package/client/src/hooks/useDragAndDrop.ts +124 -0
  80. package/client/src/hooks/useDragVariable.ts +88 -0
  81. package/client/src/hooks/useExecution.ts +313 -0
  82. package/client/src/hooks/useParameterPanel.ts +176 -0
  83. package/client/src/hooks/useReactFlowNodes.ts +189 -0
  84. package/client/src/hooks/useToolSchema.ts +209 -0
  85. package/client/src/hooks/useWhatsApp.ts +196 -0
  86. package/client/src/hooks/useWorkflowManagement.ts +46 -0
  87. package/client/src/index.css +315 -0
  88. package/client/src/main.tsx +19 -0
  89. package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
  90. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
  91. package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
  92. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
  93. package/client/src/nodeDefinitions/chatNodes.ts +135 -0
  94. package/client/src/nodeDefinitions/codeNodes.ts +54 -0
  95. package/client/src/nodeDefinitions/documentNodes.ts +379 -0
  96. package/client/src/nodeDefinitions/index.ts +15 -0
  97. package/client/src/nodeDefinitions/locationNodes.ts +463 -0
  98. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
  99. package/client/src/nodeDefinitions/skillNodes.ts +211 -0
  100. package/client/src/nodeDefinitions/toolNodes.ts +198 -0
  101. package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
  102. package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
  103. package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
  104. package/client/src/nodeDefinitions.ts +104 -0
  105. package/client/src/schemas/workflowSchema.ts +264 -0
  106. package/client/src/services/dynamicParameterService.ts +96 -0
  107. package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
  108. package/client/src/services/executionService.ts +232 -0
  109. package/client/src/services/workflowApi.ts +91 -0
  110. package/client/src/store/useAppStore.ts +582 -0
  111. package/client/src/styles/theme.ts +508 -0
  112. package/client/src/styles/zIndex.ts +17 -0
  113. package/client/src/types/ComponentTypes.ts +39 -0
  114. package/client/src/types/EdgeCondition.ts +231 -0
  115. package/client/src/types/INodeProperties.ts +288 -0
  116. package/client/src/types/NodeTypes.ts +28 -0
  117. package/client/src/utils/formatters.ts +33 -0
  118. package/client/src/utils/googleMapsLoader.ts +140 -0
  119. package/client/src/utils/locationUtils.ts +85 -0
  120. package/client/src/utils/nodeUtils.ts +31 -0
  121. package/client/src/utils/workflow.ts +30 -0
  122. package/client/src/utils/workflowExport.ts +120 -0
  123. package/client/src/vite-env.d.ts +12 -0
  124. package/client/tailwind.config.js +60 -0
  125. package/client/tsconfig.json +25 -0
  126. package/client/tsconfig.node.json +11 -0
  127. package/client/vite.config.js +35 -0
  128. package/docker-compose.prod.yml +107 -0
  129. package/docker-compose.yml +104 -0
  130. package/docs-MachinaOs/README.md +85 -0
  131. package/docs-MachinaOs/deployment/docker.mdx +228 -0
  132. package/docs-MachinaOs/deployment/production.mdx +345 -0
  133. package/docs-MachinaOs/docs.json +75 -0
  134. package/docs-MachinaOs/faq.mdx +309 -0
  135. package/docs-MachinaOs/favicon.svg +5 -0
  136. package/docs-MachinaOs/installation.mdx +160 -0
  137. package/docs-MachinaOs/introduction.mdx +114 -0
  138. package/docs-MachinaOs/logo/dark.svg +6 -0
  139. package/docs-MachinaOs/logo/light.svg +6 -0
  140. package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
  141. package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
  142. package/docs-MachinaOs/nodes/android.mdx +411 -0
  143. package/docs-MachinaOs/nodes/overview.mdx +181 -0
  144. package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
  145. package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
  146. package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
  147. package/docs-MachinaOs/quickstart.mdx +119 -0
  148. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
  149. package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
  150. package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
  151. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
  152. package/nul +0 -0
  153. package/package.json +70 -0
  154. package/scripts/build.js +158 -0
  155. package/scripts/check-ports.ps1 +33 -0
  156. package/scripts/clean.js +40 -0
  157. package/scripts/docker.js +93 -0
  158. package/scripts/kill-port.ps1 +154 -0
  159. package/scripts/start.js +210 -0
  160. package/scripts/stop.js +325 -0
  161. package/server/.dockerignore +44 -0
  162. package/server/Dockerfile +45 -0
  163. package/server/constants.py +249 -0
  164. package/server/core/__init__.py +1 -0
  165. package/server/core/cache.py +461 -0
  166. package/server/core/config.py +128 -0
  167. package/server/core/container.py +99 -0
  168. package/server/core/database.py +1211 -0
  169. package/server/core/logging.py +314 -0
  170. package/server/main.py +289 -0
  171. package/server/middleware/__init__.py +5 -0
  172. package/server/middleware/auth.py +89 -0
  173. package/server/models/__init__.py +1 -0
  174. package/server/models/auth.py +52 -0
  175. package/server/models/cache.py +24 -0
  176. package/server/models/database.py +211 -0
  177. package/server/models/nodes.py +455 -0
  178. package/server/package.json +9 -0
  179. package/server/pyproject.toml +72 -0
  180. package/server/requirements.txt +83 -0
  181. package/server/routers/__init__.py +1 -0
  182. package/server/routers/android.py +294 -0
  183. package/server/routers/auth.py +203 -0
  184. package/server/routers/database.py +151 -0
  185. package/server/routers/maps.py +142 -0
  186. package/server/routers/nodejs_compat.py +289 -0
  187. package/server/routers/webhook.py +90 -0
  188. package/server/routers/websocket.py +2127 -0
  189. package/server/routers/whatsapp.py +761 -0
  190. package/server/routers/workflow.py +200 -0
  191. package/server/services/__init__.py +1 -0
  192. package/server/services/ai.py +2415 -0
  193. package/server/services/android/__init__.py +27 -0
  194. package/server/services/android/broadcaster.py +114 -0
  195. package/server/services/android/client.py +608 -0
  196. package/server/services/android/manager.py +78 -0
  197. package/server/services/android/protocol.py +165 -0
  198. package/server/services/android_service.py +588 -0
  199. package/server/services/auth.py +131 -0
  200. package/server/services/chat_client.py +160 -0
  201. package/server/services/deployment/__init__.py +12 -0
  202. package/server/services/deployment/manager.py +706 -0
  203. package/server/services/deployment/state.py +47 -0
  204. package/server/services/deployment/triggers.py +275 -0
  205. package/server/services/event_waiter.py +785 -0
  206. package/server/services/execution/__init__.py +77 -0
  207. package/server/services/execution/cache.py +769 -0
  208. package/server/services/execution/conditions.py +373 -0
  209. package/server/services/execution/dlq.py +132 -0
  210. package/server/services/execution/executor.py +1351 -0
  211. package/server/services/execution/models.py +531 -0
  212. package/server/services/execution/recovery.py +235 -0
  213. package/server/services/handlers/__init__.py +126 -0
  214. package/server/services/handlers/ai.py +355 -0
  215. package/server/services/handlers/android.py +260 -0
  216. package/server/services/handlers/code.py +278 -0
  217. package/server/services/handlers/document.py +598 -0
  218. package/server/services/handlers/http.py +193 -0
  219. package/server/services/handlers/polyglot.py +105 -0
  220. package/server/services/handlers/tools.py +845 -0
  221. package/server/services/handlers/triggers.py +107 -0
  222. package/server/services/handlers/utility.py +822 -0
  223. package/server/services/handlers/whatsapp.py +476 -0
  224. package/server/services/maps.py +289 -0
  225. package/server/services/memory_store.py +103 -0
  226. package/server/services/node_executor.py +375 -0
  227. package/server/services/parameter_resolver.py +218 -0
  228. package/server/services/polyglot_client.py +169 -0
  229. package/server/services/scheduler.py +155 -0
  230. package/server/services/skill_loader.py +417 -0
  231. package/server/services/status_broadcaster.py +826 -0
  232. package/server/services/temporal/__init__.py +23 -0
  233. package/server/services/temporal/activities.py +344 -0
  234. package/server/services/temporal/client.py +76 -0
  235. package/server/services/temporal/executor.py +147 -0
  236. package/server/services/temporal/worker.py +251 -0
  237. package/server/services/temporal/workflow.py +355 -0
  238. package/server/services/temporal/ws_client.py +236 -0
  239. package/server/services/text.py +111 -0
  240. package/server/services/user_auth.py +172 -0
  241. package/server/services/websocket_client.py +29 -0
  242. package/server/services/workflow.py +597 -0
  243. package/server/skills/android-skill/SKILL.md +82 -0
  244. package/server/skills/assistant-personality/SKILL.md +45 -0
  245. package/server/skills/code-skill/SKILL.md +140 -0
  246. package/server/skills/http-skill/SKILL.md +161 -0
  247. package/server/skills/maps-skill/SKILL.md +170 -0
  248. package/server/skills/memory-skill/SKILL.md +154 -0
  249. package/server/skills/scheduler-skill/SKILL.md +84 -0
  250. package/server/skills/whatsapp-skill/SKILL.md +283 -0
  251. package/server/uv.lock +2916 -0
  252. package/server/whatsapp-rpc/.dockerignore +30 -0
  253. package/server/whatsapp-rpc/Dockerfile +44 -0
  254. package/server/whatsapp-rpc/Dockerfile.web +17 -0
  255. package/server/whatsapp-rpc/README.md +139 -0
  256. package/server/whatsapp-rpc/cli.js +95 -0
  257. package/server/whatsapp-rpc/configs/config.yaml +7 -0
  258. package/server/whatsapp-rpc/docker-compose.yml +35 -0
  259. package/server/whatsapp-rpc/docs/API.md +410 -0
  260. package/server/whatsapp-rpc/go.mod +67 -0
  261. package/server/whatsapp-rpc/go.sum +203 -0
  262. package/server/whatsapp-rpc/package.json +30 -0
  263. package/server/whatsapp-rpc/schema.json +1294 -0
  264. package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
  265. package/server/whatsapp-rpc/scripts/cli.js +162 -0
  266. package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
  267. package/server/whatsapp-rpc/src/go/config/config.go +49 -0
  268. package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
  269. package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
  270. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
  271. package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
  272. package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
  273. package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
  274. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
  275. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
  276. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
  277. package/server/whatsapp-rpc/web/app.py +609 -0
  278. package/server/whatsapp-rpc/web/requirements.txt +6 -0
  279. package/server/whatsapp-rpc/web/rpc_client.py +427 -0
  280. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
  281. package/server/whatsapp-rpc/web/templates/base.html +150 -0
  282. package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
  283. package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
  284. package/server/whatsapp-rpc/web/templates/groups.html +328 -0
  285. package/server/whatsapp-rpc/web/templates/messages.html +465 -0
  286. package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
  287. package/server/whatsapp-rpc/web/templates/send.html +259 -0
  288. package/server/whatsapp-rpc/web/templates/settings.html +459 -0
@@ -0,0 +1,446 @@
1
+ package server
2
+
3
+ import (
4
+ "encoding/base64"
5
+ "encoding/json"
6
+ "os"
7
+ "sync"
8
+
9
+ "github.com/gorilla/websocket"
10
+ "github.com/sirupsen/logrus"
11
+
12
+ "whatsapp-rpc/src/go/whatsapp"
13
+ )
14
+
15
+ // JSON-RPC 2.0 message types
16
+ type RPCRequest struct {
17
+ JSONRPC string `json:"jsonrpc"`
18
+ ID interface{} `json:"id,omitempty"`
19
+ Method string `json:"method"`
20
+ Params json.RawMessage `json:"params,omitempty"`
21
+ }
22
+
23
+ type RPCResponse struct {
24
+ JSONRPC string `json:"jsonrpc"`
25
+ ID interface{} `json:"id,omitempty"`
26
+ Result interface{} `json:"result,omitempty"`
27
+ Error *RPCError `json:"error,omitempty"`
28
+ }
29
+
30
+ type RPCError struct {
31
+ Code int `json:"code"`
32
+ Message string `json:"message"`
33
+ }
34
+
35
+ // RPCHandler processes JSON-RPC requests
36
+ type RPCHandler struct {
37
+ service *whatsapp.Service
38
+ logger *logrus.Logger
39
+ }
40
+
41
+ // NewRPCHandler creates a new RPC handler
42
+ func NewRPCHandler(service *whatsapp.Service, logger *logrus.Logger) *RPCHandler {
43
+ return &RPCHandler{
44
+ service: service,
45
+ logger: logger,
46
+ }
47
+ }
48
+
49
+ // HandleRequest processes a JSON-RPC request and returns a response
50
+ func (h *RPCHandler) HandleRequest(req *RPCRequest) RPCResponse {
51
+ resp := RPCResponse{JSONRPC: "2.0", ID: req.ID}
52
+
53
+ switch req.Method {
54
+ case "status":
55
+ resp.Result = h.service.GetStatus()
56
+
57
+ case "start":
58
+ if err := h.service.Start(); err != nil {
59
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
60
+ } else {
61
+ resp.Result = map[string]string{"message": "Started"}
62
+ }
63
+
64
+ case "stop":
65
+ h.service.Shutdown()
66
+ resp.Result = map[string]string{"message": "Stopped"}
67
+
68
+ case "restart":
69
+ // Full restart: cleanup QR codes, reset session, then start fresh
70
+ h.service.CleanupQRCodes()
71
+ if err := h.service.Reset(); err != nil {
72
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
73
+ } else if err := h.service.Start(); err != nil {
74
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
75
+ } else {
76
+ resp.Result = map[string]string{"message": "Restarted (session cleared)"}
77
+ }
78
+
79
+ case "reset":
80
+ if err := h.service.Reset(); err != nil {
81
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
82
+ } else {
83
+ resp.Result = map[string]string{"message": "Reset"}
84
+ }
85
+
86
+ case "diagnostics":
87
+ resp.Result = h.service.GetDiagnostics()
88
+
89
+ case "qr":
90
+ if qr := h.service.GetCurrentQRCode(); qr != nil {
91
+ result := map[string]interface{}{
92
+ "has_qr": true,
93
+ "code": qr.Code,
94
+ "filename": qr.Filename,
95
+ }
96
+ // Read PNG file and encode to base64 for Docker compatibility
97
+ // qr.Filename already contains full path like "data/qr/qr_xxx.png"
98
+ if data, err := os.ReadFile(qr.Filename); err == nil {
99
+ result["image_data"] = base64.StdEncoding.EncodeToString(data)
100
+ }
101
+ resp.Result = result
102
+ } else {
103
+ resp.Error = &RPCError{Code: -32001, Message: "No QR available"}
104
+ }
105
+
106
+ case "send":
107
+ var msgReq whatsapp.MessageRequest
108
+ if err := json.Unmarshal(req.Params, &msgReq); err != nil {
109
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
110
+ } else if err := h.service.SendEnhancedMessage(&msgReq); err != nil {
111
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
112
+ } else {
113
+ resp.Result = map[string]string{"message": "Sent"}
114
+ }
115
+
116
+ case "media":
117
+ var p struct {
118
+ MessageID string `json:"message_id"`
119
+ }
120
+ if err := json.Unmarshal(req.Params, &p); err != nil {
121
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
122
+ } else if data, mime, err := h.service.DownloadMedia(p.MessageID); err != nil {
123
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
124
+ } else {
125
+ resp.Result = map[string]interface{}{
126
+ "data": data,
127
+ "mime_type": mime,
128
+ }
129
+ }
130
+
131
+ case "groups":
132
+ if groups, err := h.service.GetGroups(); err != nil {
133
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
134
+ } else {
135
+ resp.Result = groups
136
+ }
137
+
138
+ case "group_info":
139
+ var p struct {
140
+ GroupID string `json:"group_id"`
141
+ }
142
+ if err := json.Unmarshal(req.Params, &p); err != nil {
143
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
144
+ } else if p.GroupID == "" {
145
+ resp.Error = &RPCError{Code: -32602, Message: "group_id is required"}
146
+ } else if info, err := h.service.GetGroupInfo(p.GroupID); err != nil {
147
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
148
+ } else {
149
+ resp.Result = info
150
+ }
151
+
152
+ case "group_update":
153
+ var p whatsapp.GroupUpdateRequest
154
+ if err := json.Unmarshal(req.Params, &p); err != nil {
155
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
156
+ } else if p.GroupID == "" {
157
+ resp.Error = &RPCError{Code: -32602, Message: "group_id is required"}
158
+ } else if p.Name == "" && p.Topic == "" {
159
+ resp.Error = &RPCError{Code: -32602, Message: "name or topic is required"}
160
+ } else if err := h.service.UpdateGroupInfo(p.GroupID, p.Name, p.Topic); err != nil {
161
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
162
+ } else {
163
+ resp.Result = map[string]string{"message": "Updated"}
164
+ }
165
+
166
+ case "contact_check":
167
+ var p struct {
168
+ Phones []string `json:"phones"`
169
+ }
170
+ if err := json.Unmarshal(req.Params, &p); err != nil {
171
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
172
+ } else if len(p.Phones) == 0 {
173
+ resp.Error = &RPCError{Code: -32602, Message: "phones array is required and must not be empty"}
174
+ } else if results, err := h.service.CheckContacts(p.Phones); err != nil {
175
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
176
+ } else {
177
+ resp.Result = results
178
+ }
179
+
180
+ case "contact_profile_pic":
181
+ var p struct {
182
+ JID string `json:"jid"`
183
+ Preview bool `json:"preview"`
184
+ }
185
+ if err := json.Unmarshal(req.Params, &p); err != nil {
186
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
187
+ } else if p.JID == "" {
188
+ resp.Error = &RPCError{Code: -32602, Message: "jid is required"}
189
+ } else if result, err := h.service.GetProfilePicture(p.JID, p.Preview); err != nil {
190
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
191
+ } else {
192
+ resp.Result = result
193
+ }
194
+
195
+ case "contacts":
196
+ var p struct {
197
+ Query string `json:"query"`
198
+ }
199
+ if err := json.Unmarshal(req.Params, &p); err != nil {
200
+ // If params parsing fails, use empty query (list all)
201
+ p.Query = ""
202
+ }
203
+ if contacts, err := h.service.GetContacts(p.Query); err != nil {
204
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
205
+ } else {
206
+ resp.Result = map[string]interface{}{
207
+ "contacts": contacts,
208
+ "total": len(contacts),
209
+ }
210
+ }
211
+
212
+ case "contact_info":
213
+ var p struct {
214
+ Phone string `json:"phone"`
215
+ }
216
+ if err := json.Unmarshal(req.Params, &p); err != nil {
217
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
218
+ } else if p.Phone == "" {
219
+ resp.Error = &RPCError{Code: -32602, Message: "phone is required"}
220
+ } else if info, err := h.service.GetContactInfo(p.Phone); err != nil {
221
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
222
+ } else {
223
+ resp.Result = info
224
+ }
225
+
226
+ case "typing":
227
+ var p struct {
228
+ JID string `json:"jid"`
229
+ State string `json:"state"`
230
+ Media string `json:"media"`
231
+ }
232
+ if err := json.Unmarshal(req.Params, &p); err != nil {
233
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
234
+ } else if p.JID == "" {
235
+ resp.Error = &RPCError{Code: -32602, Message: "jid is required"}
236
+ } else if p.State == "" {
237
+ resp.Error = &RPCError{Code: -32602, Message: "state is required ('composing' or 'paused')"}
238
+ } else if err := h.service.SendTyping(p.JID, p.State, p.Media); err != nil {
239
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
240
+ } else {
241
+ resp.Result = map[string]string{"message": "Typing indicator sent"}
242
+ }
243
+
244
+ case "presence":
245
+ var p struct {
246
+ Status string `json:"status"`
247
+ }
248
+ if err := json.Unmarshal(req.Params, &p); err != nil {
249
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
250
+ } else if p.Status == "" {
251
+ resp.Error = &RPCError{Code: -32602, Message: "status is required ('available' or 'unavailable')"}
252
+ } else if err := h.service.SetPresence(p.Status); err != nil {
253
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
254
+ } else {
255
+ resp.Result = map[string]string{"message": "Presence set"}
256
+ }
257
+
258
+ case "mark_read":
259
+ var p struct {
260
+ MessageIDs []string `json:"message_ids"`
261
+ ChatJID string `json:"chat_jid"`
262
+ SenderJID string `json:"sender_jid"`
263
+ }
264
+ if err := json.Unmarshal(req.Params, &p); err != nil {
265
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
266
+ } else if len(p.MessageIDs) == 0 {
267
+ resp.Error = &RPCError{Code: -32602, Message: "message_ids array is required"}
268
+ } else if p.ChatJID == "" {
269
+ resp.Error = &RPCError{Code: -32602, Message: "chat_jid is required"}
270
+ } else if err := h.service.MarkRead(p.MessageIDs, p.ChatJID, p.SenderJID); err != nil {
271
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
272
+ } else {
273
+ resp.Result = map[string]string{"message": "Messages marked as read"}
274
+ }
275
+
276
+ case "group_participants_add":
277
+ var p struct {
278
+ GroupID string `json:"group_id"`
279
+ Participants []string `json:"participants"`
280
+ }
281
+ if err := json.Unmarshal(req.Params, &p); err != nil {
282
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
283
+ } else if p.GroupID == "" {
284
+ resp.Error = &RPCError{Code: -32602, Message: "group_id is required"}
285
+ } else if len(p.Participants) == 0 {
286
+ resp.Error = &RPCError{Code: -32602, Message: "participants array is required and must not be empty"}
287
+ } else if result, err := h.service.AddGroupParticipants(p.GroupID, p.Participants); err != nil {
288
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
289
+ } else {
290
+ resp.Result = result
291
+ }
292
+
293
+ case "group_participants_remove":
294
+ var p struct {
295
+ GroupID string `json:"group_id"`
296
+ Participants []string `json:"participants"`
297
+ }
298
+ if err := json.Unmarshal(req.Params, &p); err != nil {
299
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
300
+ } else if p.GroupID == "" {
301
+ resp.Error = &RPCError{Code: -32602, Message: "group_id is required"}
302
+ } else if len(p.Participants) == 0 {
303
+ resp.Error = &RPCError{Code: -32602, Message: "participants array is required and must not be empty"}
304
+ } else if result, err := h.service.RemoveGroupParticipants(p.GroupID, p.Participants); err != nil {
305
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
306
+ } else {
307
+ resp.Result = result
308
+ }
309
+
310
+ case "group_invite_link":
311
+ var p struct {
312
+ GroupID string `json:"group_id"`
313
+ }
314
+ if err := json.Unmarshal(req.Params, &p); err != nil {
315
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
316
+ } else if p.GroupID == "" {
317
+ resp.Error = &RPCError{Code: -32602, Message: "group_id is required"}
318
+ } else if result, err := h.service.GetGroupInviteLink(p.GroupID); err != nil {
319
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
320
+ } else {
321
+ resp.Result = result
322
+ }
323
+
324
+ case "group_revoke_invite":
325
+ var p struct {
326
+ GroupID string `json:"group_id"`
327
+ }
328
+ if err := json.Unmarshal(req.Params, &p); err != nil {
329
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
330
+ } else if p.GroupID == "" {
331
+ resp.Error = &RPCError{Code: -32602, Message: "group_id is required"}
332
+ } else if result, err := h.service.RevokeGroupInviteLink(p.GroupID); err != nil {
333
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
334
+ } else {
335
+ resp.Result = result
336
+ }
337
+
338
+ case "chat_history":
339
+ var p struct {
340
+ ChatID string `json:"chat_id"`
341
+ Phone string `json:"phone"`
342
+ GroupID string `json:"group_id"`
343
+ Limit int `json:"limit"`
344
+ Offset int `json:"offset"`
345
+ SenderPhone string `json:"sender_phone"`
346
+ TextOnly bool `json:"text_only"`
347
+ }
348
+ if err := json.Unmarshal(req.Params, &p); err != nil {
349
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
350
+ } else {
351
+ // Resolve chat_id from phone or group_id if not directly provided
352
+ chatID := p.ChatID
353
+ if chatID == "" {
354
+ if p.Phone != "" {
355
+ chatID = p.Phone + "@s.whatsapp.net"
356
+ } else if p.GroupID != "" {
357
+ chatID = p.GroupID
358
+ } else {
359
+ resp.Error = &RPCError{Code: -32602, Message: "Either chat_id, phone, or group_id is required"}
360
+ break
361
+ }
362
+ }
363
+
364
+ // Set defaults
365
+ limit := p.Limit
366
+ if limit <= 0 {
367
+ limit = 50
368
+ }
369
+ if limit > 500 {
370
+ limit = 500
371
+ }
372
+
373
+ if result, err := h.service.GetChatHistory(chatID, limit, p.Offset, p.SenderPhone, p.TextOnly); err != nil {
374
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
375
+ } else {
376
+ resp.Result = result
377
+ }
378
+ }
379
+
380
+ case "rate_limit_get":
381
+ resp.Result = map[string]interface{}{
382
+ "config": h.service.GetRateLimitConfig(),
383
+ "stats": h.service.GetRateLimitStats(),
384
+ }
385
+
386
+ case "rate_limit_set":
387
+ var config whatsapp.RateLimitConfig
388
+ if err := json.Unmarshal(req.Params, &config); err != nil {
389
+ resp.Error = &RPCError{Code: -32602, Message: "Invalid params: " + err.Error()}
390
+ } else if err := h.service.SetRateLimitConfig(&config); err != nil {
391
+ resp.Error = &RPCError{Code: -32000, Message: err.Error()}
392
+ } else {
393
+ resp.Result = map[string]interface{}{
394
+ "message": "Rate limit config updated",
395
+ "config": h.service.GetRateLimitConfig(),
396
+ }
397
+ }
398
+
399
+ case "rate_limit_stats":
400
+ resp.Result = h.service.GetRateLimitStats()
401
+
402
+ case "rate_limit_unpause":
403
+ h.service.UnpauseRateLimiting()
404
+ resp.Result = map[string]interface{}{
405
+ "message": "Rate limiting unpaused",
406
+ "stats": h.service.GetRateLimitStats(),
407
+ }
408
+
409
+ default:
410
+ resp.Error = &RPCError{Code: -32601, Message: "Method not found: " + req.Method}
411
+ }
412
+
413
+ return resp
414
+ }
415
+
416
+ // ForwardEvents sends WhatsApp events as JSON-RPC notifications
417
+ func (h *RPCHandler) ForwardEvents(conn *websocket.Conn, mu *sync.Mutex, done chan struct{}) {
418
+ eventChan := h.service.GetEventChannel()
419
+ for {
420
+ select {
421
+ case <-done:
422
+ return
423
+ case event, ok := <-eventChan:
424
+ if !ok {
425
+ return
426
+ }
427
+ notif := RPCRequest{
428
+ JSONRPC: "2.0",
429
+ Method: "event." + event.Type,
430
+ Params: mustMarshal(event.Data),
431
+ }
432
+ mu.Lock()
433
+ if err := conn.WriteJSON(notif); err != nil {
434
+ mu.Unlock()
435
+ h.logger.Errorf("Failed to send event: %v", err)
436
+ return
437
+ }
438
+ mu.Unlock()
439
+ }
440
+ }
441
+ }
442
+
443
+ func mustMarshal(v interface{}) json.RawMessage {
444
+ b, _ := json.Marshal(v)
445
+ return b
446
+ }
@@ -0,0 +1,112 @@
1
+ package server
2
+
3
+ import (
4
+ "net/http"
5
+ "sync"
6
+
7
+ "github.com/gin-gonic/gin"
8
+ "github.com/gorilla/websocket"
9
+ "github.com/sirupsen/logrus"
10
+
11
+ "whatsapp-rpc/src/go/whatsapp"
12
+ )
13
+
14
+ // Server handles WebSocket RPC connections
15
+ type Server struct {
16
+ whatsapp *whatsapp.Service
17
+ logger *logrus.Logger
18
+ upgrader websocket.Upgrader
19
+ }
20
+
21
+ // New creates a new WebSocket RPC server
22
+ func New(whatsappService *whatsapp.Service, logger *logrus.Logger) *Server {
23
+ return &Server{
24
+ whatsapp: whatsappService,
25
+ logger: logger,
26
+ upgrader: websocket.Upgrader{
27
+ CheckOrigin: func(r *http.Request) bool {
28
+ return true
29
+ },
30
+ ReadBufferSize: 1024 * 1024, // 1 MB read buffer
31
+ WriteBufferSize: 100 * 1024 * 1024, // 100 MB write buffer for large media
32
+ },
33
+ }
34
+ }
35
+
36
+ // SetupRoutes configures WebSocket RPC routes only
37
+ func (s *Server) SetupRoutes() *gin.Engine {
38
+ gin.SetMode(gin.ReleaseMode)
39
+ router := gin.New()
40
+ router.Use(gin.Recovery())
41
+
42
+ // CORS middleware
43
+ router.Use(func(c *gin.Context) {
44
+ c.Header("Access-Control-Allow-Origin", "*")
45
+ c.Header("Access-Control-Allow-Methods", "GET, OPTIONS")
46
+ c.Header("Access-Control-Allow-Headers", "Content-Type")
47
+ if c.Request.Method == "OPTIONS" {
48
+ c.AbortWithStatus(200)
49
+ return
50
+ }
51
+ c.Next()
52
+ })
53
+
54
+ // WebSocket RPC endpoint - the only endpoint
55
+ router.GET("/ws/rpc", s.handleWebSocketRPC)
56
+
57
+ // Health check
58
+ router.GET("/health", func(c *gin.Context) {
59
+ c.JSON(200, gin.H{"status": "ok", "type": "websocket-rpc"})
60
+ })
61
+
62
+ return router
63
+ }
64
+
65
+ // handleWebSocketRPC handles bidirectional JSON-RPC over WebSocket
66
+ func (s *Server) handleWebSocketRPC(c *gin.Context) {
67
+ conn, err := s.upgrader.Upgrade(c.Writer, c.Request, nil)
68
+ if err != nil {
69
+ s.logger.Errorf("WebSocket upgrade failed: %v", err)
70
+ return
71
+ }
72
+ defer conn.Close()
73
+
74
+ s.logger.Info("RPC client connected")
75
+
76
+ var writeMu sync.Mutex
77
+ done := make(chan struct{})
78
+ handler := NewRPCHandler(s.whatsapp, s.logger)
79
+
80
+ // Forward events as JSON-RPC notifications
81
+ go handler.ForwardEvents(conn, &writeMu, done)
82
+
83
+ // Send initial status
84
+ status := s.whatsapp.GetStatus()
85
+ writeMu.Lock()
86
+ conn.WriteJSON(RPCRequest{
87
+ JSONRPC: "2.0",
88
+ Method: "event.status",
89
+ Params: mustMarshal(status),
90
+ })
91
+ writeMu.Unlock()
92
+
93
+ // Read and handle incoming requests
94
+ for {
95
+ var req RPCRequest
96
+ if err := conn.ReadJSON(&req); err != nil {
97
+ s.logger.Debugf("RPC client disconnected: %v", err)
98
+ close(done)
99
+ return
100
+ }
101
+
102
+ s.logger.Debugf("RPC request: %s", req.Method)
103
+
104
+ // Process request and send response
105
+ resp := handler.HandleRequest(&req)
106
+ if req.ID != nil {
107
+ writeMu.Lock()
108
+ conn.WriteJSON(resp)
109
+ writeMu.Unlock()
110
+ }
111
+ }
112
+ }