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,166 @@
1
+ package whatsapp
2
+
3
+ import (
4
+ "database/sql"
5
+ "time"
6
+
7
+ "github.com/sirupsen/logrus"
8
+ _ "modernc.org/sqlite"
9
+ )
10
+
11
+ // HistoryStore handles message persistence for chat history
12
+ type HistoryStore struct {
13
+ db *sql.DB
14
+ logger *logrus.Logger
15
+ }
16
+
17
+ // MessageRecord represents a stored message
18
+ type MessageRecord struct {
19
+ MessageID string `json:"message_id"`
20
+ ChatID string `json:"chat_id"`
21
+ Sender string `json:"sender"`
22
+ SenderPhone string `json:"sender_phone"`
23
+ MessageType string `json:"message_type"`
24
+ Text string `json:"text"`
25
+ Timestamp time.Time `json:"timestamp"`
26
+ IsGroup bool `json:"is_group"`
27
+ IsFromMe bool `json:"is_from_me"`
28
+ }
29
+
30
+ // ChatHistoryResult is the response for chat_history queries
31
+ type ChatHistoryResult struct {
32
+ Messages []MessageRecord `json:"messages"`
33
+ Total int `json:"total"`
34
+ HasMore bool `json:"has_more"`
35
+ }
36
+
37
+ // NewHistoryStore creates a new history store with SQLite persistence
38
+ func NewHistoryStore(dbPath string, logger *logrus.Logger) (*HistoryStore, error) {
39
+ // Use separate file for history to avoid conflicts with whatsmeow's session store
40
+ historyPath := dbPath + "_history"
41
+ db, err := sql.Open("sqlite", "file:"+historyPath+"?_journal_mode=WAL&_busy_timeout=5000")
42
+ if err != nil {
43
+ return nil, err
44
+ }
45
+
46
+ store := &HistoryStore{db: db, logger: logger}
47
+ if err := store.initTable(); err != nil {
48
+ return nil, err
49
+ }
50
+ return store, nil
51
+ }
52
+
53
+ func (h *HistoryStore) initTable() error {
54
+ _, err := h.db.Exec(`
55
+ CREATE TABLE IF NOT EXISTS message_history (
56
+ message_id TEXT PRIMARY KEY,
57
+ chat_id TEXT NOT NULL,
58
+ sender TEXT NOT NULL,
59
+ sender_phone TEXT,
60
+ message_type TEXT NOT NULL,
61
+ text TEXT,
62
+ timestamp INTEGER NOT NULL,
63
+ is_group INTEGER NOT NULL DEFAULT 0,
64
+ is_from_me INTEGER NOT NULL DEFAULT 0,
65
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
66
+ );
67
+ CREATE INDEX IF NOT EXISTS idx_chat_id ON message_history(chat_id);
68
+ CREATE INDEX IF NOT EXISTS idx_timestamp ON message_history(timestamp DESC);
69
+ CREATE INDEX IF NOT EXISTS idx_sender_phone ON message_history(sender_phone);
70
+ `)
71
+ return err
72
+ }
73
+
74
+ // StoreMessage persists a message to the history store
75
+ func (h *HistoryStore) StoreMessage(msg MessageRecord) error {
76
+ _, err := h.db.Exec(`
77
+ INSERT OR IGNORE INTO message_history
78
+ (message_id, chat_id, sender, sender_phone, message_type, text, timestamp, is_group, is_from_me)
79
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
80
+ `, msg.MessageID, msg.ChatID, msg.Sender, msg.SenderPhone, msg.MessageType,
81
+ msg.Text, msg.Timestamp.Unix(), boolToInt(msg.IsGroup), boolToInt(msg.IsFromMe))
82
+ return err
83
+ }
84
+
85
+ // GetChatHistory retrieves messages for a chat with optional filters
86
+ func (h *HistoryStore) GetChatHistory(chatID string, limit, offset int, senderPhone string, textOnly bool) (*ChatHistoryResult, error) {
87
+ // Build query with filters
88
+ query := `SELECT message_id, chat_id, sender, sender_phone, message_type, text, timestamp, is_group, is_from_me
89
+ FROM message_history WHERE chat_id = ?`
90
+ args := []interface{}{chatID}
91
+
92
+ if senderPhone != "" {
93
+ query += " AND sender_phone = ?"
94
+ args = append(args, senderPhone)
95
+ }
96
+ if textOnly {
97
+ query += " AND message_type = 'text'"
98
+ }
99
+
100
+ // Count total matching
101
+ countQuery := "SELECT COUNT(*) FROM message_history WHERE chat_id = ?"
102
+ countArgs := []interface{}{chatID}
103
+ if senderPhone != "" {
104
+ countQuery += " AND sender_phone = ?"
105
+ countArgs = append(countArgs, senderPhone)
106
+ }
107
+ if textOnly {
108
+ countQuery += " AND message_type = 'text'"
109
+ }
110
+
111
+ var total int
112
+ h.db.QueryRow(countQuery, countArgs...).Scan(&total)
113
+
114
+ // Get messages ordered by timestamp descending (newest first)
115
+ query += " ORDER BY timestamp DESC LIMIT ? OFFSET ?"
116
+ args = append(args, limit, offset)
117
+
118
+ rows, err := h.db.Query(query, args...)
119
+ if err != nil {
120
+ return nil, err
121
+ }
122
+ defer rows.Close()
123
+
124
+ messages := []MessageRecord{}
125
+ for rows.Next() {
126
+ var msg MessageRecord
127
+ var ts int64
128
+ var isGroup, isFromMe int
129
+ err := rows.Scan(&msg.MessageID, &msg.ChatID, &msg.Sender, &msg.SenderPhone,
130
+ &msg.MessageType, &msg.Text, &ts, &isGroup, &isFromMe)
131
+ if err != nil {
132
+ continue
133
+ }
134
+ msg.Timestamp = time.Unix(ts, 0)
135
+ msg.IsGroup = isGroup == 1
136
+ msg.IsFromMe = isFromMe == 1
137
+ messages = append(messages, msg)
138
+ }
139
+
140
+ return &ChatHistoryResult{
141
+ Messages: messages,
142
+ Total: total,
143
+ HasMore: offset+len(messages) < total,
144
+ }, nil
145
+ }
146
+
147
+ // Close closes the database connection
148
+ func (h *HistoryStore) Close() error {
149
+ if h.db != nil {
150
+ return h.db.Close()
151
+ }
152
+ return nil
153
+ }
154
+
155
+ // ClearHistory deletes all messages from the history store
156
+ func (h *HistoryStore) ClearHistory() error {
157
+ _, err := h.db.Exec("DELETE FROM message_history")
158
+ return err
159
+ }
160
+
161
+ func boolToInt(b bool) int {
162
+ if b {
163
+ return 1
164
+ }
165
+ return 0
166
+ }
@@ -0,0 +1,390 @@
1
+ package whatsapp
2
+
3
+ import (
4
+ "context"
5
+ "encoding/base64"
6
+ "fmt"
7
+ "strings"
8
+ "time"
9
+
10
+ "go.mau.fi/whatsmeow"
11
+ "go.mau.fi/whatsmeow/proto/waE2E"
12
+ "go.mau.fi/whatsmeow/types"
13
+ "google.golang.org/protobuf/proto"
14
+ )
15
+
16
+ // SendMessage - Legacy simple message sending (kept for backward compatibility)
17
+ func (s *Service) SendMessage(phone, message string) error {
18
+ req := &MessageRequest{
19
+ Phone: phone,
20
+ Message: message,
21
+ Type: "text",
22
+ }
23
+ return s.SendEnhancedMessage(req)
24
+ }
25
+
26
+ // SendEnhancedMessage - Comprehensive message sending with all WhatsApp features
27
+ func (s *Service) SendEnhancedMessage(req *MessageRequest) error {
28
+ if !s.client.IsConnected() {
29
+ return fmt.Errorf("WhatsApp not connected")
30
+ }
31
+
32
+ var targetJID types.JID
33
+ var err error
34
+
35
+ // Log received data for debugging
36
+ s.logger.Infof("SendEnhancedMessage: Type=%s, Phone='%s', GroupID='%s'", req.Type, req.Phone, req.GroupID)
37
+
38
+ // Determine target JID (individual or group)
39
+ if req.GroupID != "" {
40
+ targetJID, err = types.ParseJID(req.GroupID)
41
+ if err != nil {
42
+ return fmt.Errorf("invalid group ID '%s': %w", req.GroupID, err)
43
+ }
44
+ } else if req.Phone != "" {
45
+ // Validate phone number is not empty after trimming
46
+ if len(req.Phone) == 0 {
47
+ return fmt.Errorf("phone number cannot be empty")
48
+ }
49
+ targetJID = types.NewJID(req.Phone, types.DefaultUserServer)
50
+ } else {
51
+ return fmt.Errorf("either phone or group_id is required (received: phone='%s', group_id='%s')", req.Phone, req.GroupID)
52
+ }
53
+
54
+ // Determine recipient for rate limiting (use phone or group ID)
55
+ recipient := req.Phone
56
+ if recipient == "" {
57
+ recipient = req.GroupID
58
+ }
59
+
60
+ // Check rate limits before proceeding
61
+ if err := s.CheckRateLimit(recipient); err != nil {
62
+ s.logger.Warnf("Rate limit check failed: %v", err)
63
+ return err
64
+ }
65
+
66
+ // Send typing indicator if enabled (simulates human behavior)
67
+ s.SendTypingIfEnabled(targetJID.String())
68
+
69
+ // Check if message contains links for extra delay
70
+ hasLinks := strings.Contains(req.Message, "http://") || strings.Contains(req.Message, "https://")
71
+
72
+ // Apply message delay (with randomization for human-like behavior)
73
+ s.ApplyMessageDelay(hasLinks)
74
+
75
+ // Create message based on type
76
+ var message *waE2E.Message
77
+ switch req.Type {
78
+ case "text":
79
+ message = s.createTextMessage(req)
80
+ case "image":
81
+ message, err = s.createImageMessage(req)
82
+ case "document":
83
+ message, err = s.createDocumentMessage(req)
84
+ case "audio":
85
+ message, err = s.createAudioMessage(req)
86
+ case "video":
87
+ message, err = s.createVideoMessage(req)
88
+ case "location":
89
+ message = s.createLocationMessage(req)
90
+ case "sticker":
91
+ message, err = s.createStickerMessage(req)
92
+ case "contact":
93
+ message = s.createContactMessage(req)
94
+ default:
95
+ return fmt.Errorf("unsupported message type: %s", req.Type)
96
+ }
97
+
98
+ if err != nil {
99
+ return fmt.Errorf("failed to create %s message: %w", req.Type, err)
100
+ }
101
+
102
+ // Add reply context if specified
103
+ if req.Reply != nil {
104
+ msgText := message.GetConversation()
105
+ message.ExtendedTextMessage = &waE2E.ExtendedTextMessage{
106
+ Text: &msgText,
107
+ ContextInfo: &waE2E.ContextInfo{
108
+ StanzaID: proto.String(req.Reply.MessageID),
109
+ Participant: proto.String(req.Reply.Sender),
110
+ QuotedMessage: &waE2E.Message{Conversation: proto.String(req.Reply.Content)},
111
+ },
112
+ }
113
+ message.Conversation = nil
114
+ }
115
+
116
+ // Send the message
117
+ resp, err := s.client.SendMessage(context.Background(), targetJID, message)
118
+ if err != nil {
119
+ return fmt.Errorf("failed to send message: %w", err)
120
+ }
121
+
122
+ // Record message sent for rate limiting stats
123
+ s.RecordMessageSent(recipient)
124
+
125
+ // Log successful send
126
+ s.logger.Infof("Message sent successfully: %s to %s", resp.ID, targetJID.String())
127
+
128
+ // Send event notification
129
+ s.safeEventSend(Event{
130
+ Type: "message_sent",
131
+ Data: map[string]interface{}{
132
+ "message_id": resp.ID,
133
+ "to": targetJID.String(),
134
+ "type": req.Type,
135
+ "timestamp": resp.Timestamp,
136
+ },
137
+ Time: time.Now(),
138
+ })
139
+
140
+ return nil
141
+ }
142
+
143
+ // SendTyping sends typing indicator
144
+ // TODO: Fix context API breaking changes
145
+ /*
146
+ func (s *Service) SendTyping(jidStr string, typing bool) error {
147
+ if !s.client.IsConnected() {
148
+ return fmt.Errorf("WhatsApp not connected")
149
+ }
150
+
151
+ var presence types.Presence
152
+ if typing {
153
+ presence = types.PresenceAvailable
154
+ } else {
155
+ presence = types.PresenceUnavailable
156
+ }
157
+
158
+ sendErr := s.client.SendPresence(context.Background(), presence)
159
+ if sendErr != nil {
160
+ return fmt.Errorf("failed to send typing indicator: %w", sendErr)
161
+ }
162
+
163
+ return nil
164
+ }
165
+
166
+ // MarkMessageAsRead marks a message as read
167
+ func (s *Service) MarkMessageAsRead(messageID, senderJID string) error {
168
+ if !s.client.IsConnected() {
169
+ return fmt.Errorf("WhatsApp not connected")
170
+ }
171
+
172
+ jid, err := types.ParseJID(senderJID)
173
+ if err != nil {
174
+ return fmt.Errorf("invalid sender JID: %w", err)
175
+ }
176
+
177
+ err = s.client.MarkRead(context.Background(), []string{messageID}, time.Now(), jid, jid)
178
+ if err != nil {
179
+ return fmt.Errorf("failed to mark message as read: %w", err)
180
+ }
181
+
182
+ return nil
183
+ }
184
+ */
185
+
186
+ // Helper functions to create different message types
187
+
188
+ func (s *Service) createTextMessage(req *MessageRequest) *waE2E.Message {
189
+ return &waE2E.Message{
190
+ Conversation: proto.String(req.Message),
191
+ }
192
+ }
193
+
194
+ func (s *Service) createImageMessage(req *MessageRequest) (*waE2E.Message, error) {
195
+ if req.MediaData == nil {
196
+ return nil, fmt.Errorf("media_data is required for image messages")
197
+ }
198
+
199
+ // Decode base64 media data
200
+ mediaBytes, err := base64.StdEncoding.DecodeString(req.MediaData.Data)
201
+ if err != nil {
202
+ return nil, fmt.Errorf("invalid base64 media data: %w", err)
203
+ }
204
+
205
+ // Upload media to WhatsApp servers
206
+ uploaded, err := s.client.Upload(context.Background(), mediaBytes, whatsmeow.MediaImage)
207
+ if err != nil {
208
+ return nil, fmt.Errorf("failed to upload image: %w", err)
209
+ }
210
+
211
+ // Create image message
212
+ message := &waE2E.Message{
213
+ ImageMessage: &waE2E.ImageMessage{
214
+ Caption: proto.String(req.MediaData.Caption),
215
+ Mimetype: proto.String(req.MediaData.MimeType),
216
+ URL: proto.String(uploaded.URL),
217
+ DirectPath: proto.String(uploaded.DirectPath),
218
+ MediaKey: uploaded.MediaKey,
219
+ FileEncSHA256: uploaded.FileEncSHA256,
220
+ FileSHA256: uploaded.FileSHA256,
221
+ FileLength: proto.Uint64(uint64(len(mediaBytes))),
222
+ },
223
+ }
224
+
225
+ return message, nil
226
+ }
227
+
228
+ func (s *Service) createDocumentMessage(req *MessageRequest) (*waE2E.Message, error) {
229
+ if req.MediaData == nil {
230
+ return nil, fmt.Errorf("media_data is required for document messages")
231
+ }
232
+
233
+ // Decode base64 media data
234
+ mediaBytes, err := base64.StdEncoding.DecodeString(req.MediaData.Data)
235
+ if err != nil {
236
+ return nil, fmt.Errorf("invalid base64 media data: %w", err)
237
+ }
238
+
239
+ // Upload media to WhatsApp servers
240
+ uploaded, err := s.client.Upload(context.Background(), mediaBytes, whatsmeow.MediaDocument)
241
+ if err != nil {
242
+ return nil, fmt.Errorf("failed to upload document: %w", err)
243
+ }
244
+
245
+ // Create document message
246
+ message := &waE2E.Message{
247
+ DocumentMessage: &waE2E.DocumentMessage{
248
+ Title: proto.String(req.MediaData.Filename),
249
+ FileName: proto.String(req.MediaData.Filename),
250
+ Mimetype: proto.String(req.MediaData.MimeType),
251
+ URL: proto.String(uploaded.URL),
252
+ DirectPath: proto.String(uploaded.DirectPath),
253
+ MediaKey: uploaded.MediaKey,
254
+ FileEncSHA256: uploaded.FileEncSHA256,
255
+ FileSHA256: uploaded.FileSHA256,
256
+ FileLength: proto.Uint64(uint64(len(mediaBytes))),
257
+ },
258
+ }
259
+
260
+ return message, nil
261
+ }
262
+
263
+ func (s *Service) createAudioMessage(req *MessageRequest) (*waE2E.Message, error) {
264
+ if req.MediaData == nil {
265
+ return nil, fmt.Errorf("media_data is required for audio messages")
266
+ }
267
+
268
+ // Decode base64 media data
269
+ mediaBytes, err := base64.StdEncoding.DecodeString(req.MediaData.Data)
270
+ if err != nil {
271
+ return nil, fmt.Errorf("invalid base64 media data: %w", err)
272
+ }
273
+
274
+ // Upload media to WhatsApp servers
275
+ uploaded, err := s.client.Upload(context.Background(), mediaBytes, whatsmeow.MediaAudio)
276
+ if err != nil {
277
+ return nil, fmt.Errorf("failed to upload audio: %w", err)
278
+ }
279
+
280
+ // Create audio message
281
+ message := &waE2E.Message{
282
+ AudioMessage: &waE2E.AudioMessage{
283
+ Mimetype: proto.String(req.MediaData.MimeType),
284
+ URL: proto.String(uploaded.URL),
285
+ DirectPath: proto.String(uploaded.DirectPath),
286
+ MediaKey: uploaded.MediaKey,
287
+ FileEncSHA256: uploaded.FileEncSHA256,
288
+ FileSHA256: uploaded.FileSHA256,
289
+ FileLength: proto.Uint64(uint64(len(mediaBytes))),
290
+ PTT: proto.Bool(strings.Contains(req.MediaData.MimeType, "ogg")), // Voice note for OGG
291
+ },
292
+ }
293
+
294
+ return message, nil
295
+ }
296
+
297
+ func (s *Service) createVideoMessage(req *MessageRequest) (*waE2E.Message, error) {
298
+ if req.MediaData == nil {
299
+ return nil, fmt.Errorf("media_data is required for video messages")
300
+ }
301
+
302
+ // Decode base64 media data
303
+ mediaBytes, err := base64.StdEncoding.DecodeString(req.MediaData.Data)
304
+ if err != nil {
305
+ return nil, fmt.Errorf("invalid base64 media data: %w", err)
306
+ }
307
+
308
+ // Upload media to WhatsApp servers
309
+ uploaded, err := s.client.Upload(context.Background(), mediaBytes, whatsmeow.MediaVideo)
310
+ if err != nil {
311
+ return nil, fmt.Errorf("failed to upload video: %w", err)
312
+ }
313
+
314
+ // Create video message
315
+ message := &waE2E.Message{
316
+ VideoMessage: &waE2E.VideoMessage{
317
+ Caption: proto.String(req.MediaData.Caption),
318
+ Mimetype: proto.String(req.MediaData.MimeType),
319
+ URL: proto.String(uploaded.URL),
320
+ DirectPath: proto.String(uploaded.DirectPath),
321
+ MediaKey: uploaded.MediaKey,
322
+ FileEncSHA256: uploaded.FileEncSHA256,
323
+ FileSHA256: uploaded.FileSHA256,
324
+ FileLength: proto.Uint64(uint64(len(mediaBytes))),
325
+ },
326
+ }
327
+
328
+ return message, nil
329
+ }
330
+
331
+ func (s *Service) createLocationMessage(req *MessageRequest) *waE2E.Message {
332
+ if req.Location == nil {
333
+ return &waE2E.Message{Conversation: proto.String("Invalid location data")}
334
+ }
335
+
336
+ return &waE2E.Message{
337
+ LocationMessage: &waE2E.LocationMessage{
338
+ DegreesLatitude: proto.Float64(req.Location.Latitude),
339
+ DegreesLongitude: proto.Float64(req.Location.Longitude),
340
+ Name: proto.String(req.Location.Name),
341
+ Address: proto.String(req.Location.Address),
342
+ },
343
+ }
344
+ }
345
+
346
+ func (s *Service) createStickerMessage(req *MessageRequest) (*waE2E.Message, error) {
347
+ if req.MediaData == nil {
348
+ return nil, fmt.Errorf("media_data is required for sticker messages")
349
+ }
350
+
351
+ // Decode base64 media data
352
+ mediaBytes, err := base64.StdEncoding.DecodeString(req.MediaData.Data)
353
+ if err != nil {
354
+ return nil, fmt.Errorf("invalid base64 media data: %w", err)
355
+ }
356
+
357
+ // Upload media to WhatsApp servers
358
+ uploaded, err := s.client.Upload(context.Background(), mediaBytes, whatsmeow.MediaImage)
359
+ if err != nil {
360
+ return nil, fmt.Errorf("failed to upload sticker: %w", err)
361
+ }
362
+
363
+ // Create sticker message
364
+ message := &waE2E.Message{
365
+ StickerMessage: &waE2E.StickerMessage{
366
+ Mimetype: proto.String(req.MediaData.MimeType),
367
+ URL: proto.String(uploaded.URL),
368
+ DirectPath: proto.String(uploaded.DirectPath),
369
+ MediaKey: uploaded.MediaKey,
370
+ FileEncSHA256: uploaded.FileEncSHA256,
371
+ FileSHA256: uploaded.FileSHA256,
372
+ FileLength: proto.Uint64(uint64(len(mediaBytes))),
373
+ },
374
+ }
375
+
376
+ return message, nil
377
+ }
378
+
379
+ func (s *Service) createContactMessage(req *MessageRequest) *waE2E.Message {
380
+ if req.Contact == nil {
381
+ return &waE2E.Message{Conversation: proto.String("Invalid contact data")}
382
+ }
383
+
384
+ return &waE2E.Message{
385
+ ContactMessage: &waE2E.ContactMessage{
386
+ DisplayName: proto.String(req.Contact.DisplayName),
387
+ Vcard: proto.String(req.Contact.VCard),
388
+ },
389
+ }
390
+ }