Compare commits

...

390 Commits
1.5.4 ... 1.7.0

Author SHA1 Message Date
Davidson Gomes
b87558d301 Merge branch 'release/1.7.0' 2024-03-27 16:36:26 -03:00
Davidson Gomes
9f1003e94e rabbitmq 2024-03-27 15:05:00 -03:00
Davidson Gomes
73c003907b rabbitmq 2024-03-27 14:54:51 -03:00
Davidson Gomes
b65d292af4 Merge tag '1.7.0' into develop
v
2024-03-27 14:26:06 -03:00
Davidson Gomes
43bdbf1018 Merge branch 'release/1.7.0' 2024-03-27 14:26:03 -03:00
Davidson Gomes
ec7986420d rabbitmq 2024-03-27 13:21:39 -03:00
Davidson Gomes
0b23153316 Merge tag '1.7.0' into develop
v
2024-03-27 12:30:15 -03:00
Davidson Gomes
ad4f5e5e65 Merge branch 'release/1.7.0' 2024-03-27 12:30:11 -03:00
Davidson Gomes
1d48532146 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-03-27 09:54:22 -03:00
Davidson Gomes
950803b2aa rabbitmq 2024-03-27 09:54:05 -03:00
Davidson Gomes
a41d92f4da Merge pull request #479 from dv336699/develop
feat(endpoint): add setPresence endpoint
2024-03-24 06:44:15 -03:00
Davidson Gomes
70d4eb393f Merge tag '1.7.0' into develop
* Added update message endpoint
* Add translate capabilities to QRMessages in CW
* Join in Group by Invite Code
* Read messages from whatsapp in chatwoot
* Add support to use use redis in cacheservice
* Add support for labels
* Command to clearcache from chatwoot inbox
* Whatsapp Cloud API Oficial

* Proxy configuration improvements
* Correction in sending lists
* Adjust in webhook_base64
* Correction in typebot text formatting
* Correction in chatwoot text formatting and render list message
* Only use a axios request to get file mimetype if necessary
* When possible use the original file extension
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
* Remove message ids cache in chatwoot to use chatwoot's api itself
* Adjusts the quoted message, now has contextInfo in the message Raw
* Collecting responses with text or numbers in Typebot
* Added sendList endpoint to swagger documentation
* Implemented a function to synchronize message deletions on WhatsApp, automatically reflecting in Chatwoot.
* Improvement on numbers validation
* Fix polls in message sending
* Sending status message
* Message 'connection successfully' spamming
* Invalidate the conversation cache if reopen_conversation is false and the conversation was resolved
* Fix looping when deleting a message in chatwoot
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
* Correction in the sendList Function
* Implement contact upsert in messaging-history.set
* Improve proxy error handling
* Refactor fetching participants for group in WhatsApp service
* Fixed problem where the typebot final keyword did not work
* Typebot's wait now pauses the flow and composing is defined by the delay_message parameter in set typebot
* Composing over 20s now loops until finished
2024-03-19 14:10:33 -03:00
Davidson Gomes
9f162127d0 Merge branch 'release/1.7.0' 2024-03-19 14:10:15 -03:00
Davidson Gomes
4cd30dabc4 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-03-19 14:09:56 -03:00
Davidson Gomes
8f78728863 Merge tag '1.7.0' into develop
v
2024-03-19 14:09:44 -03:00
Davidson Gomes
e7c5d33d50 Merge branch 'release/1.7.0' 2024-03-19 14:09:40 -03:00
Diego Vieira
5400f31acb feat(endpoint): move setPresence endpoint to instance 2024-03-19 08:31:53 +00:00
Diego Vieira
e58f1d778e feat(endpoint): add setPresence endpoint 2024-03-19 00:58:45 +00:00
Davidson Gomes
5f5db2011e Merge pull request #478 from jaison-x/pr2
fix: add missing example env variables
2024-03-18 15:35:59 -03:00
Davidson Gomes
aea4d89f62 Merge pull request #477 from jaison-x/pr
fix(chatwoot): fix config name
2024-03-18 15:35:13 -03:00
jaison-x
ccda17d704 fix: add missing example env variables 2024-03-18 14:41:36 -03:00
jaison-x
4ab6c438cf fix(chatwoot): fix config name 2024-03-18 14:37:50 -03:00
Davidson Gomes
219e63c226 Merge pull request #475 from jaison-x/message-select
fix: add option to select specific fields on find message repository
2024-03-17 09:32:21 -03:00
Davidson Gomes
ee1e4623a3 Merge pull request #474 from jaison-x/edit-message
feat(chatwoot): show edited messages from WhatsApp in Chatwoot
2024-03-17 09:31:42 -03:00
Davidson Gomes
5982212903 Merge pull request #473 from jaison-x/pr
feat(chatwoot): read last message on WhatsApp when a message is sent from Chatwoot
2024-03-17 09:30:04 -03:00
jaison-x
32e6ecb12d fix: add option to select specific fields on find message repository 2024-03-15 14:09:07 -03:00
jaison-x
3b774558f4 feat(chatwoot): show edited messages from WhatsApp in Chatwoot 2024-03-15 14:01:21 -03:00
jaison-x
072171da18 feat(chatwoot): read last message on WhatsApp when a message is sent from Chatwoot 2024-03-15 13:46:04 -03:00
Davidson Gomes
8292221790 Merge tag '1.7.0' into develop
v
2024-03-11 18:24:11 -03:00
Davidson Gomes
901954de33 Merge branch 'release/1.7.0' 2024-03-11 18:24:06 -03:00
Davidson Gomes
286fe03500 v 1.7.0 2024-03-11 18:23:58 -03:00
Davidson Gomes
9c4f02634b test 2024-03-08 14:12:00 -03:00
Davidson Gomes
434f39de1f test 2024-03-08 13:52:12 -03:00
Davidson Gomes
e72b543f38 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-03-08 13:48:58 -03:00
Davidson Gomes
826e818802 test 2024-03-08 13:48:38 -03:00
Davidson Gomes
cbf846d32f Merge pull request #462 from lindenbergp/patch-1
Update variables  .env.example docker
2024-03-08 13:38:42 -03:00
Davidson Gomes
55811a39a5 ajuste connection 2024-03-08 13:35:27 -03:00
Davidson Gomes
a81f5953fc test 2024-03-08 12:58:23 -03:00
Davidson Gomes
bd09328ec2 test 2024-03-08 12:08:41 -03:00
Davidson Gomes
17ecc88f80 test 2024-03-08 12:02:13 -03:00
Davidson Gomes
7bc4c7b36e ajustes rabbitmq 2024-03-08 11:09:08 -03:00
Davidson Gomes
79189ac5d7 ajustes rabbitmq 2024-03-08 10:59:52 -03:00
Davidson Gomes
060c4e6e72 ajustes rabbitmq 2024-03-08 10:54:11 -03:00
Davidson Gomes
8f7c518487 ajustes rabbitmq 2024-03-08 10:46:43 -03:00
Davidson Gomes
12761cbfce ajustes rabbitmq 2024-03-08 10:10:40 -03:00
Davidson Gomes
2b30c273a4 ajustes rabbitmq 2024-03-08 09:47:04 -03:00
Davidson Gomes
03684a893d ajustes rabbitmq 2024-03-08 09:34:08 -03:00
Lindenberg Pinheiro
fef27111b9 Update variables .env.example docker
- Variáveis do Docker referente ao feat #395 
- Docker variables related to the feat #395
2024-03-07 20:50:50 -03:00
Davidson Gomes
b04ed66686 Merge pull request #451 from judsonjuniorr/feat/rabbit-per-events
RabbitMQ improvements
2024-03-07 20:00:04 -03:00
Davidson Gomes
f5df8bca20 Merge pull request #461 from judsonjuniorr/refactor/temp_delete_option
Refactor/temp delete option
2024-03-07 19:59:17 -03:00
Davidson Gomes
060af66c09 Merge pull request #460 from judsonjuniorr/fix/numbers-validation
Change the way numbers are validated on whatsapp
2024-03-07 19:59:05 -03:00
Judson Cairo
a6adbd61db Implemented an option to toggle temp instances deletion 2024-03-07 19:26:43 -03:00
Judson Cairo
bab58054f7 Change the way numbers are validated on whatsapp 2024-03-07 19:18:32 -03:00
Judson Cairo
e27e990cd6 Adapt to make compatible with current running services 2024-03-07 18:36:15 -03:00
Judson Cairo
196c2e0ed8 Removed logs & updated version 2024-03-07 18:28:56 -03:00
Davidson Gomes
499bd4328a Merge pull request #457 from gabrielporfiro/main
fix: incorrect images in project
2024-03-07 17:50:49 -03:00
Gabriel Porfiro
e389047aaf Update docker-compose.yaml 2024-03-06 18:52:01 -03:00
Gabriel Porfiro
da04ff284b Update docker-compose.yaml 2024-03-06 18:51:36 -03:00
Judson Cairo
10b48aed97 Update RABBITMQ_MODE to global in .env.example and Dockerfile 2024-03-04 20:27:34 -03:00
Judson Cairo
ac6e9ae994 Improvements in RabbitMQ, added 2 new modes 2024-03-04 20:24:15 -03:00
Judson Cairo
7dd589fb6c Make rabbitMQ per events 2024-03-04 12:16:26 -03:00
Davidson Gomes
6b930058d1 feat wa_business: send audio message 2024-03-02 12:03:11 -03:00
Davidson Gomes
aef3495a79 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-02-28 13:48:07 -03:00
Davidson Gomes
da341a95c6 fix: writing over 20s now loops until finished 2024-02-28 13:47:54 -03:00
Davidson Gomes
bca5ec6482 fix: writing over 20s now loops until finished 2024-02-28 13:47:32 -03:00
Davidson Gomes
f0a0cb7269 Merge pull request #434 from jaison-x/pr
feat(chatwoot): add some translations on chatwoot messages
2024-02-21 12:39:53 -03:00
jaison-x
238b7618b4 feat(chatwoot): add some translations on chatwoot messages
Add typescript to transpile .json files inside src folder.
2024-02-21 12:05:41 -03:00
Davidson Gomes
4f206f67c9 Merge pull request #433 from w3nder/develop
Add blockUser functionality
2024-02-20 18:26:46 -03:00
w3nder
fa513bf784 Update blockUserSchema in validate.schema.ts 2024-02-20 17:47:38 -03:00
w3nder
a68b0b3878 Add check number and ignore Group or Broadcast 2024-02-20 17:47:32 -03:00
w3nder
249489e697 Add blockUser functionality 2024-02-20 17:05:07 -03:00
Davidson Gomes
e2c67d7dae fix: integration 2024-02-19 16:04:48 -03:00
Davidson Gomes
703bc310a7 fix: create instance baileys 2024-02-18 11:57:44 -03:00
Davidson Gomes
4caecfa680 fix: adjusts in media 2024-02-18 10:40:51 -03:00
Davidson Gomes
fd4fde2543 fix: adjusts in media 2024-02-18 10:22:33 -03:00
Davidson Gomes
763c5de03f fix: adjusts in messageType 2024-02-18 09:57:14 -03:00
Davidson Gomes
4e160a46dd fix: adjusts in messageType 2024-02-18 09:41:23 -03:00
Davidson Gomes
56aaf18663 fix: adjusts in fetch instances 2024-02-18 09:22:54 -03:00
Davidson Gomes
df841aed27 fix: adjusts in fetch instances 2024-02-18 09:06:59 -03:00
Davidson Gomes
d4372a0332 fix: adjusts in messageType 2024-02-18 08:55:14 -03:00
Davidson Gomes
1595da789d fix: adjusts in messageType 2024-02-18 08:43:40 -03:00
Davidson Gomes
7e65cb1d19 fix: find intance by number 2024-02-18 08:11:24 -03:00
Davidson Gomes
a931ee7afb fix: logout wa business instance 2024-02-18 07:52:21 -03:00
Davidson Gomes
bc6944736e fix: expose integration in fetch instance 2024-02-18 07:44:42 -03:00
Davidson Gomes
16c2e28e9c fix: remve console.log 2024-02-18 07:27:24 -03:00
Davidson Gomes
ab89ef8db3 feat: whatsapp cloud api components in template 2024-02-17 18:41:35 -03:00
Davidson Gomes
9ef14d11f8 feat: whatsapp cloud api webhooks 2024-02-17 18:02:00 -03:00
Davidson Gomes
e753990da3 feat: whatsapp cloud api 2024-02-17 17:54:23 -03:00
Davidson Gomes
f469c2e65d feat: whatsapp cloud api 2024-02-17 17:43:25 -03:00
Davidson Gomes
0525501b87 feat: whatsapp cloud api 2024-02-17 17:42:49 -03:00
Davidson Gomes
3a37fd9d32 fix: typebot's wait now pauses the flow and composing is defined by the delay_message parameter in set typebot 2024-02-16 10:31:15 -03:00
Davidson Gomes
b095c9dfb9 fix: keyword_finish dont work 2024-02-16 10:15:31 -03:00
Davidson Gomes
cd00effcfe Merge pull request #421 from judsonjuniorr/Groups-participant-names
Refactor fetching participants for group in WhatsApp service
2024-02-16 08:29:21 -03:00
Davidson Gomes
1cd0334ccd Merge pull request #420 from judsonjuniorr/proxy-error-handler
Improve proxy error handling
2024-02-16 08:27:49 -03:00
Davidson Gomes
3b1a16844e fix: update create jid to BR numbers 2024-02-15 18:50:08 -03:00
Judson Cairo
6995e8a451 Refactor fetching participants for group in WhatsApp service 2024-02-14 16:25:38 -03:00
Judson Cairo
1dd0f319fc Fix error handling in testProxy method 2024-02-14 16:08:32 -03:00
Judson Cairo
5ce96369cf Refactor testProxy error handling and logging 2024-02-14 16:05:52 -03:00
Judson Cairo
0791d78e28 Improve proxy error handling 2024-02-14 15:27:34 -03:00
Davidson Gomes
cecbb7c34e Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-02-13 08:33:08 -03:00
Davidson Gomes
525daff5fe Merge pull request #419 from judsonjuniorr/improvement/contacts-saving
Implement contact upsert in messaging-history.set
2024-02-12 19:59:21 -03:00
Judson Cairo
b2c51b4b8c Implement contact upsert in messaging-history.set 2024-02-12 11:46:51 -03:00
Davidson Gomes
ffddb05ba3 gitignore .vscode 2024-02-10 16:30:54 -03:00
Davidson Gomes
4bb81b9a41 Merge pull request #411 from judsonjuniorr/feat/labels
Changed label update to chat instead of contacts
2024-02-09 09:09:25 -03:00
Judson Cairo
3df4e8d2ba Changed label update to chat instead of contacts 2024-02-08 15:38:27 -03:00
Davidson Gomes
3edef873bc Merge pull request #409 from judsonjuniorr/feat/labels
Feat/labels
2024-02-08 14:49:01 -03:00
Davidson Gomes
c04b5cb851 Merge pull request #410 from jaison-x/clear-cache
feat(chatwoot): command to clearcache from chatwoot inbox
2024-02-08 14:47:12 -03:00
Davidson Gomes
fabd717d92 Merge pull request #408 from jaison-x/pr
fix(chatwoot): fix qrcode filename
2024-02-08 14:46:56 -03:00
jaison-x
945bcf5fad feat(chatwoot): command to clearcache from chatwoot inbox 2024-02-08 13:49:16 -03:00
jaison-x
d35d755379 feat(chatwoot): command to clearcache from chatwoot inbox 2024-02-08 13:34:50 -03:00
Judson Cairo
a2622cb38e Remove unused classes and imports from label dto 2024-02-08 13:33:53 -03:00
jaison-x
b58fd78450 fix(chatwoot): fix qrcode filename 2024-02-08 13:33:16 -03:00
Judson Cairo
e8d32066b4 Add 'add' field to the 'label' object in swagger.yaml and update event handling in whatsapp.service.ts 2024-02-08 13:31:56 -03:00
Judson Cairo
6d3779eb83 Merge branch 'develop' into feat/labels 2024-02-08 13:10:23 -03:00
Judson Cairo
c7bed04c80 Add labels property to ContactRaw model and update labels in LABELS_ASSOCIATION event 2024-02-08 13:05:06 -03:00
Davidson Gomes
b0e956cfa9 Merge pull request #406 from judsonjuniorr/contact-upsert-improvement
Extracted profile picture request from contacts.upsert function
2024-02-08 12:52:09 -03:00
Davidson Gomes
8608b7cded Merge pull request #404 from judsonjuniorr/instance-deletion
Temp instance deletion
2024-02-08 12:51:36 -03:00
Judson Cairo
23f1b4ac03 Implementation of Whatsapp labels management 2024-02-08 09:16:24 -03:00
Judson Cairo
27900c214f Extracted profile picture request from contacts.upsert function 2024-02-07 18:42:12 -03:00
Judson Cairo
704701e251 Add list method to AuthRepository and delete temp instances in WAMonitoringService 2024-02-07 16:34:26 -03:00
Judson Cairo
34c53d352a Refactor code and remove commented out sections 2024-02-07 15:16:37 -03:00
Davidson Gomes
32026d1fd4 Merge pull request #402 from judsonjuniorr/improv-name-validation
Improvement on whatsapp number validation
2024-02-07 10:37:33 -03:00
Judson Cairo
35cdce0d52 Check multiple numbers only once in the database 2024-02-07 08:53:47 -03:00
Judson Cairo
e59098cf61 Changed whatsapp number response to 200
Nothing is being created so the code 201 is not correct
2024-02-07 08:44:54 -03:00
Davidson Gomes
d8ca480b19 Merge pull request #395 from jaison-x/import-messages-chatwoot
feat(chatwoot): import history messages to chatwoot on whatsapp connection
2024-02-05 15:26:21 -03:00
Davidson Gomes
803b123ade Merge pull request #397 from deivisonrpg/fix/chatwoot-add-identifier-to-contact-filter
Fix(chatwoot): Include identifier to contact filter
2024-02-04 15:04:08 -03:00
Deivison Lincoln
cffb673fba Include identifier to contact filter in chatwoot 2024-02-02 18:20:24 -03:00
jaison-x
1f6535d61b fix: param for Object.entries cant be undefined 2024-02-02 16:46:29 -03:00
jaison-x
8a5ebe83a3 feat(chatwoot): import history messages to chatwoot on whatsapp connection
Messages are imported direct to chatwoot database. Media and group messages are ignored.

New env.yml variables:
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI: URI to connect direct on chatwoot database.
CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE: Indicates to use a text placeholder on media messages.

New instance setting:
sync_full_history: Indicates to request a full history sync to baileys.

New chatwoot options:
import_contacts: Indicates to import contacts.
import_messages: Indicates to import messages.
days_limit_import_messages: Number of days to limit history messages to import.
2024-02-02 15:32:34 -03:00
Davidson Gomes
cc17d61016 Merge pull request #394 from deivisonrpg/feat/chatwoot-reject-message-wpp-not-exists
Feat Chatwoot - Reject Message If is not a valid wpp number
2024-02-02 14:05:45 -03:00
Davidson Gomes
0547ff719c Merge pull request #393 from deivisonrpg/fix/chatwoot-validate-9-digit
Fix/chatwoot validate 9 digit
2024-02-02 14:05:02 -03:00
Deivison Lincoln
c130846fe8 Change message_type
Change message_type to 'outgoing'
2024-02-02 08:57:44 -03:00
Deivison Lincoln
b3adde3a7a Feat Reject Message If is not a valid wpp number 2024-02-01 23:42:52 -03:00
Deivison Lincoln
54603002a6 Fix for brazil 9 digit 2024-02-01 22:13:19 -03:00
Deivison Lincoln
b995cdfc32 Fix for contats find payload 2024-02-01 18:53:36 -03:00
Davidson Gomes
b09546577a Merge pull request #390 from leandrosroc/develop
feat: If contact stored with name, name added in return
2024-02-01 17:45:53 -03:00
Davidson Gomes
6e84c1dc4c Merge pull request #392 from jaison-x/hotfix
hotfix: bug on chatwoot sdk
2024-02-01 17:44:48 -03:00
jaison-x
f41f3aaba8 Update chatwoot.service.ts
hotfix: bug on chatwoot sdk
2024-02-01 17:31:50 -03:00
Leandro Santos Rocha
8fa61e4632 Merge branch 'EvolutionAPI:develop' into develop 2024-02-01 16:20:04 -03:00
Davidson Gomes
7dacd752d3 Merge pull request #382 from yvescleuder/fix-with-nine-number-brazil
Fix with nine number brazil
2024-01-31 11:51:45 -03:00
Leandro Santos Rocha
7439d2401d Fix error ; 2024-01-31 11:39:04 -03:00
Leandro Santos Rocha
dfb1ee0c56 Adjust to use the same jid 2024-01-31 11:37:19 -03:00
Leandro Santos Rocha
3da73b821d Removed console.log 2024-01-31 10:55:37 -03:00
Leandro Rocha
66b82ac10a If contact stored with name, name added in return 2024-01-31 10:50:04 -03:00
Davidson Gomes
058acc5042 changelog 2024-01-29 11:43:42 -03:00
Davidson Gomes
cdf822291f lint 2024-01-29 11:35:30 -03:00
Davidson Gomes
3f9e872e1c Merge pull request #381 from edisoncm-ti/develop
fix: collecting responses with text or numbers in Typebot
2024-01-29 11:30:22 -03:00
Davidson Gomes
94aa3067f6 Merge pull request #380 from drauber/qr_translation
Add translate capabilities to QRMessages in CW
2024-01-29 11:30:00 -03:00
Davidson Gomes
bff8064597 Merge pull request #379 from judsonjuniorr/sendList-swagger-docs
Added sendList endpoint to swagger documentation
2024-01-29 11:29:15 -03:00
Davidson Gomes
6b2d4e2585 Merge pull request #378 from moraisamilton/develop
Implemented a function to synchronize message deletions on WhatsApp, automatically reflecting in Chatwoot.
2024-01-29 11:28:59 -03:00
Davidson Gomes
56dfc2ae82 Merge pull request #377 from judsonjuniorr/validate-numbers
Improvement on numbers validation
2024-01-29 11:27:48 -03:00
Yves Clêuder Lima de Jesus
535d5ee47f fix: change method search for filter 2024-01-28 10:36:42 -03:00
Yves Clêuder Lima de Jesus
838905f3dd fix: position splice 2024-01-27 22:37:58 -03:00
Yves Clêuder Lima de Jesus
59f5208c5c fix: search number without 9 in number from brazil 2024-01-27 22:29:04 -03:00
Edison Martins
32a1a715ea Merge branch 'EvolutionAPI:develop' into develop 2024-01-26 19:11:34 -03:00
edisoncm-ti
3755d3870e fix: collecting responses with text or numbers in Typebot 2024-01-26 19:10:20 -03:00
Douglas Rauber at Nitro
0edc8a9284 Add translate capabilities to QRMessages in CW 2024-01-25 16:19:08 -03:00
Amilton Morais
5449d63602 Merge branch 'develop' of https://github.com/moraisamilton/evolution-api into develop 2024-01-24 13:46:02 -03:00
Amilton Morais
dfa72fd6af Update start.sh 2024-01-24 13:43:19 -03:00
Amilton Morais
0e50da324b Implemented a function to synchronize message deletions on WhatsApp, automatically reflecting in Chatwoot
A new variable has been created, and a function has been implemented to manage the deletion of messages on WhatsApp. Now, when deleting a message for everyone on WhatsApp, the same action will be automatically performed on Chatwoot, ensuring consistency across platforms
2024-01-24 13:38:29 -03:00
Judson Junior
679f8118f6 Added sendList endpoint to swagger documentation 2024-01-23 23:59:12 -03:00
Amilton Morais
804d177782 Udate "@whiskeysockets/baileys": "6.6.0" and models
The message.model.ts class was updated to version 6.6.0 of baileys. 'unknown','desktop' was included to avoid errors using version 6.6.0 of baileys
2024-01-23 19:34:36 -03:00
Judson Junior
eb96c9fece Added new validation on the 9 digit
This is to prevent returning another jid when only one digit is different and not the 9 digit
2024-01-23 18:37:31 -03:00
Judson Junior
ed6c50621c Improved the method of numbers validation 2024-01-23 18:16:15 -03:00
Davidson Gomes
7f74de07ed Merge pull request #375 from judsonjuniorr/poll-sending
Fix polls in message sending
2024-01-22 13:44:49 -03:00
Judson Junior
9e5bf93580 Fix polls in message sending 2024-01-22 13:39:11 -03:00
Davidson Gomes
9d685da12d Merge pull request #374 from judsonjuniorr/proxy-improvements
Proxy improvements
2024-01-21 18:16:15 -03:00
Davidson Gomes
6bb40eb502 Merge pull request #373 from leandrosroc/develop
Join in Group by Invite Code
2024-01-21 18:14:40 -03:00
Davidson Gomes
1ceee572cf Merge pull request #372 from edisoncm-ti/develop
Typebot: Remove quebra de linha no index final do array | Obtem a resposta de um sendList (Lista)
2024-01-21 18:13:26 -03:00
Davidson Gomes
391ffea4ab Merge pull request #371 from judsonjuniorr/develop
Add number parameter to OnWhatsAppDto constructor
2024-01-21 18:12:31 -03:00
Judson Junior
5292e569d9 Remove unused dependencies and refactor proxy handling
In some tests made on the testProxy function, the `new ProxyAgent()` was not working, even adding it the IP result was the same.

I've change it to the `new HttpsProxyAgent()` and it worked.

Refactored the proxy agent to center the same function/creation the same where it is used
2024-01-21 13:54:42 -03:00
Judson Junior
82e111f1be Fix proxy handling in WAStartupService preventing instance to be created 2024-01-21 13:01:35 -03:00
Judson Junior
1e9b3a1e42 Update proxy controller to use a different IP lookup service 2024-01-21 13:01:06 -03:00
Judson Junior
35520d85a2 Add https-proxy-agent dependency and handle NotFoundException in ProxyController 2024-01-21 13:00:23 -03:00
Leandro Rocha
e19e37eef4 Join in Group by Invite Code 2024-01-21 02:13:24 -03:00
edisoncm-ti
814795f566 Merge branch 'develop' of https://github.com/edisoncm-ti/evolution-api into develop 2024-01-20 18:42:40 -03:00
edisoncm-ti
1a633dcf10 feat: obter a resposta de um sendList (Lista) e prosseguir o fluxo Typebot 2024-01-20 18:31:26 -03:00
edisoncm-ti
c9b0e66641 feat: Obter a resposta de um sendList (Lista) e prosseguir o fluxo Typebot 2024-01-20 18:28:00 -03:00
Judson Junior
440ff2f3ea Add number parameter to OnWhatsAppDto constructor 2024-01-20 11:44:10 -03:00
edisoncm-ti
96be63f50b fix: remove quebra de linha no index final do array quando usado variavel no Typebot 2024-01-20 11:09:05 -03:00
Davidson Gomes
69a323691f Merge pull request #370 from leandrosroc/main
fix: Sending status message
2024-01-19 16:38:59 -03:00
Leandro Santos Rocha
4357fcf7ef Adjusted so that when it is "list", it sends correctly 2024-01-18 21:30:49 -03:00
Leandro Rocha
5f79513617 fix: Sending status message 2024-01-18 20:25:58 -03:00
Davidson Gomes
ba3ccbbc9a Merge pull request #369 from jaison-x/pr
fix: message 'connection successfully' spamming
2024-01-18 11:55:07 -03:00
jaison-x
f182436673 fix: message 'connection successfully' spamming 2024-01-17 19:56:26 -03:00
Davidson Gomes
793b907fe6 Merge pull request #363 from jaison-x/read_messages_chatwoot
feat(chatwoot): read messages from whatsapp in chatwoot
2024-01-16 09:39:17 -03:00
jaison-x
c75dfcd499 feat(chatwoot): read messages from whatsapp in chatwoot
It works only with messages read from whatsapp to chatwoot.
2024-01-15 20:40:29 -03:00
Davidson Gomes
29a48f7914 Merge pull request #360 from jaison-x/cache
feat(cacheservice): add support to use use redis in cacheservice
2024-01-15 10:21:35 -03:00
jaison-x
095a8aa9dd fix: process.env can be null 2024-01-12 16:15:44 -03:00
jaison-x
2d8b5f04e9 feat(cacheservice): add suport to use use redis in cacheservice 2024-01-12 16:04:55 -03:00
Davidson Gomes
ba9f97bc3e Merge pull request #355 from AlanMartines/main
Fix: Corrects the generation of the hash variable name
2024-01-11 12:57:17 -03:00
Davidson Gomes
ed51f44ee8 Merge pull request #351 from jaison-x/cache-chatwoot
fix(chatwoot): invalidate the conversation cache if reopen_conversation is false and the conversation was resolved
2024-01-11 12:56:37 -03:00
Alan Martines
933d787108 Fix: Corrects the generation of the hash variable name, error when generating with white spaces and special characters. 2024-01-10 09:27:25 -04:00
jaison-x
0bc12733a3 fix(chatwoot): invalidate the conversation cache if reopen_conversation is false and the conversation was resolved 2024-01-08 22:20:16 -03:00
Davidson Gomes
8cfd9c44ab Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-01-07 19:11:07 -03:00
Davidson Gomes
3ddff84be0 test: docker with platform arm 2024-01-07 19:10:56 -03:00
Davidson Gomes
afb5361a1b Merge pull request #342 from jaison-x/cache-chatwoot
perf(chatwoot): create cache for the most used/expensive functions in chatwoot
2024-01-04 17:05:39 -03:00
jaison-x
f376047632 perf(chatwoot): create cache for the most used/expensive functions in chatwoot 2024-01-03 18:42:54 -03:00
Davidson Gomes
7373eea842 feat: Added update message endpoint 2024-01-03 10:18:20 -03:00
Davidson Gomes
a446df4620 fix: Adjusts the quoted message, now has contextInfo in the message Raw 2024-01-02 14:04:54 -03:00
Davidson Gomes
306c6dd21d fix: Adjusts the quoted message, now has contextInfo in the message Raw 2024-01-02 13:49:31 -03:00
Davidson Gomes
380ba8860c Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-01-02 12:03:22 -03:00
Davidson Gomes
6c8c8ddcfc version: baileys 6.5.0 2024-01-02 12:03:11 -03:00
Davidson Gomes
0c09c5e140 Merge pull request #338 from jaison-x/pr
fix(chatwoot): fix looping when deleting a message in chatwoot
2024-01-01 21:47:35 -03:00
jaison-x
b761fba8cb fix(chatwoot): fix looping when deleting a message in chatwoot 2024-01-01 18:11:25 -03:00
Davidson Gomes
dfceda3942 version: 1.6.2 2023-12-29 19:15:51 -03:00
Davidson Gomes
85d1825236 version: 1.6.2 2023-12-29 19:12:29 -03:00
Davidson Gomes
8f8c7e26c7 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-12-29 19:11:28 -03:00
Davidson Gomes
181768d91f Merge pull request #336 from jaison-x/adjust-filename-chatwoot
perf(chatwoot): only use a axios request to get file mimetype if necessary
2023-12-29 19:11:22 -03:00
Davidson Gomes
2dcd4d8fd3 fix: Correction in chatwoot text formatting and render list message 2023-12-29 19:10:59 -03:00
jaison-x
d909550134 fix(chatwoot): add file extension to var fileName 2023-12-29 18:42:07 -03:00
jaison-x
c564ec41e2 perf(chatwoot): Only use a axios request to get mimetype file if necessary 2023-12-29 18:15:05 -03:00
Davidson Gomes
af94a0e174 Merge pull request #334 from jaison-x/adjust-filename-chatwoot
fix(chatwoot): when possible use the original file extension
2023-12-29 17:10:43 -03:00
jaison-x
fcd8815fca fix(chatwoot): when possible use the original file extension
In some cases mimeTypes.extension() return false to csv and other file types
2023-12-29 14:52:16 -03:00
Davidson Gomes
5bd3f28117 Merge pull request #332 from jaison-x/adjust-filename-chatwoot
fix(chatwoot): when receiving a file from whatsapp, use the original filename in chatwoot if possible
2023-12-29 10:36:17 -03:00
Davidson Gomes
384bde333e Merge pull request #331 from jaison-x/pr
refactor(chatwoot): remove message ids cache in chatwoot to use chatwoot's api itself
2023-12-29 10:36:01 -03:00
Davidson Gomes
d06ec604b9 version: 1.6.2 2023-12-29 10:34:23 -03:00
Davidson Gomes
d8ce23f2a0 fix: exchange rabbitmq 2023-12-29 09:46:59 -03:00
Davidson Gomes
3ccb983377 fix: chatwoot service 2023-12-28 18:24:29 -03:00
Davidson Gomes
19fb9fcd31 fix: webhook 2023-12-28 18:20:38 -03:00
Davidson Gomes
5f4a1b96ce fix: correction in typebot text formatting 2023-12-28 18:08:30 -03:00
Davidson Gomes
6983f385fc fix: correction in typebot text formatting 2023-12-28 18:07:24 -03:00
Davidson Gomes
2aadd1cac5 fix: Adjust in webhook_base64 2023-12-28 17:32:19 -03:00
jaison-x
bfa7d429bd refactor(chatwoot): remove message ids cache in chatwoot to use chatwoot's api itself.
Remove use of disc cache to optimize performance.

BREAKING CHANGE: to make this, we need to use the param `source_id` from message in chatwoot.
This param is only available from api in chatwoot version => 3.4.0.
2023-12-28 14:43:50 -03:00
Davidson Gomes
3238150b92 fix: Proxy configuration improvements 2023-12-28 10:36:23 -03:00
Davidson Gomes
3603571967 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-12-28 10:36:01 -03:00
Davidson Gomes
7c2a8c0abb fix: Proxy configuration improvements 2023-12-28 10:35:41 -03:00
jaison-x
8b5f73badd fix: added support to use source_id to check chatwoot's webhook needs to be ignored.
With this, messageCache is used to support Chatwoot version <= 3.3.1.

After this version we can remove use of message cache and use only the source_id field to check chatwoot's webhook needs to be ignored. It's have much better performance.
2023-12-27 13:56:32 -03:00
Davidson Gomes
ff57fd3d23 Merge pull request #322 from w3nder/develop
fix: Correção na Função sendList
2023-12-27 10:09:14 -03:00
jaison-x
49aa1ea17c fix: when receiving a file from whatsapp, use the original filename in chatwoot when possible 2023-12-26 01:21:05 -03:00
jaison-x
7430897085 fix: when receiving a file from whatsapp, use the original filename in chatwoot when possible 2023-12-25 23:58:07 -03:00
jaison-x
82894a1c4f refactor: change the message ids cache in chatwoot to use a in memory cache
Remove use of disc cache for optimize performance.
To make this, we need change to use only one instance of ChatwootService in entire application.
2023-12-25 22:29:03 -03:00
w3nder
3bb4217a45 fix: Correção na Função sendList
Ao disparar uma lista de mensagens, agora enviamos elas com o tipo 'PRODUCT_LIST' e realizamos a correção necessária na função patchMessageBeforeSending para o tipo 'SINGLE_SELECT'
2023-12-24 14:32:05 -03:00
Davidson Gomes
dfc8330035 Merge tag '1.6.1' into develop
* Fixed Lid Messages
* Fixed sending variables to typebot
* Fixed sending variables from typebot
* Correction sending s3/minio media to chatwoot and typebot
* Fixed the problem with typebot closing at the end of the flow, now this is optional with the TYPEBOT_KEEP_OPEN variable
* Fixed chatwoot Bold, Italic and Underline formatting using Regex
* Added the sign_delimiter property to the Chatwoot configuration, allowing you to set a different delimiter for the signature. Default when not defined \n
* Include instance Id field in the instance configuration
* Fixed the pairing code
* Adjusts in typebot
* Fix the problem when disconnecting the instance and connecting again using mongodb
* Options to disable docs and manager
* When deleting a message in whatsapp, delete the message in chatwoot too
2023-12-22 11:44:21 -03:00
Davidson Gomes
45e03d87c7 Merge branch 'release/1.6.1' 2023-12-22 11:44:12 -03:00
Davidson Gomes
3e358e5d26 version: 1.6.1 2023-12-22 11:43:58 -03:00
Davidson Gomes
dc5dae04eb fix: when deleting a message in whatsapp, delete the message in chatwoot too 2023-12-22 11:43:10 -03:00
Davidson Gomes
9128b1f47d fix: when deleting a message in whatsapp, delete the message in chatwoot too 2023-12-22 11:43:03 -03:00
Davidson Gomes
16a8226ba7 Merge pull request #317 from jaison-x/pr
fix: when deleting a message in whatsapp, delete the message in chatwoot too
2023-12-21 16:37:01 -03:00
jaison-x
9ecaf3199d Merge branch 'pr' of https://github.com/jaison-x/evolution-api into pr 2023-12-21 07:42:37 -03:00
jaison-x
a44cd0373e fix: simple adjust in key object 2023-12-21 07:42:33 -03:00
jaison-x
2c0b629302 Update whatsapp.service.ts 2023-12-21 00:23:33 -03:00
jaison-x
89a37a1771 fix: show message Connection successfully established after a sucessfull connection on chatwoot 2023-12-20 18:11:41 -03:00
jaison-x
cadc038966 Merge remote-tracking branch 'upstream/develop' into pr 2023-12-20 17:54:52 -03:00
jaison-x
07e8449379 fix: when deleting a message in whatsapp, delete the message in chatwoot too
The message model schema was changed. Old format in message model was field chatwootMessageId. Now we have a document chatwoot with new properties.

I cant find a simple way to create a migration function up then the old field was no migrate to new format.
2023-12-20 17:53:37 -03:00
Davidson Gomes
71e908ad1a Merge pull request #312 from stgcompany/develop
Exclude all .env files on .gitignore and renamed one to .env.example
2023-12-20 12:22:03 -03:00
Davidson Gomes
035c85b775 Merge pull request #313 from gabrielpastori1/issue-template
Add issue templates
2023-12-20 12:21:06 -03:00
Gabriel Pastori
a87b753151 Update issue templates 2023-12-20 12:13:24 -03:00
Eduardo Chaves
edaa4aff7e Add any .env file to .gitignore 2023-12-20 10:31:14 -03:00
Eduardo Chaves
be02610349 Renamed .env to .env.example 2023-12-20 10:29:32 -03:00
Davidson Gomes
1f65731165 fix: Add options to disable docs and manager 2023-12-20 10:03:44 -03:00
Davidson Gomes
fb61fb7849 Merge pull request #310 from gabrielpastori1/disable-docs-manager
Add options to disable docs and manager
2023-12-20 10:02:46 -03:00
Davidson Gomes
060a945aea fix: Fix the problem when disconnecting the instance and connecting again using mongodb 2023-12-20 09:56:53 -03:00
Davidson Gomes
2797250f34 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-12-20 09:56:32 -03:00
Davidson Gomes
97b3f9b3c7 fix: Fix the problem when disconnecting the instance and connecting again using mongodb 2023-12-20 09:55:51 -03:00
Gabriel Pastori
9363301d2a Add DISABLE_DOCS and DISABLE_MANAGER 2023-12-19 21:18:40 -03:00
Davidson Gomes
d93a826e28 Merge pull request #309 from jaison-x/pr
fix: message can be undefined
2023-12-19 18:59:59 -03:00
jaison-x
244fe0835e Update chatwoot.service.ts
fix: message can be undefined
2023-12-19 18:49:31 -03:00
Davidson Gomes
5f1a5d6589 Merge pull request #308 from PurpShell/fix/message-retry
Fix: message retry mechanism
2023-12-19 17:34:39 -03:00
Rajeh Taher
4e27c22292 fix: message retry 2023-12-19 22:28:50 +02:00
Davidson Gomes
53ee270096 Merge pull request #307 from gabrielpastori1/chatwoot-format
Chatwoot Agent Name
2023-12-19 17:15:32 -03:00
Gabriel Pastori
a00ad20c08 chatwoot agent available_name 2023-12-19 16:41:52 -03:00
Davidson Gomes
e8764dd1c6 fix: Fix the problem when disconnecting the instance and connecting again using mongodb 2023-12-19 14:55:16 -03:00
Davidson Gomes
a869d38499 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-12-19 14:27:47 -03:00
Davidson Gomes
7bf7c96587 Merge pull request #301 from drauber/addLogInfo
Add connected number and instance name to connected log
2023-12-19 14:27:41 -03:00
Davidson Gomes
72b857a92f Merge pull request #299 from gabrielpastori1/chatwoot-format
Chatwoot format
2023-12-19 14:27:05 -03:00
Douglas Rauber at Nitro
380d6a43a5 Add connected number and instance name to connected log 2023-12-19 08:02:29 -03:00
Gabriel Pastori
076f2b492e fix: chatwoot media process 2023-12-18 21:23:56 -03:00
Davidson Gomes
547f981c47 fix: adjusts in typebot 2023-12-18 16:46:04 -03:00
Davidson Gomes
8bfc62a3b2 fix: adjusts in typebot 2023-12-18 16:27:37 -03:00
Davidson Gomes
d39776a314 fix: fixed the pairing code 2023-12-18 15:27:22 -03:00
Davidson Gomes
35641d0543 fix: fixed the pairing code 2023-12-18 15:24:19 -03:00
Davidson Gomes
038cd6f149 Merge pull request #293 from gabrielpastori1/chatwoot-format
Chatwoot format
2023-12-18 12:15:00 -03:00
Davidson Gomes
38978dd447 test: adjusts baileys lids 2023-12-18 12:07:02 -03:00
Gabriel Pastori
fb24b7eaa7 Merge branch 'develop' into chatwoot-format 2023-12-17 13:30:54 -03:00
Gabriel Pastori
b4ce45bc4b Add new env and fix message formatting in Chatwoot to changelog 2023-12-17 13:25:03 -03:00
Gabriel Pastori
8d04198309 fix: monospace and bold 2023-12-17 13:24:43 -03:00
Davidson Gomes
da796347c4 fix: include instance Id field in the instance configuration 2023-12-17 09:37:18 -03:00
Davidson Gomes
2d6a29664a fix: include instance Id field in the instance configuration 2023-12-17 07:59:24 -03:00
Davidson Gomes
4ba5cfceaf fix: include instance Id field in the instance configuration 2023-12-17 07:50:17 -03:00
Davidson Gomes
7cc324e1c0 fix: include instance Id field in the instance configuration 2023-12-17 07:04:33 -03:00
Davidson Gomes
cf89601269 fix: include instance Id field in the instance configuration 2023-12-17 06:59:05 -03:00
Davidson Gomes
c07e23bf8d Merge pull request #290 from gabrielpastori1/chatwoot-format
Chatwoot format
2023-12-17 06:32:37 -03:00
Gabriel Pastori
5aa89d85f3 fix: remove comments 2023-12-16 18:39:46 -03:00
Gabriel Pastori
4ed1edf53d chatwoot_sign_delimiter 2023-12-16 17:23:39 -03:00
Gabriel Pastori
1be1326b52 Refactor message formatting in ChatwootService (Bold, italic, etc) 2023-12-16 15:58:01 -03:00
Davidson Gomes
42ae7d1568 Merge pull request #286 from gomessguii/feature/dockerfile-optimization
Dockerfile modified to use multi-stage build
2023-12-15 14:51:49 -03:00
Guilherme Oliveira
b781c83545 Dockerfile modified to use multi-stage build 2023-12-15 14:46:31 -03:00
Davidson Gomes
e48cea18e7 Merge pull request #283 from gabrielpastori1/typeboot-keep-open
simple add keep open
2023-12-15 13:53:06 -03:00
Gabriel Pastori
ff82987144 simple add keep open 2023-12-15 12:39:07 -03:00
Davidson Gomes
f612a45550 fix: correction sending s3/minio media to chatwoot and typebot 2023-12-14 17:20:37 -03:00
Davidson Gomes
182dce4840 fix: correction sending s3/minio media to chatwoot and typebot 2023-12-14 16:42:03 -03:00
Davidson Gomes
4e41e072d6 fix: correction sending s3/minio media to chatwoot and typebot 2023-12-14 16:13:15 -03:00
Davidson Gomes
a369c16db8 fix: correction sending s3/minio media to chatwoot and typebot 2023-12-14 15:59:06 -03:00
Davidson Gomes
1fc820787a fix: correction sending s3/minio media to chatwoot and typebot 2023-12-14 15:57:34 -03:00
Davidson Gomes
d3a83ba89e fix: correction sending s3/minio media to chatwoot and typebot 2023-12-14 15:35:17 -03:00
Davidson Gomes
20fb66e2f7 fix: correction sending s3/minio media to chatwoot and typebot 2023-12-14 11:50:05 -03:00
Davidson Gomes
a44646161b fix: variables in typebot 2023-12-14 08:35:45 -03:00
Davidson Gomes
87027ea2d0 update: manager 2023-12-12 18:16:02 -03:00
Davidson Gomes
c9757cbb4b fix: fixed lids messages 2023-12-12 17:45:12 -03:00
Davidson Gomes
9a8f4aefe0 Merge tag '1.6.0' into develop
* Added AWS SQS Integration
* Added support for new typebot API
* Added endpoint sendPresence
* New Instance Manager
* Added auto_create to the chatwoot set to create the inbox automatically or not
* Added reply, delete and message reaction in chatwoot v3.3.1

* Adjusts in proxy
* Adjusts in start session for Typebot
* Added mimetype field when sending media
* Ajusts in validations to messages.upsert
* Fixed messages not received: error handling when updating contact in chatwoot
* Fix workaround to manage param data as an array in mongodb
* Removed await from webhook when sending a message
* Update typebot.service.ts - element.underline change ~ for *
* Adjusts in proxy
* Removed api restart on receiving an error
* Fixes in mongodb and chatwoot
* Adjusted return from queries in mongodb
* Added restart instance when update profile picture
* Correction of chatwoot functioning with admin flows
* Fixed problem that did not generate qrcode with the chatwoot_conversation_pending option enabled
* Fixed issue where CSAT opened a new ticket when reopen_conversation was disabled
* Fixed issue sending contact to Chatwoot via iOS

- Chatwoot: v3.3.1
- Typebot: v2.20.0
2023-12-12 17:44:42 -03:00
Davidson Gomes
3545b80050 Merge branch 'release/1.6.0' 2023-12-12 17:44:24 -03:00
Davidson Gomes
379855714e version: 1.6.0 2023-12-12 17:44:00 -03:00
Davidson Gomes
ff06cd7643 feat: Added support for new typebot API 2023-12-12 16:55:46 -03:00
Davidson Gomes
ade3952016 fix: Fixed issue sending contact to Chatwoot via iOS 2023-12-12 16:28:01 -03:00
Davidson Gomes
f246516a6e fix: fixed issue where CSAT opened a new ticket when reopen_conversation was disabled 2023-12-12 16:08:09 -03:00
Davidson Gomes
c296bf4178 fix: Fixed problem that did not generate qrcode with the chatwoot_conversation_pending option enabled 2023-12-12 15:46:10 -03:00
Davidson Gomes
1568554a1c feat: added reply, delete and message reaction in chatwoot v3.3.1 2023-12-12 15:24:54 -03:00
Davidson Gomes
ae66be197e feat: added reply, delete and message reaction in chatwoot v3.3.1 2023-12-12 15:16:09 -03:00
Davidson Gomes
3e904aa160 fix: correction of chatwoot functioning with admin flows 2023-12-12 13:56:47 -03:00
Davidson Gomes
64c1440c46 fix: correction of chatwoot functioning with admin flows 2023-12-12 13:56:41 -03:00
Davidson Gomes
f069a41390 fix: added restart instance when update profile pricture 2023-12-11 18:17:18 -03:00
Davidson Gomes
d4a33e2290 fix: added restart instance when update profile pricture 2023-12-11 18:16:11 -03:00
Davidson Gomes
7ee5bcecff fix: added restart instance when update profile pricture 2023-12-11 18:15:24 -03:00
Davidson Gomes
48f6ee8846 feat: added auto_create to the chatwoot set to create the inbox automatically or not 2023-12-11 17:32:19 -03:00
Davidson Gomes
7a24f52782 feat: added auto_create to the chatwoot set to create the inbox automatically or not 2023-12-11 17:32:07 -03:00
Davidson Gomes
324d46120b feat: new manager 2023-12-11 15:31:43 -03:00
Davidson Gomes
87baec5ff8 Merge pull request #250 from gabrielpastori1/manager
Add Manager
2023-12-11 15:27:43 -03:00
Gabriel Pastori
b2e144f35c add manager 2023-12-11 15:06:42 -03:00
Davidson Gomes
87a8e25662 Merge pull request #249 from gabrielpastori1/typebot-auto-create-session
fix: only create if is paused
2023-12-11 12:57:51 -03:00
Gabriel Pastori
8e88f00fb2 fix: only create if is paused 2023-12-11 12:42:09 -03:00
Davidson Gomes
d14505d59a Merge pull request #248 from gabrielpastori1/fix-chatwoot-find
Fix chatwoot find
2023-12-11 12:32:19 -03:00
Davidson Gomes
1e6d4347fa Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-12-11 12:09:08 -03:00
Davidson Gomes
b8d9a8c072 fix: adjusted return from queries in mongodb 2023-12-11 12:08:58 -03:00
Gabriel Pastori
fd15ae5e8c Fix condition to check for empty result object 2023-12-11 12:00:33 -03:00
Davidson Gomes
fb6377414b Merge pull request #237 from gabrielpastori1/add-send-presence
Add sendPresence
2023-12-11 10:47:26 -03:00
Davidson Gomes
9a5dbe055e fix: adjusts in connection 2023-12-08 18:44:52 -03:00
Gabriel Pastori
42dd280aca change sendPresence from sendMessage to chat 2023-12-08 18:40:48 -03:00
Davidson Gomes
41b2946cdc Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-12-06 15:22:08 -03:00
Davidson Gomes
a90f0f2c59 Fixes in mongodb and chatwoot 2023-12-06 15:21:50 -03:00
Gabriel Pastori
4222c0e53b Fix logger message in sendPresence route 2023-12-06 00:32:27 -03:00
Gabriel Pastori
ee0f0f0be0 add send presence router 2023-12-06 00:31:35 -03:00
Davidson Gomes
f8d874453c Merge pull request #233 from gabrielpastori1/typebot-auto-create-session
Add session creation for typebot service
2023-12-05 18:49:31 -03:00
Gabriel Pastori
aa891489f0 Add session creation for typebot service 2023-12-03 21:00:26 -03:00
Davidson Gomes
4c69b059d4 proxy 2023-12-01 21:24:38 -03:00
Davidson Gomes
359bd9f762 fix: adjusts in proxy 2023-12-01 20:09:22 -03:00
Davidson Gomes
2de0b61726 fix: adjusts in proxy 2023-12-01 20:09:19 -03:00
Davidson Gomes
d75163aa57 test: adjusts wasocket 2023-12-01 15:04:57 -03:00
Davidson Gomes
e49f30641e test: adjusts wasocket 2023-12-01 14:30:20 -03:00
Davidson Gomes
1631c2c342 test: adjusts wasocket 2023-12-01 09:42:56 -03:00
Davidson Gomes
cf7de369b2 test: adjusts wasocket 2023-12-01 08:22:40 -03:00
Davidson Gomes
78c03d8f2f test: adjusts wasocket 2023-12-01 08:21:36 -03:00
Davidson Gomes
876320b849 feat: added compatibility with typebot v2 2023-11-30 17:13:05 -03:00
Davidson Gomes
a1d13f8ff3 changelog 2023-11-30 07:12:14 -03:00
Davidson Gomes
3c19bdfaa9 fix: Removed await from webhook when sending a message 2023-11-30 07:09:11 -03:00
Davidson Gomes
4fa895086e fix: adjusts in validation to messages.upsert 2023-11-30 07:02:25 -03:00
Davidson Gomes
9945d8debb Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-11-30 07:01:48 -03:00
Davidson Gomes
4362de2198 fix: adjusts in validation to messages.upsert 2023-11-30 07:01:26 -03:00
Davidson Gomes
a5c5879e4f fix: adjusts in validation to messages.upsert 2023-11-30 07:01:08 -03:00
Davidson Gomes
1a57f4f33d Merge pull request #215 from suissa/patch-3
Update typebot.service.ts - element.underline change ~ for *
2023-11-29 08:47:52 -03:00
Davidson Gomes
a99e173168 Merge pull request #224 from jaison-x/pr
fix: workaround to manage param data as an array in mongodb
2023-11-29 08:46:12 -03:00
Davidson Gomes
e02a28f61e Merge pull request #228 from raimartinsb/develop
fix messages not received: error handling when updating contact
2023-11-29 08:45:24 -03:00
raimartinsb
26d3ff97ce fix messages not received: error handling when updating contact 2023-11-28 11:10:29 -03:00
Davidson Gomes
57fb3c9785 fix: fixed lids messages 2023-11-27 19:58:23 -03:00
Davidson Gomes
edeb970a82 fix: fixed lids messages 2023-11-27 19:57:59 -03:00
jaison-x
e17baddf01 fix: workaround to manage param data as an array in mongodb
When saving data from a group session (sender-key-xxxxx@g.us::xxxxx::xx) we receive the data param as an array.
In mongodb we can't save an array as a root document. Bacause this, in this case we save the array in a property called content_array.
2023-11-24 18:59:19 -03:00
Davidson Gomes
a277d36696 feat: sqs 2023-11-20 17:53:29 -03:00
Davidson Gomes
6c9e86e17a feat: sqs 2023-11-20 17:52:36 -03:00
Davidson Gomes
e75ef21eb6 Merge pull request #216 from craines/main
fix: Removed await from webhook when sending a message
2023-11-18 16:38:32 -03:00
craines
04e5443b82 fix: send reaction 2023-11-15 21:52:56 -03:00
craines
8b4cdf3b9b fix: Removed await from webhook when sending a message 2023-11-14 16:47:25 -03:00
Jean Carlo Nascimento
37f1620f7c Update typebot.service.ts - element.underline change ~ for *
There's no underline in WhatsApp ~ use strikethrough instead, I switched to * bold.
2023-11-12 19:11:47 -03:00
Davidson Gomes
b0a0e805cf Merge pull request #187 from jaison-x/deleting-instances
Deleting instances
2023-11-09 14:41:43 -03:00
Davidson Gomes
c619e253a2 Merge pull request #198 from gabrielpastori1/fix-typebot-error-handle
Handle erros in Typebot
2023-11-09 14:32:30 -03:00
Davidson Gomes
2bd111f1e2 Merge pull request #197 from vitorogen/develop
Handle optional chaining for 'settings.msg_call', this change prevent…
2023-11-05 19:44:27 -03:00
Davidson Gomes
40174b50eb Merge pull request #190 from w3nder/develop
fix: size of group participants
2023-11-05 19:43:45 -03:00
Davidson Gomes
e0fe28717f Merge pull request #186 from jaison-x/adjust-mongo-deletion
-> Adjusting function cleaningStoreFiles to remove itens from missing…
2023-11-05 19:42:24 -03:00
Gabriel Pastori
ac5fc22043 try catch entire sendTypebot 2023-10-30 18:05:18 -03:00
Gabriel Pastori
e30f196dad remove duplicate 2023-10-29 13:22:41 -03:00
Gabriel Pastori
9e4e1ce8ec Merge branch 'fix-typebot-error-handle' of https://github.com/gabrielpastori1/ticketme-evolution-api into fix-typebot-error-handle 2023-10-29 13:01:37 -03:00
Gabriel Pastori
f710898844 fix: in error catch in createNewSession 2023-10-29 12:59:29 -03:00
Gabriel Pastori
94633484ca Merge branch 'develop' into fix-typebot-error-handle 2023-10-29 12:54:05 -03:00
Gabriel Pastori
0817c2589f expand try catch for all function 2023-10-29 12:49:26 -03:00
Gabriel Pastori
8a99386b33 handle erros 2023-10-28 21:21:12 -03:00
Vitor
52d6a563d6 Handle optional chaining for 'settings.msg_call', this change prevents 'TypeError: Cannot read properties of undefined' errors when 'msg_call' is undefined. 2023-10-27 22:49:57 -03:00
Davidson Gomes
d0a5ae1da4 fix: Removed senderKeyDistributionMessage 2023-10-26 18:01:17 -03:00
Davidson Gomes
783c00a1d9 fix: Added mimetype field when sending media 2023-10-25 10:29:24 -03:00
Davidson Gomes
bc70ec8b07 fix: start session 2023-10-25 08:19:44 -03:00
Davidson Gomes
50e1efe5d7 fix: start session 2023-10-25 07:00:58 -03:00
Davidson Gomes
d8629e53f1 fix: start session 2023-10-24 13:16:35 -03:00
Davidson Gomes
cc9df1dabb fix: start session 2023-10-24 12:06:20 -03:00
Davidson Gomes
cd6cb8182e fix: start session 2023-10-24 12:05:58 -03:00
Wender Teixeira
5b0e90e5b9 Update whatsapp.service.ts 2023-10-19 22:41:16 -03:00
jaison-x
3c3bbc84b3 -> When deleting a instance in connecting state, the event remove.instance was not triggered; 2023-10-18 19:52:33 -03:00
Jaison
23615cff4f -> Adjusting function cleaningStoreFiles to remove itens from missing mongo collections;
-> AuthModel was using a wrong filter;
2023-10-18 18:57:11 -03:00
Davidson Gomes
daadc6cb68 fix: adjusts in proxy 2023-10-18 18:09:26 -03:00
Davidson Gomes
e157a2a36b fix: adjusts in proxy 2023-10-18 18:09:01 -03:00
Davidson Gomes
d8d7debfee fix: lid messages 2023-10-11 17:27:49 -03:00
Davidson Gomes
a82b206fe6 test: messages.upsert 2023-10-10 10:00:11 -03:00
Davidson Gomes
a4416214c8 Merge tag '1.5.4' into develop
* Baileys logger typing issue resolved
* Solved problem with duplicate messages in chatwoot
2023-10-09 20:43:50 -03:00
108 changed files with 10015 additions and 3344 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,38 @@
---
name: "[EN] Bug report"
about: Create a report to help us improve
title: "[EN][BUG]"
labels: bug
assignees: ''
---
### Title: [Brief Description of the Bug]
#### Description:
Describe in detail the problem you encountered. Include any relevant context that may help understand the origin of the bug.
#### Steps to Reproduce:
1. List the steps necessary to reproduce the problem.
2. Try to be as specific as possible.
3. If the problem occurs in a specific scenario, describe it here.
#### Expected Behavior:
Describe what you expected to happen when following the steps above.
#### Current Behavior:
Explain what actually happens when you follow the steps above.
#### Screenshots/Videos:
If possible, add screenshots or videos illustrating the problem. This can be extremely helpful in understanding the issue.
#### Environment:
- **Server:** [e.g., Ubuntu 18.04]
- **API Version:** [e.g., 1.5.4]
- **Other Hardware/Software Specifications:** [e.g., CPU, GPU]
#### Submitting Logs:
Please attach logs that may be related to the problem. If the logs contain sensitive information, consider sending them privately to one of the project maintainers.
#### Additional Notes:
Include here any other information that you think might be useful in understanding or resolving the bug.

View File

@@ -0,0 +1,28 @@
---
name: "[EN] Feature request"
about: Suggest an idea for the API
title: "[EN][FEAT]"
labels: enhancement
assignees: ''
---
### Title: [Brief Description of Feature Request]
#### Detailed Description:
Clearly and in detail, describe the functionality you wish to be implemented. Explain how this fits into the context of the project.
#### Rationale:
Explain why this functionality would be useful for the project. This helps in understanding the importance and priority of the request.
#### Usage Examples:
Provide specific examples of how this feature could be used. This can include scenarios or use cases where the feature would be particularly beneficial.
#### Possible Implementations:
If you have ideas on how this feature might be implemented, please share them here. This is not mandatory but can be helpful for the development team.
#### Impact on the Project:
Discuss how this new feature could impact other parts of the project, if applicable.
#### Additional Notes:
Any other information you believe is relevant to your request.

View File

@@ -0,0 +1,38 @@
---
name: "[PT] Reportar bug"
about: Reportar um problema
title: "[PT][BUG]"
labels: bug
assignees: ''
---
### Título: [Breve Descrição do Bug]
#### Descrição:
Descreva detalhadamente o problema que você encontrou. Inclua qualquer contexto relevante que possa ajudar a entender a origem do bug.
#### Passos para Reproduzir:
1. Liste os passos necessários para reproduzir o problema.
2. Tente ser o mais específico possível.
3. Se o problema ocorrer em um cenário específico, descreva-o aqui.
#### Comportamento Esperado:
Descreva o que você esperava que acontecesse quando seguisse os passos acima.
#### Comportamento Atual:
Explique o que realmente acontece quando você segue os passos acima.
#### Capturas de Tela/Vídeos:
Se possível, adicione capturas de tela ou vídeos que ilustrem o problema. Isso pode ser extremamente útil para entender o problema.
#### Ambiente:
- **Servidor:** [ex: Ubuntu 18.04]
- **Versão da API:** [ex: 1.5.4]
- **Outras Especificações de Hardware/Software:** [ex: CPU, GPU]
#### Envio de Logs:
Por favor, anexe os logs que possam estar relacionados ao problema. Se os logs contiverem informações sensíveis, considere enviá-los de forma privada para um dos mantenedores do projeto.
#### Notas Adicionais:
Inclua aqui qualquer outra informação que você ache que possa ser útil para entender ou resolver o bug.

View File

@@ -0,0 +1,28 @@
---
name: "[PT] Solicitar recurso"
about: Sugira novos recursos para a API
title: "[PT][FEAT]"
labels: enhancement
assignees: ''
---
### Título: [Breve Descrição da Solicitação de Recurso]
#### Descrição Detalhada:
Descreva claramente e em detalhes a funcionalidade que você deseja que seja implementada. Explique como isso se encaixa no contexto do projeto.
#### Racional:
Explique por que essa funcionalidade seria útil para o projeto. Isso ajuda a entender a importância e a prioridade da solicitação.
#### Exemplos de Uso:
Forneça exemplos específicos de como essa funcionalidade poderia ser utilizada. Isso pode incluir cenários ou casos de uso onde a funcionalidade seria particularmente benéfica.
#### Possíveis Implementações:
Se você tem ideias sobre como essa funcionalidade pode ser implementada, por favor, compartilhe-as aqui. Isso não é obrigatório, mas pode ser útil para a equipe de desenvolvimento.
#### Impacto no Projeto:
Discuta como essa nova funcionalidade poderia impactar outras partes do projeto, se aplicável.
#### Notas Adicionais:
Qualquer outra informação que você acredita ser relevante para a sua solicitação.

View File

@@ -0,0 +1,64 @@
name: Build Docker image
on:
push:
tags: ['v*']
jobs:
build-amd:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Extract existing image metadata
id: image-meta
uses: docker/metadata-action@v4
with:
images: atendai/evolution-api
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push AMD image
uses: docker/build-push-action@v4
with:
context: .
labels: ${{ steps.image-meta.outputs.labels }}
platforms: linux/amd64
push: true
build-arm:
runs-on: buildjet-4vcpu-ubuntu-2204-arm
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Extract existing image metadata
id: image-meta
uses: docker/metadata-action@v4
with:
images: atendai/evolution-api
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push ARM image
uses: docker/build-push-action@v4
with:
context: .
labels: ${{ steps.image-meta.outputs.labels }}
platforms: linux/arm64
push: true

4
.gitignore vendored
View File

@@ -4,6 +4,8 @@
/Docker/.env
.vscode
# Logs
logs/**.json
*.log
@@ -39,8 +41,10 @@ docker-compose.yaml
/test/
/src/env.yml
/store
*.env
/temp/*
.DS_Store
*.DS_Store
.tool-versions

11
.vscode/settings.json vendored
View File

@@ -5,7 +5,12 @@
"editor.smoothScrolling": true,
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll": true
}
"source.fixAll.eslint": "explicit",
"source.fixAll": "explicit"
},
"prisma-smart-formatter.typescript.defaultFormatter": "esbenp.prettier-vscode",
"prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma",
"i18n-ally.localesPaths": [
"store/messages"
]
}

View File

@@ -1,3 +1,100 @@
# 1.7.0 (2024-03-11 18:23)
### Feature
* Added update message endpoint
* Add translate capabilities to QRMessages in CW
* Join in Group by Invite Code
* Read messages from whatsapp in chatwoot
* Add support to use use redis in cacheservice
* Add support for labels
* Command to clearcache from chatwoot inbox
* Whatsapp Cloud API Oficial
### Fixed
* Proxy configuration improvements
* Correction in sending lists
* Adjust in webhook_base64
* Correction in typebot text formatting
* Correction in chatwoot text formatting and render list message
* Only use a axios request to get file mimetype if necessary
* When possible use the original file extension
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
* Remove message ids cache in chatwoot to use chatwoot's api itself
* Adjusts the quoted message, now has contextInfo in the message Raw
* Collecting responses with text or numbers in Typebot
* Added sendList endpoint to swagger documentation
* Implemented a function to synchronize message deletions on WhatsApp, automatically reflecting in Chatwoot.
* Improvement on numbers validation
* Fix polls in message sending
* Sending status message
* Message 'connection successfully' spamming
* Invalidate the conversation cache if reopen_conversation is false and the conversation was resolved
* Fix looping when deleting a message in chatwoot
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
* Correction in the sendList Function
* Implement contact upsert in messaging-history.set
* Improve proxy error handling
* Refactor fetching participants for group in WhatsApp service
* Fixed problem where the typebot final keyword did not work
* Typebot's wait now pauses the flow and composing is defined by the delay_message parameter in set typebot
* Composing over 20s now loops until finished
# 1.6.1 (2023-12-22 11:43)
### Fixed
* Fixed Lid Messages
* Fixed sending variables to typebot
* Fixed sending variables from typebot
* Correction sending s3/minio media to chatwoot and typebot
* Fixed the problem with typebot closing at the end of the flow, now this is optional with the TYPEBOT_KEEP_OPEN variable
* Fixed chatwoot Bold, Italic and Underline formatting using Regex
* Added the sign_delimiter property to the Chatwoot configuration, allowing you to set a different delimiter for the signature. Default when not defined \n
* Include instance Id field in the instance configuration
* Fixed the pairing code
* Adjusts in typebot
* Fix the problem when disconnecting the instance and connecting again using mongodb
* Options to disable docs and manager
* When deleting a message in whatsapp, delete the message in chatwoot too
# 1.6.0 (2023-12-12 17:24)
### Feature
* Added AWS SQS Integration
* Added support for new typebot API
* Added endpoint sendPresence
* New Instance Manager
* Added auto_create to the chatwoot set to create the inbox automatically or not
* Added reply, delete and message reaction in chatwoot v3.3.1
### Fixed
* Adjusts in proxy
* Adjusts in start session for Typebot
* Added mimetype field when sending media
* Ajusts in validations to messages.upsert
* Fixed messages not received: error handling when updating contact in chatwoot
* Fix workaround to manage param data as an array in mongodb
* Removed await from webhook when sending a message
* Update typebot.service.ts - element.underline change ~ for *
* Removed api restart on receiving an error
* Fixes in mongodb and chatwoot
* Adjusted return from queries in mongodb
* Added restart instance when update profile picture
* Correction of chatwoot functioning with admin flows
* Fixed problem that did not generate qrcode with the chatwoot_conversation_pending option enabled
* Fixed issue where CSAT opened a new ticket when reopen_conversation was disabled
* Fixed issue sending contact to Chatwoot via iOS
### Integrations
* Chatwoot: v3.3.1
* Typebot: v2.20.0
# 1.5.4 (2023-10-09 20:43)
### Fixed
@@ -75,9 +172,9 @@
### Integrations
- Chatwoot: v2.18.0 - v3.0.0
- Typebot: v2.16.0
- Manager Evolution API
* Chatwoot: v2.18.0 - v3.0.0
* Typebot: v2.16.0
* Manager Evolution API
# 1.4.8 (2023-07-27 10:27)
@@ -165,7 +262,7 @@
### Integrations
- Chatwoot: v2.18.0 - v3.0.0 (Beta)
* Chatwoot: v2.18.0 - v3.0.0 (Beta)
# 1.3.2 (2023-07-21 17:19)
@@ -181,7 +278,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.3.1 (2023-07-20 07:48)
@@ -191,7 +288,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.3.0 (2023-07-19 11:33)
@@ -228,7 +325,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.2.2 (2023-07-15 09:36)
@@ -239,7 +336,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.2.1 (2023-07-14 19:04)

View File

@@ -16,6 +16,7 @@ LOG_BAILEYS=error
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE=false
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
# Temporary data storage
STORE_MESSAGES=true
@@ -47,10 +48,23 @@ REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evdocker
RABBITMQ_ENABLED=false
RABBITMQ_RABBITMQ_MODE=global
RABBITMQ_EXCHANGE_NAME=evolution_exchange
RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
WEBSOCKET_ENABLED=false
WA_BUSINESS_TOKEN_WEBHOOK=evolution
WA_BUSINESS_URL=https://graph.facebook.com
WA_BUSINESS_VERSION=v18.0
WA_BUSINESS_LANGUAGE=pt_BR
SQS_ENABLED=false
SQS_ACCESS_KEY_ID=
SQS_SECRET_ACCESS_KEY=
SQS_ACCOUNT_ID=
SQS_REGION=
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
## Define a global webhook that will listen for enabled events from all instances
@@ -78,6 +92,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
WEBHOOK_EVENTS_GROUPS_UPDATE=true
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
WEBHOOK_EVENTS_LABELS_EDIT=true
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
WEBHOOK_EVENTS_CALL=true
# This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
@@ -99,6 +115,19 @@ CONFIG_SESSION_PHONE_NAME=Chrome
QRCODE_LIMIT=30
QRCODE_COLOR=#198754
# old | latest
TYPEBOT_API_VERSION=latest
TYPEBOT_KEEP_OPEN=false
#Chatwoot
# If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot.
CHATWOOT_MESSAGE_DELETE=false # false | true
# If you leave this option as true, when sending a message in Chatwoot, the client's last message will be marked as read on WhatsApp.
CHATWOOT_MESSAGE_READ=false # false | true
# This db connection is used to import messages from whatsapp to chatwoot database
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgres://user:password@hostname:port/dbname
CHATWOOT_IMPORT_DATABASE_PLACEHOLDER_MEDIA_MESSAGE=true
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
@@ -112,3 +141,5 @@ AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
# seconds - 3600s ===1h | zero (0) - never expires
AUTHENTICATION_JWT_EXPIRIN_IN=0
AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`'
LANGUAGE=en # pt-BR, en

View File

@@ -4,7 +4,7 @@ services:
api:
container_name: evolution_api
image: davidsongomes/evolution-api
image: atendai/evolution-api
restart: always
ports:
- 8080:8080

View File

@@ -16,6 +16,7 @@ LOG_BAILEYS=error
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE=false
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
# Temporary data storage
STORE_MESSAGES=true
@@ -73,6 +74,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
WEBHOOK_EVENTS_GROUPS_UPDATE=true
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
WEBHOOK_EVENTS_LABELS_EDIT=true
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
# This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false

View File

@@ -62,7 +62,7 @@ services:
api:
container_name: evolution_api
image: davidsongomes/evolution-api
image: atendai/evolution-api
restart: always
depends_on:
- mongodb

View File

@@ -1,6 +1,6 @@
FROM node:20.7.0-alpine
FROM node:20.7.0-alpine AS builder
LABEL version="1.5.4" description="Api to control whatsapp features through http requests."
LABEL version="1.7.0" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com"
@@ -11,9 +11,19 @@ WORKDIR /evolution
COPY ./package.json .
RUN npm install
COPY . .
RUN npm run build
FROM node:20.7.0-alpine AS final
ENV TZ=America/Sao_Paulo
ENV DOCKER_ENV=true
ENV SERVER_TYPE=http
ENV SERVER_PORT=8080
ENV SERVER_URL=http://localhost:8080
ENV CORS_ORIGIN=*
@@ -25,6 +35,7 @@ ENV LOG_COLOR=true
ENV LOG_BAILEYS=error
ENV DEL_INSTANCE=false
ENV DEL_TEMP_INSTANCES=true
ENV STORE_MESSAGES=true
ENV STORE_MESSAGE_UP=true
@@ -52,16 +63,31 @@ ENV REDIS_URI=redis://redis:6379
ENV REDIS_PREFIX_KEY=evolution
ENV RABBITMQ_ENABLED=false
ENV RABBITMQ_MODE=global
ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange
ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
ENV WEBSOCKET_ENABLED=false
ENV WA_BUSINESS_TOKEN_WEBHOOK=evolution
ENV WA_BUSINESS_URL=https://graph.facebook.com
ENV WA_BUSINESS_VERSION=v18.0
ENV WA_BUSINESS_LANGUAGE=pt_BR
ENV SQS_ENABLED=false
ENV SQS_ACCESS_KEY_ID=
ENV SQS_SECRET_ACCESS_KEY=
ENV SQS_ACCOUNT_ID=
ENV SQS_REGION=
ENV WEBHOOK_GLOBAL_URL=
ENV WEBHOOK_GLOBAL_ENABLED=false
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=false
ENV WEBHOOK_EVENTS_INSTANCE_CREATE=false
ENV WEBHOOK_EVENTS_INSTANCE_DELETE=false
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=true
ENV WEBHOOK_EVENTS_MESSAGES_SET=true
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=true
@@ -80,6 +106,8 @@ ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true
ENV WEBHOOK_EVENTS_LABELS_EDIT=true
ENV WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
ENV WEBHOOK_EVENTS_CALL=true
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
@@ -98,6 +126,8 @@ ENV CONFIG_SESSION_PHONE_NAME=Chrome
ENV QRCODE_LIMIT=30
ENV QRCODE_COLOR=#198754
ENV TYPEBOT_API_VERSION=latest
ENV AUTHENTICATION_TYPE=apikey
ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976
@@ -114,10 +144,8 @@ ENV AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
ENV AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
ENV AUTHENTICATION_INSTANCE_CHATWOOT_URL=<url>
RUN npm install
WORKDIR /evolution
COPY . .
RUN npm run build
COPY --from=builder /evolution .
CMD [ "node", "./dist/src/main.js" ]

View File

@@ -39,12 +39,13 @@ This code was produced based on the baileys library and it is still under develo
</a>
</div>
#### Buy me coffe
#### Buy me coffe - PIX
<div align="center">
<a href="https://bmc.link/evolutionapi" target="_blank" rel="noopener noreferrer">
<img src="./public/images/bmc_qr.png" style="width: 50% !important;">
<img src="./public/images/qrcode-pix.png" style="width: 50% !important;">
</a>
<p><b>CHAVE PIX (Telefone):</b> (74)99987-9409</p>
</div>
</br>

View File

@@ -3,7 +3,7 @@ version: '3.3'
services:
api:
container_name: evolution_api
image: davidsongomes/evolution-api:latest
image: atendai/evolution-api:latest
restart: always
ports:
- 8080:8080

View File

@@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "1.5.4",
"version": "1.7.0",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js",
"scripts": {
@@ -46,40 +46,47 @@
"@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "^6.5.0",
"@whiskeysockets/baileys": "6.6.0",
"amqplib": "^0.10.3",
"axios": "^1.3.5",
"class-validator": "^0.13.2",
"aws-sdk": "^2.1499.0",
"axios": "^1.6.5",
"class-validator": "^0.14.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"dayjs": "^1.11.7",
"eventemitter2": "^6.4.9",
"evolution-manager": "^0.4.13",
"exiftool-vendored": "^22.0.0",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"form-data": "^4.0.0",
"hbs": "^4.2.0",
"https-proxy-agent": "^7.0.2",
"i18next": "^23.7.19",
"jimp": "^0.16.13",
"join": "^3.0.0",
"js-yaml": "^4.1.0",
"jsonschema": "^1.4.1",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.10.39",
"link-preview-js": "^3.0.4",
"mongoose": "^6.10.5",
"node-cache": "^5.1.2",
"node-mime-types": "^1.1.0",
"node-windows": "^1.0.0-beta.8",
"parse-bmfont-xml": "^1.1.4",
"pg": "^8.11.3",
"pino": "^8.11.0",
"proxy-agent": "^6.3.0",
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"redis": "^4.6.5",
"sharp": "^0.30.7",
"sharp": "^0.32.2",
"socket.io": "^4.7.1",
"socks-proxy-agent": "^8.0.1",
"swagger-ui-express": "^5.0.0",
"uuid": "^9.0.0",
"xml2js": "^0.6.2",
"yamljs": "^0.3.0"
},
"devDependencies": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -3,7 +3,13 @@ import { readFileSync } from 'fs';
import { load } from 'js-yaml';
import { join } from 'path';
export type HttpServer = { TYPE: 'http' | 'https'; PORT: number; URL: string };
export type HttpServer = {
TYPE: 'http' | 'https';
PORT: number;
URL: string;
DISABLE_DOCS: boolean;
DISABLE_MANAGER: boolean;
};
export type HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE';
export type Cors = {
@@ -28,6 +34,7 @@ export type SaveData = {
MESSAGE_UPDATE: boolean;
CONTACTS: boolean;
CHATS: boolean;
LABELS: boolean;
};
export type StoreConf = {
@@ -35,6 +42,7 @@ export type StoreConf = {
MESSAGE_UP: boolean;
CONTACTS: boolean;
CHATS: boolean;
LABELS: boolean;
};
export type CleanStoreConf = {
@@ -63,19 +71,34 @@ export type Redis = {
export type Rabbitmq = {
ENABLED: boolean;
MODE: string; // global, single, isolated
EXCHANGE_NAME: string; // available for global and single, isolated mode will use instance name as exchange
URI: string;
};
export type Sqs = {
ENABLED: boolean;
ACCESS_KEY_ID: string;
SECRET_ACCESS_KEY: string;
ACCOUNT_ID: string;
REGION: string;
};
export type Websocket = {
ENABLED: boolean;
};
export type Chatwoot = {
USE_REPLY_ID: boolean;
export type WaBusiness = {
TOKEN_WEBHOOK: string;
URL: string;
VERSION: string;
LANGUAGE: string;
};
export type EventsWebhook = {
APPLICATION_STARTUP: boolean;
INSTANCE_CREATE: boolean;
INSTANCE_DELETE: boolean;
QRCODE_UPDATED: boolean;
MESSAGES_SET: boolean;
MESSAGES_UPSERT: boolean;
@@ -91,6 +114,8 @@ export type EventsWebhook = {
CHATS_DELETE: boolean;
CHATS_UPSERT: boolean;
CONNECTION_UPDATE: boolean;
LABELS_EDIT: boolean;
LABELS_ASSOCIATION: boolean;
GROUPS_UPSERT: boolean;
GROUP_UPDATE: boolean;
GROUP_PARTICIPANTS_UPDATE: boolean;
@@ -115,15 +140,42 @@ export type Auth = {
export type DelInstance = number | boolean;
export type Language = string | 'en';
export type GlobalWebhook = {
URL: string;
ENABLED: boolean;
WEBHOOK_BY_EVENTS: boolean;
};
export type CacheConfRedis = {
ENABLED: boolean;
URI: string;
PREFIX_KEY: string;
TTL: number;
};
export type CacheConfLocal = {
ENABLED: boolean;
TTL: number;
};
export type SslConf = { PRIVKEY: string; FULLCHAIN: string };
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
export type QrCode = { LIMIT: number; COLOR: string };
export type Typebot = { API_VERSION: string; KEEP_OPEN: boolean };
export type Chatwoot = {
MESSAGE_DELETE: boolean;
MESSAGE_READ: boolean;
IMPORT: {
DATABASE: {
CONNECTION: {
URI: string;
};
};
PLACEHOLDER_MEDIA_MESSAGE: boolean;
};
};
export type CacheConf = { REDIS: CacheConfRedis; LOCAL: CacheConfLocal };
export type Production = boolean;
export interface Env {
@@ -135,15 +187,21 @@ export interface Env {
DATABASE: Database;
REDIS: Redis;
RABBITMQ: Rabbitmq;
SQS: Sqs;
WEBSOCKET: Websocket;
WA_BUSINESS: WaBusiness;
LOG: Log;
DEL_INSTANCE: DelInstance;
DEL_TEMP_INSTANCES: boolean;
LANGUAGE: Language;
WEBHOOK: Webhook;
CONFIG_SESSION_PHONE: ConfigSessionPhone;
QRCODE: QrCode;
TYPEBOT: Typebot;
CHATWOOT: Chatwoot;
CACHE: CacheConf;
AUTHENTICATION: Auth;
PRODUCTION?: Production;
CHATWOOT?: Chatwoot;
}
export type Key = keyof Env;
@@ -163,8 +221,8 @@ export class ConfigService {
this.env = !(process.env?.DOCKER_ENV === 'true') ? this.envYaml() : this.envProcess();
this.env.PRODUCTION = process.env?.NODE_ENV === 'PROD';
if (process.env?.DOCKER_ENV === 'true') {
this.env.SERVER.TYPE = 'http';
this.env.SERVER.PORT = 8080;
this.env.SERVER.TYPE = process.env.SERVER_TYPE as 'http' | 'http';
this.env.SERVER.PORT = Number.parseInt(process.env.SERVER_PORT) || 8080;
}
}
@@ -175,9 +233,11 @@ export class ConfigService {
private envProcess(): Env {
return {
SERVER: {
TYPE: process.env.SERVER_TYPE as 'http' | 'https',
TYPE: (process.env.SERVER_TYPE as 'http' | 'https') || 'http',
PORT: Number.parseInt(process.env.SERVER_PORT) || 8080,
URL: process.env.SERVER_URL,
DISABLE_DOCS: process.env?.SERVER_DISABLE_DOCS === 'true',
DISABLE_MANAGER: process.env?.SERVER_DISABLE_MANAGER === 'true',
},
CORS: {
ORIGIN: process.env.CORS_ORIGIN.split(',') || ['*'],
@@ -193,6 +253,7 @@ export class ConfigService {
MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true',
CONTACTS: process.env?.STORE_CONTACTS === 'true',
CHATS: process.env?.STORE_CHATS === 'true',
LABELS: process.env?.STORE_LABELS === 'true',
},
CLEAN_STORE: {
CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL)
@@ -215,6 +276,7 @@ export class ConfigService {
MESSAGE_UPDATE: process.env?.DATABASE_SAVE_MESSAGE_UPDATE === 'true',
CONTACTS: process.env?.DATABASE_SAVE_DATA_CONTACTS === 'true',
CHATS: process.env?.DATABASE_SAVE_DATA_CHATS === 'true',
LABELS: process.env?.DATABASE_SAVE_DATA_LABELS === 'true',
},
},
REDIS: {
@@ -224,11 +286,26 @@ export class ConfigService {
},
RABBITMQ: {
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
MODE: process.env?.RABBITMQ_MODE || 'isolated',
EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange',
URI: process.env.RABBITMQ_URI || '',
},
SQS: {
ENABLED: process.env?.SQS_ENABLED === 'true',
ACCESS_KEY_ID: process.env.SQS_ACCESS_KEY_ID || '',
SECRET_ACCESS_KEY: process.env.SQS_SECRET_ACCESS_KEY || '',
ACCOUNT_ID: process.env.SQS_ACCOUNT_ID || '',
REGION: process.env.SQS_REGION || '',
},
WEBSOCKET: {
ENABLED: process.env?.WEBSOCKET_ENABLED === 'true',
},
WA_BUSINESS: {
TOKEN_WEBHOOK: process.env.WA_BUSINESS_TOKEN_WEBHOOK || '',
URL: process.env.WA_BUSINESS_URL || '',
VERSION: process.env.WA_BUSINESS_VERSION || '',
LANGUAGE: process.env.WA_BUSINESS_LANGUAGE || 'en',
},
LOG: {
LEVEL: (process.env?.LOG_LEVEL.split(',') as LogLevel[]) || [
'ERROR',
@@ -246,6 +323,10 @@ export class ConfigService {
DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE)
? process.env.DEL_INSTANCE === 'true'
: Number.parseInt(process.env.DEL_INSTANCE) || false,
DEL_TEMP_INSTANCES: isBooleanString(process.env?.DEL_TEMP_INSTANCES)
? process.env.DEL_TEMP_INSTANCES === 'true'
: true,
LANGUAGE: process.env?.LANGUAGE || 'en',
WEBHOOK: {
GLOBAL: {
URL: process.env?.WEBHOOK_GLOBAL_URL || '',
@@ -254,6 +335,8 @@ export class ConfigService {
},
EVENTS: {
APPLICATION_STARTUP: process.env?.WEBHOOK_EVENTS_APPLICATION_STARTUP === 'true',
INSTANCE_CREATE: process.env?.WEBHOOK_EVENTS_INSTANCE_CREATE === 'true',
INSTANCE_DELETE: process.env?.WEBHOOK_EVENTS_INSTANCE_DELETE === 'true',
QRCODE_UPDATED: process.env?.WEBHOOK_EVENTS_QRCODE_UPDATED === 'true',
MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true',
MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true',
@@ -269,6 +352,8 @@ export class ConfigService {
CHATS_UPSERT: process.env?.WEBHOOK_EVENTS_CHATS_UPSERT === 'true',
CHATS_DELETE: process.env?.WEBHOOK_EVENTS_CHATS_DELETE === 'true',
CONNECTION_UPDATE: process.env?.WEBHOOK_EVENTS_CONNECTION_UPDATE === 'true',
LABELS_EDIT: process.env?.WEBHOOK_EVENTS_LABELS_EDIT === 'true',
LABELS_ASSOCIATION: process.env?.WEBHOOK_EVENTS_LABELS_ASSOCIATION === 'true',
GROUPS_UPSERT: process.env?.WEBHOOK_EVENTS_GROUPS_UPSERT === 'true',
GROUP_UPDATE: process.env?.WEBHOOK_EVENTS_GROUPS_UPDATE === 'true',
GROUP_PARTICIPANTS_UPDATE: process.env?.WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE === 'true',
@@ -289,6 +374,34 @@ export class ConfigService {
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
COLOR: process.env.QRCODE_COLOR || '#198754',
},
TYPEBOT: {
API_VERSION: process.env?.TYPEBOT_API_VERSION || 'old',
KEEP_OPEN: process.env.TYPEBOT_KEEP_OPEN === 'true',
},
CHATWOOT: {
MESSAGE_DELETE: process.env.CHATWOOT_MESSAGE_DELETE === 'false',
MESSAGE_READ: process.env.CHATWOOT_MESSAGE_READ === 'false',
IMPORT: {
DATABASE: {
CONNECTION: {
URI: process.env.CHATWOOT_IMPORT_DATABASE_CONNECTION_URI || '',
},
},
PLACEHOLDER_MEDIA_MESSAGE: process.env?.CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE === 'true',
},
},
CACHE: {
REDIS: {
ENABLED: process.env?.CACHE_REDIS_ENABLED === 'true',
URI: process.env?.CACHE_REDIS_URI || '',
PREFIX_KEY: process.env?.CACHE_REDIS_PREFIX_KEY || 'evolution-cache',
TTL: Number.parseInt(process.env?.CACHE_REDIS_TTL) || 604800,
},
LOCAL: {
ENABLED: process.env?.CACHE_LOCAL_ENABLED === 'true',
TTL: Number.parseInt(process.env?.CACHE_REDIS_TTL) || 86400,
},
},
AUTHENTICATION: {
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
API_KEY: {
@@ -302,9 +415,6 @@ export class ConfigService {
SECRET: process.env.AUTHENTICATION_JWT_SECRET || 'L=0YWt]b2w[WF>#>:&E`',
},
},
CHATWOOT: {
USE_REPLY_ID: process.env?.USE_REPLY_ID === 'true',
},
};
}
}

View File

@@ -8,7 +8,6 @@ export function onUnexpectedError() {
stderr: process.stderr.fd,
error,
});
process.exit(1);
});
process.on('unhandledRejection', (error, origin) => {
@@ -18,6 +17,5 @@ export function onUnexpectedError() {
stderr: process.stderr.fd,
error,
});
process.exit(1);
});
}

View File

@@ -9,6 +9,8 @@ SERVER:
TYPE: http # https
PORT: 8080 # 443
URL: localhost
DISABLE_MANAGER: false
DISABLE_DOCS: false
CORS:
ORIGIN:
@@ -45,6 +47,7 @@ LOG:
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE: false # or false
DEL_TEMP_INSTANCES: true # Delete instances with status closed on start
# Temporary data storage
STORE:
@@ -81,11 +84,26 @@ REDIS:
RABBITMQ:
ENABLED: false
MODE: "global"
EXCHANGE_NAME: "evolution_exchange"
URI: "amqp://guest:guest@localhost:5672"
SQS:
ENABLED: true
ACCESS_KEY_ID: ""
SECRET_ACCESS_KEY: ""
ACCOUNT_ID: ""
REGION: "us-east-1"
WEBSOCKET:
ENABLED: false
WA_BUSINESS:
TOKEN_WEBHOOK: evolution
URL: https://graph.facebook.com
VERSION: v18.0
LANGUAGE: pt_BR
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
WEBHOOK:
@@ -117,6 +135,8 @@ WEBHOOK:
GROUP_UPDATE: true
GROUP_PARTICIPANTS_UPDATE: true
CONNECTION_UPDATE: true
LABELS_EDIT: true
LABELS_ASSOCIATION: true
CALL: true
# This event fires every time a new token is requested via the refresh route
NEW_JWT_TOKEN: false
@@ -139,6 +159,33 @@ QRCODE:
LIMIT: 30
COLOR: "#198754"
TYPEBOT:
API_VERSION: "old" # old | latest
KEEP_OPEN: false
CHATWOOT:
# If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot.
MESSAGE_DELETE: true # false | true
# If you leave this option as true, when sending a message in Chatwoot, the client's last message will be marked as read on WhatsApp.
MESSAGE_READ: false # false | true
IMPORT:
# This db connection is used to import messages from whatsapp to chatwoot database
DATABASE:
CONNECTION:
URI: "postgres://user:password@hostname:port/dbname"
PLACEHOLDER_MEDIA_MESSAGE: true
# Cache to optimize application performance
CACHE:
REDIS:
ENABLED: false
URI: "redis://localhost:6379"
PREFIX_KEY: "evolution-cache"
TTL: 604800
LOCAL:
ENABLED: false
TTL: 86400
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
@@ -155,6 +202,4 @@ AUTHENTICATION:
EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires
SECRET: L=0YWt]b2w[WF>#>:&E`
# Configure to chatwoot
CHATWOOT:
USE_REPLY_ID: false
LANGUAGE: "pt-BR" # pt-BR, en

View File

@@ -25,7 +25,7 @@ info:
</font>
[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442)
version: 1.5.4
version: 1.7.0
contact:
name: DavidsonGomes
email: contato@agenciadgcode.com
@@ -51,6 +51,7 @@ tags:
- name: Send Message Controller
- name: Chat Controller
- name: Group Controller
- name: Label Controller
- name: Profile Settings
- name: JWT
- name: Settings
@@ -940,6 +941,71 @@ paths:
description: Successful response
content:
application/json: {}
/message/sendList/{instanceName}:
post:
tags:
- Send Message Controller
summary: Send a list to a specified instance.
description: This endpoint allows users to send a list to a chat.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
number:
type: string
options:
type: object
properties:
delay:
type: integer
presence:
type: string
listMessage:
type: object
properties:
title:
type: string
description:
type: string
footerText:
type: string
nullable: true
buttonText:
type: string
sections:
type: array
items:
type: object
properties:
title:
type: string
rows:
type: array
items:
type: object
properties:
title:
type: string
description:
type: string
rowId:
type: string
parameters:
- name: instanceName
in: path
required: true
schema:
type: string
description: The name of the instance to which the poll should be sent.
example: "evolution"
responses:
"200":
description: Successful response
content:
application/json: {}
/chat/whatsappNumbers/{instanceName}:
post:
@@ -1791,6 +1857,8 @@ paths:
"GROUP_UPDATE",
"GROUP_PARTICIPANTS_UPDATE",
"CONNECTION_UPDATE",
"LABELS_EDIT",
"LABELS_ASSOCIATION",
"CALL",
"NEW_JWT_TOKEN",
]
@@ -1867,6 +1935,8 @@ paths:
"GROUP_UPDATE",
"GROUP_PARTICIPANTS_UPDATE",
"CONNECTION_UPDATE",
"LABELS_EDIT",
"LABELS_ASSOCIATION",
"CALL",
"NEW_JWT_TOKEN",
]
@@ -1943,6 +2013,8 @@ paths:
"GROUP_UPDATE",
"GROUP_PARTICIPANTS_UPDATE",
"CONNECTION_UPDATE",
"LABELS_EDIT",
"LABELS_ASSOCIATION",
"CALL",
"NEW_JWT_TOKEN",
]
@@ -1981,6 +2053,97 @@ paths:
content:
application/json: {}
/label/findLabels/{instanceName}:
get:
tags:
- Label Controller
summary: List all labels for an instance.
parameters:
- name: instanceName
in: path
schema:
type: string
required: true
description: "- required"
example: "evolution"
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
type: object
properties:
color:
type: integer
name:
type: string
id:
type: string
predefinedId:
type: string
required:
- color
- name
- id
/label/handleLabel/{instanceName}:
put:
tags:
- Label Controller
summary: Change the label (add or remove) for an specific chat.
parameters:
- name: instanceName
in: path
schema:
type: string
required: true
description: "- required"
example: "evolution"
requestBody:
content:
application/json:
schema:
type: object
properties:
number:
type: string
labelId:
type: string
action:
type: string
enum:
- add
- remove
required:
- number
- labelId
- action
example:
number: '553499999999'
labelId: '1'
action: add
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: object
properties:
numberJid:
type: string
labelId:
type: string
remove:
type: boolean
add:
type: boolean
required:
- numberJid
- labelId
/settings/set/{instanceName}:
post:
tags:
@@ -2011,6 +2174,9 @@ paths:
read_status:
type: boolean
description: "Indicates whether to mark status messages as read."
sync_full_history:
type: boolean
description: "Indicates whether to request a full history messages sync on connect."
parameters:
- name: instanceName
in: path
@@ -2076,6 +2242,15 @@ paths:
conversation_pending:
type: boolean
description: "Indicates whether to mark conversations as pending."
import_contacts:
type: boolean
description: "Indicates whether to import contacts from phone to Chatwoot when connecting."
import_messages:
type: boolean
description: "Indicates whether to import messages from phone to Chatwoot when connecting."
days_limit_import_messages:
type: number
description: "Indicates number of days to limit messages imported to Chatwoot."
parameters:
- name: instanceName
in: path

22
src/libs/cacheengine.ts Normal file
View File

@@ -0,0 +1,22 @@
import { CacheConf, ConfigService } from '../config/env.config';
import { ICache } from '../whatsapp/abstract/abstract.cache';
import { LocalCache } from './localcache';
import { RedisCache } from './rediscache';
export class CacheEngine {
private engine: ICache;
constructor(private readonly configService: ConfigService, module: string) {
const cacheConf = configService.get<CacheConf>('CACHE');
if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') {
this.engine = new RedisCache(configService, module);
} else if (cacheConf?.LOCAL?.ENABLED) {
this.engine = new LocalCache(configService, module);
}
}
public getEngine() {
return this.engine;
}
}

48
src/libs/localcache.ts Normal file
View File

@@ -0,0 +1,48 @@
import NodeCache from 'node-cache';
import { CacheConf, CacheConfLocal, ConfigService } from '../config/env.config';
import { ICache } from '../whatsapp/abstract/abstract.cache';
export class LocalCache implements ICache {
private conf: CacheConfLocal;
static localCache = new NodeCache();
constructor(private readonly configService: ConfigService, private readonly module: string) {
this.conf = this.configService.get<CacheConf>('CACHE')?.LOCAL;
}
async get(key: string): Promise<any> {
return LocalCache.localCache.get(this.buildKey(key));
}
async set(key: string, value: any, ttl?: number) {
return LocalCache.localCache.set(this.buildKey(key), value, ttl || this.conf.TTL);
}
async has(key: string) {
return LocalCache.localCache.has(this.buildKey(key));
}
async delete(key: string) {
return LocalCache.localCache.del(this.buildKey(key));
}
async deleteAll(appendCriteria?: string) {
const keys = await this.keys(appendCriteria);
if (!keys?.length) {
return 0;
}
return LocalCache.localCache.del(keys);
}
async keys(appendCriteria?: string) {
const filter = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}`;
return LocalCache.localCache.keys().filter((key) => key.substring(0, filter.length) === filter);
}
buildKey(key: string) {
return `${this.module}:${key}`;
}
}

View File

@@ -0,0 +1,49 @@
import postgresql from 'pg';
import { Chatwoot, configService } from '../config/env.config';
import { Logger } from '../config/logger.config';
const { Pool } = postgresql;
class Postgres {
private logger = new Logger(Postgres.name);
private pool;
private connected = false;
getConnection(connectionString: string) {
if (this.connected) {
return this.pool;
} else {
this.pool = new Pool({
connectionString,
ssl: {
rejectUnauthorized: false,
},
});
this.pool.on('error', () => {
this.logger.error('postgres disconnected');
this.connected = false;
});
try {
this.logger.verbose('connecting new postgres');
this.connected = true;
} catch (e) {
this.connected = false;
this.logger.error('postgres connect exception caught: ' + e);
return null;
}
return this.pool;
}
}
getChatwootConnection() {
const uri = configService.get<Chatwoot>('CHATWOOT').IMPORT.DATABASE.CONNECTION.URI;
return this.getConnection(uri);
}
}
export const postgresClient = new Postgres();

View File

@@ -5,49 +5,55 @@ import { Redis } from '../config/env.config';
import { Logger } from '../config/logger.config';
export class RedisCache {
async disconnect() {
await this.client.disconnect();
this.statusConnection = false;
}
constructor() {
this.logger.verbose('instance created');
process.on('beforeExit', async () => {
this.logger.verbose('instance destroyed');
if (this.statusConnection) {
this.logger.verbose('instance disconnect');
await this.client.disconnect();
}
});
}
private readonly logger = new Logger(RedisCache.name);
private client: RedisClientType;
private statusConnection = false;
private instanceName: string;
private redisEnv: Redis;
constructor() {
this.logger.verbose('RedisCache instance created');
process.on('beforeExit', () => {
this.logger.verbose('RedisCache instance destroyed');
this.disconnect();
});
}
public set reference(reference: string) {
this.logger.verbose('set reference: ' + reference);
this.instanceName = reference;
}
public async connect(redisEnv: Redis) {
this.logger.verbose('connecting');
this.logger.verbose('Connecting to Redis...');
this.client = createClient({ url: redisEnv.URI });
this.logger.verbose('connected in ' + redisEnv.URI);
this.client.on('error', (err) => this.logger.error('Redis Client Error ' + err));
await this.client.connect();
this.statusConnection = true;
this.redisEnv = redisEnv;
this.logger.verbose(`Connected to ${redisEnv.URI}`);
}
private readonly logger = new Logger(RedisCache.name);
private client: RedisClientType;
public async disconnect() {
if (this.statusConnection) {
await this.client.disconnect();
this.statusConnection = false;
this.logger.verbose('Redis client disconnected');
}
}
public async instanceKeys(): Promise<string[]> {
const keys: string[] = [];
try {
this.logger.verbose('instance keys: ' + this.redisEnv.PREFIX_KEY + ':*');
return await this.client.sendCommand(['keys', this.redisEnv.PREFIX_KEY + ':*']);
this.logger.verbose('Fetching instance keys');
for await (const key of this.client.scanIterator({ MATCH: `${this.redisEnv.PREFIX_KEY}:*` })) {
keys.push(key);
}
} catch (error) {
this.logger.error(error);
this.logger.error('Error fetching instance keys ' + error);
}
return keys;
}
public async keyExists(key?: string) {

View File

@@ -0,0 +1,59 @@
import { createClient, RedisClientType } from 'redis';
import { CacheConf, CacheConfRedis, configService } from '../config/env.config';
import { Logger } from '../config/logger.config';
class Redis {
private logger = new Logger(Redis.name);
private client: RedisClientType = null;
private conf: CacheConfRedis;
private connected = false;
constructor() {
this.conf = configService.get<CacheConf>('CACHE')?.REDIS;
}
getConnection(): RedisClientType {
if (this.connected) {
return this.client;
} else {
this.client = createClient({
url: this.conf.URI,
});
this.client.on('connect', () => {
this.logger.verbose('redis connecting');
});
this.client.on('ready', () => {
this.logger.verbose('redis ready');
this.connected = true;
});
this.client.on('error', () => {
this.logger.error('redis disconnected');
this.connected = false;
});
this.client.on('end', () => {
this.logger.verbose('redis connection ended');
this.connected = false;
});
try {
this.logger.verbose('connecting new redis client');
this.client.connect();
this.connected = true;
this.logger.verbose('connected to new redis client');
} catch (e) {
this.connected = false;
this.logger.error('redis connect exception caught: ' + e);
return null;
}
return this.client;
}
}
}
export const redisClient = new Redis();

83
src/libs/rediscache.ts Normal file
View File

@@ -0,0 +1,83 @@
import { RedisClientType } from 'redis';
import { CacheConf, CacheConfRedis, ConfigService } from '../config/env.config';
import { Logger } from '../config/logger.config';
import { ICache } from '../whatsapp/abstract/abstract.cache';
import { redisClient } from './rediscache.client';
export class RedisCache implements ICache {
private readonly logger = new Logger(RedisCache.name);
private client: RedisClientType;
private conf: CacheConfRedis;
constructor(private readonly configService: ConfigService, private readonly module: string) {
this.conf = this.configService.get<CacheConf>('CACHE')?.REDIS;
this.client = redisClient.getConnection();
}
async get(key: string): Promise<any> {
try {
return JSON.parse(await this.client.get(this.buildKey(key)));
} catch (error) {
this.logger.error(error);
}
}
async set(key: string, value: any, ttl?: number) {
try {
await this.client.setEx(this.buildKey(key), ttl || this.conf?.TTL, JSON.stringify(value));
} catch (error) {
this.logger.error(error);
}
}
async has(key: string) {
try {
return (await this.client.exists(this.buildKey(key))) > 0;
} catch (error) {
this.logger.error(error);
}
}
async delete(key: string) {
try {
return await this.client.del(this.buildKey(key));
} catch (error) {
this.logger.error(error);
}
}
async deleteAll(appendCriteria?: string) {
try {
const keys = await this.keys(appendCriteria);
if (!keys?.length) {
return 0;
}
return await this.client.del(keys);
} catch (error) {
this.logger.error(error);
}
}
async keys(appendCriteria?: string) {
try {
const match = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}*`;
const keys = [];
for await (const key of this.client.scanIterator({
MATCH: match,
COUNT: 100,
})) {
keys.push(key);
}
return [...new Set(keys)];
} catch (error) {
this.logger.error(error);
}
}
buildKey(key: string) {
return `${this.conf?.PREFIX_KEY}:${this.module}:${key}`;
}
}

97
src/libs/sqs.server.ts Normal file
View File

@@ -0,0 +1,97 @@
import { SQS } from 'aws-sdk';
import { configService, Sqs } from '../config/env.config';
import { Logger } from '../config/logger.config';
const logger = new Logger('SQS');
let sqs: SQS;
export const initSQS = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return new Promise<void>((resolve, reject) => {
const awsConfig = configService.get<Sqs>('SQS');
sqs = new SQS({
accessKeyId: awsConfig.ACCESS_KEY_ID,
secretAccessKey: awsConfig.SECRET_ACCESS_KEY,
region: awsConfig.REGION,
});
logger.info('SQS initialized');
resolve();
});
};
export const getSQS = (): SQS => {
return sqs;
};
export const initQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
const queues = events.map((event) => {
return `${event.replace(/_/g, '_').toLowerCase()}`;
});
const sqs = getSQS();
queues.forEach((event) => {
const queueName = `${instanceName}_${event}.fifo`;
sqs.createQueue(
{
QueueName: queueName,
Attributes: {
FifoQueue: 'true',
},
},
(err, data) => {
if (err) {
logger.error(`Error creating queue ${queueName}: ${err.message}`);
} else {
logger.info(`Queue ${queueName} created: ${data.QueueUrl}`);
}
},
);
});
};
export const removeQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
const sqs = getSQS();
const queues = events.map((event) => {
return `${event.replace(/_/g, '_').toLowerCase()}`;
});
queues.forEach((event) => {
const queueName = `${instanceName}_${event}.fifo`;
sqs.getQueueUrl(
{
QueueName: queueName,
},
(err, data) => {
if (err) {
logger.error(`Error getting queue URL for ${queueName}: ${err.message}`);
} else {
const queueUrl = data.QueueUrl;
sqs.deleteQueue(
{
QueueUrl: queueUrl,
},
(deleteErr) => {
if (deleteErr) {
logger.error(`Error deleting queue ${queueName}: ${deleteErr.message}`);
} else {
logger.info(`Queue ${queueName} deleted`);
}
},
);
}
},
);
});
};

View File

@@ -6,13 +6,14 @@ import cors from 'cors';
import express, { json, NextFunction, Request, Response, urlencoded } from 'express';
import { join } from 'path';
import { Auth, configService, Cors, HttpServer, Rabbitmq, Webhook } from './config/env.config';
import { Auth, configService, Cors, HttpServer, Rabbitmq, Sqs, Webhook } from './config/env.config';
import { onUnexpectedError } from './config/error.config';
import { Logger } from './config/logger.config';
import { ROOT_DIR } from './config/path.config';
import { swaggerRouter } from './docs/swagger.conf';
import { initAMQP } from './libs/amqp.server';
import { initIO } from './libs/socket.server';
import { initSQS } from './libs/sqs.server';
import { ServerUP } from './utils/server-up';
import { HttpStatus, router } from './whatsapp/routers/index.router';
import { waMonitor } from './whatsapp/whatsapp.module';
@@ -52,7 +53,8 @@ function bootstrap() {
app.use('/store', express.static(join(ROOT_DIR, 'store')));
app.use('/', router);
app.use(swaggerRouter);
if (!configService.get('SERVER').DISABLE_DOCS) app.use(swaggerRouter);
app.use(
(err: Error, req: Request, res: Response, next: NextFunction) => {
@@ -128,6 +130,8 @@ function bootstrap() {
if (configService.get<Rabbitmq>('RABBITMQ')?.ENABLED) initAMQP();
if (configService.get<Sqs>('SQS')?.ENABLED) initSQS();
onUnexpectedError();
}

View File

@@ -0,0 +1,472 @@
import { inbox } from '@figuro/chatwoot-sdk';
import { proto } from '@whiskeysockets/baileys';
import { Chatwoot, configService } from '../config/env.config';
import { Logger } from '../config/logger.config';
import { postgresClient } from '../libs/postgres.client';
import { InstanceDto } from '../whatsapp/dto/instance.dto';
import { ChatwootRaw, ContactRaw, MessageRaw } from '../whatsapp/models';
import { ChatwootService } from '../whatsapp/services/chatwoot.service';
type ChatwootUser = {
user_type: string;
user_id: number;
};
type FksChatwoot = {
phone_number: string;
contact_id: string;
conversation_id: string;
};
type firstLastTimestamp = {
first: number;
last: number;
};
type IWebMessageInfo = Omit<proto.IWebMessageInfo, 'key'> & Partial<Pick<proto.IWebMessageInfo, 'key'>>;
class ChatwootImport {
private logger = new Logger(ChatwootImport.name);
private repositoryMessagesCache = new Map<string, Set<string>>();
private historyMessages = new Map<string, MessageRaw[]>();
private historyContacts = new Map<string, ContactRaw[]>();
public getRepositoryMessagesCache(instance: InstanceDto) {
return this.repositoryMessagesCache.has(instance.instanceName)
? this.repositoryMessagesCache.get(instance.instanceName)
: null;
}
public setRepositoryMessagesCache(instance: InstanceDto, repositoryMessagesCache: Set<string>) {
this.repositoryMessagesCache.set(instance.instanceName, repositoryMessagesCache);
}
public deleteRepositoryMessagesCache(instance: InstanceDto) {
this.repositoryMessagesCache.delete(instance.instanceName);
}
public addHistoryMessages(instance: InstanceDto, messagesRaw: MessageRaw[]) {
const actualValue = this.historyMessages.has(instance.instanceName)
? this.historyMessages.get(instance.instanceName)
: [];
this.historyMessages.set(instance.instanceName, actualValue.concat(messagesRaw));
}
public addHistoryContacts(instance: InstanceDto, contactsRaw: ContactRaw[]) {
const actualValue = this.historyContacts.has(instance.instanceName)
? this.historyContacts.get(instance.instanceName)
: [];
this.historyContacts.set(instance.instanceName, actualValue.concat(contactsRaw));
}
public deleteHistoryMessages(instance: InstanceDto) {
this.historyMessages.delete(instance.instanceName);
}
public deleteHistoryContacts(instance: InstanceDto) {
this.historyContacts.delete(instance.instanceName);
}
public clearAll(instance: InstanceDto) {
this.deleteRepositoryMessagesCache(instance);
this.deleteHistoryMessages(instance);
this.deleteHistoryContacts(instance);
}
public getHistoryMessagesLenght(instance: InstanceDto) {
return this.historyMessages.get(instance.instanceName)?.length ?? 0;
}
public async importHistoryContacts(instance: InstanceDto, provider: ChatwootRaw) {
try {
if (this.getHistoryMessagesLenght(instance) > 0) {
return;
}
const pgClient = postgresClient.getChatwootConnection();
let totalContactsImported = 0;
const contacts = this.historyContacts.get(instance.instanceName) || [];
if (contacts.length === 0) {
return 0;
}
let contactsChunk: ContactRaw[] = this.sliceIntoChunks(contacts, 3000);
while (contactsChunk.length > 0) {
// inserting contacts in chatwoot db
let sqlInsert = `INSERT INTO contacts
(name, phone_number, account_id, identifier, created_at, updated_at) VALUES `;
const bindInsert = [provider.account_id];
for (const contact of contactsChunk) {
bindInsert.push(contact.pushName);
const bindName = `$${bindInsert.length}`;
bindInsert.push(`+${contact.id.split('@')[0]}`);
const bindPhoneNumber = `$${bindInsert.length}`;
bindInsert.push(contact.id);
const bindIdentifier = `$${bindInsert.length}`;
sqlInsert += `(${bindName}, ${bindPhoneNumber}, $1, ${bindIdentifier}, NOW(), NOW()),`;
}
if (sqlInsert.slice(-1) === ',') {
sqlInsert = sqlInsert.slice(0, -1);
}
sqlInsert += ` ON CONFLICT (identifier, account_id)
DO UPDATE SET
name = EXCLUDED.name,
phone_number = EXCLUDED.phone_number,
identifier = EXCLUDED.identifier`;
totalContactsImported += (await pgClient.query(sqlInsert, bindInsert))?.rowCount ?? 0;
contactsChunk = this.sliceIntoChunks(contacts, 3000);
}
this.deleteHistoryContacts(instance);
return totalContactsImported;
} catch (error) {
this.logger.error(`Error on import history contacts: ${error.toString()}`);
}
}
public async importHistoryMessages(
instance: InstanceDto,
chatwootService: ChatwootService,
inbox: inbox,
provider: ChatwootRaw,
) {
try {
const pgClient = postgresClient.getChatwootConnection();
const chatwootUser = await this.getChatwootUser(provider);
if (!chatwootUser) {
throw new Error('User not found to import messages.');
}
let totalMessagesImported = 0;
const messagesOrdered = this.historyMessages.get(instance.instanceName) || [];
if (messagesOrdered.length === 0) {
return 0;
}
// ordering messages by number and timestamp asc
messagesOrdered.sort((a, b) => {
return (
parseInt(a.key.remoteJid) - parseInt(b.key.remoteJid) ||
(a.messageTimestamp as number) - (b.messageTimestamp as number)
);
});
const allMessagesMappedByPhoneNumber = this.createMessagesMapByPhoneNumber(messagesOrdered);
// Map structure: +552199999999 => { first message timestamp from number, last message timestamp from number}
const phoneNumbersWithTimestamp = new Map<string, firstLastTimestamp>();
allMessagesMappedByPhoneNumber.forEach((messages: MessageRaw[], phoneNumber: string) => {
phoneNumbersWithTimestamp.set(phoneNumber, {
first: messages[0]?.messageTimestamp as number,
last: messages[messages.length - 1]?.messageTimestamp as number,
});
});
// processing messages in batch
const batchSize = 4000;
let messagesChunk: MessageRaw[] = this.sliceIntoChunks(messagesOrdered, batchSize);
while (messagesChunk.length > 0) {
// Map structure: +552199999999 => MessageRaw[]
const messagesByPhoneNumber = this.createMessagesMapByPhoneNumber(messagesChunk);
if (messagesByPhoneNumber.size > 0) {
const fksByNumber = await this.selectOrCreateFksFromChatwoot(
provider,
inbox,
phoneNumbersWithTimestamp,
messagesByPhoneNumber,
);
// inserting messages in chatwoot db
let sqlInsertMsg = `INSERT INTO messages
(content, account_id, inbox_id, conversation_id, message_type, private, content_type,
sender_type, sender_id, created_at, updated_at) VALUES `;
const bindInsertMsg = [provider.account_id, inbox.id];
messagesByPhoneNumber.forEach((messages: MessageRaw[], phoneNumber: string) => {
const fksChatwoot = fksByNumber.get(phoneNumber);
messages.forEach((message) => {
if (!message.message) {
return;
}
if (!fksChatwoot?.conversation_id || !fksChatwoot?.contact_id) {
return;
}
const contentMessage = this.getContentMessage(chatwootService, message);
if (!contentMessage) {
return;
}
bindInsertMsg.push(contentMessage);
const bindContent = `$${bindInsertMsg.length}`;
bindInsertMsg.push(fksChatwoot.conversation_id);
const bindConversationId = `$${bindInsertMsg.length}`;
bindInsertMsg.push(message.key.fromMe ? '1' : '0');
const bindMessageType = `$${bindInsertMsg.length}`;
bindInsertMsg.push(message.key.fromMe ? chatwootUser.user_type : 'Contact');
const bindSenderType = `$${bindInsertMsg.length}`;
bindInsertMsg.push(message.key.fromMe ? chatwootUser.user_id : fksChatwoot.contact_id);
const bindSenderId = `$${bindInsertMsg.length}`;
bindInsertMsg.push(message.messageTimestamp as number);
const bindmessageTimestamp = `$${bindInsertMsg.length}`;
sqlInsertMsg += `(${bindContent}, $1, $2, ${bindConversationId}, ${bindMessageType}, FALSE, 0,
${bindSenderType},${bindSenderId}, to_timestamp(${bindmessageTimestamp}), to_timestamp(${bindmessageTimestamp})),`;
});
});
if (bindInsertMsg.length > 2) {
if (sqlInsertMsg.slice(-1) === ',') {
sqlInsertMsg = sqlInsertMsg.slice(0, -1);
}
totalMessagesImported += (await pgClient.query(sqlInsertMsg, bindInsertMsg))?.rowCount ?? 0;
}
}
messagesChunk = this.sliceIntoChunks(messagesOrdered, batchSize);
}
this.deleteHistoryMessages(instance);
this.deleteRepositoryMessagesCache(instance);
this.importHistoryContacts(instance, provider);
return totalMessagesImported;
} catch (error) {
this.logger.error(`Error on import history messages: ${error.toString()}`);
this.deleteHistoryMessages(instance);
this.deleteRepositoryMessagesCache(instance);
}
}
public async selectOrCreateFksFromChatwoot(
provider: ChatwootRaw,
inbox: inbox,
phoneNumbersWithTimestamp: Map<string, firstLastTimestamp>,
messagesByPhoneNumber: Map<string, MessageRaw[]>,
): Promise<Map<string, FksChatwoot>> {
const pgClient = postgresClient.getChatwootConnection();
const bindValues = [provider.account_id, inbox.id];
const phoneNumberBind = Array.from(messagesByPhoneNumber.keys())
.map((phoneNumber) => {
const phoneNumberTimestamp = phoneNumbersWithTimestamp.get(phoneNumber);
if (phoneNumberTimestamp) {
bindValues.push(phoneNumber);
let bindStr = `($${bindValues.length},`;
bindValues.push(phoneNumberTimestamp.first);
bindStr += `$${bindValues.length},`;
bindValues.push(phoneNumberTimestamp.last);
return `${bindStr}$${bindValues.length})`;
}
})
.join(',');
// select (or insert when necessary) data from tables contacts, contact_inboxes, conversations from chatwoot db
const sqlFromChatwoot = `WITH
phone_number AS (
SELECT phone_number, created_at::INTEGER, last_activity_at::INTEGER FROM (
VALUES
${phoneNumberBind}
) as t (phone_number, created_at, last_activity_at)
),
only_new_phone_number AS (
SELECT * FROM phone_number
WHERE phone_number NOT IN (
SELECT phone_number
FROM contacts
JOIN contact_inboxes ci ON ci.contact_id = contacts.id AND ci.inbox_id = $2
JOIN conversations con ON con.contact_inbox_id = ci.id
AND con.account_id = $1
AND con.inbox_id = $2
AND con.contact_id = contacts.id
WHERE contacts.account_id = $1
)
),
new_contact AS (
INSERT INTO contacts (name, phone_number, account_id, identifier, created_at, updated_at)
SELECT REPLACE(p.phone_number, '+', ''), p.phone_number, $1, CONCAT(REPLACE(p.phone_number, '+', ''),
'@s.whatsapp.net'), to_timestamp(p.created_at), to_timestamp(p.last_activity_at)
FROM only_new_phone_number AS p
ON CONFLICT(identifier, account_id) DO UPDATE SET updated_at = EXCLUDED.updated_at
RETURNING id, phone_number, created_at, updated_at
),
new_contact_inbox AS (
INSERT INTO contact_inboxes (contact_id, inbox_id, source_id, created_at, updated_at)
SELECT new_contact.id, $2, gen_random_uuid(), new_contact.created_at, new_contact.updated_at
FROM new_contact
RETURNING id, contact_id, created_at, updated_at
),
new_conversation AS (
INSERT INTO conversations (account_id, inbox_id, status, contact_id,
contact_inbox_id, uuid, last_activity_at, created_at, updated_at)
SELECT $1, $2, 0, new_contact_inbox.contact_id, new_contact_inbox.id, gen_random_uuid(),
new_contact_inbox.updated_at, new_contact_inbox.created_at, new_contact_inbox.updated_at
FROM new_contact_inbox
RETURNING id, contact_id
)
SELECT new_contact.phone_number, new_conversation.contact_id, new_conversation.id AS conversation_id
FROM new_conversation
JOIN new_contact ON new_conversation.contact_id = new_contact.id
UNION
SELECT p.phone_number, c.id contact_id, con.id conversation_id
FROM phone_number p
JOIN contacts c ON c.phone_number = p.phone_number
JOIN contact_inboxes ci ON ci.contact_id = c.id AND ci.inbox_id = $2
JOIN conversations con ON con.contact_inbox_id = ci.id AND con.account_id = $1
AND con.inbox_id = $2 AND con.contact_id = c.id`;
const fksFromChatwoot = await pgClient.query(sqlFromChatwoot, bindValues);
return new Map(fksFromChatwoot.rows.map((item: FksChatwoot) => [item.phone_number, item]));
}
public async getChatwootUser(provider: ChatwootRaw): Promise<ChatwootUser> {
try {
const pgClient = postgresClient.getChatwootConnection();
const sqlUser = `SELECT owner_type AS user_type, owner_id AS user_id
FROM access_tokens
WHERE token = $1`;
return (await pgClient.query(sqlUser, [provider.token]))?.rows[0] || false;
} catch (error) {
this.logger.error(`Error on getChatwootUser: ${error.toString()}`);
}
}
public createMessagesMapByPhoneNumber(messages: MessageRaw[]): Map<string, MessageRaw[]> {
return messages.reduce((acc: Map<string, MessageRaw[]>, message: MessageRaw) => {
if (!this.isIgnorePhoneNumber(message?.key?.remoteJid)) {
const phoneNumber = message?.key?.remoteJid?.split('@')[0];
if (phoneNumber) {
const phoneNumberPlus = `+${phoneNumber}`;
const messages = acc.has(phoneNumberPlus) ? acc.get(phoneNumberPlus) : [];
messages.push(message);
acc.set(phoneNumberPlus, messages);
}
}
return acc;
}, new Map());
}
public async getContactsOrderByRecentConversations(
inbox: inbox,
provider: ChatwootRaw,
limit = 50,
): Promise<{ id: number; phone_number: string; identifier: string }[]> {
try {
const pgClient = postgresClient.getChatwootConnection();
const sql = `SELECT contacts.id, contacts.identifier, contacts.phone_number
FROM conversations
JOIN contacts ON contacts.id = conversations.contact_id
WHERE conversations.account_id = $1
AND inbox_id = $2
ORDER BY conversations.last_activity_at DESC
LIMIT $3`;
return (await pgClient.query(sql, [provider.account_id, inbox.id, limit]))?.rows;
} catch (error) {
this.logger.error(`Error on get recent conversations: ${error.toString()}`);
}
}
public getContentMessage(chatwootService: ChatwootService, msg: IWebMessageInfo) {
const contentMessage = chatwootService.getConversationMessage(msg.message);
if (contentMessage) {
return contentMessage;
}
if (!configService.get<Chatwoot>('CHATWOOT').IMPORT.PLACEHOLDER_MEDIA_MESSAGE) {
return '';
}
const types = {
documentMessage: msg.message.documentMessage,
documentWithCaptionMessage: msg.message.documentWithCaptionMessage?.message?.documentMessage,
imageMessage: msg.message.imageMessage,
videoMessage: msg.message.videoMessage,
audioMessage: msg.message.audioMessage,
stickerMessage: msg.message.stickerMessage,
templateMessage: msg.message.templateMessage?.hydratedTemplate?.hydratedContentText,
};
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
switch (typeKey) {
case 'documentMessage':
return `_<File: ${msg.message.documentMessage.fileName}${
msg.message.documentMessage.caption ? ` ${msg.message.documentMessage.caption}` : ''
}>_`;
case 'documentWithCaptionMessage':
return `_<File: ${msg.message.documentWithCaptionMessage.message.documentMessage.fileName}${
msg.message.documentWithCaptionMessage.message.documentMessage.caption
? ` ${msg.message.documentWithCaptionMessage.message.documentMessage.caption}`
: ''
}>_`;
case 'templateMessage':
return msg.message.templateMessage.hydratedTemplate.hydratedTitleText
? `*${msg.message.templateMessage.hydratedTemplate.hydratedTitleText}*\\n`
: '' + msg.message.templateMessage.hydratedTemplate.hydratedContentText;
case 'imageMessage':
return '_<Image Message>_';
case 'videoMessage':
return '_<Video Message>_';
case 'audioMessage':
return '_<Audio Message>_';
case 'stickerMessage':
return '_<Sticker Message>_';
default:
return '';
}
}
public sliceIntoChunks(arr: any[], chunkSize: number) {
return arr.splice(0, chunkSize);
}
public isGroup(remoteJid: string) {
return remoteJid.includes('@g.us');
}
public isIgnorePhoneNumber(remoteJid: string) {
return this.isGroup(remoteJid) || remoteJid === 'status@broadcast' || remoteJid === '0@s.whatsapp.net';
}
}
export const chatwootImport = new ChatwootImport();

32
src/utils/i18n.ts Normal file
View File

@@ -0,0 +1,32 @@
import fs from 'fs';
import i18next from 'i18next';
import path from 'path';
import { ConfigService, Language } from '../config/env.config';
const languages = ['en', 'pt-BR'];
const translationsPath = path.join(__dirname, 'translations');
const configService: ConfigService = new ConfigService();
const resources: any = {};
languages.forEach((language) => {
const languagePath = path.join(translationsPath, `${language}.json`);
if (fs.existsSync(languagePath)) {
resources[language] = {
translation: require(languagePath),
};
}
});
i18next.init({
resources,
fallbackLng: 'en',
lng: configService.get<Language>('LANGUAGE'),
debug: false,
interpolation: {
escapeValue: false,
},
});
export default i18next;

View File

@@ -0,0 +1,17 @@
import { HttpsProxyAgent } from 'https-proxy-agent';
import { wa } from '../whatsapp/types/wa.types';
export function makeProxyAgent(proxy: wa.Proxy | string) {
if (typeof proxy === 'string') {
return new HttpsProxyAgent(proxy);
}
const { host, password, port, protocol, username } = proxy;
let proxyUrl = `${protocol}://${host}:${port}`;
if (username && password) {
proxyUrl = `${protocol}://${username}:${password}@${host}:${port}`;
}
return new HttpsProxyAgent(proxyUrl);
}

View File

@@ -0,0 +1,26 @@
{
"qrgeneratedsuccesfully": "QRCode successfully generated!",
"scanqr": "Scan this QR code within the next 40 seconds.",
"qrlimitreached": "QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.",
"numbernotinwhatsapp": "The message was not sent as the contact is not a valid Whatsapp number.",
"cw.inbox.connected": "🚀 Connection successfully established!",
"cw.inbox.disconnect": "🚨 Disconnecting WhatsApp from inbox *{{inboxName}}*.",
"cw.inbox.alreadyConnected": "🚨 {{inboxName}} instance is connected.",
"cw.inbox.clearCache": "✅ {{inboxName}} instance cache cleared.",
"cw.inbox.notFound": "⚠️ {{inboxName}} instance not found.",
"cw.inbox.status": "⚠️ {{inboxName}} instance status: *{{state}}*.",
"cw.import.startImport": "💬 Starting to import messages. Please wait...",
"cw.import.importingMessages": "💬 Importing messages. More one moment...",
"cw.import.messagesImported": "💬 {{totalMessagesImported}} messages imported. Refresh page to see the new messages.",
"cw.import.messagesException": "💬 Something went wrong in importing messages.",
"cw.locationMessage.location": "Location",
"cw.locationMessage.latitude": "Latitude",
"cw.locationMessage.longitude": "Longitude",
"cw.locationMessage.locationName": "Name",
"cw.locationMessage.locationAddress": "Address",
"cw.locationMessage.locationUrl": "URL",
"cw.contactMessage.contact": "Contact",
"cw.contactMessage.name": "Name",
"cw.contactMessage.number": "Number",
"cw.message.edited": "Edited Message"
}

View File

@@ -0,0 +1,26 @@
{
"qrgeneratedsuccesfully": "QRCode gerado com sucesso!",
"scanqr": "Escaneie o QRCode com o WhatsApp nos próximos 40 segundos.",
"qrlimitreached": "Limite de geração de QRCode atingido! Para gerar um novo QRCode, envie o texto 'init' nesta conversa.",
"numbernotinwhatsapp": "A mensagem não foi enviada, pois o contato não é um número válido do WhatsApp.",
"cw.inbox.connected": "🚀 Conectado com sucesso!",
"cw.inbox.disconnect": "🚨 Instância *{{inboxName}}* desconectada do WhatsApp.",
"cw.inbox.alreadyConnected": "🚨 Instância *{{inboxName}}* já está conectada.",
"cw.inbox.clearCache": "✅ Instância *{{inboxName}}* cache removido.",
"cw.inbox.notFound": "⚠️ Instância *{{inboxName}}* não encontrada.",
"cw.inbox.status": "⚠️ Status da instância {{inboxName}}: *{{state}}*.",
"cw.import.startImport": "💬 Iniciando importação de mensagens. Por favor, aguarde...",
"cw.import.importingMessages": "💬 Importando mensagens. Mais um momento...",
"cw.import.messagesImported": "💬 {{totalMessagesImported}} mensagens importadas. Atualize a página para ver as novas mensagens.",
"cw.import.messagesException": "💬 Não foi possível importar as mensagens.",
"cw.locationMessage.location": "Localização",
"cw.locationMessage.latitude": "Latitude",
"cw.locationMessage.longitude": "Longitude",
"cw.locationMessage.locationName": "Nome",
"cw.locationMessage.locationAddress": "Endereço",
"cw.locationMessage.locationUrl": "URL",
"cw.contactMessage.contact": "Contato",
"cw.contactMessage.name": "Nome",
"cw.contactMessage.number": "Número",
"cw.message.edited": "Mensagem editada"
}

View File

@@ -25,7 +25,14 @@ export async function useMultiFileAuthStateDb(
const writeData = async (data: any, key: string): Promise<any> => {
try {
await client.connect();
return await collection.replaceOne({ _id: key }, JSON.parse(JSON.stringify(data, BufferJSON.replacer)), {
let msgParsed = JSON.parse(JSON.stringify(data, BufferJSON.replacer));
if (Array.isArray(msgParsed)) {
msgParsed = {
_id: key,
content_array: msgParsed,
};
}
return await collection.replaceOne({ _id: key }, msgParsed, {
upsert: true,
});
} catch (error) {
@@ -36,7 +43,10 @@ export async function useMultiFileAuthStateDb(
const readData = async (key: string): Promise<any> => {
try {
await client.connect();
const data = await collection.findOne({ _id: key });
let data = (await collection.findOne({ _id: key })) as any;
if (data?.content_array) {
data = data.content_array;
}
const creds = JSON.stringify(data);
return JSON.parse(creds, BufferJSON.reviver);
} catch (error) {
@@ -91,7 +101,7 @@ export async function useMultiFileAuthStateDb(
},
},
saveCreds: async () => {
return writeData(creds, 'creds');
return await writeData(creds, 'creds');
},
};
}

View File

@@ -53,6 +53,8 @@ export const instanceNameSchema: JSONSchema7 = {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@@ -149,6 +151,28 @@ export const textMessageSchema: JSONSchema7 = {
required: ['textMessage', 'number'],
};
export const presenceSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { ...numberDefinition },
options: { ...optionsSchema, required: ['presence', 'delay'] },
},
required: ['options', 'number'],
};
export const presenceOnlySchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
presence: {
type: 'string',
enum: ['unavailable', 'available', 'composing', 'recording', 'paused'],
},
},
required: ['presence'],
};
export const pollMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -265,6 +289,26 @@ export const audioMessageSchema: JSONSchema7 = {
required: ['audioMessage', 'number'],
};
export const templateMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { ...numberDefinition },
options: { ...optionsSchema },
templateMessage: {
type: 'object',
properties: {
name: { type: 'string' },
language: { type: 'string' },
components: { type: 'array' },
},
required: ['name', 'language'],
...isNotEmpty('name', 'language'),
},
},
required: ['templateMessage', 'number'],
};
export const buttonMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -507,6 +551,17 @@ export const privacySettingsSchema: JSONSchema7 = {
required: ['privacySettings'],
};
export const blockUserSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { type: 'string' },
status: { type: 'string', enum: ['block', 'unblock'] },
},
required: ['number', 'status'],
...isNotEmpty('number', 'status'),
};
export const archiveChatSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -582,6 +637,26 @@ export const profileStatusSchema: JSONSchema7 = {
...isNotEmpty('status'),
};
export const updateMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { type: 'string' },
text: { type: 'string' },
key: {
type: 'object',
properties: {
id: { type: 'string' },
remoteJid: { type: 'string' },
fromMe: { type: 'boolean', enum: [true, false] },
},
required: ['id', 'fromMe', 'remoteJid'],
...isNotEmpty('id', 'remoteJid'),
},
},
...isNotEmpty('number', 'text', 'key'),
};
export const profilePictureSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -741,6 +816,16 @@ export const groupInviteSchema: JSONSchema7 = {
...isNotEmpty('inviteCode'),
};
export const AcceptGroupInviteSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' },
},
required: ['inviteCode'],
...isNotEmpty('inviteCode'),
};
export const updateParticipantsSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -857,6 +942,8 @@ export const webhookSchema: JSONSchema7 = {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@@ -879,8 +966,13 @@ export const chatwootSchema: JSONSchema7 = {
token: { type: 'string' },
url: { type: 'string' },
sign_msg: { type: 'boolean', enum: [true, false] },
sign_delimiter: { type: ['string', 'null'] },
reopen_conversation: { type: 'boolean', enum: [true, false] },
conversation_pending: { type: 'boolean', enum: [true, false] },
auto_create: { type: 'boolean', enum: [true, false] },
import_contacts: { type: 'boolean', enum: [true, false] },
import_messages: { type: 'boolean', enum: [true, false] },
days_limit_import_messages: { type: 'number' },
},
required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'],
...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'),
@@ -896,9 +988,10 @@ export const settingsSchema: JSONSchema7 = {
always_online: { type: 'boolean', enum: [true, false] },
read_messages: { type: 'boolean', enum: [true, false] },
read_status: { type: 'boolean', enum: [true, false] },
sync_full_history: { type: 'boolean', enum: [true, false] },
},
required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'],
...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'),
required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'],
...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'),
};
export const websocketSchema: JSONSchema7 = {
@@ -931,6 +1024,8 @@ export const websocketSchema: JSONSchema7 = {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@@ -974,6 +1069,53 @@ export const rabbitmqSchema: JSONSchema7 = {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
},
required: ['enabled'],
...isNotEmpty('enabled'),
};
export const sqsSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 0,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@@ -1031,7 +1173,18 @@ export const proxySchema: JSONSchema7 = {
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
proxy: { type: 'string' },
proxy: {
type: 'object',
properties: {
host: { type: 'string' },
port: { type: 'string' },
protocol: { type: 'string' },
username: { type: 'string' },
password: { type: 'string' },
},
required: ['host', 'port', 'protocol'],
...isNotEmpty('host', 'port', 'protocol'),
},
},
required: ['enabled', 'proxy'],
...isNotEmpty('enabled', 'proxy'),
@@ -1050,3 +1203,14 @@ export const chamaaiSchema: JSONSchema7 = {
required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'],
...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'),
};
export const handleLabelSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { ...numberDefinition },
labelId: { type: 'string' },
action: { type: 'string', enum: ['add', 'remove'] },
},
required: ['number', 'labelId', 'action'],
};

View File

@@ -0,0 +1,13 @@
export interface ICache {
get(key: string): Promise<any>;
set(key: string, value: any, ttl?: number): void;
has(key: string): Promise<boolean>;
keys(appendCriteria?: string): Promise<string[]>;
delete(key: string | string[]): Promise<number>;
deleteAll(appendCriteria?: string): Promise<number>;
}

View File

@@ -21,7 +21,6 @@ const logger = new Logger('Validate');
export abstract class RouterBroker {
constructor() {}
public routerPath(path: string, param = true) {
// const route = param ? '/:instanceName/' + path : '/' + path;
let route = '/' + path;
param ? (route += '/:instanceName') : null;
@@ -56,10 +55,6 @@ export abstract class RouterBroker {
message = stack.replace('instance.', '');
}
return message;
// return {
// property: property.replace('instance.', ''),
// message,
// };
});
logger.error(message);
throw new BadRequestException(message);

View File

@@ -1,6 +1,7 @@
import { Logger } from '../../config/logger.config';
import {
ArchiveChatDto,
BlockUserDto,
DeleteMessage,
getBase64FromMediaMessageDto,
NumberDto,
@@ -9,6 +10,8 @@ import {
ProfilePictureDto,
ProfileStatusDto,
ReadMessageDto,
SendPresenceDto,
UpdateMessageDto,
WhatsAppNumberDto,
} from '../dto/chat.dto';
import { InstanceDto } from '../dto/instance.dto';
@@ -77,6 +80,11 @@ export class ChatController {
return await this.waMonitor.waInstances[instanceName].fetchChats();
}
public async sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) {
logger.verbose('requested sendPresence from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].sendPresence(data);
}
public async fetchPrivacySettings({ instanceName }: InstanceDto) {
logger.verbose('requested fetchPrivacySettings from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings();
@@ -111,4 +119,14 @@ export class ChatController {
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].removeProfilePicture();
}
public async updateMessage({ instanceName }: InstanceDto, data: UpdateMessageDto) {
logger.verbose('requested updateMessage from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].updateMessage(data);
}
public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) {
logger.verbose('requested blockUser from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].blockUser(data);
}
}

View File

@@ -3,15 +3,22 @@ import { isURL } from 'class-validator';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions';
import { CacheEngine } from '../../libs/cacheengine';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto';
import { RepositoryBroker } from '../repository/repository.manager';
import { CacheService } from '../services/cache.service';
import { ChatwootService } from '../services/chatwoot.service';
import { waMonitor } from '../whatsapp.module';
const logger = new Logger('ChatwootController');
export class ChatwootController {
constructor(private readonly chatwootService: ChatwootService, private readonly configService: ConfigService) {}
constructor(
private readonly chatwootService: ChatwootService,
private readonly configService: ConfigService,
private readonly repository: RepositoryBroker,
) {}
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
logger.verbose('requested createChatwoot from ' + instance.instanceName + ' instance');
@@ -32,6 +39,7 @@ export class ChatwootController {
if (data.sign_msg !== true && data.sign_msg !== false) {
throw new BadRequestException('sign_msg is required');
}
if (data.sign_msg === false) data.sign_delimiter = null;
}
if (!data.enabled) {
@@ -40,13 +48,18 @@ export class ChatwootController {
data.token = '';
data.url = '';
data.sign_msg = false;
data.sign_delimiter = null;
data.reopen_conversation = false;
data.conversation_pending = false;
data.import_contacts = false;
data.import_messages = false;
data.days_limit_import_messages = 0;
data.auto_create = false;
}
data.name_inbox = instance.instanceName;
const result = this.chatwootService.create(instance, data);
const result = await this.chatwootService.create(instance, data);
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
@@ -64,7 +77,7 @@ export class ChatwootController {
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
if (Object.keys(result).length === 0) {
if (Object.keys(result || {}).length === 0) {
return {
enabled: false,
url: '',
@@ -86,7 +99,9 @@ export class ChatwootController {
public async receiveWebhook(instance: InstanceDto, data: any) {
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
const chatwootService = new ChatwootService(waMonitor, this.configService);
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository, chatwootCache);
return chatwootService.receiveWebhook(instance, data);
}

View File

@@ -1,5 +1,6 @@
import { Logger } from '../../config/logger.config';
import {
AcceptGroupInvite,
CreateGroupDto,
GetParticipant,
GroupDescriptionDto,
@@ -65,6 +66,11 @@ export class GroupController {
return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
}
public async acceptInviteCode(instance: InstanceDto, inviteCode: AcceptGroupInvite) {
logger.verbose('requested acceptInviteCode from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].acceptInviteCode(inviteCode);
}
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid);

View File

@@ -1,23 +1,28 @@
import { delay } from '@whiskeysockets/baileys';
import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import { v4 } from 'uuid';
import { ConfigService, HttpServer } from '../../config/env.config';
import { ConfigService, HttpServer, WaBusiness } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
import { RedisCache } from '../../libs/redis.client';
import { InstanceDto } from '../dto/instance.dto';
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
import { RepositoryBroker } from '../repository/repository.manager';
import { AuthService, OldToken } from '../services/auth.service';
import { CacheService } from '../services/cache.service';
import { ChatwootService } from '../services/chatwoot.service';
import { IntegrationService } from '../services/integration.service';
import { WAMonitoringService } from '../services/monitor.service';
import { RabbitmqService } from '../services/rabbitmq.service';
import { SettingsService } from '../services/settings.service';
import { SqsService } from '../services/sqs.service';
import { TypebotService } from '../services/typebot.service';
import { WebhookService } from '../services/webhook.service';
import { WebsocketService } from '../services/websocket.service';
import { WAStartupService } from '../services/whatsapp.service';
import { wa } from '../types/wa.types';
import { BaileysStartupService } from '../services/whatsapp.baileys.service';
import { BusinessStartupService } from '../services/whatsapp.business.service';
import { Events, Integration, wa } from '../types/wa.types';
export class InstanceController {
constructor(
@@ -31,8 +36,11 @@ export class InstanceController {
private readonly settingsService: SettingsService,
private readonly websocketService: WebsocketService,
private readonly rabbitmqService: RabbitmqService,
private readonly sqsService: SqsService,
private readonly typebotService: TypebotService,
private readonly integrationService: IntegrationService,
private readonly cache: RedisCache,
private readonly chatwootCache: CacheService,
) {}
private readonly logger = new Logger(InstanceController.name);
@@ -45,6 +53,7 @@ export class InstanceController {
events,
qrcode,
number,
integration,
token,
chatwoot_account_id,
chatwoot_token,
@@ -52,16 +61,22 @@ export class InstanceController {
chatwoot_sign_msg,
chatwoot_reopen_conversation,
chatwoot_conversation_pending,
chatwoot_import_contacts,
chatwoot_import_messages,
chatwoot_days_limit_import_messages,
reject_call,
msg_call,
groups_ignore,
always_online,
read_messages,
read_status,
sync_full_history,
websocket_enabled,
websocket_events,
rabbitmq_enabled,
rabbitmq_events,
sqs_enabled,
sqs_events,
typebot_url,
typebot,
typebot_expire,
@@ -76,10 +91,41 @@ export class InstanceController {
this.logger.verbose('checking duplicate token');
await this.authService.checkDuplicateToken(token);
if (!token && integration === Integration.WHATSAPP_BUSINESS) {
throw new BadRequestException('token is required');
}
this.logger.verbose('creating instance');
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
let instance: BaileysStartupService | BusinessStartupService;
if (integration === Integration.WHATSAPP_BUSINESS) {
instance = new BusinessStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
this.chatwootCache,
);
} else {
instance = new BaileysStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
this.chatwootCache,
);
}
await this.waMonitor.saveInstance({ integration, instanceName, token, number });
instance.instanceName = instanceName;
const instanceId = v4();
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
instanceName,
instanceId: instanceId,
});
this.logger.verbose('instance: ' + instance.instanceName + ' created');
this.waMonitor.waInstances[instance.instanceName] = instance;
@@ -89,6 +135,7 @@ export class InstanceController {
const hash = await this.authService.generateHash(
{
instanceName: instance.instanceName,
instanceId: instanceId,
},
token,
);
@@ -126,6 +173,8 @@ export class InstanceController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@@ -176,6 +225,8 @@ export class InstanceController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@@ -223,6 +274,8 @@ export class InstanceController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@@ -243,6 +296,55 @@ export class InstanceController {
}
}
let sqsEvents: string[];
if (sqs_enabled) {
this.logger.verbose('creating sqs');
try {
let newEvents: string[] = [];
if (sqs_events.length === 0) {
newEvents = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
} else {
newEvents = sqs_events;
}
this.sqsService.create(instance, {
enabled: true,
events: newEvents,
});
sqsEvents = (await this.sqsService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
if (typebot_url) {
try {
if (!isURL(typebot_url, { require_tld: false })) {
@@ -270,16 +372,34 @@ export class InstanceController {
const settings: wa.LocalSettings = {
reject_call: reject_call || false,
msg_call: msg_call || '',
groups_ignore: groups_ignore || false,
groups_ignore: groups_ignore || true,
always_online: always_online || false,
read_messages: read_messages || false,
read_status: read_status || false,
sync_full_history: sync_full_history ?? false,
};
this.logger.verbose('settings: ' + JSON.stringify(settings));
this.settingsService.create(instance, settings);
let webhook_wa_business = null,
access_token_wa_business = '';
if (integration === Integration.WHATSAPP_BUSINESS) {
if (!number) {
throw new BadRequestException('number is required');
}
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
webhook_wa_business = `${urlServer}/webhook/whatsapp/${encodeURIComponent(instance.instanceName)}`;
access_token_wa_business = this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK;
}
this.integrationService.create(instance, {
integration,
number,
token,
});
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
let getQrcode: wa.QrCode;
@@ -293,6 +413,10 @@ export class InstanceController {
const result = {
instance: {
instanceName: instance.instanceName,
instanceId: instanceId,
integration: integration,
webhook_wa_business,
access_token_wa_business,
status: 'created',
},
hash,
@@ -310,6 +434,10 @@ export class InstanceController {
enabled: rabbitmq_enabled,
events: rabbitmqEvents,
},
sqs: {
enabled: sqs_enabled,
events: sqsEvents,
},
typebot: {
enabled: typebot_url ? true : false,
url: typebot_url,
@@ -371,15 +499,11 @@ export class InstanceController {
number,
reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false,
import_contacts: chatwoot_import_contacts ?? true,
import_messages: chatwoot_import_messages ?? true,
days_limit_import_messages: chatwoot_days_limit_import_messages ?? 60,
auto_create: true,
});
this.chatwootService.initInstanceChatwoot(
instance,
instance.instanceName.split('-cwId-')[0],
`${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
qrcode,
number,
);
} catch (error) {
this.logger.log(error);
}
@@ -387,6 +511,10 @@ export class InstanceController {
return {
instance: {
instanceName: instance.instanceName,
instanceId: instanceId,
integration: integration,
webhook_wa_business,
access_token_wa_business,
status: 'created',
},
hash,
@@ -404,6 +532,10 @@ export class InstanceController {
enabled: rabbitmq_enabled,
events: rabbitmqEvents,
},
sqs: {
enabled: sqs_enabled,
events: sqsEvents,
},
typebot: {
enabled: typebot_url ? true : false,
url: typebot_url,
@@ -423,6 +555,9 @@ export class InstanceController {
sign_msg: chatwoot_sign_msg || false,
reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false,
import_contacts: chatwoot_import_contacts ?? true,
import_messages: chatwoot_import_messages ?? true,
days_limit_import_messages: chatwoot_days_limit_import_messages || 60,
number,
name_inbox: instance.instanceName,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
@@ -485,6 +620,7 @@ export class InstanceController {
switch (state) {
case 'open':
this.logger.verbose('logging out instance: ' + instanceName);
instance.clearCacheChatwoot();
await instance.reloadConnection();
await delay(2000);
@@ -507,17 +643,24 @@ export class InstanceController {
};
}
public async fetchInstances({ instanceName }: InstanceDto) {
public async fetchInstances({ instanceName, instanceId, number }: InstanceDto) {
if (instanceName) {
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
this.logger.verbose('instanceName: ' + instanceName);
return this.waMonitor.instanceInfo(instanceName);
} else if (instanceId || number) {
return this.waMonitor.instanceInfoById(instanceId, number);
}
this.logger.verbose('requested fetchInstances (all instances)');
return this.waMonitor.instanceInfo();
}
public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {
this.logger.verbose('requested sendPresence from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].setPresence(data);
}
public async logout({ instanceName }: InstanceDto) {
this.logger.verbose('requested logout from ' + instanceName + ' instance');
const { instance } = await this.connectionState({ instanceName });
@@ -527,11 +670,7 @@ export class InstanceController {
}
try {
this.logger.verbose('logging out instance: ' + instanceName);
await this.waMonitor.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName);
this.logger.verbose('close connection instance: ' + instanceName);
this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
this.waMonitor.waInstances[instanceName]?.logoutInstance();
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
} catch (error) {
@@ -548,20 +687,28 @@ export class InstanceController {
}
try {
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
if (instance.state === 'connecting') {
this.logger.verbose('logging out instance: ' + instanceName);
await this.logout({ instanceName });
delete this.waMonitor.waInstances[instanceName];
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
} else {
this.logger.verbose('deleting instance: ' + instanceName);
delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
}
this.logger.verbose('deleting instance: ' + instanceName);
try {
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
instanceName,
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
});
} catch (error) {
this.logger.error(error);
}
delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
} catch (error) {
throw new BadRequestException(error.toString());
}

View File

@@ -0,0 +1,20 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { HandleLabelDto } from '../dto/label.dto';
import { WAMonitoringService } from '../services/monitor.service';
const logger = new Logger('LabelController');
export class LabelController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async fetchLabels({ instanceName }: InstanceDto) {
logger.verbose('requested fetchLabels from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchLabels();
}
public async handleLabel({ instanceName }: InstanceDto, data: HandleLabelDto) {
logger.verbose('requested chat label change from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].handleLabel(data);
}
}

View File

@@ -1,19 +1,36 @@
import axios from 'axios';
import { Logger } from '../../config/logger.config';
import { BadRequestException, NotFoundException } from '../../exceptions';
import { makeProxyAgent } from '../../utils/makeProxyAgent';
import { InstanceDto } from '../dto/instance.dto';
import { ProxyDto } from '../dto/proxy.dto';
import { WAMonitoringService } from '../services/monitor.service';
import { ProxyService } from '../services/proxy.service';
const logger = new Logger('ProxyController');
export class ProxyController {
constructor(private readonly proxyService: ProxyService) {}
constructor(private readonly proxyService: ProxyService, private readonly waMonitor: WAMonitoringService) {}
public async createProxy(instance: InstanceDto, data: ProxyDto) {
logger.verbose('requested createProxy from ' + instance.instanceName + ' instance');
if (!this.waMonitor.waInstances[instance.instanceName]) {
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
}
if (!data.enabled) {
logger.verbose('proxy disabled');
data.proxy = '';
data.proxy = null;
}
if (data.proxy) {
const testProxy = await this.testProxy(data.proxy);
if (!testProxy) {
throw new BadRequestException('Invalid proxy');
}
logger.verbose('proxy enabled');
}
return this.proxyService.create(instance, data);
@@ -21,6 +38,35 @@ export class ProxyController {
public async findProxy(instance: InstanceDto) {
logger.verbose('requested findProxy from ' + instance.instanceName + ' instance');
if (!this.waMonitor.waInstances[instance.instanceName]) {
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
}
return this.proxyService.find(instance);
}
private async testProxy(proxy: ProxyDto['proxy']) {
logger.verbose('requested testProxy');
try {
const serverIp = await axios.get('https://icanhazip.com/');
const response = await axios.get('https://icanhazip.com/', {
httpsAgent: makeProxyAgent(proxy),
});
logger.verbose('[testProxy] from IP: ' + response?.data + ' To IP: ' + serverIp?.data);
return response?.data !== serverIp?.data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.data) {
logger.error('testProxy error: ' + error.response.data);
} else if (axios.isAxiosError(error)) {
logger.error('testProxy error: ');
logger.verbose(error.cause ?? error.message);
} else {
logger.error('testProxy error: ');
logger.verbose(error);
}
return false;
}
}
}

View File

@@ -38,6 +38,8 @@ export class RabbitmqController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',

View File

@@ -14,6 +14,7 @@ import {
SendReactionDto,
SendStatusDto,
SendStickerDto,
SendTemplateDto,
SendTextDto,
} from '../dto/sendMessage.dto';
import { WAMonitoringService } from '../services/monitor.service';
@@ -28,6 +29,11 @@ export class SendMessageController {
return await this.waMonitor.waInstances[instanceName].textMessage(data);
}
public async sendTemplate({ instanceName }: InstanceDto, data: SendTemplateDto) {
logger.verbose('requested sendList from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].templateMessage(data);
}
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) {
logger.verbose('requested sendMedia from ' + instanceName + ' instance');

View File

@@ -1,7 +1,4 @@
// import { isURL } from 'class-validator';
import { Logger } from '../../config/logger.config';
// import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { SettingsDto } from '../dto/settings.dto';
import { SettingsService } from '../services/settings.service';
@@ -19,6 +16,7 @@ export class SettingsController {
public async findSettings(instance: InstanceDto) {
logger.verbose('requested findSettings from ' + instance.instanceName + ' instance');
return this.settingsService.find(instance);
const settings = this.settingsService.find(instance);
return settings;
}
}

View File

@@ -0,0 +1,58 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { SqsDto } from '../dto/sqs.dto';
import { SqsService } from '../services/sqs.service';
const logger = new Logger('SqsController');
export class SqsController {
constructor(private readonly sqsService: SqsService) {}
public async createSqs(instance: InstanceDto, data: SqsDto) {
logger.verbose('requested createSqs from ' + instance.instanceName + ' instance');
if (!data.enabled) {
logger.verbose('sqs disabled');
data.events = [];
}
if (data.events.length === 0) {
logger.verbose('sqs events empty');
data.events = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
}
return this.sqsService.create(instance, data);
}
public async findSqs(instance: InstanceDto) {
logger.verbose('requested findSqs from ' + instance.instanceName + ' instance');
return this.sqsService.find(instance);
}
}

View File

@@ -1,23 +0,0 @@
import { Request, Response } from 'express';
import { Auth, ConfigService, HttpServer } from '../../config/env.config';
import { HttpStatus } from '../routers/index.router';
import { WAMonitoringService } from '../services/monitor.service';
export class ViewsController {
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
public async manager(request: Request, response: Response) {
try {
const token = this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY;
const port = this.configService.get<HttpServer>('SERVER').PORT;
const instances = await this.waMonitor.instanceInfo();
console.log('INSTANCES: ', instances);
return response.status(HttpStatus.OK).render('manager', { token, port, instances });
} catch (error) {
console.log('ERROR: ', error);
}
}
}

View File

@@ -4,12 +4,13 @@ import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto';
import { WAMonitoringService } from '../services/monitor.service';
import { WebhookService } from '../services/webhook.service';
const logger = new Logger('WebhookController');
export class WebhookController {
constructor(private readonly webhookService: WebhookService) {}
constructor(private readonly webhookService: WebhookService, private readonly waMonitor: WAMonitoringService) {}
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
@@ -46,6 +47,8 @@ export class WebhookController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@@ -61,4 +64,9 @@ export class WebhookController {
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
return this.webhookService.find(instance);
}
public async receiveWebhook(instance: InstanceDto, data: any) {
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].connectToWhatsapp(data);
}
}

View File

@@ -38,6 +38,8 @@ export class WebsocketController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',

View File

@@ -1,7 +1,12 @@
import { proto, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
export class OnWhatsAppDto {
constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {}
constructor(
public readonly jid: string,
public readonly exists: boolean,
public readonly number: string,
public readonly name?: string,
) {}
}
export class getBase64FromMediaMessageDto {
@@ -26,8 +31,12 @@ export class NumberBusiness {
message?: string;
description?: string;
email?: string;
websites?: string[];
website?: string[];
address?: string;
about?: string;
vertical?: string;
profilehandle?: string;
}
export class ProfileNameDto {
@@ -83,3 +92,31 @@ export class DeleteMessage {
remoteJid: string;
participant?: string;
}
export class Options {
delay?: number;
presence?: WAPresence;
}
class OptionsMessage {
options: Options;
}
export class Metadata extends OptionsMessage {
number: string;
}
export class SendPresenceDto extends Metadata {
options: {
presence: WAPresence;
delay: number;
};
}
export class UpdateMessageDto extends Metadata {
number: string;
key: proto.IMessageKey;
text: string;
}
export class BlockUserDto {
number: string;
status: 'block' | 'unblock';
}

View File

@@ -5,7 +5,12 @@ export class ChatwootDto {
url?: string;
name_inbox?: string;
sign_msg?: boolean;
sign_delimiter?: string;
number?: string;
reopen_conversation?: boolean;
conversation_pending?: boolean;
import_contacts?: boolean;
import_messages?: boolean;
days_limit_import_messages?: number;
auto_create?: boolean;
}

View File

@@ -32,6 +32,10 @@ export class GroupInvite {
inviteCode: string;
}
export class AcceptGroupInvite {
inviteCode: string;
}
export class GroupSendInvite {
groupJid: string;
description: string;

View File

@@ -1,7 +1,11 @@
import { WAPresence } from "@whiskeysockets/baileys";
export class InstanceDto {
instanceName: string;
instanceId?: string;
qrcode?: boolean;
number?: string;
integration?: string;
token?: string;
webhook?: string;
webhook_by_events?: boolean;
@@ -13,16 +17,22 @@ export class InstanceDto {
always_online?: boolean;
read_messages?: boolean;
read_status?: boolean;
sync_full_history?: boolean;
chatwoot_account_id?: string;
chatwoot_token?: string;
chatwoot_url?: string;
chatwoot_sign_msg?: boolean;
chatwoot_reopen_conversation?: boolean;
chatwoot_conversation_pending?: boolean;
chatwoot_import_contacts?: boolean;
chatwoot_import_messages?: boolean;
chatwoot_days_limit_import_messages?: number;
websocket_enabled?: boolean;
websocket_events?: string[];
rabbitmq_enabled?: boolean;
rabbitmq_events?: string[];
sqs_enabled?: boolean;
sqs_events?: string[];
typebot_url?: string;
typebot?: string;
typebot_expire?: number;
@@ -30,6 +40,9 @@ export class InstanceDto {
typebot_delay_message?: number;
typebot_unknown_message?: string;
typebot_listening_from_me?: boolean;
proxy_enabled?: boolean;
proxy_proxy?: string;
proxy?: string;
}
export class SetPresenceDto {
presence: WAPresence;
}

View File

@@ -0,0 +1,5 @@
export class IntegrationDto {
integration: string;
number: string;
token: string;
}

View File

@@ -0,0 +1,12 @@
export class LabelDto {
id?: string;
name: string;
color: number;
predefinedId?: string;
}
export class HandleLabelDto {
number: string;
labelId: string;
action: 'add' | 'remove';
}

View File

@@ -1,4 +1,12 @@
class Proxy {
host: string;
port: string;
protocol: string;
username?: string;
password?: string;
}
export class ProxyDto {
enabled: boolean;
proxy: string;
proxy: Proxy;
}

View File

@@ -46,9 +46,13 @@ class PollMessage {
values: string[];
messageSecret?: Uint8Array;
}
export class SendTextDto extends Metadata {
textMessage: TextMessage;
}
export class SendPresence extends Metadata {
textMessage: TextMessage;
}
export class SendStatusDto extends Metadata {
statusMessage: StatusMessage;
@@ -61,6 +65,7 @@ export class SendPollDto extends Metadata {
export type MediaType = 'image' | 'document' | 'video' | 'audio';
export class MediaMessage {
mediatype: MediaType;
mimetype?: string;
caption?: string;
// for document
fileName?: string;
@@ -137,6 +142,16 @@ export class ContactMessage {
email?: string;
url?: string;
}
export class TemplateMessage {
name: string;
language: string;
components: any;
}
export class SendTemplateDto extends Metadata {
templateMessage: TemplateMessage;
}
export class SendContactDto extends Metadata {
contactMessage: ContactMessage[];
}

View File

@@ -5,4 +5,5 @@ export class SettingsDto {
always_online?: boolean;
read_messages?: boolean;
read_status?: boolean;
sync_full_history?: boolean;
}

View File

@@ -0,0 +1,4 @@
export class SqsDto {
enabled: boolean;
events?: string[];
}

View File

@@ -6,12 +6,14 @@ export class AuthRaw {
_id?: string;
jwt?: string;
apikey?: string;
instanceId?: string;
}
const authSchema = new Schema<AuthRaw>({
_id: { type: String, _id: true },
jwt: { type: String, minlength: 1 },
apikey: { type: String, minlength: 1 },
instanceId: { type: String, minlength: 1 },
});
export const AuthModel = dbserver?.model(AuthRaw.name, authSchema, 'authentication');

View File

@@ -7,12 +7,19 @@ export class ChatRaw {
id?: string;
owner: string;
lastMsgTimestamp?: number;
labels?: string[];
}
type ChatRawBoolean<T> = {
[P in keyof T]?: 0 | 1;
};
export type ChatRawSelect = ChatRawBoolean<ChatRaw>;
const chatSchema = new Schema<ChatRaw>({
_id: { type: String, _id: true },
id: { type: String, required: true, minlength: 1 },
owner: { type: String, required: true, minlength: 1 },
labels: { type: [String], default: [] },
});
export const ChatModel = dbserver?.model(ChatRaw.name, chatSchema, 'chats');

View File

@@ -10,9 +10,13 @@ export class ChatwootRaw {
url?: string;
name_inbox?: string;
sign_msg?: boolean;
sign_delimiter?: string;
number?: string;
reopen_conversation?: boolean;
conversation_pending?: boolean;
import_contacts?: boolean;
import_messages?: boolean;
days_limit_import_messages?: number;
}
const chatwootSchema = new Schema<ChatwootRaw>({
@@ -23,9 +27,13 @@ const chatwootSchema = new Schema<ChatwootRaw>({
url: { type: String, required: true },
name_inbox: { type: String, required: true },
sign_msg: { type: Boolean, required: true },
sign_delimiter: { type: String, required: false },
number: { type: String, required: true },
reopen_conversation: { type: Boolean, required: true },
conversation_pending: { type: Boolean, required: true },
import_contacts: { type: Boolean, required: true },
import_messages: { type: Boolean, required: true },
days_limit_import_messages: { type: Number, required: true },
});
export const ChatwootModel = dbserver?.model(ChatwootRaw.name, chatwootSchema, 'chatwoot');

View File

@@ -10,6 +10,11 @@ export class ContactRaw {
owner: string;
}
type ContactRawBoolean<T> = {
[P in keyof T]?: 0 | 1;
};
export type ContactRawSelect = ContactRawBoolean<ContactRaw>;
const contactSchema = new Schema<ContactRaw>({
_id: { type: String, _id: true },
pushName: { type: String, minlength: 1 },

View File

@@ -3,10 +3,13 @@ export * from './chamaai.model';
export * from './chat.model';
export * from './chatwoot.model';
export * from './contact.model';
export * from './integration.model';
export * from './label.model';
export * from './message.model';
export * from './proxy.model';
export * from './rabbitmq.model';
export * from './settings.model';
export * from './sqs.model';
export * from './typebot.model';
export * from './webhook.model';
export * from './websocket.model';

View File

@@ -0,0 +1,20 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
export class IntegrationRaw {
_id?: string;
integration?: string;
number?: string;
token?: string;
}
const sqsSchema = new Schema<IntegrationRaw>({
_id: { type: String, _id: true },
integration: { type: String, required: true },
number: { type: String, required: true },
token: { type: String, required: true },
});
export const IntegrationModel = dbserver?.model(IntegrationRaw.name, sqsSchema, 'integration');
export type IntegrationModel = typeof IntegrationModel;

View File

@@ -0,0 +1,29 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
export class LabelRaw {
_id?: string;
id?: string;
owner: string;
name: string;
color: number;
predefinedId?: string;
}
type LabelRawBoolean<T> = {
[P in keyof T]?: 0 | 1;
};
export type LabelRawSelect = LabelRawBoolean<LabelRaw>;
const labelSchema = new Schema<LabelRaw>({
_id: { type: String, _id: true },
id: { type: String, required: true, minlength: 1 },
owner: { type: String, required: true, minlength: 1 },
name: { type: String, required: true, minlength: 1 },
color: { type: Number, required: true, min: 0, max: 19 },
predefinedId: { type: String },
});
export const LabelModel = dbserver?.model(LabelRaw.name, labelSchema, 'labels');
export type ILabelModel = typeof LabelModel;

View File

@@ -10,6 +10,14 @@ class Key {
participant?: string;
}
class ChatwootMessage {
messageId?: number;
inboxId?: number;
conversationId?: number;
contactInbox?: { sourceId: string };
isRead?: boolean;
}
export class MessageRaw {
_id?: string;
key?: Key;
@@ -19,11 +27,21 @@ export class MessageRaw {
messageType?: string;
messageTimestamp?: number | Long.Long;
owner: string;
source?: 'android' | 'web' | 'ios';
source?: 'android' | 'web' | 'ios' | 'unknown' | 'desktop';
source_id?: string;
source_reply_id?: string;
chatwoot?: ChatwootMessage;
contextInfo?: any;
}
type MessageRawBoolean<T> = {
[P in keyof T]?: 0 | 1;
};
export type MessageRawSelect = Omit<Omit<MessageRawBoolean<MessageRaw>, 'key'>, 'chatwoot'> & {
key?: MessageRawBoolean<Key>;
chatwoot?: MessageRawBoolean<ChatwootMessage>;
};
const messageSchema = new Schema<MessageRaw>({
_id: { type: String, _id: true },
key: {
@@ -36,11 +54,23 @@ const messageSchema = new Schema<MessageRaw>({
participant: { type: String },
messageType: { type: String },
message: { type: Object },
source: { type: String, minlength: 3, enum: ['android', 'web', 'ios'] },
source: { type: String, minlength: 3, enum: ['android', 'web', 'ios', 'unknown', 'desktop'] },
messageTimestamp: { type: Number, required: true },
owner: { type: String, required: true, minlength: 1 },
chatwoot: {
messageId: { type: Number },
inboxId: { type: Number },
conversationId: { type: Number },
contactInbox: { type: Object },
isRead: { type: Boolean },
},
});
messageSchema.index({ 'chatwoot.messageId': 1, owner: 1 });
messageSchema.index({ 'key.id': 1 });
messageSchema.index({ 'key.id': 1, owner: 1 });
messageSchema.index({ owner: 1 });
export const MessageModel = dbserver?.model(MessageRaw.name, messageSchema, 'messages');
export type IMessageModel = typeof MessageModel;

View File

@@ -2,16 +2,30 @@ import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
class Proxy {
host?: string;
port?: string;
protocol?: string;
username?: string;
password?: string;
}
export class ProxyRaw {
_id?: string;
enabled?: boolean;
proxy?: string;
proxy?: Proxy;
}
const proxySchema = new Schema<ProxyRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
proxy: { type: String, required: true },
proxy: {
host: { type: String, required: true },
port: { type: String, required: true },
protocol: { type: String, required: true },
username: { type: String, required: false },
password: { type: String, required: false },
},
});
export const ProxyModel = dbserver?.model(ProxyRaw.name, proxySchema, 'proxy');

View File

@@ -10,6 +10,7 @@ export class SettingsRaw {
always_online?: boolean;
read_messages?: boolean;
read_status?: boolean;
sync_full_history?: boolean;
}
const settingsSchema = new Schema<SettingsRaw>({
@@ -20,6 +21,7 @@ const settingsSchema = new Schema<SettingsRaw>({
always_online: { type: Boolean, required: true },
read_messages: { type: Boolean, required: true },
read_status: { type: Boolean, required: true },
sync_full_history: { type: Boolean, required: true },
});
export const SettingsModel = dbserver?.model(SettingsRaw.name, settingsSchema, 'settings');

View File

@@ -0,0 +1,18 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
export class SqsRaw {
_id?: string;
enabled?: boolean;
events?: string[];
}
const sqsSchema = new Schema<SqsRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
events: { type: [String], required: true },
});
export const SqsModel = dbserver?.model(SqsRaw.name, sqsSchema, 'sqs');
export type ISqsModel = typeof SqsModel;

View File

@@ -1,14 +1,18 @@
import { readFileSync } from 'fs';
import { opendirSync, readFileSync } from 'fs';
import { join } from 'path';
import { Auth, ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { AUTH_DIR } from '../../config/path.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { AuthRaw, IAuthModel } from '../models';
import { AuthRaw, IAuthModel, IntegrationModel } from '../models';
export class AuthRepository extends Repository {
constructor(private readonly authModel: IAuthModel, readonly configService: ConfigService) {
constructor(
private readonly authModel: IAuthModel,
private readonly integrationModel: IntegrationModel,
readonly configService: ConfigService,
) {
super(configService);
this.auth = configService.get<Auth>('AUTHENTICATION');
}
@@ -19,6 +23,7 @@ export class AuthRepository extends Repository {
public async create(data: AuthRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating auth');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving auth to db');
const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
@@ -62,4 +67,69 @@ export class AuthRepository extends Repository {
return {};
}
}
public async list(): Promise<AuthRaw[]> {
try {
if (this.dbSettings.ENABLED) {
this.logger.verbose('listing auth in db');
return await this.authModel.find();
}
this.logger.verbose('listing auth in store');
const auths: AuthRaw[] = [];
const openDir = opendirSync(join(AUTH_DIR, this.auth.TYPE), {
encoding: 'utf-8',
});
for await (const dirent of openDir) {
if (dirent.isFile()) {
auths.push(
JSON.parse(
readFileSync(join(AUTH_DIR, this.auth.TYPE, dirent.name), {
encoding: 'utf-8',
}),
),
);
}
}
return auths;
} catch (error) {
return [];
}
}
public async findInstanceNameById(instanceId: string): Promise<string | null> {
try {
this.logger.verbose('finding auth by instanceId');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding auth in db');
const response = await this.authModel.findOne({ instanceId });
return response._id;
}
this.logger.verbose('finding auth in store is not supported');
} catch (error) {
return null;
}
}
public async findInstanceNameByNumber(number: string): Promise<string | null> {
try {
this.logger.verbose('finding auth by number');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding auth in db');
const instance = await this.integrationModel.findOne({ number });
const response = await this.authModel.findOne({ _id: instance._id });
return response._id;
}
this.logger.verbose('finding auth in store is not supported');
} catch (error) {
return null;
}
}
}

View File

@@ -4,9 +4,10 @@ import { join } from 'path';
import { ConfigService, StoreConf } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { ChatRaw, IChatModel } from '../models';
import { ChatRaw, ChatRawSelect, IChatModel } from '../models';
export class ChatQuery {
select?: ChatRawSelect;
where: ChatRaw;
}
@@ -69,7 +70,7 @@ export class ChatRepository extends Repository {
this.logger.verbose('finding chats');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding chats in db');
return await this.chatModel.find({ owner: query.where.owner });
return await this.chatModel.find({ owner: query.where.owner }).select(query.select ?? {});
}
this.logger.verbose('finding chats in store');
@@ -114,4 +115,63 @@ export class ChatRepository extends Repository {
return { error: error?.toString() };
}
}
public async update(data: ChatRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
try {
this.logger.verbose('updating chats');
if (data.length === 0) {
this.logger.verbose('no chats to update');
return;
}
if (this.dbSettings.ENABLED && saveDb) {
this.logger.verbose('updating chats in db');
const chats = data.map((chat) => {
return {
updateOne: {
filter: { id: chat.id },
update: { ...chat },
upsert: true,
},
};
});
const { nModified } = await this.chatModel.bulkWrite(chats);
this.logger.verbose('chats updated in db: ' + nModified + ' chats');
return { insertCount: nModified };
}
this.logger.verbose('updating chats in store');
const store = this.configService.get<StoreConf>('STORE');
if (store.CONTACTS) {
this.logger.verbose('updating chats in store');
data.forEach((chat) => {
this.writeStore({
path: join(this.storePath, 'chats', instanceName),
fileName: chat.id,
data: chat,
});
this.logger.verbose(
'chats updated in store in path: ' + join(this.storePath, 'chats', instanceName) + '/' + chat.id,
);
});
this.logger.verbose('chats updated in store: ' + data.length + ' chats');
return { insertCount: data.length };
}
this.logger.verbose('chats not updated');
return { insertCount: 0 };
} catch (error) {
return error;
} finally {
data = undefined;
}
}
}

View File

@@ -4,12 +4,18 @@ import { join } from 'path';
import { ConfigService, StoreConf } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { ContactRaw, IContactModel } from '../models';
import { ContactRaw, ContactRawSelect, IContactModel } from '../models';
export class ContactQuery {
select?: ContactRawSelect;
where: ContactRaw;
}
export class ContactQueryMany {
owner: ContactRaw['owner'];
ids: ContactRaw['id'][];
}
export class ContactRepository extends Repository {
constructor(private readonly contactModel: IContactModel, private readonly configService: ConfigService) {
super(configService);
@@ -129,7 +135,7 @@ export class ContactRepository extends Repository {
this.logger.verbose('finding contacts');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding contacts in db');
return await this.contactModel.find({ ...query.where });
return await this.contactModel.find({ ...query.where }).select(query.select ?? {});
}
this.logger.verbose('finding contacts in store');
@@ -168,4 +174,54 @@ export class ContactRepository extends Repository {
return [];
}
}
public async findManyById(query: ContactQueryMany): Promise<ContactRaw[]> {
try {
this.logger.verbose('finding contacts');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding contacts in db');
return await this.contactModel.find({
owner: query.owner,
id: { $in: query.ids },
});
}
this.logger.verbose('finding contacts in store');
const contacts: ContactRaw[] = [];
if (query.ids.length > 0) {
this.logger.verbose('finding contacts in store by id');
query.ids.forEach((id) => {
contacts.push(
JSON.parse(
readFileSync(join(this.storePath, 'contacts', query.owner, id + '.json'), {
encoding: 'utf-8',
}),
),
);
});
} else {
this.logger.verbose('finding contacts in store by owner');
const openDir = opendirSync(join(this.storePath, 'contacts', query.owner), {
encoding: 'utf-8',
});
for await (const dirent of openDir) {
if (dirent.isFile()) {
contacts.push(
JSON.parse(
readFileSync(join(this.storePath, 'contacts', query.owner, dirent.name), {
encoding: 'utf-8',
}),
),
);
}
}
}
this.logger.verbose('contacts found in store: ' + contacts.length + ' contacts');
return contacts;
} catch (error) {
return [];
}
}
}

View File

@@ -0,0 +1,64 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { IntegrationModel, IntegrationRaw } from '../models';
export class IntegrationRepository extends Repository {
constructor(private readonly integrationModel: IntegrationModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('IntegrationRepository');
public async create(data: IntegrationRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating integration');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving integration to db');
const insert = await this.integrationModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('integration saved to db: ' + insert.modifiedCount + ' integration');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving integration to store');
this.writeStore<IntegrationRaw>({
path: join(this.storePath, 'integration'),
fileName: instance,
data,
});
this.logger.verbose(
'integration saved to store in path: ' + join(this.storePath, 'integration') + '/' + instance,
);
this.logger.verbose('integration created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<IntegrationRaw> {
try {
this.logger.verbose('finding integration');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding integration in db');
return await this.integrationModel.findOne({ _id: instance });
}
this.logger.verbose('finding integration in store');
return JSON.parse(
readFileSync(join(this.storePath, 'integration', instance + '.json'), {
encoding: 'utf-8',
}),
) as IntegrationRaw;
} catch (error) {
return {};
}
}
}

View File

@@ -0,0 +1,111 @@
import { opendirSync, readFileSync, rmSync } from 'fs';
import { join } from 'path';
import { ConfigService, StoreConf } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { ILabelModel, LabelRaw, LabelRawSelect } from '../models';
export class LabelQuery {
select?: LabelRawSelect;
where: Partial<LabelRaw>;
}
export class LabelRepository extends Repository {
constructor(private readonly labelModel: ILabelModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('LabelRepository');
public async insert(data: LabelRaw, instanceName: string, saveDb = false): Promise<IInsert> {
this.logger.verbose('inserting labels');
try {
if (this.dbSettings.ENABLED && saveDb) {
this.logger.verbose('saving labels to db');
const insert = await this.labelModel.findOneAndUpdate({ id: data.id }, data, { upsert: true });
this.logger.verbose(`label ${data.name} saved to db`);
return { insertCount: Number(!!insert._id) };
}
this.logger.verbose('saving label to store');
const store = this.configService.get<StoreConf>('STORE');
if (store.LABELS) {
this.logger.verbose('saving label to store');
this.writeStore<LabelRaw>({
path: join(this.storePath, 'labels', instanceName),
fileName: data.id,
data,
});
this.logger.verbose(
'labels saved to store in path: ' + join(this.storePath, 'labels', instanceName) + '/' + data.id,
);
this.logger.verbose(`label ${data.name} saved to store`);
return { insertCount: 1 };
}
this.logger.verbose('labels not saved to store');
return { insertCount: 0 };
} catch (error) {
return error;
} finally {
data = undefined;
}
}
public async find(query: LabelQuery): Promise<LabelRaw[]> {
try {
this.logger.verbose('finding labels');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding labels in db');
return await this.labelModel.find({ owner: query.where.owner }).select(query.select ?? {});
}
this.logger.verbose('finding labels in store');
const labels: LabelRaw[] = [];
const openDir = opendirSync(join(this.storePath, 'labels', query.where.owner));
for await (const dirent of openDir) {
if (dirent.isFile()) {
labels.push(
JSON.parse(
readFileSync(join(this.storePath, 'labels', query.where.owner, dirent.name), {
encoding: 'utf-8',
}),
),
);
}
}
this.logger.verbose('labels found in store: ' + labels.length + ' labels');
return labels;
} catch (error) {
return [];
}
}
public async delete(query: LabelQuery) {
try {
this.logger.verbose('deleting labels');
if (this.dbSettings.ENABLED) {
this.logger.verbose('deleting labels in db');
return await this.labelModel.deleteOne({ ...query.where });
}
this.logger.verbose('deleting labels in store');
rmSync(join(this.storePath, 'labels', query.where.owner, query.where.id + '.josn'), {
force: true,
recursive: true,
});
return { deleted: { labelId: query.where.id } };
} catch (error) {
return { error: error?.toString() };
}
}
}

View File

@@ -1,12 +1,13 @@
import { opendirSync, readFileSync } from 'fs';
import { opendirSync, readFileSync, rmSync } from 'fs';
import { join } from 'path';
import { ConfigService, StoreConf } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { IMessageModel, MessageRaw } from '../models';
import { IMessageModel, MessageRaw, MessageRawSelect } from '../models';
export class MessageQuery {
select?: MessageRawSelect;
where: MessageRaw;
limit?: number;
}
@@ -18,6 +19,28 @@ export class MessageRepository extends Repository {
private readonly logger = new Logger('MessageRepository');
public buildQuery(query: MessageQuery): MessageQuery {
for (const [o, p] of Object.entries(query?.where || {})) {
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
for (const [k, v] of Object.entries(p)) {
query.where[`${o}.${k}`] = v;
}
delete query.where[o];
}
}
for (const [o, p] of Object.entries(query?.select || {})) {
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
for (const [k, v] of Object.entries(p)) {
query.select[`${o}.${k}`] = v;
}
delete query.select[o];
}
}
return query;
}
public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
this.logger.verbose('inserting messages');
@@ -91,15 +114,11 @@ export class MessageRepository extends Repository {
this.logger.verbose('finding messages');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding messages in db');
if (query?.where?.key) {
for (const [k, v] of Object.entries(query.where.key)) {
query.where['key.' + k] = v;
}
delete query?.where?.key;
}
query = this.buildQuery(query);
return await this.messageModel
.find({ ...query.where })
.select(query.select || {})
.sort({ messageTimestamp: -1 })
.limit(query?.limit ?? 0);
}
@@ -141,7 +160,81 @@ export class MessageRepository extends Repository {
})
.splice(0, query?.limit ?? messages.length);
} catch (error) {
this.logger.error(`error on message find: ${error.toString()}`);
return [];
}
}
public async update(data: MessageRaw[], instanceName: string, saveDb?: boolean): Promise<IInsert> {
try {
if (this.dbSettings.ENABLED && saveDb) {
this.logger.verbose('updating messages in db');
const messages = data.map((message) => {
return {
updateOne: {
filter: { 'key.id': message.key.id },
update: { ...message },
},
};
});
const { nModified } = await this.messageModel.bulkWrite(messages);
this.logger.verbose('messages updated in db: ' + nModified + ' messages');
return { insertCount: nModified };
}
this.logger.verbose('updating messages in store');
const store = this.configService.get<StoreConf>('STORE');
if (store.MESSAGES) {
this.logger.verbose('updating messages in store');
data.forEach((message) => {
this.writeStore({
path: join(this.storePath, 'messages', instanceName),
fileName: message.key.id,
data: message,
});
this.logger.verbose(
'messages updated in store in path: ' +
join(this.storePath, 'messages', instanceName) +
'/' +
message.key.id,
);
});
this.logger.verbose('messages updated in store: ' + data.length + ' messages');
return { insertCount: data.length };
}
this.logger.verbose('messages not updated');
return { insertCount: 0 };
} catch (error) {
this.logger.error(error);
}
}
public async delete(query: MessageQuery) {
try {
this.logger.verbose('deleting message');
if (this.dbSettings.ENABLED) {
this.logger.verbose('deleting message in db');
query = this.buildQuery(query);
return await this.messageModel.deleteOne({ ...query.where });
}
this.logger.verbose('deleting message in store');
rmSync(join(this.storePath, 'messages', query.where.owner, query.where.key.id + '.json'), {
force: true,
recursive: true,
});
return { deleted: { messageId: query.where.key.id } };
} catch (error) {
return { error: error?.toString() };
}
}
}

View File

@@ -9,11 +9,14 @@ import { ChamaaiRepository } from './chamaai.repository';
import { ChatRepository } from './chat.repository';
import { ChatwootRepository } from './chatwoot.repository';
import { ContactRepository } from './contact.repository';
import { IntegrationRepository } from './integration.repository';
import { LabelRepository } from './label.repository';
import { MessageRepository } from './message.repository';
import { MessageUpRepository } from './messageUp.repository';
import { ProxyRepository } from './proxy.repository';
import { RabbitmqRepository } from './rabbitmq.repository';
import { SettingsRepository } from './settings.repository';
import { SqsRepository } from './sqs.repository';
import { TypebotRepository } from './typebot.repository';
import { WebhookRepository } from './webhook.repository';
import { WebsocketRepository } from './websocket.repository';
@@ -28,10 +31,13 @@ export class RepositoryBroker {
public readonly settings: SettingsRepository,
public readonly websocket: WebsocketRepository,
public readonly rabbitmq: RabbitmqRepository,
public readonly sqs: SqsRepository,
public readonly typebot: TypebotRepository,
public readonly proxy: ProxyRepository,
public readonly chamaai: ChamaaiRepository,
public readonly integration: IntegrationRepository,
public readonly auth: AuthRepository,
public readonly labels: LabelRepository,
private configService: ConfigService,
dbServer?: MongoClient,
) {
@@ -63,9 +69,11 @@ export class RepositoryBroker {
const settingsDir = join(storePath, 'settings');
const websocketDir = join(storePath, 'websocket');
const rabbitmqDir = join(storePath, 'rabbitmq');
const sqsDir = join(storePath, 'sqs');
const typebotDir = join(storePath, 'typebot');
const proxyDir = join(storePath, 'proxy');
const chamaaiDir = join(storePath, 'chamaai');
const integrationDir = join(storePath, 'integration');
const tempDir = join(storePath, 'temp');
if (!fs.existsSync(authDir)) {
@@ -108,6 +116,10 @@ export class RepositoryBroker {
this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir);
fs.mkdirSync(rabbitmqDir, { recursive: true });
}
if (!fs.existsSync(sqsDir)) {
this.logger.verbose('creating sqs dir: ' + sqsDir);
fs.mkdirSync(sqsDir, { recursive: true });
}
if (!fs.existsSync(typebotDir)) {
this.logger.verbose('creating typebot dir: ' + typebotDir);
fs.mkdirSync(typebotDir, { recursive: true });
@@ -120,6 +132,10 @@ export class RepositoryBroker {
this.logger.verbose('creating chamaai dir: ' + chamaaiDir);
fs.mkdirSync(chamaaiDir, { recursive: true });
}
if (!fs.existsSync(integrationDir)) {
this.logger.verbose('creating integration dir: ' + integrationDir);
fs.mkdirSync(integrationDir, { recursive: true });
}
if (!fs.existsSync(tempDir)) {
this.logger.verbose('creating temp dir: ' + tempDir);
fs.mkdirSync(tempDir, { recursive: true });

View File

@@ -0,0 +1,62 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { ISqsModel, SqsRaw } from '../models';
export class SqsRepository extends Repository {
constructor(private readonly sqsModel: ISqsModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('SqsRepository');
public async create(data: SqsRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating sqs');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving sqs to db');
const insert = await this.sqsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('sqs saved to db: ' + insert.modifiedCount + ' sqs');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving sqs to store');
this.writeStore<SqsRaw>({
path: join(this.storePath, 'sqs'),
fileName: instance,
data,
});
this.logger.verbose('sqs saved to store in path: ' + join(this.storePath, 'sqs') + '/' + instance);
this.logger.verbose('sqs created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<SqsRaw> {
try {
this.logger.verbose('finding sqs');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding sqs in db');
return await this.sqsModel.findOne({ _id: instance });
}
this.logger.verbose('finding sqs in store');
return JSON.parse(
readFileSync(join(this.storePath, 'sqs', instance + '.json'), {
encoding: 'utf-8',
}),
) as SqsRaw;
} catch (error) {
return {};
}
}
}

View File

@@ -3,21 +3,26 @@ import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import {
archiveChatSchema,
blockUserSchema,
contactValidateSchema,
deleteMessageSchema,
messageUpSchema,
messageValidateSchema,
presenceSchema,
presenceOnlySchema,
privacySettingsSchema,
profileNameSchema,
profilePictureSchema,
profileSchema,
profileStatusSchema,
readMessageSchema,
updateMessageSchema,
whatsappNumberSchema,
} from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import {
ArchiveChatDto,
BlockUserDto,
DeleteMessage,
getBase64FromMediaMessageDto,
NumberDto,
@@ -26,6 +31,8 @@ import {
ProfilePictureDto,
ProfileStatusDto,
ReadMessageDto,
SendPresenceDto,
UpdateMessageDto,
WhatsAppNumberDto,
} from '../dto/chat.dto';
import { InstanceDto } from '../dto/instance.dto';
@@ -56,7 +63,7 @@ export class ChatRouter extends RouterBroker {
execute: (instance, data) => chatController.whatsappNumber(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
return res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => {
logger.verbose('request received in markMessageAsRead');
@@ -228,6 +235,22 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('sendPresence'), ...guards, async (req, res) => {
logger.verbose('request received in sendPresence');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<null>({
request: req,
schema: presenceSchema,
ClassRef: SendPresenceDto,
execute: (instance, data) => chatController.sendPresence(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
// Profile routes
.get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => {
logger.verbose('request received in fetchPrivacySettings');
@@ -347,6 +370,40 @@ export class ChatRouter extends RouterBroker {
});
return res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('updateMessage'), ...guards, async (req, res) => {
logger.verbose('request received in updateMessage');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<UpdateMessageDto>({
request: req,
schema: updateMessageSchema,
ClassRef: UpdateMessageDto,
execute: (instance, data) => chatController.updateMessage(instance, data),
});
return res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('updateBlockStatus'), ...guards, async (req, res) => {
logger.verbose('request received in updateBlockStatus');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<BlockUserDto>({
request: req,
schema: blockUserSchema,
ClassRef: BlockUserDto,
execute: (instance, data) => chatController.blockUser(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
});
}

View File

@@ -5,7 +5,6 @@ import { chatwootSchema, instanceNameSchema } from '../../validate/validate.sche
import { RouterBroker } from '../abstract/abstract.router';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto';
// import { ChatwootService } from '../services/chatwoot.service';
import { chatwootController } from '../whatsapp.module';
import { HttpStatus } from './index.router';

View File

@@ -2,6 +2,7 @@ import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import {
AcceptGroupInviteSchema,
createGroupSchema,
getParticipantsSchema,
groupInviteSchema,
@@ -16,6 +17,7 @@ import {
} from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import {
AcceptGroupInvite,
CreateGroupDto,
GetParticipant,
GroupDescriptionDto,
@@ -182,6 +184,22 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('acceptInviteCode'), ...guards, async (req, res) => {
logger.verbose('request received in acceptInviteCode');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.inviteCodeValidate<AcceptGroupInvite>({
request: req,
schema: AcceptGroupInviteSchema,
ClassRef: AcceptGroupInvite,
execute: (instance, data) => groupController.acceptInviteCode(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('sendInvite'), ...guards, async (req, res) => {
logger.verbose('request received in sendInvite');
logger.verbose('request body: ');

View File

@@ -9,10 +9,12 @@ import { ChatRouter } from './chat.router';
import { ChatwootRouter } from './chatwoot.router';
import { GroupRouter } from './group.router';
import { InstanceRouter } from './instance.router';
import { LabelRouter } from './label.router';
import { ProxyRouter } from './proxy.router';
import { RabbitmqRouter } from './rabbitmq.router';
import { MessageRouter } from './sendMessage.router';
import { SettingsRouter } from './settings.router';
import { SqsRouter } from './sqs.router';
import { TypebotRouter } from './typebot.router';
import { ViewsRouter } from './view.router';
import { WebhookRouter } from './webhook.router';
@@ -30,31 +32,37 @@ enum HttpStatus {
const router = Router();
const authType = configService.get<Auth>('AUTHENTICATION').TYPE;
const serverConfig = configService.get('SERVER');
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard[authType]];
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
if (!serverConfig.DISABLE_MANAGER) router.use('/manager', new ViewsRouter().router);
router
.get('/', (req, res) => {
res.status(HttpStatus.OK).json({
status: HttpStatus.OK,
message: 'Welcome to the Evolution API, it is working!',
version: packageJson.version,
documentation: `${req.protocol}://${req.get('host')}/docs`,
swagger: !serverConfig.DISABLE_DOCS ? `${req.protocol}://${req.get('host')}/docs` : undefined,
manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,
documentation: `https://doc.evolution-api.com`,
});
})
.use('/instance', new InstanceRouter(configService, ...guards).router)
.use('/manager', new ViewsRouter().router)
.use('/message', new MessageRouter(...guards).router)
.use('/chat', new ChatRouter(...guards).router)
.use('/group', new GroupRouter(...guards).router)
.use('/webhook', new WebhookRouter(...guards).router)
.use('/webhook', new WebhookRouter(configService, ...guards).router)
.use('/chatwoot', new ChatwootRouter(...guards).router)
.use('/settings', new SettingsRouter(...guards).router)
.use('/websocket', new WebsocketRouter(...guards).router)
.use('/rabbitmq', new RabbitmqRouter(...guards).router)
.use('/sqs', new SqsRouter(...guards).router)
.use('/typebot', new TypebotRouter(...guards).router)
.use('/proxy', new ProxyRouter(...guards).router)
.use('/chamaai', new ChamaaiRouter(...guards).router);
.use('/chamaai', new ChamaaiRouter(...guards).router)
.use('/label', new LabelRouter(...guards).router);
export { HttpStatus, router };

View File

@@ -3,9 +3,9 @@ import { RequestHandler, Router } from 'express';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { dbserver } from '../../libs/db.connect';
import { instanceNameSchema, oldTokenSchema } from '../../validate/validate.schema';
import {instanceNameSchema, oldTokenSchema, presenceOnlySchema} from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
import { OldToken } from '../services/auth.service';
import { instanceController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
@@ -98,6 +98,22 @@ export class InstanceRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('setPresence'), ...guards, async (req, res) => {
logger.verbose('request received in setPresence');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<null>({
request: req,
schema: presenceOnlySchema,
ClassRef: SetPresenceDto,
execute: (instance, data) => instanceController.setPresence(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
.delete(this.routerPath('logout'), ...guards, async (req, res) => {
logger.verbose('request received in logoutInstances');
logger.verbose('request body: ');

View File

@@ -0,0 +1,53 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import { handleLabelSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { HandleLabelDto, LabelDto } from '../dto/label.dto';
import { labelController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('LabelRouter');
export class LabelRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.get(this.routerPath('findLabels'), ...guards, async (req, res) => {
logger.verbose('request received in findLabels');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<LabelDto>({
request: req,
schema: null,
ClassRef: LabelDto,
execute: (instance) => labelController.fetchLabels(instance),
});
return res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('handleLabel'), ...guards, async (req, res) => {
logger.verbose('request received in handleLabel');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<HandleLabelDto>({
request: req,
schema: handleLabelSchema,
ClassRef: HandleLabelDto,
execute: (instance, data) => labelController.handleLabel(instance, data),
});
return res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@@ -12,6 +12,7 @@ import {
reactionMessageSchema,
statusMessageSchema,
stickerMessageSchema,
templateMessageSchema,
textMessageSchema,
} from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
@@ -26,6 +27,7 @@ import {
SendReactionDto,
SendStatusDto,
SendStickerDto,
SendTemplateDto,
SendTextDto,
} from '../dto/sendMessage.dto';
import { sendMessageController } from '../whatsapp.module';
@@ -85,6 +87,22 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendTemplate'), ...guards, async (req, res) => {
logger.verbose('request received in sendTemplate');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendTemplateDto>({
request: req,
schema: templateMessageSchema,
ClassRef: SendTemplateDto,
execute: (instance, data) => sendMessageController.sendTemplate(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendButtons'), ...guards, async (req, res) => {
logger.verbose('request received in sendButtons');
logger.verbose('request body: ');

View File

@@ -5,7 +5,6 @@ import { instanceNameSchema, settingsSchema } from '../../validate/validate.sche
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { SettingsDto } from '../dto/settings.dto';
// import { SettingsService } from '../services/settings.service';
import { settingsController } from '../whatsapp.module';
import { HttpStatus } from './index.router';

View File

@@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import { instanceNameSchema, sqsSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { SqsDto } from '../dto/sqs.dto';
import { sqsController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('SqsRouter');
export class SqsRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setSqs');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SqsDto>({
request: req,
schema: sqsSchema,
ClassRef: SqsDto,
execute: (instance, data) => sqsController.createSqs(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findSqs');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => sqsController.findSqs(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@@ -1,14 +1,32 @@
import { Router } from 'express';
import fs from 'fs';
import mime from 'mime-types';
import { RouterBroker } from '../abstract/abstract.router';
import { viewsController } from '../whatsapp.module';
export class ViewsRouter extends RouterBroker {
constructor() {
super();
this.router.get('/', (req, res) => {
return viewsController.manager(req, res);
const basePath = 'evolution-manager/dist';
const indexPath = require.resolve(`${basePath}/index.html`);
this.router.get('/*', (req, res) => {
try {
const pathname = req.url.split('?')[0];
// verify if url is a file in dist folder
if (pathname === '/') throw {};
const filePath = require.resolve(`${basePath}${pathname}`);
const contentType = mime.lookup(filePath) || 'text/plain';
res.set('Content-Type', contentType);
res.end(fs.readFileSync(filePath));
} catch {
res.set('Content-Type', 'text/html');
res.send(fs.readFileSync(indexPath));
}
});
}

View File

@@ -1,5 +1,6 @@
import { RequestHandler, Router } from 'express';
import { ConfigService, WaBusiness } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { instanceNameSchema, webhookSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
@@ -11,7 +12,7 @@ import { HttpStatus } from './index.router';
const logger = new Logger('WebhookRouter');
export class WebhookRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
@@ -45,6 +46,31 @@ export class WebhookRouter extends RouterBroker {
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('whatsapp'), async (req, res) => {
logger.verbose('request received in webhook');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance, data) => webhookController.receiveWebhook(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('whatsapp'), async (req, res) => {
logger.verbose('request received in webhook');
logger.verbose('request query: ');
logger.verbose(req.query);
if (req.query['hub.verify_token'] === this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK)
res.send(req.query['hub.challenge']);
else res.send('Error, wrong validation token');
logger.verbose('Error, wrong validation token');
});
}

View File

@@ -46,7 +46,10 @@ export class AuthService {
this.logger.verbose('JWT token created: ' + token);
const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName);
const auth = await this.repository.auth.create(
{ jwt: token, instanceId: instance.instanceId },
instance.instanceName,
);
this.logger.verbose('JWT token saved in database');
@@ -66,7 +69,7 @@ export class AuthService {
this.logger.verbose(token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey);
const auth = await this.repository.auth.create({ apikey }, instance.instanceName);
const auth = await this.repository.auth.create({ apikey, instanceId: instance.instanceId }, instance.instanceName);
this.logger.verbose('APIKEY saved in database');

View File

@@ -0,0 +1,62 @@
import { Logger } from '../../config/logger.config';
import { ICache } from '../abstract/abstract.cache';
export class CacheService {
private readonly logger = new Logger(CacheService.name);
constructor(private readonly cache: ICache) {
if (cache) {
this.logger.verbose(`cacheservice created using cache engine: ${cache.constructor?.name}`);
} else {
this.logger.verbose(`cacheservice disabled`);
}
}
async get(key: string): Promise<any> {
if (!this.cache) {
return;
}
this.logger.verbose(`cacheservice getting key: ${key}`);
return this.cache.get(key);
}
async set(key: string, value: any) {
if (!this.cache) {
return;
}
this.logger.verbose(`cacheservice setting key: ${key}`);
this.cache.set(key, value);
}
async has(key: string) {
if (!this.cache) {
return;
}
this.logger.verbose(`cacheservice has key: ${key}`);
return this.cache.has(key);
}
async delete(key: string) {
if (!this.cache) {
return;
}
this.logger.verbose(`cacheservice deleting key: ${key}`);
return this.cache.delete(key);
}
async deleteAll(appendCriteria?: string) {
if (!this.cache) {
return;
}
this.logger.verbose(`cacheservice deleting all keys`);
return this.cache.deleteAll(appendCriteria);
}
async keys(appendCriteria?: string) {
if (!this.cache) {
return;
}
this.logger.verbose(`cacheservice getting all keys`);
return this.cache.keys(appendCriteria);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { IntegrationDto } from '../dto/integration.dto';
import { IntegrationRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class IntegrationService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(IntegrationService.name);
public create(instance: InstanceDto, data: IntegrationDto) {
this.logger.verbose('create integration: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setIntegration(data);
return { integration: { ...instance, integration: data } };
}
public async find(instance: InstanceDto): Promise<IntegrationRaw> {
try {
this.logger.verbose('find integration: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findIntegration();
if (Object.keys(result).length === 0) {
throw new Error('Integration not found');
}
return result;
} catch (error) {
return { integration: '', number: '', token: '' };
}
}
}

View File

@@ -1,26 +1,33 @@
import { execSync } from 'child_process';
import EventEmitter2 from 'eventemitter2';
import { opendirSync, readdirSync, rmSync } from 'fs';
import { existsSync, mkdirSync, opendirSync, readdirSync, rmSync, writeFileSync } from 'fs';
import { Db } from 'mongodb';
import { Collection } from 'mongoose';
import { join } from 'path';
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
import { NotFoundException } from '../../exceptions';
import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client';
import {
AuthModel,
ChamaaiModel,
ChatwootModel,
ContactModel,
MessageModel,
MessageUpModel,
LabelModel,
ProxyModel,
RabbitmqModel,
SettingsModel,
TypebotModel,
WebhookModel,
WebsocketModel,
} from '../models';
import { RepositoryBroker } from '../repository/repository.manager';
import { WAStartupService } from './whatsapp.service';
import { Integration } from '../types/wa.types';
import { CacheService } from './cache.service';
import { BaileysStartupService } from './whatsapp.baileys.service';
import { BusinessStartupService } from './whatsapp.business.service';
export class WAMonitoringService {
constructor(
@@ -28,12 +35,12 @@ export class WAMonitoringService {
private readonly configService: ConfigService,
private readonly repository: RepositoryBroker,
private readonly cache: RedisCache,
private readonly chatwootCache: CacheService,
) {
this.logger.verbose('instance created');
this.removeInstance();
this.noConnection();
this.delInstanceFiles();
Object.assign(this.db, configService.get<Database>('DATABASE'));
Object.assign(this.redis, configService.get<Redis>('REDIS'));
@@ -48,10 +55,8 @@ export class WAMonitoringService {
private dbInstance: Db;
private dbStore = dbserver;
private readonly logger = new Logger(WAMonitoringService.name);
public readonly waInstances: Record<string, WAStartupService> = {};
public readonly waInstances: Record<string, BaileysStartupService | BusinessStartupService> = {};
public delInstanceTime(instance: string) {
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
@@ -61,9 +66,11 @@ export class WAMonitoringService {
setTimeout(async () => {
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
this.waInstances[instance]?.client?.ws?.close();
this.waInstances[instance]?.client?.end(undefined);
if ((await this.waInstances[instance].findIntegration()).integration === Integration.WHATSAPP_BAILEYS) {
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
this.waInstances[instance]?.client?.ws?.close();
this.waInstances[instance]?.client?.end(undefined);
}
this.waInstances[instance]?.removeRabbitmqQueues();
delete this.waInstances[instance];
} else {
@@ -100,12 +107,23 @@ export class WAMonitoringService {
};
}
const findIntegration = await this.waInstances[key].findIntegration();
let integration: any;
if (findIntegration) {
integration = {
...findIntegration,
webhook_wa_business: `${urlServer}/webhook/whatsapp/${encodeURIComponent(key)}`,
};
}
if (value.connectionStatus.state === 'open') {
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
const instanceData = {
instance: {
instanceName: key,
instanceId: (await this.repository.auth.find(key))?.instanceId,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
@@ -117,9 +135,11 @@ export class WAMonitoringService {
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (await this.repository.auth.find(key)).apikey;
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
instanceData.instance['chatwoot'] = chatwoot;
instanceData.instance['integration'] = integration;
}
instances.push(instanceData);
@@ -129,6 +149,7 @@ export class WAMonitoringService {
const instanceData = {
instance: {
instanceName: key,
instanceId: (await this.repository.auth.find(key))?.instanceId,
status: value.connectionStatus.state,
},
};
@@ -136,9 +157,11 @@ export class WAMonitoringService {
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (await this.repository.auth.find(key)).apikey;
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
instanceData.instance['chatwoot'] = chatwoot;
instanceData.instance['integration'] = integration;
}
instances.push(instanceData);
@@ -151,6 +174,32 @@ export class WAMonitoringService {
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
}
public async instanceInfoById(instanceId?: string, number?: string) {
this.logger.verbose('get instance info');
let instanceName: string;
if (instanceId) {
instanceName = await this.repository.auth.findInstanceNameById(instanceId);
if (!instanceName) {
throw new NotFoundException(`Instance "${instanceId}" not found`);
}
} else if (number) {
instanceName = await this.repository.auth.findInstanceNameByNumber(number);
if (!instanceName) {
throw new NotFoundException(`Instance "${number}" not found`);
}
}
if (!instanceName) {
throw new NotFoundException(`Instance "${instanceId}" not found`);
}
if (instanceName && !this.waInstances[instanceName]) {
throw new NotFoundException(`Instance "${instanceName}" not found`);
}
return this.instanceInfo(instanceName);
}
private delInstanceFiles() {
this.logger.verbose('cron to delete instance files started');
setInterval(async () => {
@@ -187,6 +236,7 @@ export class WAMonitoringService {
public async cleaningUp(instanceName: string) {
this.logger.verbose('cleaning up instance: ' + instanceName);
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('cleaning up instance in database: ' + instanceName);
await this.repository.dbServer.connect();
@@ -227,20 +277,24 @@ export class WAMonitoringService {
execSync(`rm -rf ${join(STORE_DIR, 'typebot', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'websocket', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'labels', instanceName + '*')}`);
return;
}
this.logger.verbose('cleaning store database instance: ' + instanceName);
await AuthModel.deleteMany({ owner: instanceName });
await ContactModel.deleteMany({ owner: instanceName });
await MessageModel.deleteMany({ owner: instanceName });
await MessageUpModel.deleteMany({ owner: instanceName });
await AuthModel.deleteMany({ _id: instanceName });
await WebhookModel.deleteMany({ _id: instanceName });
await ChatwootModel.deleteMany({ _id: instanceName });
await ChamaaiModel.deleteMany({ _id: instanceName });
await ProxyModel.deleteMany({ _id: instanceName });
await RabbitmqModel.deleteMany({ _id: instanceName });
await TypebotModel.deleteMany({ _id: instanceName });
await WebsocketModel.deleteMany({ _id: instanceName });
await SettingsModel.deleteMany({ _id: instanceName });
await LabelModel.deleteMany({ owner: instanceName });
await ContactModel.deleteMany({ owner: instanceName });
return;
}
@@ -261,11 +315,57 @@ export class WAMonitoringService {
}
}
private async setInstance(name: string) {
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
instance.instanceName = name;
this.logger.verbose('Instance loaded: ' + name);
public async saveInstance(data: any) {
this.logger.verbose('Save instance');
try {
const msgParsed = JSON.parse(JSON.stringify(data));
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
await this.repository.dbServer.connect();
await this.dbInstance.collection(data.instanceName).replaceOne({ _id: 'integration' }, msgParsed, {
upsert: true,
});
} else {
const path = join(INSTANCE_DIR, data.instanceName);
if (!existsSync(path)) mkdirSync(path, { recursive: true });
writeFileSync(path + '/integration.json', JSON.stringify(msgParsed));
}
} catch (error) {
this.logger.error(error);
}
}
private async setInstance(name: string) {
const integration = await this.repository.integration.find(name);
let instance: BaileysStartupService | BusinessStartupService;
if (integration && integration.integration === Integration.WHATSAPP_BUSINESS) {
instance = new BusinessStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
this.chatwootCache,
);
instance.instanceName = name;
} else {
instance = new BaileysStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
this.chatwootCache,
);
instance.instanceName = name;
if (!integration) {
await instance.setIntegration({ integration: Integration.WHATSAPP_BAILEYS });
}
}
this.logger.verbose('Instance loaded: ' + name);
await instance.connectToWhatsapp();
this.logger.verbose('connectToWhatsapp: ' + name);
@@ -289,7 +389,7 @@ export class WAMonitoringService {
this.logger.verbose('Database enabled');
await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections();
await this.deleteTempInstances(collections);
if (collections.length > 0) {
this.logger.verbose('Reading collections and setting instances');
await Promise.all(collections.map((coll) => this.setInstance(coll.namespace.replace(/^[\w-]+\./, ''))));
@@ -346,6 +446,7 @@ export class WAMonitoringService {
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
this.logger.verbose('logout instance: ' + instanceName);
try {
this.waInstances[instanceName]?.clearCacheChatwoot();
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName);
} finally {
@@ -377,4 +478,27 @@ export class WAMonitoringService {
}
});
}
private async deleteTempInstances(collections: Collection<Document>[]) {
const shouldDelete = this.configService.get<boolean>('DEL_TEMP_INSTANCES');
if (!shouldDelete) {
this.logger.verbose('Temp instances deletion is disabled');
return;
}
this.logger.verbose('Cleaning up temp instances');
const auths = await this.repository.auth.list();
if (auths.length === 0) {
this.logger.verbose('No temp instances found');
return;
}
let tempInstances = 0;
auths.forEach((auth) => {
if (collections.find((coll) => coll.namespace.replace(/^[\w-]+\./, '') === auth._id)) {
return;
}
tempInstances++;
this.eventEmitter.emit('remove.instance', auth._id, 'inner');
});
this.logger.verbose('Temp instances removed: ' + tempInstances);
}
}

View File

@@ -27,7 +27,7 @@ export class ProxyService {
return result;
} catch (error) {
return { enabled: false, proxy: '' };
return { enabled: false, proxy: null };
}
}
}

View File

@@ -26,7 +26,7 @@ export class SettingsService {
return result;
} catch (error) {
return { reject_call: false, msg_call: '', groups_ignore: false };
return { reject_call: false, msg_call: '', groups_ignore: true };
}
}
}

View File

@@ -0,0 +1,35 @@
import { Logger } from '../../config/logger.config';
import { initQueues } from '../../libs/sqs.server';
import { InstanceDto } from '../dto/instance.dto';
import { SqsDto } from '../dto/sqs.dto';
import { SqsRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class SqsService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(SqsService.name);
public create(instance: InstanceDto, data: SqsDto) {
this.logger.verbose('create sqs: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setSqs(data);
initQueues(instance.instanceName, data.events);
return { sqs: { ...instance, sqs: data } };
}
public async find(instance: InstanceDto): Promise<SqsRaw> {
try {
this.logger.verbose('find sqs: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findSqs();
if (Object.keys(result).length === 0) {
throw new Error('Sqs not found');
}
return result;
} catch (error) {
return { enabled: false, events: [] };
}
}
}

Some files were not shown because too many files have changed in this diff Show More