mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-31 01:17:45 -06:00
Compare commits
71 Commits
13f96a366b
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6efa879081 | ||
|
|
2534ec2307 | ||
|
|
933a28de26 | ||
|
|
b1b07b7e7f | ||
|
|
bb831d590f | ||
|
|
cb41e65e29 | ||
|
|
52a8d9ea71 | ||
|
|
c7b7a9992e | ||
|
|
f46699ef3f | ||
|
|
72b0833ce2 | ||
|
|
2e3c8184ef | ||
|
|
6f2bef678c | ||
|
|
3325044500 | ||
|
|
6ede76f8cc | ||
|
|
2fee5053f3 | ||
|
|
67c4aa640b | ||
|
|
604c9f968b | ||
|
|
076449e5d6 | ||
|
|
5faf3d18d6 | ||
|
|
52fa931140 | ||
|
|
cd800f2976 | ||
|
|
4f642e17a7 | ||
|
|
afa6d633c6 | ||
|
|
2e3e752719 | ||
|
|
de11e6f9ca | ||
|
|
26e7eefe51 | ||
|
|
b55c9fcab7 | ||
|
|
86b194af5f | ||
|
|
3c1573c400 | ||
|
|
178386594c | ||
|
|
cea1fa0979 | ||
|
|
38be0b49d9 | ||
|
|
04ac880fcc | ||
|
|
3864366e75 | ||
|
|
2756d7e61c | ||
|
|
bb36bfe424 | ||
|
|
6277c5d084 | ||
|
|
b1d77019f5 | ||
|
|
8d5c7d875e | ||
|
|
abd0351f8f | ||
|
|
c7a2aa51ee | ||
|
|
bbf60e30b0 | ||
|
|
2408384b0f | ||
|
|
250ddd2e89 | ||
|
|
bee309cd28 | ||
|
|
92c2ace7bc | ||
|
|
faed3f4574 | ||
|
|
baff4e8f5e | ||
|
|
1c3a7ab027 | ||
|
|
338cc93cfc | ||
|
|
930d32df3a | ||
|
|
fa6b5c28a6 | ||
|
|
8e7f348c12 | ||
|
|
5c58cb7eae | ||
|
|
879bee962b | ||
|
|
af47b859e4 | ||
|
|
1c61116a3e | ||
|
|
08a4795016 | ||
|
|
53a94af3f7 | ||
|
|
302e219f7f | ||
|
|
1e036ba3ae | ||
|
|
377993e4b0 | ||
|
|
ea88edd512 | ||
|
|
d3e3c458a0 | ||
|
|
067f0999b5 | ||
|
|
179af3f41c | ||
|
|
31a6f2d92e | ||
|
|
dc72f01625 | ||
|
|
3b139078c3 | ||
|
|
f2c2a6a64a | ||
|
|
e5a249109c |
85
CHANGELOG.md
85
CHANGELOG.md
@@ -1,4 +1,4 @@
|
||||
# 2.3.7 (develop)
|
||||
# 2.3.7 (2025-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -7,6 +7,24 @@
|
||||
- Added DTOs and validation schemas for template management
|
||||
- Enhanced template lifecycle management capabilities
|
||||
|
||||
* **Events API**: Add isLatest and progress to messages.set event
|
||||
- Allows consumers to know when history sync is complete (isLatest=true)
|
||||
- Track sync progress percentage through webhooks
|
||||
- Added extra field to EmitData type for additional payload properties
|
||||
- Updated all event controllers (webhook, rabbitmq, sqs, websocket, pusher, kafka, nats)
|
||||
|
||||
* **N8N Integration**: Add quotedMessage to payload in sendMessageToBot
|
||||
- Support for quoted messages in N8N chatbot integration
|
||||
- Enhanced message context information
|
||||
|
||||
* **WebSocket**: Add wildcard "*" to allow all hosts to connect via websocket
|
||||
- More flexible host configuration for WebSocket connections
|
||||
- Improved host validation logic in WebsocketController
|
||||
|
||||
* **Pix Support**: Handle interactive button message for pix
|
||||
- Support for interactive Pix button messages
|
||||
- Enhanced payment flow integration
|
||||
|
||||
### Fixed
|
||||
|
||||
* **Baileys Message Processor**: Fix incoming message events not working after reconnection
|
||||
@@ -60,11 +78,76 @@
|
||||
- Fixed integration issues between Chatwoot and Baileys services
|
||||
- Improved message handling and delivery
|
||||
|
||||
* **Baileys Message Loss**: Prevent message loss from WhatsApp stub placeholders
|
||||
- Fixed messages being lost and not saved to database, especially for channels/newsletters (@lid)
|
||||
- Detects WhatsApp stubs through messageStubParameters containing 'Message absent from node'
|
||||
- Prevents adding stubs to duplicate message cache
|
||||
- Allows real message to be processed when it arrives after decryption
|
||||
- Maintains stub discard to avoid saving empty placeholders
|
||||
|
||||
* **Database Contacts**: Respect DATABASE_SAVE_DATA_CONTACTS in contact updates
|
||||
- Added missing conditional checks for DATABASE_SAVE_DATA_CONTACTS configuration
|
||||
- Fixed profile picture updates attempting to save when database save is disabled
|
||||
- Fixed unawaited promise in contacts.upsert handler
|
||||
|
||||
* **Prisma/PostgreSQL**: Add unique constraint to Chat model
|
||||
- Generated migration to add unique index on instanceId and remoteJid
|
||||
- Added deduplication step before creating index to prevent constraint violations
|
||||
- Prevents chat duplication in database
|
||||
|
||||
* **MinIO Upload**: Handle messageContextInfo in media upload to prevent MinIO errors
|
||||
- Prevents errors when uploading media with messageContextInfo metadata
|
||||
- Improved error handling for media storage operations
|
||||
|
||||
* **Typebot**: Fix message routing for @lid JIDs
|
||||
- Typebot now responds to messages from JIDs ending with @lid
|
||||
- Maintains complete JID for @lid instead of extracting only number
|
||||
- Fixed condition: `remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0]`
|
||||
- Handles both @s.whatsapp.net and @lid message formats
|
||||
|
||||
* **Message Filtering**: Unify remoteJid filtering using OR with remoteJidAlt
|
||||
- Improved message filtering with alternative JID support
|
||||
- Better handling of messages with different JID formats
|
||||
|
||||
* **@lid Integration**: Multiple fixes for @lid problems, message events and chatwoot errors
|
||||
- Reorganized imports and improved message handling in BaileysStartupService
|
||||
- Enhanced remoteJid processing to handle @lid cases
|
||||
- Improved jid normalization and type safety in Chatwoot integration
|
||||
- Streamlined message handling logic and cache management
|
||||
- Refactored message handling and polling updates with decryption logic for poll votes
|
||||
- Improved event processing flow for various message types
|
||||
|
||||
* **Chatwoot Contacts**: Fix contact duplication error on import
|
||||
- Resolved 'ON CONFLICT DO UPDATE command cannot affect row a second time' error
|
||||
- Removed attempt to update identifier field in conflict (part of constraint)
|
||||
- Changed to update only updated_at field: `updated_at = NOW()`
|
||||
- Allows duplicate contacts to be updated correctly without errors
|
||||
|
||||
* **Chatwoot Service**: Fix async handling in update_last_seen method
|
||||
- Added missing await for chatwootRequest in read message processing
|
||||
- Prevents service failure when processing read messages
|
||||
|
||||
* **Metrics Access**: Fix IP validation including x-forwarded-for
|
||||
- Uses all IPs including x-forwarded-for header when checking metrics access
|
||||
- Improved security and access control for metrics endpoint
|
||||
|
||||
### Dependencies
|
||||
|
||||
* **Baileys**: Updated to version 7.0.0-rc.9
|
||||
- Latest release candidate with multiple improvements and bug fixes
|
||||
|
||||
* **AWS SDK**: Updated packages to version 3.936.0
|
||||
- Enhanced functionality and compatibility
|
||||
- Performance improvements
|
||||
|
||||
### Code Quality & Refactoring
|
||||
|
||||
* **Template Management**: Remove unused template edit/delete DTOs after refactoring
|
||||
* **Proxy Utilities**: Improve makeProxyAgent for Undici compatibility
|
||||
* **Code Formatting**: Enhance code formatting and consistency across services
|
||||
* **BaileysStartupService**: Fix indentation and remove unnecessary blank lines
|
||||
* **Event Controllers**: Guard extra spread and prevent core field override in all event controllers
|
||||
* **Import Organization**: Reorganize imports for better code structure and maintainability
|
||||
|
||||
# 2.3.6 (2025-10-21)
|
||||
|
||||
|
||||
39
package-lock.json
generated
39
package-lock.json
generated
@@ -31,6 +31,7 @@
|
||||
"eventemitter2": "^6.4.9",
|
||||
"express": "^4.21.2",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"fetch-socks": "^1.3.2",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"form-data": "^4.0.1",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
@@ -2906,6 +2907,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -2927,6 +2929,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz",
|
||||
"integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^18.19.0 || >=20.6.0"
|
||||
},
|
||||
@@ -2939,6 +2942,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz",
|
||||
"integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
@@ -2954,6 +2958,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.204.0.tgz",
|
||||
"integrity": "sha512-vV5+WSxktzoMP8JoYWKeopChy6G3HKk4UQ2hESCRDUUTZqQ3+nM3u8noVG0LmNfRWwcFBnbZ71GKC7vaYYdJ1g==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/api-logs": "0.204.0",
|
||||
"import-in-the-middle": "^1.8.1",
|
||||
@@ -3361,6 +3366,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz",
|
||||
"integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.2.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
@@ -3377,6 +3383,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz",
|
||||
"integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.2.0",
|
||||
"@opentelemetry/resources": "2.2.0",
|
||||
@@ -3394,6 +3401,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz",
|
||||
"integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
@@ -3642,6 +3650,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
|
||||
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.2",
|
||||
"generic-pool": "3.9.0",
|
||||
@@ -4932,6 +4941,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -5107,6 +5117,7 @@
|
||||
"integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.47.0",
|
||||
"@typescript-eslint/types": "8.47.0",
|
||||
@@ -5410,6 +5421,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5757,6 +5769,7 @@
|
||||
"resolved": "https://registry.npmjs.org/audio-decode/-/audio-decode-2.2.3.tgz",
|
||||
"integrity": "sha512-Z0lHvMayR/Pad9+O9ddzaBJE0DrhZkQlStrC1RwcAHF3AhQAsdwKHeLGK8fYKyp2DDU6xHxzGb4CLMui12yVrg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@wasm-audio-decoders/flac": "^0.2.4",
|
||||
"@wasm-audio-decoders/ogg-vorbis": "^0.1.15",
|
||||
@@ -6745,6 +6758,7 @@
|
||||
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"env-paths": "^2.2.1",
|
||||
"import-fresh": "^3.3.0",
|
||||
@@ -7635,6 +7649,7 @@
|
||||
"integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
@@ -7705,6 +7720,7 @@
|
||||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -7761,6 +7777,7 @@
|
||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -8367,6 +8384,7 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -8578,6 +8596,16 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-socks": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fetch-socks/-/fetch-socks-1.3.2.tgz",
|
||||
"integrity": "sha512-vkH5+Zgj2yEbU57Cei0iyLgTZ4OkEKJj56Xu3ViB5dpsl599JgEooQ3x6NVagIFRHWnWJ+7K0MO0aIV1TMgvnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socks": "^2.8.2",
|
||||
"undici": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
@@ -10344,6 +10372,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz",
|
||||
"integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/diff": "1.6.0",
|
||||
@@ -10574,6 +10603,7 @@
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.4.tgz",
|
||||
"integrity": "sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@keyv/serialize": "^1.1.1"
|
||||
}
|
||||
@@ -10669,6 +10699,7 @@
|
||||
"resolved": "https://registry.npmjs.org/link-preview-js/-/link-preview-js-3.2.0.tgz",
|
||||
"integrity": "sha512-FvrLltjOPGbTzt+RugbzM7g8XuUNLPO2U/INSLczrYdAA32E7nZVUrVL1gr61DGOArGJA2QkPGMEvNMLLsXREA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cheerio": "1.0.0-rc.11",
|
||||
"url": "0.11.0"
|
||||
@@ -12589,6 +12620,7 @@
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.9.1",
|
||||
"pg-pool": "^3.10.1",
|
||||
@@ -12898,6 +12930,7 @@
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -12927,6 +12960,7 @@
|
||||
"integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "6.19.0",
|
||||
"@prisma/engines": "6.19.0"
|
||||
@@ -14018,6 +14052,7 @@
|
||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.0.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
@@ -14860,6 +14895,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -15048,6 +15084,7 @@
|
||||
"integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@@ -15696,6 +15733,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -16211,6 +16249,7 @@
|
||||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"form-data": "^4.0.1",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"fetch-socks": "^1.3.2",
|
||||
"i18next": "^23.7.19",
|
||||
"jimp": "^1.6.0",
|
||||
"json-schema": "^0.4.0",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Instance` MODIFY `token` VARCHAR(500);
|
||||
|
||||
@@ -70,7 +70,7 @@ model Instance {
|
||||
integration String? @db.VarChar(100)
|
||||
number String? @db.VarChar(100)
|
||||
businessId String? @db.VarChar(100)
|
||||
token String? @db.VarChar(255)
|
||||
token String? @db.VarChar(500)
|
||||
clientName String? @db.VarChar(100)
|
||||
disconnectionReasonCode Int? @db.Int
|
||||
disconnectionObject Json? @db.Json
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 1. Cleanup: Remove duplicate chats, keeping the most recently updated one
|
||||
DELETE FROM "Chat"
|
||||
WHERE id IN (
|
||||
SELECT id FROM (
|
||||
SELECT id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY "instanceId", "remoteJid"
|
||||
ORDER BY "updatedAt" DESC
|
||||
) as row_num
|
||||
FROM "Chat"
|
||||
) t
|
||||
WHERE t.row_num > 1
|
||||
);
|
||||
|
||||
-- 2. Create the unique index (Constraint)
|
||||
CREATE UNIQUE INDEX "Chat_instanceId_remoteJid_key" ON "Chat"("instanceId", "remoteJid");
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Instance" ALTER COLUMN "token" TYPE VARCHAR(500);
|
||||
|
||||
@@ -70,7 +70,7 @@ model Instance {
|
||||
integration String? @db.VarChar(100)
|
||||
number String? @db.VarChar(100)
|
||||
businessId String? @db.VarChar(100)
|
||||
token String? @db.VarChar(255)
|
||||
token String? @db.VarChar(500)
|
||||
clientName String? @db.VarChar(100)
|
||||
disconnectionReasonCode Int? @db.Integer
|
||||
disconnectionObject Json? @db.JsonB
|
||||
@@ -132,6 +132,7 @@ model Chat {
|
||||
instanceId String
|
||||
unreadMessages Int @default(0)
|
||||
|
||||
@@unique([instanceId, remoteJid])
|
||||
@@index([instanceId])
|
||||
@@index([remoteJid])
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ model Instance {
|
||||
integration String? @db.VarChar(100)
|
||||
number String? @db.VarChar(100)
|
||||
businessId String? @db.VarChar(100)
|
||||
token String? @db.VarChar(255)
|
||||
token String? @db.VarChar(500)
|
||||
clientName String? @db.VarChar(100)
|
||||
disconnectionReasonCode Int? @db.Integer
|
||||
disconnectionObject Json? @db.JsonB
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
BlockUserDto,
|
||||
DecryptPollVoteDto,
|
||||
DeleteMessage,
|
||||
getBase64FromMediaMessageDto,
|
||||
MarkChatUnreadDto,
|
||||
@@ -113,4 +114,16 @@ export class ChatController {
|
||||
public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) {
|
||||
return await this.waMonitor.waInstances[instanceName].blockUser(data);
|
||||
}
|
||||
|
||||
public async decryptPollVote({ instanceName }: InstanceDto, data: DecryptPollVoteDto) {
|
||||
const pollCreationMessageKey = {
|
||||
id: data.message.key.id,
|
||||
remoteJid: data.remoteJid,
|
||||
};
|
||||
return await this.waMonitor.waInstances[instanceName].baileysDecryptPollVote(pollCreationMessageKey);
|
||||
}
|
||||
|
||||
public async fetchChannels({ instanceName }: InstanceDto, query: Query<Contact>) {
|
||||
return await this.waMonitor.waInstances[instanceName].fetchChannels(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,3 +127,12 @@ export class BlockUserDto {
|
||||
number: string;
|
||||
status: 'block' | 'unblock';
|
||||
}
|
||||
|
||||
export class DecryptPollVoteDto {
|
||||
message: {
|
||||
key: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
remoteJid: string;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -211,7 +211,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
||||
try {
|
||||
if (mediaType === 'audio') {
|
||||
await instance.audioWhatsapp({
|
||||
number: remoteJid.split('@')[0],
|
||||
number: remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0],
|
||||
delay: (settings as any)?.delayMessage || 1000,
|
||||
audio: url,
|
||||
caption: altText,
|
||||
@@ -219,7 +219,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
||||
} else {
|
||||
await instance.mediaMessage(
|
||||
{
|
||||
number: remoteJid.split('@')[0],
|
||||
number: remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0],
|
||||
delay: (settings as any)?.delayMessage || 1000,
|
||||
mediatype: mediaType,
|
||||
media: url,
|
||||
@@ -290,7 +290,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
||||
setTimeout(async () => {
|
||||
await instance.textMessage(
|
||||
{
|
||||
number: remoteJid.split('@')[0],
|
||||
number: remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0],
|
||||
delay: settings?.delayMessage || 1000,
|
||||
text: message,
|
||||
linkPreview,
|
||||
|
||||
@@ -346,6 +346,16 @@ export class ChatwootService {
|
||||
|
||||
return contact;
|
||||
} catch (error) {
|
||||
if ((error.status === 422 || error.response?.status === 422) && jid) {
|
||||
this.logger.warn(`Contact with identifier ${jid} creation failed (422). Checking if it already exists...`);
|
||||
const existingContact = await this.findContactByIdentifier(instance, jid);
|
||||
if (existingContact) {
|
||||
const contactId = existingContact.id;
|
||||
await this.addLabelToContact(this.provider.nameInbox, contactId);
|
||||
return existingContact;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.error('Error creating contact');
|
||||
console.log(error);
|
||||
return null;
|
||||
@@ -415,6 +425,55 @@ export class ChatwootService {
|
||||
}
|
||||
}
|
||||
|
||||
public async findContactByIdentifier(instance: InstanceDto, identifier: string) {
|
||||
const client = await this.clientCw(instance);
|
||||
|
||||
if (!client) {
|
||||
this.logger.warn('client not found');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Direct search by query (q) - most common way to search by identifier/email/phone
|
||||
const contact = (await (client as any).get('contacts/search', {
|
||||
params: {
|
||||
q: identifier,
|
||||
sort: 'name',
|
||||
},
|
||||
})) as any;
|
||||
|
||||
if (contact && contact.data && contact.data.payload && contact.data.payload.length > 0) {
|
||||
return contact.data.payload[0];
|
||||
}
|
||||
|
||||
// Fallback for older API versions or different response structures
|
||||
if (contact && contact.payload && contact.payload.length > 0) {
|
||||
return contact.payload[0];
|
||||
}
|
||||
|
||||
// Try search by attribute
|
||||
const contactByAttr = (await (client as any).post('contacts/filter', {
|
||||
payload: [
|
||||
{
|
||||
attribute_key: 'identifier',
|
||||
filter_operator: 'equal_to',
|
||||
values: [identifier],
|
||||
query_operator: null,
|
||||
},
|
||||
],
|
||||
})) as any;
|
||||
|
||||
if (contactByAttr && contactByAttr.payload && contactByAttr.payload.length > 0) {
|
||||
return contactByAttr.payload[0];
|
||||
}
|
||||
|
||||
// Check inside data property if using axios interceptors wrapper
|
||||
if (contactByAttr && contactByAttr.data && contactByAttr.data.payload && contactByAttr.data.payload.length > 0) {
|
||||
return contactByAttr.data.payload[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async findContact(instance: InstanceDto, phoneNumber: string) {
|
||||
const client = await this.clientCw(instance);
|
||||
|
||||
@@ -1574,7 +1633,11 @@ export class ChatwootService {
|
||||
this.logger.verbose(`Update result: ${result} rows affected`);
|
||||
|
||||
if (this.isImportHistoryAvailable()) {
|
||||
chatwootImport.updateMessageSourceID(chatwootMessageIds.messageId, key.id);
|
||||
try {
|
||||
await chatwootImport.updateMessageSourceID(chatwootMessageIds.messageId, key.id);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error updating Chatwoot message source ID: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2024,7 +2087,7 @@ export class ChatwootService {
|
||||
if (body.key.remoteJid.includes('@g.us')) {
|
||||
const participantName = body.pushName;
|
||||
const rawPhoneNumber =
|
||||
body.key.addressingMode === 'lid' && !body.key.fromMe
|
||||
body.key.addressingMode === 'lid' && !body.key.fromMe && body.key.participantAlt
|
||||
? body.key.participantAlt.split('@')[0].split(':')[0]
|
||||
: body.key.participant.split('@')[0].split(':')[0];
|
||||
const formattedPhoneNumber = parsePhoneNumberFromString(`+${rawPhoneNumber}`).formatInternational();
|
||||
@@ -2206,7 +2269,7 @@ export class ChatwootService {
|
||||
if (body.key.remoteJid.includes('@g.us')) {
|
||||
const participantName = body.pushName;
|
||||
const rawPhoneNumber =
|
||||
body.key.addressingMode === 'lid' && !body.key.fromMe
|
||||
body.key.addressingMode === 'lid' && !body.key.fromMe && body.key.participantAlt
|
||||
? body.key.participantAlt.split('@')[0].split(':')[0]
|
||||
: body.key.participant.split('@')[0].split(':')[0];
|
||||
const formattedPhoneNumber = parsePhoneNumberFromString(`+${rawPhoneNumber}`).formatInternational();
|
||||
@@ -2464,7 +2527,13 @@ export class ChatwootService {
|
||||
}
|
||||
}
|
||||
|
||||
public getNumberFromRemoteJid(remoteJid: string) {
|
||||
public normalizeJidIdentifier(remoteJid: string) {
|
||||
if (!remoteJid) {
|
||||
return '';
|
||||
}
|
||||
if (remoteJid.includes('@lid')) {
|
||||
return remoteJid;
|
||||
}
|
||||
return remoteJid.replace(/:\d+/, '').split('@')[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
|
||||
pushName: pushName,
|
||||
keyId: msg?.key?.id,
|
||||
fromMe: msg?.key?.fromMe,
|
||||
quotedMessage: msg?.contextInfo?.quotedMessage,
|
||||
instanceName: instance.instanceName,
|
||||
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
|
||||
apiKey: instance.token,
|
||||
|
||||
@@ -327,7 +327,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
||||
if (message.type === 'image') {
|
||||
await instance.mediaMessage(
|
||||
{
|
||||
number: session.remoteJid.split('@')[0],
|
||||
number: session.remoteJid,
|
||||
delay: settings?.delayMessage || 1000,
|
||||
mediatype: 'image',
|
||||
media: message.content.url,
|
||||
@@ -342,7 +342,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
||||
if (message.type === 'video') {
|
||||
await instance.mediaMessage(
|
||||
{
|
||||
number: session.remoteJid.split('@')[0],
|
||||
number: session.remoteJid,
|
||||
delay: settings?.delayMessage || 1000,
|
||||
mediatype: 'video',
|
||||
media: message.content.url,
|
||||
@@ -357,7 +357,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
||||
if (message.type === 'audio') {
|
||||
await instance.audioWhatsapp(
|
||||
{
|
||||
number: session.remoteJid.split('@')[0],
|
||||
number: session.remoteJid,
|
||||
delay: settings?.delayMessage || 1000,
|
||||
encoding: true,
|
||||
audio: message.content.url,
|
||||
@@ -441,7 +441,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
||||
*/
|
||||
private async processListMessage(instance: any, formattedText: string, remoteJid: string) {
|
||||
const listJson = {
|
||||
number: remoteJid.split('@')[0],
|
||||
number: remoteJid,
|
||||
title: '',
|
||||
description: '',
|
||||
buttonText: '',
|
||||
@@ -490,7 +490,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
||||
*/
|
||||
private async processButtonMessage(instance: any, formattedText: string, remoteJid: string) {
|
||||
const buttonJson = {
|
||||
number: remoteJid.split('@')[0],
|
||||
number: remoteJid,
|
||||
thumbnailUrl: undefined,
|
||||
title: '',
|
||||
description: '',
|
||||
|
||||
@@ -14,12 +14,24 @@ export type EmitData = {
|
||||
apiKey?: string;
|
||||
local?: boolean;
|
||||
integration?: string[];
|
||||
extra?: Record<string, any>;
|
||||
};
|
||||
|
||||
export interface EventControllerInterface {
|
||||
set(instanceName: string, data: any): Promise<any>;
|
||||
get(instanceName: string): Promise<any>;
|
||||
emit({ instanceName, origin, event, data, serverUrl, dateTime, sender, apiKey, local }: EmitData): Promise<void>;
|
||||
emit({
|
||||
instanceName,
|
||||
origin,
|
||||
event,
|
||||
data,
|
||||
serverUrl,
|
||||
dateTime,
|
||||
sender,
|
||||
apiKey,
|
||||
local,
|
||||
extra,
|
||||
}: EmitData): Promise<void>;
|
||||
}
|
||||
|
||||
export class EventController {
|
||||
|
||||
@@ -123,6 +123,7 @@ export class EventManager {
|
||||
apiKey?: string;
|
||||
local?: boolean;
|
||||
integration?: string[];
|
||||
extra?: Record<string, any>;
|
||||
}): Promise<void> {
|
||||
await this.websocket.emit(eventData);
|
||||
await this.rabbitmq.emit(eventData);
|
||||
|
||||
@@ -262,6 +262,7 @@ export class KafkaController extends EventController implements EventControllerI
|
||||
sender,
|
||||
apiKey,
|
||||
integration,
|
||||
extra,
|
||||
}: EmitData): Promise<void> {
|
||||
if (integration && !integration.includes('kafka')) {
|
||||
return;
|
||||
@@ -284,6 +285,7 @@ export class KafkaController extends EventController implements EventControllerI
|
||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||
|
||||
const message = {
|
||||
...(extra ?? {}),
|
||||
event,
|
||||
instance: instanceName,
|
||||
data,
|
||||
|
||||
@@ -47,6 +47,7 @@ export class NatsController extends EventController implements EventControllerIn
|
||||
sender,
|
||||
apiKey,
|
||||
integration,
|
||||
extra,
|
||||
}: EmitData): Promise<void> {
|
||||
if (integration && !integration.includes('nats')) {
|
||||
return;
|
||||
@@ -65,6 +66,7 @@ export class NatsController extends EventController implements EventControllerIn
|
||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||
|
||||
const message = {
|
||||
...(extra ?? {}),
|
||||
event,
|
||||
instance: instanceName,
|
||||
data,
|
||||
|
||||
@@ -121,6 +121,7 @@ export class PusherController extends EventController implements EventController
|
||||
apiKey,
|
||||
local,
|
||||
integration,
|
||||
extra,
|
||||
}: EmitData): Promise<void> {
|
||||
if (integration && !integration.includes('pusher')) {
|
||||
return;
|
||||
@@ -133,6 +134,7 @@ export class PusherController extends EventController implements EventController
|
||||
const enabledLog = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||
const eventName = event.replace(/_/g, '.').toLowerCase();
|
||||
const pusherData = {
|
||||
...(extra ?? {}),
|
||||
event,
|
||||
instance: instanceName,
|
||||
data,
|
||||
|
||||
@@ -209,6 +209,7 @@ export class RabbitmqController extends EventController implements EventControll
|
||||
sender,
|
||||
apiKey,
|
||||
integration,
|
||||
extra,
|
||||
}: EmitData): Promise<void> {
|
||||
if (integration && !integration.includes('rabbitmq')) {
|
||||
return;
|
||||
@@ -233,6 +234,7 @@ export class RabbitmqController extends EventController implements EventControll
|
||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||
|
||||
const message = {
|
||||
...(extra ?? {}),
|
||||
event,
|
||||
instance: instanceName,
|
||||
data,
|
||||
|
||||
@@ -93,6 +93,7 @@ export class SqsController extends EventController implements EventControllerInt
|
||||
sender,
|
||||
apiKey,
|
||||
integration,
|
||||
extra,
|
||||
}: EmitData): Promise<void> {
|
||||
if (integration && !integration.includes('sqs')) {
|
||||
return;
|
||||
@@ -128,6 +129,7 @@ export class SqsController extends EventController implements EventControllerInt
|
||||
const sqsUrl = `https://sqs.${sqsConfig.REGION}.amazonaws.com/${sqsConfig.ACCOUNT_ID}/${queueName}`;
|
||||
|
||||
const message = {
|
||||
...(extra ?? {}),
|
||||
event,
|
||||
instance: instanceName,
|
||||
dataType: 'json',
|
||||
|
||||
@@ -65,6 +65,7 @@ export class WebhookController extends EventController implements EventControlle
|
||||
apiKey,
|
||||
local,
|
||||
integration,
|
||||
extra,
|
||||
}: EmitData): Promise<void> {
|
||||
if (integration && !integration.includes('webhook')) {
|
||||
return;
|
||||
@@ -90,6 +91,7 @@ export class WebhookController extends EventController implements EventControlle
|
||||
const regex = /^(https?:\/\/)/;
|
||||
|
||||
const webhookData = {
|
||||
...(extra ?? {}),
|
||||
event,
|
||||
instance: instanceName,
|
||||
data,
|
||||
@@ -122,9 +124,20 @@ export class WebhookController extends EventController implements EventControlle
|
||||
|
||||
try {
|
||||
if (instance?.enabled && regex.test(instance.url)) {
|
||||
// Add custom headers for better webhook tracking and debugging
|
||||
const enhancedHeaders = {
|
||||
...webhookHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Instance-ID': this.monitor.waInstances[instanceName].instanceId,
|
||||
'X-Instance-Name': instanceName,
|
||||
'X-Event-Type': event,
|
||||
'X-Timestamp': Date.now().toString(),
|
||||
'User-Agent': 'EvolutionAPI-Webhook/2.3.7',
|
||||
};
|
||||
|
||||
const httpService = axios.create({
|
||||
baseURL,
|
||||
headers: webhookHeaders as Record<string, string> | undefined,
|
||||
headers: enhancedHeaders as Record<string, string>,
|
||||
timeout: webhookConfig.REQUEST?.TIMEOUT_MS ?? 30000,
|
||||
});
|
||||
|
||||
|
||||
@@ -33,10 +33,13 @@ export class WebsocketController extends EventController implements EventControl
|
||||
const { remoteAddress } = req.socket;
|
||||
const websocketConfig = configService.get<Websocket>('WEBSOCKET');
|
||||
const allowedHosts = websocketConfig.ALLOWED_HOSTS || '127.0.0.1,::1,::ffff:127.0.0.1';
|
||||
const isAllowedHost = allowedHosts
|
||||
.split(',')
|
||||
.map((h) => h.trim())
|
||||
.includes(remoteAddress);
|
||||
const allowAllHosts = allowedHosts.trim() === '*';
|
||||
const isAllowedHost =
|
||||
allowAllHosts ||
|
||||
allowedHosts
|
||||
.split(',')
|
||||
.map((h) => h.trim())
|
||||
.includes(remoteAddress);
|
||||
|
||||
if (params.has('EIO') && isAllowedHost) {
|
||||
return callback(null, true);
|
||||
@@ -115,6 +118,7 @@ export class WebsocketController extends EventController implements EventControl
|
||||
sender,
|
||||
apiKey,
|
||||
integration,
|
||||
extra,
|
||||
}: EmitData): Promise<void> {
|
||||
if (integration && !integration.includes('websocket')) {
|
||||
return;
|
||||
@@ -127,6 +131,7 @@ export class WebsocketController extends EventController implements EventControl
|
||||
const configEv = event.replace(/[.-]/gm, '_').toUpperCase();
|
||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBSOCKET');
|
||||
const message = {
|
||||
...(extra ?? {}),
|
||||
event,
|
||||
instance: instanceName,
|
||||
data,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { RouterBroker } from '@api/abstract/abstract.router';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
BlockUserDto,
|
||||
DecryptPollVoteDto,
|
||||
DeleteMessage,
|
||||
getBase64FromMediaMessageDto,
|
||||
MarkChatUnreadDto,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
archiveChatSchema,
|
||||
blockUserSchema,
|
||||
contactValidateSchema,
|
||||
decryptPollVoteSchema,
|
||||
deleteMessageSchema,
|
||||
markChatUnreadSchema,
|
||||
messageUpSchema,
|
||||
@@ -281,6 +283,26 @@ export class ChatRouter extends RouterBroker {
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('getPollVote'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<DecryptPollVoteDto>({
|
||||
request: req,
|
||||
schema: decryptPollVoteSchema,
|
||||
ClassRef: DecryptPollVoteDto,
|
||||
execute: (instance, data) => chatController.decryptPollVote(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('findChannels'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate({
|
||||
request: req,
|
||||
schema: contactValidateSchema,
|
||||
ClassRef: Query<Contact>,
|
||||
execute: (instance, query) => chatController.fetchChannels(instance, query as any),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -432,7 +432,13 @@ export class ChannelStartupService {
|
||||
return data;
|
||||
}
|
||||
|
||||
public async sendDataWebhook<T extends object = any>(event: Events, data: T, local = true, integration?: string[]) {
|
||||
public async sendDataWebhook<T extends object = any>(
|
||||
event: Events,
|
||||
data: T,
|
||||
local = true,
|
||||
integration?: string[],
|
||||
extra?: Record<string, any>,
|
||||
) {
|
||||
const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
|
||||
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
||||
const localISOTime = new Date(Date.now() - tzoffset).toISOString();
|
||||
@@ -453,6 +459,7 @@ export class ChannelStartupService {
|
||||
apiKey: expose && instanceApikey ? instanceApikey : null,
|
||||
local,
|
||||
integration,
|
||||
extra,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ export type Webhook = {
|
||||
};
|
||||
export type Pusher = { ENABLED: boolean; GLOBAL?: GlobalPusher; EVENTS: EventsPusher };
|
||||
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
||||
export type Baileys = { VERSION?: string };
|
||||
export type QrCode = { LIMIT: number; COLOR: string };
|
||||
export type Typebot = { ENABLED: boolean; API_VERSION: string; SEND_MEDIA_BASE64: boolean };
|
||||
export type Chatwoot = {
|
||||
@@ -410,6 +411,7 @@ export interface Env {
|
||||
WEBHOOK: Webhook;
|
||||
PUSHER: Pusher;
|
||||
CONFIG_SESSION_PHONE: ConfigSessionPhone;
|
||||
BAILEYS: Baileys;
|
||||
QRCODE: QrCode;
|
||||
TYPEBOT: Typebot;
|
||||
CHATWOOT: Chatwoot;
|
||||
@@ -800,6 +802,9 @@ export class ConfigService {
|
||||
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
|
||||
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome',
|
||||
},
|
||||
BAILEYS: {
|
||||
VERSION: process.env?.CONFIG_BAILEYS_VERSION,
|
||||
},
|
||||
QRCODE: {
|
||||
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
|
||||
COLOR: process.env.QRCODE_COLOR || '#198754',
|
||||
|
||||
@@ -35,7 +35,12 @@ function formatBRNumber(jid: string) {
|
||||
export function createJid(number: string): string {
|
||||
number = number.replace(/:\d+/, '');
|
||||
|
||||
if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) {
|
||||
if (
|
||||
number.includes('@g.us') ||
|
||||
number.includes('@s.whatsapp.net') ||
|
||||
number.includes('@lid') ||
|
||||
number.includes('@newsletter')
|
||||
) {
|
||||
return number;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import { fetchLatestBaileysVersion, WAVersion } from 'baileys';
|
||||
|
||||
import { Baileys, configService } from '../config/env.config';
|
||||
|
||||
export const fetchLatestWaWebVersion = async (options: AxiosRequestConfig<{}>) => {
|
||||
// Check if manual version is set via configuration
|
||||
const baileysConfig = configService.get<Baileys>('BAILEYS');
|
||||
const manualVersion = baileysConfig?.VERSION;
|
||||
|
||||
if (manualVersion) {
|
||||
const versionParts = manualVersion.split('.').map(Number);
|
||||
if (versionParts.length === 3 && !versionParts.some(isNaN)) {
|
||||
return {
|
||||
version: versionParts as WAVersion,
|
||||
isLatest: false,
|
||||
isManual: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await axios.get('https://web.whatsapp.com/sw.js', {
|
||||
...options,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { socksDispatcher } from 'fetch-socks';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { SocksProxyAgent } from 'socks-proxy-agent';
|
||||
import { ProxyAgent } from 'undici';
|
||||
@@ -18,12 +19,23 @@ function selectProxyAgent(proxyUrl: string): HttpsProxyAgent<string> | SocksProx
|
||||
// the end so, we add the protocol constants without the `:` to avoid confusion.
|
||||
const PROXY_HTTP_PROTOCOL = 'http:';
|
||||
const PROXY_SOCKS_PROTOCOL = 'socks:';
|
||||
const PROXY_SOCKS5_PROTOCOL = 'socks5:';
|
||||
|
||||
switch (url.protocol) {
|
||||
case PROXY_HTTP_PROTOCOL:
|
||||
return new HttpsProxyAgent(url);
|
||||
case PROXY_SOCKS_PROTOCOL:
|
||||
return new SocksProxyAgent(url);
|
||||
case PROXY_SOCKS5_PROTOCOL: {
|
||||
let urlSocks = '';
|
||||
|
||||
if (url.username && url.password) {
|
||||
urlSocks = `socks://${url.username}:${url.password}@${url.hostname}:${url.port}`;
|
||||
} else {
|
||||
urlSocks = `socks://${url.hostname}:${url.port}`;
|
||||
}
|
||||
|
||||
return new SocksProxyAgent(urlSocks);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported proxy protocol: ${url.protocol}`);
|
||||
}
|
||||
@@ -64,6 +76,8 @@ export function makeProxyAgentUndici(proxy: Proxy | string): ProxyAgent {
|
||||
proxyUrl = `${protocol}://${auth}${host}:${port}`;
|
||||
}
|
||||
|
||||
protocol = protocol.toLowerCase();
|
||||
|
||||
const PROXY_HTTP_PROTOCOL = 'http';
|
||||
const PROXY_HTTPS_PROTOCOL = 'https';
|
||||
const PROXY_SOCKS4_PROTOCOL = 'socks4';
|
||||
@@ -72,10 +86,25 @@ export function makeProxyAgentUndici(proxy: Proxy | string): ProxyAgent {
|
||||
switch (protocol) {
|
||||
case PROXY_HTTP_PROTOCOL:
|
||||
case PROXY_HTTPS_PROTOCOL:
|
||||
case PROXY_SOCKS4_PROTOCOL:
|
||||
case PROXY_SOCKS5_PROTOCOL:
|
||||
return new ProxyAgent(proxyUrl);
|
||||
|
||||
case PROXY_SOCKS4_PROTOCOL:
|
||||
case PROXY_SOCKS5_PROTOCOL: {
|
||||
let type: 4 | 5 = 5;
|
||||
|
||||
if (PROXY_SOCKS4_PROTOCOL === protocol) type = 4;
|
||||
|
||||
const url = new URL(proxyUrl);
|
||||
|
||||
return socksDispatcher({
|
||||
type: type,
|
||||
host: url.hostname,
|
||||
port: Number(url.port),
|
||||
userId: url.username || undefined,
|
||||
password: url.password || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported proxy protocol: ${protocol}`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { prismaRepository } from '@api/server.module';
|
||||
import { configService, Database } from '@config/env.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const logger = new Logger('OnWhatsappCache');
|
||||
@@ -164,9 +165,28 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
|
||||
logger.verbose(
|
||||
`[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`,
|
||||
);
|
||||
await prismaRepository.isOnWhatsapp.create({
|
||||
data: dataPayload,
|
||||
});
|
||||
try {
|
||||
await prismaRepository.isOnWhatsapp.create({
|
||||
data: dataPayload,
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Check for unique constraint violation (Prisma error code P2002)
|
||||
if (
|
||||
error instanceof Prisma.PrismaClientKnownRequestError &&
|
||||
error.code === 'P2002' &&
|
||||
(error.meta?.target as string[])?.includes('remoteJid')
|
||||
) {
|
||||
logger.verbose(
|
||||
`[saveOnWhatsappCache] Race condition detected for ${remoteJid}, updating existing record instead.`,
|
||||
);
|
||||
await prismaRepository.isOnWhatsapp.update({
|
||||
where: { remoteJid: remoteJid },
|
||||
data: dataPayload,
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Loga o erro mas não para a execução dos outros promises
|
||||
|
||||
@@ -447,3 +447,25 @@ export const buttonsMessageSchema: JSONSchema7 = {
|
||||
},
|
||||
required: ['number'],
|
||||
};
|
||||
|
||||
export const decryptPollVoteSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
},
|
||||
required: ['key'],
|
||||
},
|
||||
remoteJid: { type: 'string' },
|
||||
},
|
||||
required: ['message', 'remoteJid'],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user