machinaos 0.0.1 → 0.0.7
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 -71
- package/LICENSE +21 -21
- package/README.md +163 -87
- package/bin/cli.js +62 -106
- package/client/.dockerignore +45 -45
- package/client/Dockerfile +68 -68
- package/client/dist/assets/index-DFSC53FP.css +1 -0
- package/client/dist/assets/index-fJ-1gTf5.js +613 -0
- package/client/dist/index.html +14 -0
- package/client/eslint.config.js +34 -16
- package/client/nginx.conf +66 -66
- package/client/package.json +61 -48
- package/client/src/App.tsx +27 -27
- package/client/src/Dashboard.tsx +1200 -1172
- package/client/src/ParameterPanel.tsx +302 -300
- package/client/src/components/AIAgentNode.tsx +315 -321
- package/client/src/components/APIKeyValidator.tsx +117 -117
- package/client/src/components/ClaudeChatModelNode.tsx +17 -17
- package/client/src/components/CredentialsModal.tsx +1200 -306
- package/client/src/components/GeminiChatModelNode.tsx +17 -17
- package/client/src/components/GenericNode.tsx +356 -356
- package/client/src/components/LocationParameterPanel.tsx +153 -153
- package/client/src/components/ModelNode.tsx +285 -285
- package/client/src/components/OpenAIChatModelNode.tsx +17 -17
- package/client/src/components/OutputPanel.tsx +470 -470
- package/client/src/components/ParameterRenderer.tsx +1873 -1873
- package/client/src/components/SkillEditorModal.tsx +3 -3
- package/client/src/components/SquareNode.tsx +812 -796
- package/client/src/components/ToolkitNode.tsx +365 -365
- package/client/src/components/auth/LoginPage.tsx +247 -247
- package/client/src/components/auth/ProtectedRoute.tsx +59 -59
- package/client/src/components/base/BaseChatModelNode.tsx +270 -270
- package/client/src/components/icons/AIProviderIcons.tsx +50 -50
- package/client/src/components/maps/GoogleMapsPicker.tsx +136 -136
- package/client/src/components/maps/MapsPreviewPanel.tsx +109 -109
- package/client/src/components/maps/index.ts +25 -25
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -1094
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +64 -64
- package/client/src/components/parameterPanel/MapsSection.tsx +91 -91
- package/client/src/components/parameterPanel/MiddleSection.tsx +867 -571
- package/client/src/components/parameterPanel/OutputSection.tsx +80 -80
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +81 -81
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -436
- package/client/src/components/parameterPanel/index.ts +41 -41
- package/client/src/components/shared/DataPanel.tsx +142 -142
- package/client/src/components/shared/JSONTreeRenderer.tsx +105 -105
- package/client/src/components/ui/AIResultModal.tsx +203 -203
- package/client/src/components/ui/ApiKeyInput.tsx +93 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -81
- package/client/src/components/ui/CollapsibleSection.tsx +87 -87
- package/client/src/components/ui/ComponentItem.tsx +153 -153
- package/client/src/components/ui/ComponentPalette.tsx +320 -320
- package/client/src/components/ui/ConsolePanel.tsx +151 -43
- package/client/src/components/ui/ErrorBoundary.tsx +195 -195
- package/client/src/components/ui/InputNodesPanel.tsx +203 -203
- package/client/src/components/ui/MapSelector.tsx +313 -313
- package/client/src/components/ui/Modal.tsx +151 -148
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -1150
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -381
- package/client/src/components/ui/QRCodeDisplay.tsx +182 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -736
- package/client/src/components/ui/WorkflowSidebar.tsx +293 -293
- package/client/src/config/antdTheme.ts +186 -186
- package/client/src/contexts/AuthContext.tsx +221 -221
- package/client/src/contexts/ThemeContext.tsx +42 -42
- package/client/src/contexts/WebSocketContext.tsx +2144 -1971
- package/client/src/factories/baseChatModelFactory.ts +255 -255
- package/client/src/hooks/useAndroidOperations.ts +118 -164
- package/client/src/hooks/useApiKeyValidation.ts +106 -106
- package/client/src/hooks/useApiKeys.ts +238 -238
- package/client/src/hooks/useAppTheme.ts +17 -17
- package/client/src/hooks/useComponentPalette.ts +50 -50
- package/client/src/hooks/useDragAndDrop.ts +123 -123
- package/client/src/hooks/useDragVariable.ts +88 -88
- package/client/src/hooks/useExecution.ts +319 -313
- package/client/src/hooks/useParameterPanel.ts +176 -176
- package/client/src/hooks/useReactFlowNodes.ts +188 -188
- package/client/src/hooks/useToolSchema.ts +209 -209
- package/client/src/hooks/useWhatsApp.ts +196 -196
- package/client/src/hooks/useWorkflowManagement.ts +45 -45
- package/client/src/index.css +314 -314
- package/client/src/nodeDefinitions/aiAgentNodes.ts +335 -335
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -340
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -383
- package/client/src/nodeDefinitions/chatNodes.ts +135 -135
- package/client/src/nodeDefinitions/codeNodes.ts +54 -54
- package/client/src/nodeDefinitions/index.ts +14 -14
- package/client/src/nodeDefinitions/locationNodes.ts +462 -462
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -220
- package/client/src/nodeDefinitions/skillNodes.ts +17 -5
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -284
- package/client/src/nodeDefinitions/whatsappNodes.ts +821 -865
- package/client/src/nodeDefinitions.ts +101 -103
- package/client/src/services/dynamicParameterService.ts +95 -95
- package/client/src/services/execution/aiAgentExecutionService.ts +34 -34
- package/client/src/services/executionService.ts +227 -231
- package/client/src/services/workflowApi.ts +91 -91
- package/client/src/store/useAppStore.ts +578 -581
- package/client/src/styles/theme.ts +513 -508
- package/client/src/styles/zIndex.ts +16 -16
- package/client/src/types/ComponentTypes.ts +38 -38
- package/client/src/types/INodeProperties.ts +287 -287
- package/client/src/types/NodeTypes.ts +27 -27
- package/client/src/utils/formatters.ts +32 -32
- package/client/src/utils/googleMapsLoader.ts +139 -139
- package/client/src/utils/locationUtils.ts +84 -84
- package/client/src/utils/nodeUtils.ts +30 -30
- package/client/src/utils/workflow.ts +29 -29
- package/client/src/vite-env.d.ts +12 -12
- package/client/tailwind.config.js +59 -59
- package/client/tsconfig.json +25 -25
- package/client/vite.config.js +35 -35
- package/install.ps1 +308 -0
- package/install.sh +343 -0
- package/package.json +81 -70
- package/scripts/build.js +174 -51
- package/scripts/clean.js +40 -40
- package/scripts/start.js +234 -210
- package/scripts/stop.js +301 -325
- package/server/.dockerignore +44 -44
- package/server/Dockerfile +45 -45
- package/server/constants.py +244 -249
- package/server/core/cache.py +460 -460
- package/server/core/config.py +127 -127
- package/server/core/container.py +98 -98
- package/server/core/database.py +1296 -1210
- package/server/core/logging.py +313 -313
- package/server/main.py +288 -288
- package/server/middleware/__init__.py +5 -5
- package/server/middleware/auth.py +89 -89
- package/server/models/auth.py +52 -52
- package/server/models/cache.py +24 -24
- package/server/models/database.py +235 -210
- package/server/models/nodes.py +435 -455
- package/server/pyproject.toml +75 -72
- package/server/requirements.txt +83 -83
- package/server/routers/android.py +294 -294
- package/server/routers/auth.py +203 -203
- package/server/routers/database.py +150 -150
- package/server/routers/maps.py +141 -141
- package/server/routers/nodejs_compat.py +288 -288
- package/server/routers/webhook.py +90 -90
- package/server/routers/websocket.py +2239 -2127
- package/server/routers/whatsapp.py +761 -761
- package/server/routers/workflow.py +199 -199
- package/server/services/ai.py +2444 -2414
- package/server/services/android_service.py +588 -588
- package/server/services/auth.py +130 -130
- package/server/services/chat_client.py +160 -160
- package/server/services/deployment/manager.py +706 -706
- package/server/services/event_waiter.py +675 -785
- package/server/services/execution/executor.py +1351 -1351
- package/server/services/execution/models.py +1 -1
- package/server/services/handlers/__init__.py +122 -126
- package/server/services/handlers/ai.py +390 -355
- package/server/services/handlers/android.py +69 -260
- package/server/services/handlers/code.py +278 -278
- package/server/services/handlers/http.py +193 -193
- package/server/services/handlers/tools.py +146 -32
- package/server/services/handlers/triggers.py +107 -107
- package/server/services/handlers/utility.py +822 -822
- package/server/services/handlers/whatsapp.py +423 -476
- package/server/services/maps.py +288 -288
- package/server/services/memory_store.py +103 -103
- package/server/services/node_executor.py +372 -375
- package/server/services/scheduler.py +155 -155
- package/server/services/skill_loader.py +1 -1
- package/server/services/status_broadcaster.py +834 -826
- package/server/services/temporal/__init__.py +23 -23
- package/server/services/temporal/activities.py +344 -344
- package/server/services/temporal/client.py +76 -76
- package/server/services/temporal/executor.py +147 -147
- package/server/services/temporal/worker.py +251 -251
- package/server/services/temporal/workflow.py +355 -355
- package/server/services/temporal/ws_client.py +236 -236
- package/server/services/text.py +110 -110
- package/server/services/user_auth.py +172 -172
- package/server/services/websocket_client.py +29 -29
- package/server/services/workflow.py +597 -597
- package/server/skills/android-skill/SKILL.md +4 -4
- package/server/skills/code-skill/SKILL.md +123 -89
- package/server/skills/maps-skill/SKILL.md +3 -3
- package/server/skills/memory-skill/SKILL.md +1 -1
- package/server/skills/web-search-skill/SKILL.md +154 -0
- package/server/skills/whatsapp-skill/SKILL.md +3 -3
- package/server/uv.lock +461 -100
- package/server/whatsapp-rpc/.dockerignore +30 -30
- package/server/whatsapp-rpc/Dockerfile +44 -44
- package/server/whatsapp-rpc/Dockerfile.web +17 -17
- package/server/whatsapp-rpc/README.md +139 -139
- package/server/whatsapp-rpc/bin/whatsapp-rpc-server +0 -0
- package/server/whatsapp-rpc/cli.js +95 -95
- package/server/whatsapp-rpc/configs/config.yaml +6 -6
- package/server/whatsapp-rpc/docker-compose.yml +35 -35
- package/server/whatsapp-rpc/docs/API.md +410 -410
- package/server/whatsapp-rpc/node_modules/.package-lock.json +259 -0
- package/server/whatsapp-rpc/node_modules/chalk/license +9 -0
- package/server/whatsapp-rpc/node_modules/chalk/package.json +83 -0
- package/server/whatsapp-rpc/node_modules/chalk/readme.md +297 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/index.d.ts +325 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/index.js +225 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/utilities.js +33 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/server/whatsapp-rpc/node_modules/commander/LICENSE +22 -0
- package/server/whatsapp-rpc/node_modules/commander/Readme.md +1148 -0
- package/server/whatsapp-rpc/node_modules/commander/esm.mjs +16 -0
- package/server/whatsapp-rpc/node_modules/commander/index.js +26 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/argument.js +145 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/command.js +2179 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/error.js +43 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/help.js +462 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/option.js +329 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/suggestSimilar.js +100 -0
- package/server/whatsapp-rpc/node_modules/commander/package-support.json +16 -0
- package/server/whatsapp-rpc/node_modules/commander/package.json +80 -0
- package/server/whatsapp-rpc/node_modules/commander/typings/esm.d.mts +3 -0
- package/server/whatsapp-rpc/node_modules/commander/typings/index.d.ts +884 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/README.md +89 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/index.js +39 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/enoent.js +59 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/parse.js +91 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/escape.js +47 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/readShebang.js +23 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/resolveCommand.js +52 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/package.json +73 -0
- package/server/whatsapp-rpc/node_modules/execa/index.d.ts +955 -0
- package/server/whatsapp-rpc/node_modules/execa/index.js +309 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/command.js +119 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/error.js +87 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/kill.js +102 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/pipe.js +42 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/promise.js +36 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/stdio.js +49 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/stream.js +133 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/verbose.js +19 -0
- package/server/whatsapp-rpc/node_modules/execa/license +9 -0
- package/server/whatsapp-rpc/node_modules/execa/package.json +90 -0
- package/server/whatsapp-rpc/node_modules/execa/readme.md +822 -0
- package/server/whatsapp-rpc/node_modules/get-stream/license +9 -0
- package/server/whatsapp-rpc/node_modules/get-stream/package.json +53 -0
- package/server/whatsapp-rpc/node_modules/get-stream/readme.md +291 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/array-buffer.js +84 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/array.js +32 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/buffer.js +20 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/contents.js +101 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/index.d.ts +119 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/index.js +5 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/string.js +36 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/utils.js +11 -0
- package/server/whatsapp-rpc/node_modules/get-them-args/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/get-them-args/README.md +95 -0
- package/server/whatsapp-rpc/node_modules/get-them-args/index.js +97 -0
- package/server/whatsapp-rpc/node_modules/get-them-args/package.json +36 -0
- package/server/whatsapp-rpc/node_modules/human-signals/LICENSE +201 -0
- package/server/whatsapp-rpc/node_modules/human-signals/README.md +168 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/core.js +273 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/main.d.ts +73 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/main.js +70 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/realtime.js +16 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/signals.js +34 -0
- package/server/whatsapp-rpc/node_modules/human-signals/package.json +61 -0
- package/server/whatsapp-rpc/node_modules/is-stream/index.d.ts +81 -0
- package/server/whatsapp-rpc/node_modules/is-stream/index.js +29 -0
- package/server/whatsapp-rpc/node_modules/is-stream/license +9 -0
- package/server/whatsapp-rpc/node_modules/is-stream/package.json +44 -0
- package/server/whatsapp-rpc/node_modules/is-stream/readme.md +60 -0
- package/server/whatsapp-rpc/node_modules/isexe/LICENSE +15 -0
- package/server/whatsapp-rpc/node_modules/isexe/README.md +51 -0
- package/server/whatsapp-rpc/node_modules/isexe/index.js +57 -0
- package/server/whatsapp-rpc/node_modules/isexe/mode.js +41 -0
- package/server/whatsapp-rpc/node_modules/isexe/package.json +31 -0
- package/server/whatsapp-rpc/node_modules/isexe/test/basic.js +221 -0
- package/server/whatsapp-rpc/node_modules/isexe/windows.js +42 -0
- package/server/whatsapp-rpc/node_modules/kill-port/.editorconfig +12 -0
- package/server/whatsapp-rpc/node_modules/kill-port/.gitattributes +1 -0
- package/server/whatsapp-rpc/node_modules/kill-port/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/kill-port/README.md +140 -0
- package/server/whatsapp-rpc/node_modules/kill-port/cli.js +25 -0
- package/server/whatsapp-rpc/node_modules/kill-port/example.js +21 -0
- package/server/whatsapp-rpc/node_modules/kill-port/index.js +46 -0
- package/server/whatsapp-rpc/node_modules/kill-port/logo.png +0 -0
- package/server/whatsapp-rpc/node_modules/kill-port/package.json +41 -0
- package/server/whatsapp-rpc/node_modules/kill-port/pnpm-lock.yaml +4606 -0
- package/server/whatsapp-rpc/node_modules/kill-port/test.js +16 -0
- package/server/whatsapp-rpc/node_modules/merge-stream/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/merge-stream/README.md +78 -0
- package/server/whatsapp-rpc/node_modules/merge-stream/index.js +41 -0
- package/server/whatsapp-rpc/node_modules/merge-stream/package.json +19 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/index.d.ts +52 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/index.js +71 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/license +9 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/package.json +45 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/readme.md +90 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/index.d.ts +90 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/index.js +52 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/license +9 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/index.d.ts +31 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/index.js +12 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/license +9 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/package.json +41 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/readme.md +57 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/package.json +49 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/readme.md +104 -0
- package/server/whatsapp-rpc/node_modules/onetime/index.d.ts +59 -0
- package/server/whatsapp-rpc/node_modules/onetime/index.js +41 -0
- package/server/whatsapp-rpc/node_modules/onetime/license +9 -0
- package/server/whatsapp-rpc/node_modules/onetime/package.json +45 -0
- package/server/whatsapp-rpc/node_modules/onetime/readme.md +94 -0
- package/server/whatsapp-rpc/node_modules/path-key/index.d.ts +40 -0
- package/server/whatsapp-rpc/node_modules/path-key/index.js +16 -0
- package/server/whatsapp-rpc/node_modules/path-key/license +9 -0
- package/server/whatsapp-rpc/node_modules/path-key/package.json +39 -0
- package/server/whatsapp-rpc/node_modules/path-key/readme.md +61 -0
- package/server/whatsapp-rpc/node_modules/shebang-command/index.js +19 -0
- package/server/whatsapp-rpc/node_modules/shebang-command/license +9 -0
- package/server/whatsapp-rpc/node_modules/shebang-command/package.json +34 -0
- package/server/whatsapp-rpc/node_modules/shebang-command/readme.md +34 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/index.d.ts +22 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/index.js +2 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/license +9 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/package.json +35 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/readme.md +33 -0
- package/server/whatsapp-rpc/node_modules/shell-exec/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/shell-exec/README.md +60 -0
- package/server/whatsapp-rpc/node_modules/shell-exec/index.js +47 -0
- package/server/whatsapp-rpc/node_modules/shell-exec/package.json +29 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/LICENSE.txt +16 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/README.md +74 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.d.ts +12 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.js +10 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.d.ts +48 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.js +279 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/package.json +3 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.d.ts +29 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.js +42 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.d.ts +12 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.js +4 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.d.ts +48 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.js +275 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/package.json +3 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.d.ts +29 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.js +39 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/package.json +106 -0
- package/server/whatsapp-rpc/node_modules/strip-final-newline/index.js +14 -0
- package/server/whatsapp-rpc/node_modules/strip-final-newline/license +9 -0
- package/server/whatsapp-rpc/node_modules/strip-final-newline/package.json +43 -0
- package/server/whatsapp-rpc/node_modules/strip-final-newline/readme.md +35 -0
- package/server/whatsapp-rpc/node_modules/which/CHANGELOG.md +166 -0
- package/server/whatsapp-rpc/node_modules/which/LICENSE +15 -0
- package/server/whatsapp-rpc/node_modules/which/README.md +54 -0
- package/server/whatsapp-rpc/node_modules/which/bin/node-which +52 -0
- package/server/whatsapp-rpc/node_modules/which/package.json +43 -0
- package/server/whatsapp-rpc/node_modules/which/which.js +125 -0
- package/server/whatsapp-rpc/package-lock.json +272 -0
- package/server/whatsapp-rpc/package.json +30 -30
- package/server/whatsapp-rpc/schema.json +1294 -1294
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -66
- package/server/whatsapp-rpc/scripts/cli.js +162 -162
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -166
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -15
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -4
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -427
- package/server/whatsapp-rpc/web/app.py +609 -609
- package/server/whatsapp-rpc/web/requirements.txt +6 -6
- package/server/whatsapp-rpc/web/rpc_client.py +427 -427
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -59
- package/server/whatsapp-rpc/web/templates/base.html +149 -149
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -240
- package/server/whatsapp-rpc/web/templates/dashboard.html +319 -319
- package/server/whatsapp-rpc/web/templates/groups.html +328 -328
- package/server/whatsapp-rpc/web/templates/messages.html +465 -465
- package/server/whatsapp-rpc/web/templates/messaging.html +680 -680
- package/server/whatsapp-rpc/web/templates/send.html +258 -258
- package/server/whatsapp-rpc/web/templates/settings.html +459 -459
- package/client/src/components/ui/AndroidSettingsPanel.tsx +0 -401
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +0 -345
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +0 -140
- package/docker-compose.prod.yml +0 -107
- package/docker-compose.yml +0 -104
- package/docs-MachinaOs/README.md +0 -85
- package/docs-MachinaOs/deployment/docker.mdx +0 -228
- package/docs-MachinaOs/deployment/production.mdx +0 -345
- package/docs-MachinaOs/docs.json +0 -75
- package/docs-MachinaOs/faq.mdx +0 -309
- package/docs-MachinaOs/favicon.svg +0 -5
- package/docs-MachinaOs/installation.mdx +0 -160
- package/docs-MachinaOs/introduction.mdx +0 -114
- package/docs-MachinaOs/logo/dark.svg +0 -6
- package/docs-MachinaOs/logo/light.svg +0 -6
- package/docs-MachinaOs/nodes/ai-agent.mdx +0 -216
- package/docs-MachinaOs/nodes/ai-models.mdx +0 -240
- package/docs-MachinaOs/nodes/android.mdx +0 -411
- package/docs-MachinaOs/nodes/overview.mdx +0 -181
- package/docs-MachinaOs/nodes/schedulers.mdx +0 -316
- package/docs-MachinaOs/nodes/webhooks.mdx +0 -330
- package/docs-MachinaOs/nodes/whatsapp.mdx +0 -305
- package/docs-MachinaOs/quickstart.mdx +0 -119
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +0 -177
- package/docs-MachinaOs/tutorials/android-automation.mdx +0 -242
- package/docs-MachinaOs/tutorials/first-workflow.mdx +0 -134
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +0 -185
- package/nul +0 -0
- package/scripts/check-ports.ps1 +0 -33
- package/scripts/kill-port.ps1 +0 -154
|
@@ -1,465 +1,465 @@
|
|
|
1
|
-
{% extends "base.html" %}
|
|
2
|
-
|
|
3
|
-
{% block title %}Received Messages - WhatsApp Controller{% endblock %}
|
|
4
|
-
|
|
5
|
-
{% block content %}
|
|
6
|
-
<div class="max-w-6xl mx-auto">
|
|
7
|
-
<div class="bg-white rounded-lg shadow-md p-6">
|
|
8
|
-
<div class="flex justify-between items-center mb-6">
|
|
9
|
-
<h2 class="text-2xl font-bold text-gray-800">Received Messages</h2>
|
|
10
|
-
<div class="flex items-center space-x-4">
|
|
11
|
-
<button onclick="loadMessages()" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
|
|
12
|
-
Refresh
|
|
13
|
-
</button>
|
|
14
|
-
<button onclick="clearMessages()" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition">
|
|
15
|
-
Clear Display
|
|
16
|
-
</button>
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<!-- Messages count -->
|
|
21
|
-
<div class="mb-4 p-3 bg-gray-100 rounded">
|
|
22
|
-
<p class="text-sm text-gray-600">
|
|
23
|
-
<span class="font-semibold">Total Messages:</span>
|
|
24
|
-
<span id="message-count">0</span>
|
|
25
|
-
</p>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<!-- Messages container -->
|
|
29
|
-
<div id="messages-container" class="space-y-4 max-h-[600px] overflow-y-auto p-4 bg-gray-50 rounded">
|
|
30
|
-
<p class="text-gray-500 text-center py-8">No messages yet. Waiting for incoming messages...</p>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<!-- Media Viewer Modal -->
|
|
36
|
-
<div id="media-modal" class="fixed inset-0 bg-black bg-opacity-75 hidden z-50 flex items-center justify-center">
|
|
37
|
-
<div class="bg-white rounded-lg max-w-4xl max-h-[90vh] w-full m-4 overflow-hidden">
|
|
38
|
-
<div class="flex justify-between items-center p-4 border-b">
|
|
39
|
-
<h3 class="text-lg font-semibold" id="media-modal-title">Media Viewer</h3>
|
|
40
|
-
<button onclick="closeMediaModal()" class="text-gray-500 hover:text-gray-700">
|
|
41
|
-
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
42
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
43
|
-
</svg>
|
|
44
|
-
</button>
|
|
45
|
-
</div>
|
|
46
|
-
<div id="media-modal-content" class="p-4 overflow-auto max-h-[calc(90vh-80px)] flex items-center justify-center bg-gray-100">
|
|
47
|
-
<p class="text-gray-500">Loading...</p>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
{% endblock %}
|
|
53
|
-
|
|
54
|
-
{% block scripts %}
|
|
55
|
-
<script>
|
|
56
|
-
let messages = [];
|
|
57
|
-
let groupsCache = {}; // Cache group names by JID
|
|
58
|
-
const messagesContainer = document.getElementById('messages-container');
|
|
59
|
-
const messageCount = document.getElementById('message-count');
|
|
60
|
-
|
|
61
|
-
// Load groups cache on page load
|
|
62
|
-
loadGroupsCache();
|
|
63
|
-
|
|
64
|
-
// Subscribe to messages via WebSocket
|
|
65
|
-
socket.emit('subscribe_messages');
|
|
66
|
-
|
|
67
|
-
// Load groups into cache for name lookup
|
|
68
|
-
async function loadGroupsCache() {
|
|
69
|
-
try {
|
|
70
|
-
const response = await fetch('/api/groups');
|
|
71
|
-
const data = await response.json();
|
|
72
|
-
if (data.success && data.data) {
|
|
73
|
-
data.data.forEach(group => {
|
|
74
|
-
groupsCache[group.jid] = group.name;
|
|
75
|
-
});
|
|
76
|
-
console.log(`Cached ${Object.keys(groupsCache).length} group names`);
|
|
77
|
-
}
|
|
78
|
-
} catch (error) {
|
|
79
|
-
console.error('Failed to load groups cache:', error);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Get group name from cache
|
|
84
|
-
function getGroupName(jid) {
|
|
85
|
-
return groupsCache[jid] || null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Listen for new messages via WebSocket
|
|
89
|
-
socket.on('whatsapp_event', function(data) {
|
|
90
|
-
console.log('Received WhatsApp event:', data);
|
|
91
|
-
|
|
92
|
-
if (data.type === 'message_received') {
|
|
93
|
-
// Add new message to the beginning
|
|
94
|
-
messages.unshift(data);
|
|
95
|
-
renderMessages();
|
|
96
|
-
|
|
97
|
-
// Show notification
|
|
98
|
-
showNotification('New message received from ' + formatSender(data.data.sender));
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Refresh just re-renders (messages come via WebSocket)
|
|
103
|
-
function loadMessages() {
|
|
104
|
-
renderMessages();
|
|
105
|
-
console.log('Messages display refreshed');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Render all messages
|
|
109
|
-
function renderMessages() {
|
|
110
|
-
if (messages.length === 0) {
|
|
111
|
-
messagesContainer.innerHTML = `
|
|
112
|
-
<div class="text-center py-8">
|
|
113
|
-
<p class="text-gray-500 mb-2">No messages yet.</p>
|
|
114
|
-
<p class="text-sm text-gray-400">Messages will appear here in real-time as they are received.</p>
|
|
115
|
-
<p class="text-xs text-gray-400 mt-2">Make sure WhatsApp is connected.</p>
|
|
116
|
-
</div>
|
|
117
|
-
`;
|
|
118
|
-
messageCount.textContent = '0';
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
messageCount.textContent = messages.length;
|
|
123
|
-
|
|
124
|
-
messagesContainer.innerHTML = messages.map(msg => {
|
|
125
|
-
const data = msg.data || {};
|
|
126
|
-
return createMessageCard(data);
|
|
127
|
-
}).join('');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Create message card HTML
|
|
131
|
-
function createMessageCard(data) {
|
|
132
|
-
const messageType = data.message_type || 'unknown';
|
|
133
|
-
const timestamp = formatTimestamp(data.timestamp);
|
|
134
|
-
const sender = formatSender(data.sender);
|
|
135
|
-
const isFromMe = data.is_from_me;
|
|
136
|
-
const isGroup = data.is_group || false;
|
|
137
|
-
const isForwarded = data.is_forwarded || false;
|
|
138
|
-
const forwardingScore = data.forwarding_score || 0;
|
|
139
|
-
const chatId = data.chat_id || '';
|
|
140
|
-
|
|
141
|
-
// Get group name if it's a group message
|
|
142
|
-
let groupName = null;
|
|
143
|
-
if (isGroup && chatId) {
|
|
144
|
-
groupName = getGroupName(chatId);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
let contentHtml = '';
|
|
148
|
-
let typeIcon = '';
|
|
149
|
-
let typeColor = 'bg-blue-100 text-blue-800';
|
|
150
|
-
|
|
151
|
-
// Render based on message type
|
|
152
|
-
switch(messageType) {
|
|
153
|
-
case 'text':
|
|
154
|
-
typeIcon = '💬'; // Speech bubble
|
|
155
|
-
contentHtml = `<p class="text-gray-800">${escapeHtml(data.text || '')}</p>`;
|
|
156
|
-
break;
|
|
157
|
-
|
|
158
|
-
case 'image':
|
|
159
|
-
typeIcon = '📷'; // Camera
|
|
160
|
-
typeColor = 'bg-purple-100 text-purple-800';
|
|
161
|
-
const imageInfo = data.image || {};
|
|
162
|
-
contentHtml = `
|
|
163
|
-
<div class="flex items-center space-x-3 mb-2">
|
|
164
|
-
<svg class="w-16 h-16 text-purple-400" fill="currentColor" viewBox="0 0 20 20">
|
|
165
|
-
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"></path>
|
|
166
|
-
</svg>
|
|
167
|
-
<div class="flex-1">
|
|
168
|
-
<p class="text-sm text-gray-600">Image message</p>
|
|
169
|
-
<p class="text-xs text-gray-500">${imageInfo.mime_type || 'image'} - ${formatFileSize(imageInfo.file_length || 0)}</p>
|
|
170
|
-
${data.caption ? `<p class="text-gray-800 italic mt-1">${escapeHtml(data.caption)}</p>` : ''}
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
${imageInfo.url ? `<button onclick="viewMedia('${escapeHtml(data.message_id)}', 'image', '${escapeHtml(imageInfo.url)}')" class="mt-2 px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600 text-sm">View Image</button>` : ''}
|
|
174
|
-
`;
|
|
175
|
-
break;
|
|
176
|
-
|
|
177
|
-
case 'video':
|
|
178
|
-
typeIcon = '🎥'; // Video camera
|
|
179
|
-
typeColor = 'bg-pink-100 text-pink-800';
|
|
180
|
-
const videoInfo = data.video || {};
|
|
181
|
-
contentHtml = `
|
|
182
|
-
<div class="flex items-center space-x-3 mb-2">
|
|
183
|
-
<svg class="w-16 h-16 text-pink-400" fill="currentColor" viewBox="0 0 20 20">
|
|
184
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path>
|
|
185
|
-
</svg>
|
|
186
|
-
<div class="flex-1">
|
|
187
|
-
<p class="text-sm text-gray-600">Video message</p>
|
|
188
|
-
<p class="text-xs text-gray-500">${videoInfo.mime_type || 'video'} - ${formatFileSize(videoInfo.file_length || 0)} - ${videoInfo.seconds || 0}s</p>
|
|
189
|
-
${data.caption ? `<p class="text-gray-800 italic mt-1">${escapeHtml(data.caption)}</p>` : ''}
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
${videoInfo.url ? `<button onclick="viewMedia('${escapeHtml(data.message_id)}', 'video', '${escapeHtml(videoInfo.url)}')" class="mt-2 px-4 py-2 bg-pink-500 text-white rounded hover:bg-pink-600 text-sm">View Video</button>` : ''}
|
|
193
|
-
`;
|
|
194
|
-
break;
|
|
195
|
-
|
|
196
|
-
case 'audio':
|
|
197
|
-
typeIcon = '🎵'; // Musical note
|
|
198
|
-
typeColor = 'bg-green-100 text-green-800';
|
|
199
|
-
contentHtml = `<p class="text-sm text-gray-600">Audio message</p>`;
|
|
200
|
-
break;
|
|
201
|
-
|
|
202
|
-
case 'document':
|
|
203
|
-
typeIcon = '📄'; // Document
|
|
204
|
-
typeColor = 'bg-yellow-100 text-yellow-800';
|
|
205
|
-
contentHtml = `
|
|
206
|
-
<p class="text-sm text-gray-600 mb-2">Document</p>
|
|
207
|
-
${data.file_name ? `<p class="text-gray-700 font-mono text-xs">${escapeHtml(data.file_name)}</p>` : ''}
|
|
208
|
-
`;
|
|
209
|
-
break;
|
|
210
|
-
|
|
211
|
-
case 'sticker':
|
|
212
|
-
typeIcon = '😎'; // Sticker emoji
|
|
213
|
-
typeColor = 'bg-orange-100 text-orange-800';
|
|
214
|
-
contentHtml = `<p class="text-sm text-gray-600">Sticker</p>`;
|
|
215
|
-
break;
|
|
216
|
-
|
|
217
|
-
case 'location':
|
|
218
|
-
typeIcon = '📍'; // Pin
|
|
219
|
-
typeColor = 'bg-red-100 text-red-800';
|
|
220
|
-
contentHtml = `
|
|
221
|
-
<p class="text-sm text-gray-600 mb-2">Location</p>
|
|
222
|
-
${data.location ? `
|
|
223
|
-
<p class="text-gray-800">
|
|
224
|
-
<span class="font-semibold">${escapeHtml(data.location.name || 'Unknown')}</span><br>
|
|
225
|
-
<span class="text-sm text-gray-600">${escapeHtml(data.location.address || '')}</span><br>
|
|
226
|
-
<span class="text-xs text-gray-500">Lat: ${data.location.latitude}, Lon: ${data.location.longitude}</span>
|
|
227
|
-
</p>
|
|
228
|
-
` : ''}
|
|
229
|
-
`;
|
|
230
|
-
break;
|
|
231
|
-
|
|
232
|
-
case 'contact':
|
|
233
|
-
typeIcon = '👤'; // Contact
|
|
234
|
-
typeColor = 'bg-indigo-100 text-indigo-800';
|
|
235
|
-
contentHtml = `
|
|
236
|
-
<p class="text-sm text-gray-600 mb-2">Contact Card</p>
|
|
237
|
-
${data.contact ? `
|
|
238
|
-
<p class="text-gray-800 font-semibold">${escapeHtml(data.contact.display_name || 'Unknown')}</p>
|
|
239
|
-
` : ''}
|
|
240
|
-
`;
|
|
241
|
-
break;
|
|
242
|
-
|
|
243
|
-
case 'contacts':
|
|
244
|
-
typeIcon = '👥'; // Contacts
|
|
245
|
-
typeColor = 'bg-teal-100 text-teal-800';
|
|
246
|
-
contentHtml = `<p class="text-sm text-gray-600">Multiple contacts (${data.contacts_count || 0})</p>`;
|
|
247
|
-
break;
|
|
248
|
-
|
|
249
|
-
default:
|
|
250
|
-
typeIcon = '❓'; // Question mark
|
|
251
|
-
typeColor = 'bg-gray-100 text-gray-800';
|
|
252
|
-
contentHtml = `<p class="text-sm text-gray-600">Unknown message type: ${messageType}</p>`;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Add quoted message if exists
|
|
256
|
-
let quotedHtml = '';
|
|
257
|
-
if (data.quoted) {
|
|
258
|
-
quotedHtml = `
|
|
259
|
-
<div class="mt-2 p-2 bg-gray-100 border-l-4 border-gray-400 rounded">
|
|
260
|
-
<p class="text-xs text-gray-600">Replying to:</p>
|
|
261
|
-
<p class="text-sm text-gray-700 italic">${escapeHtml(data.quoted.content || '')}</p>
|
|
262
|
-
</div>
|
|
263
|
-
`;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Build group info line
|
|
267
|
-
let groupInfoHtml = '';
|
|
268
|
-
if (isGroup) {
|
|
269
|
-
const displayGroupName = groupName || chatId.replace('@g.us', '');
|
|
270
|
-
groupInfoHtml = `<p class="text-xs text-purple-600">in <span class="font-medium">${escapeHtml(displayGroupName)}</span></p>`;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Build forwarded indicator
|
|
274
|
-
let forwardedHtml = '';
|
|
275
|
-
if (isForwarded) {
|
|
276
|
-
const forwardedLabel = forwardingScore > 4 ? 'Forwarded many times' : 'Forwarded';
|
|
277
|
-
forwardedHtml = `<span class="inline-block px-2 py-1 rounded text-xs font-medium bg-gray-200 text-gray-700 ml-1" title="Forwarding score: ${forwardingScore}">↷ ${forwardedLabel}</span>`;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return `
|
|
281
|
-
<div class="bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition ${isFromMe ? 'border-l-4 border-green-500' : isGroup ? 'border-l-4 border-purple-500' : 'border-l-4 border-blue-500'}">
|
|
282
|
-
<div class="flex justify-between items-start mb-2">
|
|
283
|
-
<div class="flex items-center space-x-2">
|
|
284
|
-
<span class="text-2xl">${typeIcon}</span>
|
|
285
|
-
<div>
|
|
286
|
-
<p class="font-semibold text-gray-800">${sender}</p>
|
|
287
|
-
${groupInfoHtml}
|
|
288
|
-
<span class="inline-block px-2 py-1 rounded text-xs font-medium ${typeColor}">${messageType}</span>${forwardedHtml}
|
|
289
|
-
</div>
|
|
290
|
-
</div>
|
|
291
|
-
<div class="text-right">
|
|
292
|
-
<p class="text-xs text-gray-500">${timestamp}</p>
|
|
293
|
-
${isFromMe ? '<span class="text-xs text-green-600 font-medium">Sent by me</span>' : ''}
|
|
294
|
-
${isGroup && !isFromMe ? '<span class="text-xs text-purple-600 font-medium">Group message</span>' : ''}
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
<div class="mt-2">
|
|
298
|
-
${contentHtml}
|
|
299
|
-
${quotedHtml}
|
|
300
|
-
</div>
|
|
301
|
-
<div class="mt-2 pt-2 border-t text-xs text-gray-400 flex justify-between items-center">
|
|
302
|
-
<span class="font-mono">ID: ${escapeHtml(data.message_id || 'N/A')}</span>
|
|
303
|
-
${!isFromMe ? `<button onclick="markAsRead('${escapeHtml(data.message_id)}', '${escapeHtml(chatId)}', '${escapeHtml(data.sender || '')}')" class="px-2 py-1 bg-blue-500 text-white rounded text-xs hover:bg-blue-600">Mark Read</button>` : ''}
|
|
304
|
-
</div>
|
|
305
|
-
</div>
|
|
306
|
-
`;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Format timestamp
|
|
310
|
-
function formatTimestamp(timestamp) {
|
|
311
|
-
if (!timestamp) return 'Unknown time';
|
|
312
|
-
// Handle both ISO 8601 strings and Unix timestamps
|
|
313
|
-
const date = typeof timestamp === 'string' ? new Date(timestamp) : new Date(timestamp * 1000);
|
|
314
|
-
if (isNaN(date.getTime())) return 'Invalid Date';
|
|
315
|
-
return date.toLocaleString();
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Format sender
|
|
319
|
-
function formatSender(sender) {
|
|
320
|
-
if (!sender) return 'Unknown';
|
|
321
|
-
// Remove @s.whatsapp.net suffix
|
|
322
|
-
return sender.replace('@s.whatsapp.net', '').replace('@c.us', '');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Escape HTML
|
|
326
|
-
function escapeHtml(text) {
|
|
327
|
-
const div = document.createElement('div');
|
|
328
|
-
div.textContent = text;
|
|
329
|
-
return div.innerHTML;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Clear messages display
|
|
333
|
-
function clearMessages() {
|
|
334
|
-
if (confirm('Clear all displayed messages? (This will not delete them from storage)')) {
|
|
335
|
-
messages = [];
|
|
336
|
-
renderMessages();
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Format file size
|
|
341
|
-
function formatFileSize(bytes) {
|
|
342
|
-
if (!bytes || bytes === 0) return '0 B';
|
|
343
|
-
const k = 1024;
|
|
344
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
345
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
346
|
-
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// View media function
|
|
350
|
-
function viewMedia(messageId, mediaType, url) {
|
|
351
|
-
console.log('View media:', messageId, mediaType, url);
|
|
352
|
-
|
|
353
|
-
const modal = document.getElementById('media-modal');
|
|
354
|
-
const modalTitle = document.getElementById('media-modal-title');
|
|
355
|
-
const modalContent = document.getElementById('media-modal-content');
|
|
356
|
-
|
|
357
|
-
// Show modal
|
|
358
|
-
modal.classList.remove('hidden');
|
|
359
|
-
|
|
360
|
-
// Set title
|
|
361
|
-
modalTitle.textContent = `Viewing ${mediaType}`;
|
|
362
|
-
|
|
363
|
-
// Show loading
|
|
364
|
-
modalContent.innerHTML = '<p class="text-gray-500">Downloading and decrypting media...</p>';
|
|
365
|
-
|
|
366
|
-
// Fetch media via Flask RPC endpoint (returns JSON with base64 data)
|
|
367
|
-
fetch(`/api/media/${messageId}`)
|
|
368
|
-
.then(response => response.json())
|
|
369
|
-
.then(result => {
|
|
370
|
-
if (!result.success) {
|
|
371
|
-
throw new Error(result.error || 'Failed to download media');
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const mediaData = result.data;
|
|
375
|
-
const base64Data = mediaData.data;
|
|
376
|
-
const mimeType = mediaData.mime_type || 'application/octet-stream';
|
|
377
|
-
|
|
378
|
-
// Convert base64 to blob
|
|
379
|
-
const byteCharacters = atob(base64Data);
|
|
380
|
-
const byteNumbers = new Array(byteCharacters.length);
|
|
381
|
-
for (let i = 0; i < byteCharacters.length; i++) {
|
|
382
|
-
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
383
|
-
}
|
|
384
|
-
const byteArray = new Uint8Array(byteNumbers);
|
|
385
|
-
const blob = new Blob([byteArray], { type: mimeType });
|
|
386
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
387
|
-
|
|
388
|
-
if (mediaType === 'image') {
|
|
389
|
-
modalContent.innerHTML = `
|
|
390
|
-
<img src="${blobUrl}" class="max-w-full max-h-full object-contain" alt="WhatsApp Image">
|
|
391
|
-
`;
|
|
392
|
-
} else if (mediaType === 'video') {
|
|
393
|
-
modalContent.innerHTML = `
|
|
394
|
-
<video controls class="max-w-full max-h-full" autoplay>
|
|
395
|
-
<source src="${blobUrl}" type="${mimeType}">
|
|
396
|
-
Your browser does not support the video tag.
|
|
397
|
-
</video>
|
|
398
|
-
`;
|
|
399
|
-
} else if (mediaType === 'audio') {
|
|
400
|
-
modalContent.innerHTML = `
|
|
401
|
-
<audio controls autoplay class="w-full">
|
|
402
|
-
<source src="${blobUrl}" type="${mimeType}">
|
|
403
|
-
Your browser does not support the audio tag.
|
|
404
|
-
</audio>
|
|
405
|
-
`;
|
|
406
|
-
} else {
|
|
407
|
-
modalContent.innerHTML = `
|
|
408
|
-
<div class="text-center">
|
|
409
|
-
<p class="text-gray-600 mb-4">Media loaded successfully</p>
|
|
410
|
-
<a href="${blobUrl}" download class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
|
411
|
-
Download ${mediaType}
|
|
412
|
-
</a>
|
|
413
|
-
</div>
|
|
414
|
-
`;
|
|
415
|
-
}
|
|
416
|
-
})
|
|
417
|
-
.catch(error => {
|
|
418
|
-
console.error('Error loading media:', error);
|
|
419
|
-
modalContent.innerHTML = `
|
|
420
|
-
<div class="text-center text-red-600">
|
|
421
|
-
<p>Failed to load media: ${error.message}</p>
|
|
422
|
-
<p class="text-sm text-gray-500 mt-2">The message may have expired or been deleted.</p>
|
|
423
|
-
</div>
|
|
424
|
-
`;
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Close media modal
|
|
429
|
-
function closeMediaModal() {
|
|
430
|
-
const modal = document.getElementById('media-modal');
|
|
431
|
-
modal.classList.add('hidden');
|
|
432
|
-
|
|
433
|
-
// Clear content to stop videos/audio
|
|
434
|
-
const modalContent = document.getElementById('media-modal-content');
|
|
435
|
-
modalContent.innerHTML = '<p class="text-gray-500">Loading...</p>';
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Show notification
|
|
439
|
-
function showNotification(message, type = 'info') {
|
|
440
|
-
// Simple console notification for now
|
|
441
|
-
console.log(`[${type.toUpperCase()}] ${message}`);
|
|
442
|
-
|
|
443
|
-
// Could add a toast notification here in the future
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Mark message as read via WebSocket
|
|
447
|
-
function markAsRead(messageId, chatJid, senderJid) {
|
|
448
|
-
console.log('Marking as read:', messageId, chatJid, senderJid);
|
|
449
|
-
socket.emit('mark_read', {
|
|
450
|
-
message_ids: [messageId],
|
|
451
|
-
chat_jid: chatJid,
|
|
452
|
-
sender_jid: senderJid || null
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Listen for mark_read result
|
|
457
|
-
socket.on('mark_read_result', function(data) {
|
|
458
|
-
if (data.success) {
|
|
459
|
-
showNotification('Message marked as read');
|
|
460
|
-
} else {
|
|
461
|
-
showNotification('Failed to mark as read: ' + data.error, 'error');
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
</script>
|
|
465
|
-
{% endblock %}
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Received Messages - WhatsApp Controller{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="max-w-6xl mx-auto">
|
|
7
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
8
|
+
<div class="flex justify-between items-center mb-6">
|
|
9
|
+
<h2 class="text-2xl font-bold text-gray-800">Received Messages</h2>
|
|
10
|
+
<div class="flex items-center space-x-4">
|
|
11
|
+
<button onclick="loadMessages()" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
|
|
12
|
+
Refresh
|
|
13
|
+
</button>
|
|
14
|
+
<button onclick="clearMessages()" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition">
|
|
15
|
+
Clear Display
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Messages count -->
|
|
21
|
+
<div class="mb-4 p-3 bg-gray-100 rounded">
|
|
22
|
+
<p class="text-sm text-gray-600">
|
|
23
|
+
<span class="font-semibold">Total Messages:</span>
|
|
24
|
+
<span id="message-count">0</span>
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- Messages container -->
|
|
29
|
+
<div id="messages-container" class="space-y-4 max-h-[600px] overflow-y-auto p-4 bg-gray-50 rounded">
|
|
30
|
+
<p class="text-gray-500 text-center py-8">No messages yet. Waiting for incoming messages...</p>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<!-- Media Viewer Modal -->
|
|
36
|
+
<div id="media-modal" class="fixed inset-0 bg-black bg-opacity-75 hidden z-50 flex items-center justify-center">
|
|
37
|
+
<div class="bg-white rounded-lg max-w-4xl max-h-[90vh] w-full m-4 overflow-hidden">
|
|
38
|
+
<div class="flex justify-between items-center p-4 border-b">
|
|
39
|
+
<h3 class="text-lg font-semibold" id="media-modal-title">Media Viewer</h3>
|
|
40
|
+
<button onclick="closeMediaModal()" class="text-gray-500 hover:text-gray-700">
|
|
41
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
42
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
43
|
+
</svg>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
<div id="media-modal-content" class="p-4 overflow-auto max-h-[calc(90vh-80px)] flex items-center justify-center bg-gray-100">
|
|
47
|
+
<p class="text-gray-500">Loading...</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{% endblock %}
|
|
53
|
+
|
|
54
|
+
{% block scripts %}
|
|
55
|
+
<script>
|
|
56
|
+
let messages = [];
|
|
57
|
+
let groupsCache = {}; // Cache group names by JID
|
|
58
|
+
const messagesContainer = document.getElementById('messages-container');
|
|
59
|
+
const messageCount = document.getElementById('message-count');
|
|
60
|
+
|
|
61
|
+
// Load groups cache on page load
|
|
62
|
+
loadGroupsCache();
|
|
63
|
+
|
|
64
|
+
// Subscribe to messages via WebSocket
|
|
65
|
+
socket.emit('subscribe_messages');
|
|
66
|
+
|
|
67
|
+
// Load groups into cache for name lookup
|
|
68
|
+
async function loadGroupsCache() {
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch('/api/groups');
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
if (data.success && data.data) {
|
|
73
|
+
data.data.forEach(group => {
|
|
74
|
+
groupsCache[group.jid] = group.name;
|
|
75
|
+
});
|
|
76
|
+
console.log(`Cached ${Object.keys(groupsCache).length} group names`);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Failed to load groups cache:', error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Get group name from cache
|
|
84
|
+
function getGroupName(jid) {
|
|
85
|
+
return groupsCache[jid] || null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Listen for new messages via WebSocket
|
|
89
|
+
socket.on('whatsapp_event', function(data) {
|
|
90
|
+
console.log('Received WhatsApp event:', data);
|
|
91
|
+
|
|
92
|
+
if (data.type === 'message_received') {
|
|
93
|
+
// Add new message to the beginning
|
|
94
|
+
messages.unshift(data);
|
|
95
|
+
renderMessages();
|
|
96
|
+
|
|
97
|
+
// Show notification
|
|
98
|
+
showNotification('New message received from ' + formatSender(data.data.sender));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Refresh just re-renders (messages come via WebSocket)
|
|
103
|
+
function loadMessages() {
|
|
104
|
+
renderMessages();
|
|
105
|
+
console.log('Messages display refreshed');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Render all messages
|
|
109
|
+
function renderMessages() {
|
|
110
|
+
if (messages.length === 0) {
|
|
111
|
+
messagesContainer.innerHTML = `
|
|
112
|
+
<div class="text-center py-8">
|
|
113
|
+
<p class="text-gray-500 mb-2">No messages yet.</p>
|
|
114
|
+
<p class="text-sm text-gray-400">Messages will appear here in real-time as they are received.</p>
|
|
115
|
+
<p class="text-xs text-gray-400 mt-2">Make sure WhatsApp is connected.</p>
|
|
116
|
+
</div>
|
|
117
|
+
`;
|
|
118
|
+
messageCount.textContent = '0';
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
messageCount.textContent = messages.length;
|
|
123
|
+
|
|
124
|
+
messagesContainer.innerHTML = messages.map(msg => {
|
|
125
|
+
const data = msg.data || {};
|
|
126
|
+
return createMessageCard(data);
|
|
127
|
+
}).join('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Create message card HTML
|
|
131
|
+
function createMessageCard(data) {
|
|
132
|
+
const messageType = data.message_type || 'unknown';
|
|
133
|
+
const timestamp = formatTimestamp(data.timestamp);
|
|
134
|
+
const sender = formatSender(data.sender);
|
|
135
|
+
const isFromMe = data.is_from_me;
|
|
136
|
+
const isGroup = data.is_group || false;
|
|
137
|
+
const isForwarded = data.is_forwarded || false;
|
|
138
|
+
const forwardingScore = data.forwarding_score || 0;
|
|
139
|
+
const chatId = data.chat_id || '';
|
|
140
|
+
|
|
141
|
+
// Get group name if it's a group message
|
|
142
|
+
let groupName = null;
|
|
143
|
+
if (isGroup && chatId) {
|
|
144
|
+
groupName = getGroupName(chatId);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let contentHtml = '';
|
|
148
|
+
let typeIcon = '';
|
|
149
|
+
let typeColor = 'bg-blue-100 text-blue-800';
|
|
150
|
+
|
|
151
|
+
// Render based on message type
|
|
152
|
+
switch(messageType) {
|
|
153
|
+
case 'text':
|
|
154
|
+
typeIcon = '💬'; // Speech bubble
|
|
155
|
+
contentHtml = `<p class="text-gray-800">${escapeHtml(data.text || '')}</p>`;
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case 'image':
|
|
159
|
+
typeIcon = '📷'; // Camera
|
|
160
|
+
typeColor = 'bg-purple-100 text-purple-800';
|
|
161
|
+
const imageInfo = data.image || {};
|
|
162
|
+
contentHtml = `
|
|
163
|
+
<div class="flex items-center space-x-3 mb-2">
|
|
164
|
+
<svg class="w-16 h-16 text-purple-400" fill="currentColor" viewBox="0 0 20 20">
|
|
165
|
+
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"></path>
|
|
166
|
+
</svg>
|
|
167
|
+
<div class="flex-1">
|
|
168
|
+
<p class="text-sm text-gray-600">Image message</p>
|
|
169
|
+
<p class="text-xs text-gray-500">${imageInfo.mime_type || 'image'} - ${formatFileSize(imageInfo.file_length || 0)}</p>
|
|
170
|
+
${data.caption ? `<p class="text-gray-800 italic mt-1">${escapeHtml(data.caption)}</p>` : ''}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
${imageInfo.url ? `<button onclick="viewMedia('${escapeHtml(data.message_id)}', 'image', '${escapeHtml(imageInfo.url)}')" class="mt-2 px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600 text-sm">View Image</button>` : ''}
|
|
174
|
+
`;
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case 'video':
|
|
178
|
+
typeIcon = '🎥'; // Video camera
|
|
179
|
+
typeColor = 'bg-pink-100 text-pink-800';
|
|
180
|
+
const videoInfo = data.video || {};
|
|
181
|
+
contentHtml = `
|
|
182
|
+
<div class="flex items-center space-x-3 mb-2">
|
|
183
|
+
<svg class="w-16 h-16 text-pink-400" fill="currentColor" viewBox="0 0 20 20">
|
|
184
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path>
|
|
185
|
+
</svg>
|
|
186
|
+
<div class="flex-1">
|
|
187
|
+
<p class="text-sm text-gray-600">Video message</p>
|
|
188
|
+
<p class="text-xs text-gray-500">${videoInfo.mime_type || 'video'} - ${formatFileSize(videoInfo.file_length || 0)} - ${videoInfo.seconds || 0}s</p>
|
|
189
|
+
${data.caption ? `<p class="text-gray-800 italic mt-1">${escapeHtml(data.caption)}</p>` : ''}
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
${videoInfo.url ? `<button onclick="viewMedia('${escapeHtml(data.message_id)}', 'video', '${escapeHtml(videoInfo.url)}')" class="mt-2 px-4 py-2 bg-pink-500 text-white rounded hover:bg-pink-600 text-sm">View Video</button>` : ''}
|
|
193
|
+
`;
|
|
194
|
+
break;
|
|
195
|
+
|
|
196
|
+
case 'audio':
|
|
197
|
+
typeIcon = '🎵'; // Musical note
|
|
198
|
+
typeColor = 'bg-green-100 text-green-800';
|
|
199
|
+
contentHtml = `<p class="text-sm text-gray-600">Audio message</p>`;
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case 'document':
|
|
203
|
+
typeIcon = '📄'; // Document
|
|
204
|
+
typeColor = 'bg-yellow-100 text-yellow-800';
|
|
205
|
+
contentHtml = `
|
|
206
|
+
<p class="text-sm text-gray-600 mb-2">Document</p>
|
|
207
|
+
${data.file_name ? `<p class="text-gray-700 font-mono text-xs">${escapeHtml(data.file_name)}</p>` : ''}
|
|
208
|
+
`;
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case 'sticker':
|
|
212
|
+
typeIcon = '😎'; // Sticker emoji
|
|
213
|
+
typeColor = 'bg-orange-100 text-orange-800';
|
|
214
|
+
contentHtml = `<p class="text-sm text-gray-600">Sticker</p>`;
|
|
215
|
+
break;
|
|
216
|
+
|
|
217
|
+
case 'location':
|
|
218
|
+
typeIcon = '📍'; // Pin
|
|
219
|
+
typeColor = 'bg-red-100 text-red-800';
|
|
220
|
+
contentHtml = `
|
|
221
|
+
<p class="text-sm text-gray-600 mb-2">Location</p>
|
|
222
|
+
${data.location ? `
|
|
223
|
+
<p class="text-gray-800">
|
|
224
|
+
<span class="font-semibold">${escapeHtml(data.location.name || 'Unknown')}</span><br>
|
|
225
|
+
<span class="text-sm text-gray-600">${escapeHtml(data.location.address || '')}</span><br>
|
|
226
|
+
<span class="text-xs text-gray-500">Lat: ${data.location.latitude}, Lon: ${data.location.longitude}</span>
|
|
227
|
+
</p>
|
|
228
|
+
` : ''}
|
|
229
|
+
`;
|
|
230
|
+
break;
|
|
231
|
+
|
|
232
|
+
case 'contact':
|
|
233
|
+
typeIcon = '👤'; // Contact
|
|
234
|
+
typeColor = 'bg-indigo-100 text-indigo-800';
|
|
235
|
+
contentHtml = `
|
|
236
|
+
<p class="text-sm text-gray-600 mb-2">Contact Card</p>
|
|
237
|
+
${data.contact ? `
|
|
238
|
+
<p class="text-gray-800 font-semibold">${escapeHtml(data.contact.display_name || 'Unknown')}</p>
|
|
239
|
+
` : ''}
|
|
240
|
+
`;
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case 'contacts':
|
|
244
|
+
typeIcon = '👥'; // Contacts
|
|
245
|
+
typeColor = 'bg-teal-100 text-teal-800';
|
|
246
|
+
contentHtml = `<p class="text-sm text-gray-600">Multiple contacts (${data.contacts_count || 0})</p>`;
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
default:
|
|
250
|
+
typeIcon = '❓'; // Question mark
|
|
251
|
+
typeColor = 'bg-gray-100 text-gray-800';
|
|
252
|
+
contentHtml = `<p class="text-sm text-gray-600">Unknown message type: ${messageType}</p>`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Add quoted message if exists
|
|
256
|
+
let quotedHtml = '';
|
|
257
|
+
if (data.quoted) {
|
|
258
|
+
quotedHtml = `
|
|
259
|
+
<div class="mt-2 p-2 bg-gray-100 border-l-4 border-gray-400 rounded">
|
|
260
|
+
<p class="text-xs text-gray-600">Replying to:</p>
|
|
261
|
+
<p class="text-sm text-gray-700 italic">${escapeHtml(data.quoted.content || '')}</p>
|
|
262
|
+
</div>
|
|
263
|
+
`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Build group info line
|
|
267
|
+
let groupInfoHtml = '';
|
|
268
|
+
if (isGroup) {
|
|
269
|
+
const displayGroupName = groupName || chatId.replace('@g.us', '');
|
|
270
|
+
groupInfoHtml = `<p class="text-xs text-purple-600">in <span class="font-medium">${escapeHtml(displayGroupName)}</span></p>`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Build forwarded indicator
|
|
274
|
+
let forwardedHtml = '';
|
|
275
|
+
if (isForwarded) {
|
|
276
|
+
const forwardedLabel = forwardingScore > 4 ? 'Forwarded many times' : 'Forwarded';
|
|
277
|
+
forwardedHtml = `<span class="inline-block px-2 py-1 rounded text-xs font-medium bg-gray-200 text-gray-700 ml-1" title="Forwarding score: ${forwardingScore}">↷ ${forwardedLabel}</span>`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return `
|
|
281
|
+
<div class="bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition ${isFromMe ? 'border-l-4 border-green-500' : isGroup ? 'border-l-4 border-purple-500' : 'border-l-4 border-blue-500'}">
|
|
282
|
+
<div class="flex justify-between items-start mb-2">
|
|
283
|
+
<div class="flex items-center space-x-2">
|
|
284
|
+
<span class="text-2xl">${typeIcon}</span>
|
|
285
|
+
<div>
|
|
286
|
+
<p class="font-semibold text-gray-800">${sender}</p>
|
|
287
|
+
${groupInfoHtml}
|
|
288
|
+
<span class="inline-block px-2 py-1 rounded text-xs font-medium ${typeColor}">${messageType}</span>${forwardedHtml}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="text-right">
|
|
292
|
+
<p class="text-xs text-gray-500">${timestamp}</p>
|
|
293
|
+
${isFromMe ? '<span class="text-xs text-green-600 font-medium">Sent by me</span>' : ''}
|
|
294
|
+
${isGroup && !isFromMe ? '<span class="text-xs text-purple-600 font-medium">Group message</span>' : ''}
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="mt-2">
|
|
298
|
+
${contentHtml}
|
|
299
|
+
${quotedHtml}
|
|
300
|
+
</div>
|
|
301
|
+
<div class="mt-2 pt-2 border-t text-xs text-gray-400 flex justify-between items-center">
|
|
302
|
+
<span class="font-mono">ID: ${escapeHtml(data.message_id || 'N/A')}</span>
|
|
303
|
+
${!isFromMe ? `<button onclick="markAsRead('${escapeHtml(data.message_id)}', '${escapeHtml(chatId)}', '${escapeHtml(data.sender || '')}')" class="px-2 py-1 bg-blue-500 text-white rounded text-xs hover:bg-blue-600">Mark Read</button>` : ''}
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Format timestamp
|
|
310
|
+
function formatTimestamp(timestamp) {
|
|
311
|
+
if (!timestamp) return 'Unknown time';
|
|
312
|
+
// Handle both ISO 8601 strings and Unix timestamps
|
|
313
|
+
const date = typeof timestamp === 'string' ? new Date(timestamp) : new Date(timestamp * 1000);
|
|
314
|
+
if (isNaN(date.getTime())) return 'Invalid Date';
|
|
315
|
+
return date.toLocaleString();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Format sender
|
|
319
|
+
function formatSender(sender) {
|
|
320
|
+
if (!sender) return 'Unknown';
|
|
321
|
+
// Remove @s.whatsapp.net suffix
|
|
322
|
+
return sender.replace('@s.whatsapp.net', '').replace('@c.us', '');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Escape HTML
|
|
326
|
+
function escapeHtml(text) {
|
|
327
|
+
const div = document.createElement('div');
|
|
328
|
+
div.textContent = text;
|
|
329
|
+
return div.innerHTML;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Clear messages display
|
|
333
|
+
function clearMessages() {
|
|
334
|
+
if (confirm('Clear all displayed messages? (This will not delete them from storage)')) {
|
|
335
|
+
messages = [];
|
|
336
|
+
renderMessages();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Format file size
|
|
341
|
+
function formatFileSize(bytes) {
|
|
342
|
+
if (!bytes || bytes === 0) return '0 B';
|
|
343
|
+
const k = 1024;
|
|
344
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
345
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
346
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// View media function
|
|
350
|
+
function viewMedia(messageId, mediaType, url) {
|
|
351
|
+
console.log('View media:', messageId, mediaType, url);
|
|
352
|
+
|
|
353
|
+
const modal = document.getElementById('media-modal');
|
|
354
|
+
const modalTitle = document.getElementById('media-modal-title');
|
|
355
|
+
const modalContent = document.getElementById('media-modal-content');
|
|
356
|
+
|
|
357
|
+
// Show modal
|
|
358
|
+
modal.classList.remove('hidden');
|
|
359
|
+
|
|
360
|
+
// Set title
|
|
361
|
+
modalTitle.textContent = `Viewing ${mediaType}`;
|
|
362
|
+
|
|
363
|
+
// Show loading
|
|
364
|
+
modalContent.innerHTML = '<p class="text-gray-500">Downloading and decrypting media...</p>';
|
|
365
|
+
|
|
366
|
+
// Fetch media via Flask RPC endpoint (returns JSON with base64 data)
|
|
367
|
+
fetch(`/api/media/${messageId}`)
|
|
368
|
+
.then(response => response.json())
|
|
369
|
+
.then(result => {
|
|
370
|
+
if (!result.success) {
|
|
371
|
+
throw new Error(result.error || 'Failed to download media');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const mediaData = result.data;
|
|
375
|
+
const base64Data = mediaData.data;
|
|
376
|
+
const mimeType = mediaData.mime_type || 'application/octet-stream';
|
|
377
|
+
|
|
378
|
+
// Convert base64 to blob
|
|
379
|
+
const byteCharacters = atob(base64Data);
|
|
380
|
+
const byteNumbers = new Array(byteCharacters.length);
|
|
381
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
382
|
+
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
383
|
+
}
|
|
384
|
+
const byteArray = new Uint8Array(byteNumbers);
|
|
385
|
+
const blob = new Blob([byteArray], { type: mimeType });
|
|
386
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
387
|
+
|
|
388
|
+
if (mediaType === 'image') {
|
|
389
|
+
modalContent.innerHTML = `
|
|
390
|
+
<img src="${blobUrl}" class="max-w-full max-h-full object-contain" alt="WhatsApp Image">
|
|
391
|
+
`;
|
|
392
|
+
} else if (mediaType === 'video') {
|
|
393
|
+
modalContent.innerHTML = `
|
|
394
|
+
<video controls class="max-w-full max-h-full" autoplay>
|
|
395
|
+
<source src="${blobUrl}" type="${mimeType}">
|
|
396
|
+
Your browser does not support the video tag.
|
|
397
|
+
</video>
|
|
398
|
+
`;
|
|
399
|
+
} else if (mediaType === 'audio') {
|
|
400
|
+
modalContent.innerHTML = `
|
|
401
|
+
<audio controls autoplay class="w-full">
|
|
402
|
+
<source src="${blobUrl}" type="${mimeType}">
|
|
403
|
+
Your browser does not support the audio tag.
|
|
404
|
+
</audio>
|
|
405
|
+
`;
|
|
406
|
+
} else {
|
|
407
|
+
modalContent.innerHTML = `
|
|
408
|
+
<div class="text-center">
|
|
409
|
+
<p class="text-gray-600 mb-4">Media loaded successfully</p>
|
|
410
|
+
<a href="${blobUrl}" download class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
|
411
|
+
Download ${mediaType}
|
|
412
|
+
</a>
|
|
413
|
+
</div>
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
.catch(error => {
|
|
418
|
+
console.error('Error loading media:', error);
|
|
419
|
+
modalContent.innerHTML = `
|
|
420
|
+
<div class="text-center text-red-600">
|
|
421
|
+
<p>Failed to load media: ${error.message}</p>
|
|
422
|
+
<p class="text-sm text-gray-500 mt-2">The message may have expired or been deleted.</p>
|
|
423
|
+
</div>
|
|
424
|
+
`;
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Close media modal
|
|
429
|
+
function closeMediaModal() {
|
|
430
|
+
const modal = document.getElementById('media-modal');
|
|
431
|
+
modal.classList.add('hidden');
|
|
432
|
+
|
|
433
|
+
// Clear content to stop videos/audio
|
|
434
|
+
const modalContent = document.getElementById('media-modal-content');
|
|
435
|
+
modalContent.innerHTML = '<p class="text-gray-500">Loading...</p>';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Show notification
|
|
439
|
+
function showNotification(message, type = 'info') {
|
|
440
|
+
// Simple console notification for now
|
|
441
|
+
console.log(`[${type.toUpperCase()}] ${message}`);
|
|
442
|
+
|
|
443
|
+
// Could add a toast notification here in the future
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Mark message as read via WebSocket
|
|
447
|
+
function markAsRead(messageId, chatJid, senderJid) {
|
|
448
|
+
console.log('Marking as read:', messageId, chatJid, senderJid);
|
|
449
|
+
socket.emit('mark_read', {
|
|
450
|
+
message_ids: [messageId],
|
|
451
|
+
chat_jid: chatJid,
|
|
452
|
+
sender_jid: senderJid || null
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Listen for mark_read result
|
|
457
|
+
socket.on('mark_read_result', function(data) {
|
|
458
|
+
if (data.success) {
|
|
459
|
+
showNotification('Message marked as read');
|
|
460
|
+
} else {
|
|
461
|
+
showNotification('Failed to mark as read: ' + data.error, 'error');
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
</script>
|
|
465
|
+
{% endblock %}
|